/* ===== CSS Variables & Reset ===== */
:root {
  --blue-50: #eff6ff;
  --blue-100: #dbeafe;
  --blue-200: #bfdbfe;
  --blue-400: #60a5fa;
  --blue-500: #3b82f6;
  --blue-600: #2563eb;
  --blue-700: #1d4ed8;

  --purple-400: #a78bfa;
  --purple-500: #8b5cf6;
  --purple-600: #7c3aed;

  --green-400: #a3e635;
  --green-500: #84cc16;
  /* DM-notification brand green — the bright lime "Hi" pill green Jimmy uses
     (2026-06-07). Dark ink keeps the count high-contrast + accessible. */
  --qbit-notify: #a3e635;
  --qbit-notify-ink: #0c1400;
  /* Parent/child safety-alert chip — amber "attention", distinct from the
     message green. Tweakable like the notify green. */
  --qbit-alert: #f59e0b;
  --qbit-alert-ink: #1f1300;

  --gray-50: #f8fafc;
  --gray-100: #f1f5f9;
  --gray-200: #e2e8f0;
  --gray-300: #cbd5e1;
  --gray-400: #94a3b8;
  --gray-500: #64748b;
  --gray-600: #475569;
  --gray-700: #334155;
  --gray-800: #1e293b;
  --gray-900: #0f172a;

  --bg: #ffffff;
  --bg-secondary: var(--gray-50);
  --bg-card: #ffffff;
  /* --bg-soft: subtle elevated surface used for hover/active highlights
     on pill toggles, menu items, etc. Must be defined in BOTH light and
     dark themes — six call sites previously fell through to a hardcoded
     #f3f4f6 fallback, which painted near-white pills in dark mode and
     made the white text unreadable (Jimmy's screenshots 2026-05-15). */
  --bg-soft: #f3f4f6;
  /* --surface / --surface-2: card + messaging surfaces. These were never
     defined, so every `var(--surface, #fff)` / `var(--surface-2, …)` call
     site fell through to a WHITE/near-white fallback — which left message
     bubbles, the compose box, and thread rows white in dark mode, making
     the light --text unreadable (Scott 2026-06-14). Alias them onto the
     already-themed tokens so they track light/dark automatically (same
     fix class as --bg-soft above). One definition in :root is enough —
     custom properties resolve lazily, so under [data-theme="dark"] these
     pick up the dark --bg-card / --bg-soft values. */
  --surface: var(--bg-card);
  --surface-2: var(--bg-soft);
  --text: var(--gray-900);
  --text-secondary: var(--gray-500);
  --text-muted: var(--gray-400);
  --border: var(--gray-200);
  --shadow: 0 1px 3px rgba(0, 0, 0, 0.08), 0 1px 2px rgba(0, 0, 0, 0.06);
  --shadow-lg: 0 10px 25px rgba(0, 0, 0, 0.08), 0 4px 10px rgba(0, 0, 0, 0.04);
  --radius: 16px;
  --radius-sm: 10px;
  --transition: 0.2s ease;
  /* Tell the browser this surface is light so native controls (select
     dropdown popups, date/time pickers, scrollbars) render in light chrome. */
  color-scheme: light;
}

[data-theme="dark"] {
  /* The dark counterpart — without this, native <select>/<option> popups and
     date pickers kept the OS light theme and painted white-on-white in dark
     mode (tester #47228, 2026-07-02). This is the real root-cause fix. */
  color-scheme: dark;
  --bg: #0a1628;
  --bg-secondary: #111d33;
  --bg-card: #152238;
  /* Slightly lighter than --bg-card so hover/active pill highlights pop
     against the chat input chrome without going anywhere near white.
     Contrast against --text (#e2e8f0) is ~8.5:1 — comfortably above WCAG
     AAA (7:1). Matches --border for visual cohesion. */
  --bg-soft: #1e3a5f;
  --text: #e2e8f0;
  --text-secondary: #94a3b8;
  --text-muted: #64748b;
  --border: #1e3a5f;
  --shadow: 0 1px 3px rgba(0, 0, 0, 0.3), 0 1px 2px rgba(0, 0, 0, 0.2);
  --shadow-lg: 0 10px 25px rgba(0, 0, 0, 0.4), 0 4px 10px rgba(0, 0, 0, 0.2);
}

/* Native form controls follow the dark theme explicitly (tester #47228).
   color-scheme above handles the popup chrome; these give the closed control
   and the <option> rows themed colors on browsers that ignore color-scheme,
   which is what left white-on-white dropdowns/date fields in dark mode. */
[data-theme="dark"] select,
[data-theme="dark"] select option,
[data-theme="dark"] input[type="date"],
[data-theme="dark"] input[type="month"],
[data-theme="dark"] input[type="time"],
[data-theme="dark"] input[type="datetime-local"] {
  background-color: var(--bg-card);
  color: var(--text);
}

*, *::before, *::after {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

/* ─── [hidden] attribute: restore expected behavior ─────────────────
 * The user-agent stylesheet ships `[hidden] { display: none }`, but
 * any author CSS that sets `display` (e.g. `.support-row { display: flex }`,
 * `.support-count-badge { display: inline-block }`) silently wins on
 * specificity — so `el.hidden = true` in JS appears to do nothing.
 *
 * This bit us 2026-05-12 on Setup & Support: a brand-new Apple-signup
 * account with zero tickets still saw the "Your submitted tickets" row
 * because `.support-row { display: flex }` was overriding the `hidden`
 * attribute. JS was correctly setting it; the cascade was ignoring it.
 *
 * !important here is intentional and well-precedented (HTML5 Boilerplate
 * ships the same rule). If a component ever NEEDS [hidden] to mean
 * something other than `display: none` (e.g. animated reveal), it
 * should use a class toggle (`.is-collapsed`) instead of the attribute. */
[hidden] { display: none !important; }

html {
  font-size: 16px;
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  /* Contain horizontal overflow at the ROOT, not just on <body>. On iOS Safari a
     single too-wide (or position:fixed) descendant expands the layout viewport —
     which shifts EVERY page's content to the right and clips it on the right edge.
     body{overflow-x:hidden} alone doesn't stop it because the html/initial
     containing block still grows. Belt-and-suspenders with body. (Jimmy 2026-06-15) */
  overflow-x: hidden;
  width: 100%;
  max-width: 100%;
}

body {
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
  background: var(--bg);
  color: var(--text);
  line-height: 1.6;
  min-height: 100dvh;
  transition: background var(--transition), color var(--transition);
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

/* Global media guard — never let an image/video/canvas render wider than its
   container. A single uncapped image (avatar, group pic, message photo — all of
   which only load when signed in) rendering at natural width was a prime suspect
   for the right-edge clipping; this is also baseline hygiene. Icons/avatars that
   set their own width are smaller than the container, so this never affects them.
   (Jimmy 2026-06-15) */
img, video, canvas { max-width: 100%; }

/* ===== Impersonation banner =====
   Sticky top-of-viewport banner shown when an admin is impersonating
   a customer. NOT inside .app-shell so it spans the full viewport
   width — the impersonation context applies to whatever the admin is
   looking at, not just the centered Qbit column.

   Amber/orange palette: high enough contrast to never be missed, but
   not the panic-red of an error — this is debugging, not a fire. */
.impersonation-banner {
  position: sticky;
  top: 0;
  z-index: 9999;             /* above the app-header sticky too */
  background: #f59e0b;       /* amber-500 */
  color: #1a1a1a;            /* dark text on amber for contrast */
  border-bottom: 2px solid #b45309;  /* amber-700 — slight depth */
  box-shadow: 0 2px 8px rgba(180, 83, 9, .25);
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}
.impersonation-banner[hidden] { display: none; }

.impersonation-banner-inner {
  max-width: 1200px;
  margin: 0 auto;
  padding: 10px 16px;
  display: flex;
  align-items: center;
  gap: 14px;
}

.impersonation-banner-icon {
  font-size: 22px;
  flex: 0 0 auto;
}

.impersonation-banner-text {
  flex: 1 1 auto;
  min-width: 0;             /* prevents flex overflow on long emails */
  font-size: 13px;
  line-height: 1.45;
}
.impersonation-banner-line1 {
  font-weight: 600;
}
.impersonation-banner-line1 strong { font-weight: 700; }
.impersonation-banner-line2 {
  font-size: 12px;
  opacity: .85;
  font-variant-numeric: tabular-nums; /* countdown digits don't shimmy */
}

.impersonation-end-btn {
  flex: 0 0 auto;
  appearance: none;
  border: 1px solid #1a1a1a;
  background: #1a1a1a;
  color: #f59e0b;
  font-family: inherit;
  font-size: 13px;
  font-weight: 600;
  padding: 7px 14px;
  border-radius: 6px;
  cursor: pointer;
  transition: background .12s, color .12s, transform .08s;
  white-space: nowrap;
}
.impersonation-end-btn:hover {
  background: #2a2a2a;
}
.impersonation-end-btn:active {
  transform: scale(.97);
}

/* Push the rest of the page down a touch when the banner is showing.
   Without this, sticky headers can underlap the banner on certain
   scroll positions. The class is toggled on body by checkAndRender(). */
body.has-impersonation-banner .app-header {
  /* No layout change needed; the sticky banner sits ABOVE the header
     because of higher z-index. Reserved class for any future tweaks
     (e.g. adjusting top offsets on other sticky elements). */
}

/* Mobile — narrower padding, line2 wraps below line1 naturally via
   normal flow because of flex-direction on inner. */
@media (max-width: 640px) {
  .impersonation-banner-inner {
    padding: 8px 12px;
    gap: 10px;
  }
  .impersonation-banner-icon { font-size: 18px; }
  .impersonation-banner-text { font-size: 12px; }
  .impersonation-banner-line2 { font-size: 11px; }
  .impersonation-end-btn { padding: 6px 10px; font-size: 12px; }
}

/* ===== App Shell — responsive (Jimmy 2026-05-16) =====
 * Qbit was built phone-first as a 480px-max-width centered column,
 * which leaves huge empty side gutters on tablets and big monitors.
 * This 3-breakpoint strategy expands the shell so:
 *
 *   - Phone (< 600px):                  narrow column, fills viewport
 *   - Tablet portrait (600-899px):      fills 100% width with padding
 *                                       so content uses the whole screen
 *   - Tablet landscape + desktop:       comfortable readable column at
 *     (≥ 900px)                         760px centered (matches the
 *                                       width that chat already uses
 *                                       for .qbit-turn)
 *
 * The orientation:portrait media query intentionally separates "tablet
 * held vertically" from "tablet held horizontally OR desktop monitor".
 * Per Jimmy: portrait should fill the screen; landscape should stay
 * narrower because long line lengths hurt readability and the user
 * likely has other things on screen alongside Qbit.
 * ===================================================== */
.app-shell {
  max-width: 480px;
  margin: 0 auto;
  min-height: 100dvh;
  position: relative;
}

/* Tablet portrait — fill the screen with breathing room */
@media (min-width: 600px) and (orientation: portrait) {
  .app-shell {
    max-width: 100%;
    padding: 0 24px;
  }
}

/* Tablet landscape + desktop — comfortable readable column */
@media (min-width: 900px) {
  .app-shell {
    max-width: 760px;
    padding: 0;
  }
}

/* ===== Bottom action bar — WhatsApp-style global nav (Scott 2026-06-10) =====
   AI Home · DMs · Groups · Docs. Fixed to the bottom, mirrors the .app-shell
   column width at each breakpoint. Shown only when authenticated; the AppTabBar
   module (app.js) hides it on the AI chat conversation + when a DM thread is
   open (those screens have their own bottom compose box). */
.app-tabbar {
  position: fixed;
  bottom: 0;
  left: 50%;
  transform: translateX(-50%);
  width: 100%;
  max-width: 480px;
  display: flex;
  justify-content: center;   /* cluster the tabs in the middle, not edge-to-edge */
  gap: 6px;                  /* tightened from 22px so 5 tabs fit (History added 2026-06-16) */
  background: var(--bg-card);
  border-top: 1px solid var(--border);
  padding: 6px 4px;
  padding-bottom: calc(6px + env(safe-area-inset-bottom, 0px));
  z-index: 850;
  box-shadow: 0 -1px 10px rgba(0, 0, 0, 0.06);
}
.app-tabbar[hidden] { display: none; }
@media (min-width: 600px) and (orientation: portrait) {
  .app-tabbar { max-width: 100%; }
}
@media (min-width: 900px) {
  .app-tabbar { max-width: 760px; }
}
.app-tab {
  position: relative;        /* anchor for the per-tab unread badge */
  /* Flex-share instead of a fixed 58px so all 5 tabs (History returned to
     the bar 2026-07-07) fit every width: tabs shrink to fill on a 320px
     phone and cap at 78px on wide/desktop layouts so they don't sprawl —
     the extra room lets the "Docs & Photos" label render in full. */
  flex: 1 1 0;
  min-width: 0;
  max-width: 78px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 3px;
  padding: 3px 2px;
  background: none;
  border: none;
  color: var(--text-muted);
  font-family: inherit;
  font-size: 0.66rem;
  font-weight: 600;
  letter-spacing: 0.01em;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: color 0.12s ease;
}
.app-tab svg { width: 23px; height: 23px; flex-shrink: 0; }
/* The little Qbit creature tab icon (home) — one PNG (the canonical
   qbit-icon), grayscaled to match the sibling line icons on inactive tabs,
   full color when Home is the active tab. (De-pawed 2026-07-07 per Scott —
   replaced the purpose-made house+paw active/idle pair.) */
.app-tab img { width: 23px; height: 23px; flex-shrink: 0; object-fit: contain; filter: grayscale(1) opacity(0.62); transition: filter 0.12s ease; }
.app-tab.active img { filter: none; }
/* One line per label; slightly smaller than the old 0.66rem so all 5 tabs —
   including "Docs & Photos" — fit a 320px phone. Ellipsis is a safety net
   for the longest translations, not the expected rendering. */
.app-tab-label { white-space: nowrap; max-width: 100%; overflow: hidden; text-overflow: ellipsis; font-size: 0.63rem; }
.app-tab.active { color: var(--blue-500); }
.app-tab:active { transform: translateY(1px); }

/* Per-tab unread badge — DMs tab = 1:1 unread, Groups tab = group-chat
   unread (Scott 2026-06-11). Sits over the top-right of the tab icon. */
.app-tab-badge {
  position: absolute;
  top: 0;
  left: 50%;
  margin-left: 4px;          /* nudge just past the icon's right edge */
  min-width: 16px;
  height: 16px;
  padding: 0 4px;
  box-sizing: border-box;
  border-radius: 8px;
  background: var(--blue-500); /* Qbit blue — match the standard .count-badge pill site-wide (Jimmy 2026-06-18, branding consistency) */
  color: #fff;
  font-size: 0.6rem;
  font-weight: 700;
  line-height: 16px;
  text-align: center;
  pointer-events: none;
}
.app-tab-badge[hidden] { display: none; }
/* Clear the fixed tab bar so the bottom of pages isn't hidden/cramped behind
   it. Pad the ACTIVE page directly + generously, safe-area aware — on iPhones
   the home-indicator inflates the bar height, which was cramping bottom CTAs
   ("Back to My Profile", etc.) against it (Scott 2026-06-13). Higher specificity
   than the base `.page` rule so it overrides its 100px bottom. */
body.has-tabbar .page {
  padding-bottom: calc(110px + env(safe-area-inset-bottom, 0px));
}
/* The global footer (.page-footer-links / .page-trademark / .app-version) lives
   OUTSIDE every .page — it's a trailing sibling at the end of <body>. So the
   per-page padding above never clears IT from the fixed tab bar, and on the home
   page the Privacy/Terms/About line + trademark + version were getting cropped
   behind the bar (Jimmy 2026-06-14). Body is the scroll container, so clear the
   bar once at the very bottom of the document. */
body.has-tabbar {
  padding-bottom: calc(96px + env(safe-area-inset-bottom, 0px));
}
/* DM threads do NOT hide the bar: on desktop the thread is an in-flow panel so
   the bar stays put; on mobile the full-screen conversation overlay (z-index
   1200) simply covers it. (Was force-hidden before — made the bar vanish when
   opening a thread on desktop. Fixed 2026-06-10.) */

/* ===== Header ===== */
.app-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 12px 20px;
  position: sticky;
  top: 0;
  z-index: 100;
  background: var(--bg);
  border-bottom: 1px solid var(--border);
  transition: background var(--transition);
}

/* Deep-link landing buffer — when navigate() scrolls to a section via
   data-scroll-to (e.g. checklist items linking to Settings → Profile),
   the sticky .app-header above would otherwise cover the section's
   title and first input. scroll-margin-top tells the browser to leave
   ~80px of breathing room above the target so it lands cleanly under
   the header instead of behind it. */
.settings-group[id],
#auto-replenish-section,
#qbit-answer {
  scroll-margin-top: 80px;
}

.header-left {
  display: flex;
  align-items: center;
  gap: 8px;
}

.menu-btn {
  background: none;
  border: none;
  color: var(--text);
  cursor: pointer;
  padding: 6px;
  border-radius: 8px;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background var(--transition);
}

.menu-btn:hover {
  background: var(--bg-secondary);
}

.menu-btn svg {
  width: 24px;
  height: 24px;
}

.header-logo {
  font-size: 1.25rem;
  font-weight: 700;
  background: linear-gradient(135deg, var(--blue-500), var(--purple-500));
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
}
/* Brand lockup button: Qbit icon + wordmark, both tap through to Home
   (Scott 2026-06-10). Transparent button chrome so only the icon + gradient
   text show. */
.header-logo-btn {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  background: none;
  border: none;
  padding: 0;
  margin: 0;
  font: inherit;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.header-logo-icon {
  height: 36px;      /* match .header-avatar + header pills (Jimmy 2026-06-10) */
  width: auto;       /* preserve aspect — no distortion if the source isn't square */
  display: block;
  flex-shrink: 0;
}

/* ── Security activity viewer modal (Settings → Privacy & Data) ── */
.qbit-seclog-modal {
  position: fixed; inset: 0; z-index: 100000;
  display: flex; align-items: center; justify-content: center;
  background: rgba(0, 0, 0, 0.5); padding: 16px;
}
.qbit-seclog-modal[hidden] { display: none; }
.qbit-seclog-sheet {
  width: 100%; max-width: 460px; max-height: 80vh;
  display: flex; flex-direction: column;
  background: var(--bg-card); border-radius: 16px;
  box-shadow: 0 12px 40px rgba(0, 0, 0, 0.25); overflow: hidden;
}
.qbit-seclog-head {
  display: flex; align-items: center; justify-content: space-between;
  padding: 16px 18px 4px;
}
.qbit-seclog-title { margin: 0; font-size: 1.05rem; font-weight: 700; color: var(--text); }
.qbit-seclog-close {
  border: none; background: none; font-size: 1.6rem; line-height: 1;
  color: var(--text-muted); cursor: pointer; padding: 0 4px;
}
.qbit-seclog-sub { margin: 0 18px 10px; font-size: 0.8rem; color: var(--text-muted); line-height: 1.4; }
.qbit-seclog-list { overflow-y: auto; padding: 0 10px 14px; }
.qbit-seclog-loading, .qbit-seclog-empty { padding: 24px; text-align: center; color: var(--text-muted); font-size: 0.9rem; }
.qbit-seclog-row {
  display: flex; gap: 10px; align-items: flex-start;
  padding: 10px 8px; border-bottom: 1px solid var(--border);
}
.qbit-seclog-row:last-child { border-bottom: none; }
.qbit-seclog-row-icon {
  flex: 0 0 22px; width: 22px; height: 22px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  font-size: 0.8rem; font-weight: 700; margin-top: 1px;
}
.qbit-seclog-row.is-ok .qbit-seclog-row-icon { background: rgba(34, 197, 94, 0.15); color: #16a34a; }
.qbit-seclog-row.is-fail .qbit-seclog-row-icon { background: rgba(220, 38, 38, 0.13); color: #dc2626; }
.qbit-seclog-row-main { flex: 1; min-width: 0; }
.qbit-seclog-row-top { display: flex; justify-content: space-between; gap: 8px; }
.qbit-seclog-device { font-weight: 600; font-size: 0.9rem; color: var(--text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.qbit-seclog-when { flex-shrink: 0; font-size: 0.75rem; color: var(--text-muted); }
.qbit-seclog-row-meta { font-size: 0.78rem; color: var(--text-muted); margin-top: 2px; line-height: 1.35; }
.qbit-seclog-row.is-fail .qbit-seclog-row-meta { color: #dc2626; }

/* ── Group chats (Scott 2026-06-10) ── */
/* List header — the two surfaces (Messages / Groups) now have their own pages,
   driven by the bottom bar. The title swaps; there's no in-page toggle. (2026-06-13) */
/* List header row: title left, "+" new-chat right (Scott 2026-06-14). */
.messaging-list-header { display: flex; align-items: center; justify-content: space-between; }
.messaging-list-title {
  /* Match the standard page-title size used across the app (.settings-header h1
     etc. = 1.4rem) so Messages/Groups headers aren't smaller (Jimmy 2026-06-16). */
  padding: 12px 14px 8px; font-size: 1.4rem; font-weight: 700; color: var(--text-strong, var(--text));
  letter-spacing: -0.01em;
}
.messaging-newchat-btn {
  flex-shrink: 0; margin: 0 12px; height: 32px; border-radius: 16px;
  display: inline-flex; align-items: center; justify-content: center; gap: 4px;
  border: none; background: var(--blue-500, #3b82f6); color: #fff;
  font-family: inherit; font-size: 0.85rem; font-weight: 600; line-height: 1; cursor: pointer; padding: 0 13px;
  box-shadow: 0 1px 4px rgba(59, 130, 246, 0.4);
}
.messaging-newchat-btn:hover { background: var(--blue-600, #2563eb); }
/* Qbit AI in chat (Scott 2026-06-15) — pinned contact + "Ask me anything" bars. */
.messaging-qbit-row .messaging-thread-avatar,
.messaging-thread-avatar-img[src*="qbit-icon"] { background: #fff; }
/* Unified "Ask me anything" bar (Jimmy 2026-06-15): a gray section wrapping a
   white pill (Qbit icon + prompt), pinned at the top of every DM/group thread.
   Click opens the private Qbit AI thread — same look + behaviour everywhere. */
.messaging-askqbit-bar {
  padding: 8px 12px;
  border-bottom: 1px solid var(--border); background: var(--bg-soft);
}
.messaging-askqbit-pill {
  display: flex; align-items: center; gap: 9px; width: 100%;
  padding: 9px 13px; border-radius: 999px; cursor: pointer;
  border: 1px solid var(--border); background: var(--bg-card); color: var(--text-secondary);
  font-family: inherit; font-size: 0.9rem; text-align: left;
}
.messaging-askqbit-pill:hover { border-color: var(--blue-400, #60a5fa); }
.messaging-askqbit-icon { width: 22px; height: 22px; border-radius: 50%; object-fit: cover; flex-shrink: 0; background: #fff; }
.messaging-askqbit-text { flex: 1; color: var(--text); font-weight: 500; }   /* button LABEL — solid text + weight so it reads as a tappable "Ask Qbit" button, not an empty input placeholder (Jimmy 2026-06-16) */
/* Group variant: the pill holds a text input + "Ask" button → broadcasts the
   question + Qbit's answer to the group feed (Jimmy 2026-06-15). */
.messaging-askqbit-form { gap: 8px; cursor: text; }
.messaging-askqbit-input {
  flex: 1; min-width: 0; border: none; background: transparent; outline: none;
  font-family: inherit; font-size: 0.9rem; color: var(--text); padding: 0;
}
.messaging-askqbit-input::placeholder { color: var(--text-secondary); }
/* Round gradient send button with a paper-airplane icon — MATCHES the home-screen
   composer's .chat-send-btn so Qbit's UI is identical site-wide (Jimmy 2026-06-17). */
.messaging-askqbit-send {
  flex-shrink: 0; width: 38px; height: 38px; border: none; border-radius: 50%; cursor: pointer;
  background: linear-gradient(135deg, var(--blue-500), var(--purple-500));
  color: #fff; display: flex; align-items: center; justify-content: center;
  transition: transform var(--transition), box-shadow var(--transition);
}
.messaging-askqbit-send:hover { transform: scale(1.05); box-shadow: 0 4px 12px rgba(59, 130, 246, 0.4); }
.messaging-askqbit-send svg { width: 18px; height: 18px; }

/* Qbit answer bubbles render markdown via window.formatAnswer() (the SAME
   formatter the home screen uses), so tighten the block elements to sit cleanly
   inside a chat bubble (Jimmy 2026-06-17). Structured answers get a little more
   width than a normal message bubble. */
.messaging-qbit-msg .messaging-bubble { max-width: 92%; }
.messaging-bubble-md { display: block; line-height: 1.4; }
.messaging-bubble-md > :first-child { margin-top: 0; }
.messaging-bubble-md > :last-child { margin-bottom: 0; }
.messaging-bubble-md p { margin: 0 0 8px; }
.messaging-bubble-md h2, .messaging-bubble-md h3, .messaging-bubble-md h4 { margin: 10px 0 6px; line-height: 1.25; }
.messaging-bubble-md h2 { font-size: 1.05rem; }
.messaging-bubble-md h3 { font-size: 1rem; }
.messaging-bubble-md h4 { font-size: 0.95rem; }
.messaging-bubble-md ul, .messaging-bubble-md ol { margin: 4px 0 8px; padding-left: 1.25em; }
.messaging-bubble-md li { margin: 2px 0; }
.messaging-bubble-md a { color: var(--blue-600, #2563eb); text-decoration: underline; word-break: break-word; }
.messaging-bubble-md code { background: rgba(127,127,127,0.15); padding: 1px 4px; border-radius: 4px; font-size: 0.9em; }
.messaging-bubble-md hr { border: none; border-top: 1px solid var(--border); margin: 10px 0; }
.messaging-bubble-md img, .messaging-bubble-md video { max-width: 100%; border-radius: 8px; }
.messaging-bubble-md table { border-collapse: collapse; width: 100%; font-size: 0.85em; margin: 4px 0 8px; }
.messaging-bubble-md th, .messaging-bubble-md td { border: 1px solid var(--border); padding: 4px 6px; text-align: left; }

/* "Reply to Qbit" follow-up affordance under Qbit's latest answer (Jimmy
   2026-06-17, entry point A). A subtle pill that expands to a small inline input
   tied to Qbit, reusing the round gradient airplane send button. */
.messaging-qbit-followup { display: flex; justify-content: flex-start; margin: 2px 0 8px; padding-left: 4px; }
.messaging-qbit-followup-pill {
  display: inline-flex; align-items: center; gap: 7px; cursor: pointer;
  border: 1px solid var(--blue-400, #60a5fa); background: rgba(59,130,246,0.06);
  color: var(--blue-600, #2563eb); border-radius: 999px; padding: 5px 13px;
  font-family: inherit; font-size: 0.8rem; font-weight: 600;
}
.messaging-qbit-followup-pill:hover { background: rgba(59,130,246,0.12); }
.messaging-qbit-followup-icon {
  width: 14px; height: 14px; border-radius: 50%; flex-shrink: 0;
  background: linear-gradient(135deg, var(--blue-500), var(--purple-500));
}
.messaging-qbit-followup-form {
  display: flex; align-items: center; gap: 8px; width: 100%; max-width: 92%;
  border: 2px solid var(--blue-400, #60a5fa); border-radius: 999px; padding: 4px 5px 4px 14px;
}
.messaging-qbit-followup-input {
  flex: 1; min-width: 0; border: none; background: transparent; outline: none;
  font-family: inherit; font-size: 0.9rem; color: var(--text); padding: 0;
}
.messaging-qbit-followup-input::placeholder { color: var(--text-secondary); }
.messaging-qbit-followup-form .messaging-askqbit-send { width: 32px; height: 32px; }
.messaging-qbit-followup-form .messaging-askqbit-send svg { width: 16px; height: 16px; }
/* "Qbit is thinking…" indicator (Scott 2026-06-15) */
.messaging-thinking-bubble { padding: 12px 14px; }
.messaging-typing-dots { display: inline-flex; align-items: center; gap: 4px; }
.messaging-typing-dots span {
  width: 7px; height: 7px; border-radius: 50%; background: var(--text-secondary);
  opacity: 0.4; animation: qbit-typing 1.2s infinite ease-in-out;
}
.messaging-typing-dots span:nth-child(2) { animation-delay: 0.2s; }
.messaging-typing-dots span:nth-child(3) { animation-delay: 0.4s; }
@keyframes qbit-typing {
  0%, 60%, 100% { opacity: 0.3; transform: translateY(0); }
  30% { opacity: 1; transform: translateY(-3px); }
}
/* ⋮ overflow menu for Report/Block in the DM header (Scott 2026-06-15) */
.messaging-overflow { position: relative; display: inline-flex; }
.messaging-overflow-btn { font-size: 1.2rem; line-height: 1; padding: 4px 9px; }
.messaging-overflow-menu {
  position: absolute; top: 100%; right: 0; z-index: 9999; margin-top: 4px;
  min-width: 150px; background: var(--bg-card); border: 1px solid var(--border);
  border-radius: 10px; box-shadow: var(--shadow-lg); padding: 4px;
}
.messaging-overflow-item {
  display: block; width: 100%; text-align: left; border: none; background: transparent;
  padding: 9px 12px; border-radius: 7px; cursor: pointer; font-family: inherit;
  font-size: 0.9rem; color: var(--text);
}
.messaging-overflow-item:hover { background: var(--bg-soft); }
/* "Connect with People" is the whole page now (Scott 2026-06-15) — strip the
   section card chrome + the now-redundant collapsible header (the page title +
   subtitle already say it). The QR/share body shows directly on the page. */
#page-connections .qbit-code-block .qbit-code-collapse-toggle { display: none; }
#page-connections .qbit-code-block,
#page-connections .qbit-code-block .settings-card {
  border: none; background: none; box-shadow: none;
}
/* "New chat" sheet — reuses .messaging-group-modal / -sheet scaffold. */
.messaging-newchat-body { display: flex; flex-direction: column; }
.messaging-newchat-list { max-height: 52vh; overflow-y: auto; }
.messaging-newchat-connect {
  display: flex; align-items: center; gap: 10px; width: 100%;
  margin-top: 8px; padding: 12px 14px; border: none; border-top: 1px solid var(--border);
  background: transparent; color: var(--blue-600, #2563eb);
  font-family: inherit; font-size: 0.92rem; font-weight: 600; cursor: pointer;
}
.messaging-newchat-connect:hover { background: rgba(59, 130, 246, 0.06); }
.messaging-newchat-plus {
  flex-shrink: 0; width: 28px; height: 28px; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  background: rgba(59, 130, 246, 0.12); font-size: 1.1rem; line-height: 1;
}
/* Per-row unread marker — solid Qbit-blue count pill (Scott 2026-06-14;
   the tiny paw img it once carried was dropped 2026-06-14 and its styles
   retired with the rest of the paw motif 2026-07-07). Replaces WhatsApp's
   plain green dot. */
.messaging-unread-badge {
  display: inline-flex; align-items: center; gap: 3px; flex-shrink: 0;
  min-width: 18px; height: 18px; padding: 0 7px; border-radius: 9px;
  /* Solid Qbit blue — match the standard .count-badge pill (Jimmy 2026-06-18);
     was a soft 15% tint with blue text. */
  background: var(--blue-500); color: #fff;
  font-size: 0.74rem; font-weight: 700; line-height: 1; font-variant-numeric: tabular-nums;
}
.messaging-newgroup-btn {
  display: block; width: calc(100% - 16px); margin: 8px; padding: 10px;
  border: 1px dashed var(--blue-400, #60a5fa); background: rgba(59, 130, 246, 0.06);
  color: var(--blue-600, #2563eb); font-family: inherit; font-size: 0.88rem; font-weight: 600;
  border-radius: 10px; cursor: pointer;
}
.messaging-newgroup-btn:hover { background: rgba(59, 130, 246, 0.12); }
/* Group avatar — purple→blue gradient to read distinctly from DM avatars. */
.messaging-group-avatar {
  background: linear-gradient(135deg, var(--purple-500, #8b5cf6), var(--blue-500, #3b82f6)) !important;
  color: #fff !important;
}
/* Create-group + add-people modal (member picker) */
.messaging-group-modal {
  position: fixed; inset: 0; z-index: 100000; display: flex; align-items: center; justify-content: center;
  background: rgba(0, 0, 0, 0.5); padding: 16px;
}
.messaging-group-sheet {
  width: 100%; max-width: 420px; max-height: 80vh; display: flex; flex-direction: column;
  background: var(--bg-card); border-radius: 16px; box-shadow: 0 12px 40px rgba(0, 0, 0, 0.25); overflow: hidden;
}
.messaging-group-head { display: flex; align-items: center; justify-content: space-between; padding: 14px 16px 6px; }
.messaging-group-title { margin: 0; font-size: 1.05rem; font-weight: 700; color: var(--text); }
.messaging-group-close { border: none; background: none; font-size: 1.6rem; line-height: 1; color: var(--text-muted); cursor: pointer; padding: 0 4px; }
.messaging-group-name-input {
  margin: 4px 16px 8px; padding: 10px 12px; border: 1px solid var(--border); border-radius: 10px;
  font-family: inherit; font-size: 0.95rem; background: var(--bg-soft, #f9fafb); color: var(--text);
}
.messaging-pick-list { overflow-y: auto; padding: 4px 8px; flex: 1; min-height: 60px; }
.messaging-pick-row { display: flex; align-items: center; gap: 10px; padding: 10px 8px; border-radius: 8px; cursor: pointer; }
.messaging-pick-row:hover { background: var(--bg-soft, #f3f4f6); }
.messaging-pick-row input { width: 18px; height: 18px; accent-color: var(--blue-500); flex-shrink: 0; }
.messaging-pick-name { font-size: 0.92rem; color: var(--text); }
.messaging-group-confirm {
  margin: 8px 16px 16px; padding: 12px; border: none; border-radius: 10px;
  background: linear-gradient(135deg, var(--blue-500), var(--purple-500, #8b5cf6)); color: #fff;
  font-family: inherit; font-size: 0.95rem; font-weight: 700; cursor: pointer;
}
/* Invite outsiders to a group — link/QR + email/phone (Scott/Jimmy 2026-06-14) */
.messaging-invite-cta {
  display: block; width: calc(100% - 32px); margin: 0 16px 8px; padding: 11px;
  border: 1px dashed var(--blue-400, #60a5fa); background: rgba(59, 130, 246, 0.06);
  color: var(--blue-600, #2563eb); font-family: inherit; font-size: 0.9rem; font-weight: 600;
  border-radius: 10px; cursor: pointer; text-align: center;
}
.messaging-invite-cta:hover { background: rgba(59, 130, 246, 0.12); }
.messaging-invite-body { padding: 4px 16px 16px; }
.messaging-invite-hint { font-size: 0.85rem; color: var(--text-secondary); margin: 0 0 12px; line-height: 1.45; }
.messaging-invite-qr { display: flex; justify-content: center; margin: 0 0 12px; }
.messaging-invite-qr img { width: 168px; height: 168px; image-rendering: pixelated; border-radius: 8px; }
.messaging-invite-linkrow, .messaging-invite-sendrow { display: flex; gap: 8px; align-items: center; }
.messaging-invite-link, .messaging-invite-target {
  flex: 1; min-width: 0; padding: 10px 12px; border: 1px solid var(--border); border-radius: 9px;
  font-family: inherit; font-size: 0.88rem; background: var(--bg-card); color: var(--text);
}
.messaging-invite-copy, .messaging-invite-send {
  flex-shrink: 0; padding: 10px 16px; border: none; border-radius: 9px; cursor: pointer;
  background: var(--blue-500); color: #fff; font-family: inherit; font-size: 0.88rem; font-weight: 600;
}
.messaging-invite-or { text-align: center; font-size: 0.8rem; color: var(--text-muted); margin: 14px 0 10px; }
.messaging-invite-status { font-size: 0.85rem; color: var(--blue-600, #2563eb); margin-top: 10px; min-height: 1.1em; text-align: center; }
/* "Share via…" — primary share action on the group-invite popup (Scott
   2026-06-14, replaced the email/phone send). Full-width native-share button. */
.messaging-invite-share {
  display: flex; align-items: center; justify-content: center; gap: 8px;
  width: 100%; margin-top: 12px; padding: 11px 16px;
  border: none; border-radius: 9px; cursor: pointer;
  background: var(--blue-500); color: #fff;
  font-family: inherit; font-size: 0.9rem; font-weight: 600;
}
.messaging-invite-share:hover { filter: brightness(1.05); }
.messaging-invite-share svg { flex-shrink: 0; }

/* Founder Admin panel + member list + tappable group title (Bucket 2, Scott 2026-06-14) */
.messaging-conversation-titlewrap { display: flex; flex-direction: column; min-width: 0; flex: 1; }
.messaging-titlewrap-tappable { cursor: pointer; }
.messaging-conversation-sub { font-size: 0.74rem; color: var(--text-secondary); line-height: 1.15; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.messaging-titlewrap-tappable .messaging-conversation-sub { color: var(--blue-500); }
.messaging-admin-section { margin: 4px 0 14px; }
.messaging-admin-label { font-size: 0.82rem; font-weight: 600; color: var(--text-secondary); margin-bottom: 8px; }
.messaging-admin-radio { display: flex; align-items: center; gap: 9px; padding: 8px 4px; font-size: 0.92rem; cursor: pointer; }
.messaging-admin-radio input { width: 17px; height: 17px; accent-color: var(--blue-500); }
.messaging-admin-action {
  display: block; width: 100%; text-align: left; padding: 12px 14px; margin-top: 8px;
  border: 1px solid var(--border); border-radius: 10px; background: var(--surface, var(--bg-card));
  color: var(--text); font-family: inherit; font-size: 0.92rem; font-weight: 600; cursor: pointer;
}
.messaging-admin-action:hover { background: var(--bg-soft); }
.messaging-admin-danger { color: #dc2626; border-color: rgba(220,38,38,0.35); }
[data-theme="dark"] .messaging-admin-danger { color: #fca5a5; }
.messaging-members-list { display: flex; flex-direction: column; gap: 2px; max-height: 60vh; overflow-y: auto; }
.messaging-member-row {
  display: flex; align-items: center; gap: 10px; width: 100%; text-align: left;
  padding: 9px 8px; border: none; border-radius: 10px; background: transparent;
  color: var(--text); font-family: inherit; font-size: 0.92rem; cursor: pointer;
}
.messaging-member-row:hover:not([disabled]) { background: var(--bg-soft); }
.messaging-member-row[disabled] { cursor: default; opacity: 0.85; }
.messaging-member-avatar { width: 34px; height: 34px; border-radius: 50%; flex-shrink: 0; object-fit: cover; display: inline-flex; align-items: center; justify-content: center; }
.messaging-member-initial { background: linear-gradient(135deg, #3b82f6, #8b5cf6); color: #fff; font-weight: 700; font-size: 0.9rem; }
.messaging-member-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.messaging-member-dm { flex-shrink: 0; font-size: 0.8rem; font-weight: 600; color: var(--blue-500); }
.header-logo-btn:active { transform: translateY(0.5px); }

.header-right {
  display: flex;
  align-items: center;
  gap: 8px;
}

.header-share-btn {
  background: none;
  border: none;
  color: var(--text-muted);
  cursor: pointer;
  padding: 6px;
  border-radius: 8px;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: color var(--transition), background var(--transition);
}

.header-share-btn:hover {
  color: var(--blue-500);
  background: var(--bg-secondary);
}

/* Header Sign In button — only visible when signed out (display toggled
   in JS). Pill-shaped to look distinct from the other header icons. */
.header-signin-btn {
  background: linear-gradient(135deg, var(--blue-500), var(--purple-500, #8b5cf6));
  color: #ffffff;
  border: none;
  font-size: 0.88rem;
  font-weight: 700;
  padding: 8px 18px;
  border-radius: 999px;
  cursor: pointer;
  letter-spacing: 0.01em;
  box-shadow: 0 2px 8px rgba(59,130,246,0.25);
  transition: transform 0.15s ease, box-shadow 0.15s ease, opacity 0.15s ease;
}
.header-signin-btn:hover {
  transform: translateY(-1px);
  box-shadow: 0 4px 12px rgba(59,130,246,0.35);
}
.header-signin-btn:active {
  transform: translateY(0);
  opacity: 0.9;
}
/* Signed-in state — same button, but Sign OUT shouldn't shout the way
   Sign IN does. Subtle gray pill with a soft border, no shadow, no
   gradient. Still tappable, just intentionally less prominent so it
   doesn't compete with primary actions on the page. */
.header-signin-btn.is-signout {
  background: var(--bg-secondary);
  color: var(--text-muted);
  border: 1px solid var(--border);
  box-shadow: none;
  font-weight: 600;
}
.header-signin-btn.is-signout:hover {
  background: var(--border);
  color: var(--text);
  box-shadow: none;
  transform: none;
}

/* Profile menu — wraps the avatar button + the dropdown panel below it.
   Position: relative anchors the absolutely-positioned dropdown to it.
   Click-outside detection lives in JS (ProfileMenu.bindOutsideClose). */
.profile-menu-wrap {
  position: relative;
}

.header-avatar {
  width: 36px;
  height: 36px;
  border-radius: 50%;
  background: linear-gradient(135deg, var(--blue-400), var(--purple-400));
  display: flex;
  align-items: center;
  justify-content: center;
  color: white;
  font-weight: 600;
  font-size: 0.85rem;
  border: none;
  cursor: pointer;
  padding: 0;
  position: relative;
  overflow: hidden;
  transition: transform .15s, box-shadow .15s;
}
.header-avatar:hover { transform: scale(1.05); box-shadow: 0 4px 12px rgba(59, 130, 246, .35); }
.header-avatar:active { transform: scale(0.96); }
.header-avatar:focus-visible { outline: 2px solid var(--blue-500); outline-offset: 2px; }
.header-avatar-img {
  width: 100%; height: 100%; object-fit: cover; border-radius: 50%;
  position: absolute; inset: 0;
}

/* Dropdown panel anchored under the avatar. Right-aligned so it doesn't
   overflow the viewport on narrow screens. */
.profile-menu {
  position: absolute;
  top: calc(100% + 8px);
  right: 0;
  min-width: 260px;
  background: var(--bg-card, var(--bg));
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 14px;
  box-shadow: 0 12px 28px rgba(0,0,0,.18), 0 4px 10px rgba(0,0,0,.10);
  padding: 8px;
  z-index: 250;
  animation: profile-menu-in .14s ease-out;
}
@keyframes profile-menu-in {
  from { opacity: 0; transform: translateY(-6px); }
  to   { opacity: 1; transform: none; }
}

.profile-menu-header {
  display: flex; align-items: center; gap: 12px;
  padding: 12px 12px 8px;
}
.profile-menu-avatar {
  position: relative;
  width: 56px; height: 56px;
  border-radius: 50%;
  background: linear-gradient(135deg, var(--blue-400), var(--purple-400));
  border: none;
  cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  color: white;
  font-weight: 700;
  font-size: 1.4rem;
  overflow: visible;
  flex-shrink: 0;
}
.profile-menu-avatar:hover .profile-menu-avatar-camera { transform: scale(1.08); }
.profile-menu-avatar-img {
  width: 100%; height: 100%; object-fit: cover; border-radius: 50%;
  position: absolute; inset: 0;
}
.profile-menu-avatar-initial {
  display: flex; align-items: center; justify-content: center;
  width: 100%; height: 100%; border-radius: 50%;
}
.profile-menu-avatar-camera {
  position: absolute;
  right: -2px; bottom: -2px;
  width: 22px; height: 22px;
  border-radius: 50%;
  background: var(--blue-500);
  color: #fff;
  display: flex; align-items: center; justify-content: center;
  border: 2px solid var(--bg-card, var(--bg));
  transition: transform .15s;
  box-shadow: 0 1px 3px rgba(0,0,0,.2);
}
.profile-menu-name-block { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.profile-menu-name {
  font-size: 0.95rem; font-weight: 700; color: var(--text);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  max-width: 160px;
}
.profile-menu-email {
  font-size: 0.78rem; color: var(--text-muted);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  max-width: 160px;
}

.profile-menu-divider {
  height: 1px; background: var(--border); margin: 6px 4px;
}

.profile-menu-item {
  display: flex; align-items: center; gap: 12px;
  width: 100%;
  padding: 10px 12px;
  border: none; border-radius: 8px;
  background: transparent;
  color: var(--text);
  font-family: inherit; font-size: 0.92rem;
  text-align: left; cursor: pointer;
  transition: background .12s, color .12s;
}
.profile-menu-item:hover { background: var(--bg-secondary, rgba(0,0,0,.04)); }
.profile-menu-item svg { color: var(--text-muted); flex-shrink: 0; }
.profile-menu-item:hover svg { color: var(--text); }
.profile-menu-item-danger { color: #b91c1c; }
.profile-menu-item-danger svg { color: #b91c1c; }
.profile-menu-item-danger:hover { background: #fee2e2; color: #991b1b; }
.profile-menu-item-danger:hover svg { color: #991b1b; }
[data-theme="dark"] .profile-menu-item-danger { color: #fca5a5; }
[data-theme="dark"] .profile-menu-item-danger svg { color: #fca5a5; }
[data-theme="dark"] .profile-menu-item-danger:hover { background: rgba(220,38,38,.18); color: #fecaca; }
[data-theme="dark"] .profile-menu-item-danger:hover svg { color: #fecaca; }

.profile-menu-badge {
  margin-left: auto;
  font-size: 0.62rem; font-weight: 700;
  letter-spacing: .04em; text-transform: uppercase;
  background: linear-gradient(135deg, var(--blue-500), var(--purple-400));
  color: #fff;
  padding: 2px 8px; border-radius: 10px;
}

/* ===== Knowledge Base stub page ===== */
.kb-stub-card {
  max-width: 640px; margin: 24px auto;
  padding: 36px 28px;
  background: var(--bg-card, var(--bg));
  border: 1px solid var(--border);
  border-radius: 18px;
  text-align: center;
}
.kb-stub-icon {
  display: inline-flex; align-items: center; justify-content: center;
  width: 88px; height: 88px;
  margin-bottom: 16px;
  border-radius: 50%;
  background: linear-gradient(135deg, rgba(59,130,246,.12), rgba(168,85,247,.12));
  color: var(--blue-500);
}
.kb-stub-card h2 { margin: 0 0 8px; font-size: 1.35rem; font-weight: 700; }
.kb-stub-lede { margin: 0 auto 24px; font-size: 0.95rem; line-height: 1.55; color: var(--text-secondary); max-width: 520px; }
.kb-stub-features { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; margin: 24px 0; text-align: left; }
.kb-stub-feature {
  display: flex; flex-direction: column; gap: 4px;
  padding: 14px;
  background: var(--bg-secondary, rgba(0,0,0,.03));
  border-radius: 12px;
}
.kb-stub-feature strong { font-size: 0.88rem; }
.kb-stub-feature span { font-size: 0.78rem; color: var(--text-muted); line-height: 1.45; }
.kb-stub-foot { margin-top: 8px; font-size: 0.82rem; }

/* Consolidated save bar between Ads and Preferences. The .settings-save-btn
   inside is auto-injected (data-force-save-button) and uses the existing
   gradient styling — we just lay out the surrounding card to feel like
   a clear "page-bottom action" rather than a setting in its own right. */
.settings-save-bar {
  padding: 4px 0 0 0;
  background: transparent;
  border: none;
  box-shadow: none;
}
.settings-save-bar-hint {
  margin: 0;
  padding: 14px 18px 8px 18px;
  font-size: 0.78rem;
  color: var(--text-muted);
  text-align: center;
  font-style: italic;
}

/* "Why this is safe" reassurance card on the My Documents page.
   Replaces the old kb-stub-features grid — kept compact + readable
   so users can scan it without scrolling far. Each row is one
   trust principle with a bold lead-in + plain explanation. */
.kb-trust-card {
  padding: 14px 18px;
}
.kb-trust-row {
  display: block;
  padding: 10px 0;
  font-size: 0.88rem;
  line-height: 1.5;
  color: var(--text-muted);
  border-bottom: 1px solid var(--border);
}
.kb-trust-row:last-child { border-bottom: none; }
.kb-trust-row strong {
  color: var(--text);
  margin-right: 6px;
  font-weight: 700;
}
@media (max-width: 600px) {
  .kb-stub-features { grid-template-columns: 1fr; }
}

/* ===== Slide-out Menu ===== */
.menu-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.5);
  /* Above the bottom tab bar (z:850) so the scrim dims it too — the drawer
     is a top-level overlay, not something the tab bar should sit over.
     (Slide-menu bottom-clip fix 2026-06-23) */
  z-index: 900;
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.3s, visibility 0.3s;
}

.menu-overlay.open {
  opacity: 1;
  visibility: visible;
}

.slide-menu {
  position: fixed;
  top: 0;
  left: 0;
  bottom: 0;
  width: 300px;
  max-width: 85vw;
  background: var(--bg);
  /* Above the bottom tab bar (z:850) so the drawer overlays it, instead of
     the tab bar covering the last items (Share link + footer). (2026-06-23) */
  z-index: 901;
  transform: translateX(-100%);
  transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
  display: flex;
  flex-direction: column;
  /* Inset the column above the iOS home indicator so the footer + last nav
     item (Share) clear it instead of being clipped at the screen edge. */
  padding-bottom: env(safe-area-inset-bottom, 0px);
  box-shadow: var(--shadow-lg);
}

.slide-menu.open {
  transform: translateX(0);
}

.menu-header {
  padding: 24px 20px;
  display: flex;
  align-items: center;
  gap: 14px;
  border-bottom: 1px solid var(--border);
  text-decoration: none;
  color: inherit;
  cursor: pointer;
  transition: background var(--transition);
}

.menu-header:hover {
  background: rgba(59, 130, 246, 0.06);
}

.menu-avatar {
  width: 52px;
  height: 52px;
  border-radius: 50%;
  object-fit: cover;
  box-shadow: 0 0 0 3px var(--blue-200);
}

[data-theme="dark"] .menu-avatar {
  box-shadow: 0 0 0 3px var(--blue-700);
}

.menu-user-info h3 {
  font-size: 1rem;
  font-weight: 600;
}

.menu-user-info p {
  font-size: 0.8rem;
  color: var(--text-secondary);
}

.menu-nav {
  flex: 1;
  padding: 12px 0;
  overflow-y: auto;
}

.menu-nav a {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 13px 22px;
  color: var(--text);
  text-decoration: none;
  font-size: 0.95rem;
  font-weight: 500;
  transition: background var(--transition);
  cursor: pointer;
}

.menu-nav a:hover,
.menu-nav a.active {
  background: var(--bg-secondary);
}

.menu-nav a.active {
  color: var(--blue-500);
}

.menu-nav a svg {
  width: 22px;
  height: 22px;
  opacity: 0.7;
}

.menu-nav a.active svg {
  opacity: 1;
}

/* Branded PNG home icon in the slide menu — purpose-made active/inactive
   versions (Jimmy 2026-06-23). Gray "slideout" icon when Home isn't the active
   page; colored "active" icon when it is. CSS swaps them on .active. */
.menu-nav a img {
  width: 22px;
  height: 22px;
  flex-shrink: 0;
  object-fit: contain;
}
/* Slide-menu Home creature — grayscale until Home is the active entry
   (same treatment as the bottom-bar tab; de-pawed 2026-07-07). */
.menu-nav a .menu-home-img { filter: grayscale(1) opacity(0.62); transition: filter 0.12s ease; }
.menu-nav a.active .menu-home-img { filter: none; }

/* Sign-in CTA — only shown when signed out. Gradient highlight makes it
   the most obvious action for a new/returning unauthenticated user. */
.menu-signin-link {
  background: linear-gradient(135deg, rgba(59,130,246,0.12), rgba(139,92,246,0.12));
  color: var(--blue-500) !important;
  font-weight: 700 !important;
  margin: 8px 14px;
  border-radius: 10px;
  padding: 13px 18px !important;
  border: 1px solid rgba(59,130,246,0.25);
}
.menu-signin-link:hover {
  background: linear-gradient(135deg, rgba(59,130,246,0.20), rgba(139,92,246,0.20)) !important;
}
.menu-signin-link svg {
  opacity: 1 !important;
  color: var(--blue-500);
}

.menu-divider {
  height: 1px;
  background: var(--border);
  margin: 8px 20px;
}

.menu-footer {
  padding: 16px 20px;
  border-top: 1px solid var(--border);
  font-size: 0.75rem;
  color: var(--text-muted);
  text-align: center;
}

/* ===== Pages ===== */
.page {
  display: none;
  padding: 0 20px 100px;
  animation: fadeIn 0.25s ease;
}

.page.active {
  display: block;
}

@keyframes fadeIn {
  from { opacity: 0; transform: translateY(8px); }
  to { opacity: 1; transform: translateY(0); }
}

/* ===== Home Page ===== */
.hero {
  text-align: center;
  padding: 32px 0 24px;
}

.hero-avatar {
  width: 100px;
  height: 100px;
  margin: 0 auto 4px;
  position: relative;
}

.hero-avatar img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  filter: drop-shadow(0 4px 12px rgba(59, 130, 246, 0.3));
}

.speech-bubble {
  position: absolute;
  top: -4px;
  /* Anchor the bubble's LEFT edge just inside the avatar's right side
     (the bottom-left "tail" overlaps Qbit's cheek). The bubble then grows
     RIGHTWARD as content grows — so "Hi Maximilian!" extends out to the
     right instead of crawling leftward into Qbit's face. */
  left: calc(100% - 22px);
  right: auto;
  background: var(--green-400);
  color: #1a2e05;
  font-weight: 700;
  font-size: 0.7rem;
  padding: 3px 10px;
  border-radius: 12px 12px 12px 2px;
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15);
  white-space: nowrap;     /* keep the greeting on one line */
  max-width: 160px;        /* safety net for very long names — won't break layout */
  overflow: hidden;
  text-overflow: ellipsis;
}

.hero h1 {
  font-size: 1.5rem;
  font-weight: 700;
  margin-bottom: 6px;
  background: linear-gradient(135deg, var(--blue-500), var(--purple-500));
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
}

.hero-tagline {
  color: var(--blue-500);
  font-weight: 600;
  font-size: 0.9rem;
  margin-bottom: 4px;
}

.hero p {
  color: var(--text-secondary);
  font-size: 0.95rem;
  /* Belt-and-suspenders: even with the responsive full/short swap
     below, balance ensures any unforeseen wrap (e.g. landscape narrow
     viewports, large accessibility font sizes) still splits cleanly
     instead of orphaning a single word. */
  text-wrap: balance;
}

/* RESPONSIVE TAGLINE SWAP (2026-05-14)
   Scott's full tagline ("Welcome to Qbit, I'm here to help with anything
   you need!") carries intent in its phrasing — "you need" is the warmth
   marker, and Scott specifically asked we keep it whenever possible.
   But it's 57 chars and wraps awkwardly on phones under ~460px wide.

   Solution: ship BOTH copies in the HTML, hide one with CSS based on
   viewport. Default state = full tagline visible, short hidden.
   Under 460px = swap. Cleaner than JS-driven copy switching, and
   no flash of the wrong copy on first paint. */
.hero-intro-short { display: none; }

@media (max-width: 460px) {
  .hero-intro-full  { display: none; }
  .hero-intro-short { display: block; }
}

/* Add to Homescreen Banner */
/* ============================================================
 * SW UPDATE BANNER — small floating toast bottom-right when a new
 * version of the app is ready. Replaces the old auto-reload-on-SW-
 * update behavior so users in the middle of reading / typing /
 * uploading don't get yanked back to the home page. (See app.js
 * showUpdateBanner() for the trigger logic and the 2026-05-12
 * incident note.)
 * ============================================================ */
.sw-update-banner {
  position: fixed;
  /* Bottom-right on desktop; iOS PWA safe-area inset added so it
     doesn't get hidden behind the home indicator on iPhone. */
  bottom: calc(16px + env(safe-area-inset-bottom, 0px));
  right: 16px;
  z-index: 1100;            /* above modals (which sit at 1000) */
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 12px 10px 16px;
  background: linear-gradient(135deg, var(--blue-500), var(--purple-600));
  color: white;
  border-radius: 12px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.2);
  font-size: 13px;
  /* Mobile: span the bottom of the viewport with a comfy gutter
     instead of corner-floating; easier to thumb. */
  max-width: calc(100vw - 32px);
}
@media (max-width: 480px) {
  .sw-update-banner {
    left: 16px;
    right: 16px;
    justify-content: space-between;
  }
}
.sw-update-banner-icon {
  font-size: 16px;
  line-height: 1;
}
.sw-update-banner-text {
  font-weight: 500;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.sw-update-banner-reload {
  border: none;
  background: rgba(255, 255, 255, 0.22);
  color: white;
  font-weight: 600;
  font-size: 13px;
  padding: 6px 12px;
  border-radius: 8px;
  cursor: pointer;
  transition: background 0.15s ease;
}
.sw-update-banner-reload:hover {
  background: rgba(255, 255, 255, 0.35);
}
.sw-update-banner-dismiss {
  border: none;
  background: transparent;
  color: rgba(255, 255, 255, 0.85);
  font-size: 22px;
  line-height: 1;
  cursor: pointer;
  padding: 0 4px;
  border-radius: 4px;
}
.sw-update-banner-dismiss:hover {
  background: rgba(255, 255, 255, 0.15);
  color: white;
}

.pwa-banner {
  background: linear-gradient(135deg, var(--blue-500), var(--purple-600));
  color: white;
  padding: 14px 18px;
  border-radius: var(--radius-sm);
  margin: 0 0 20px;
  display: flex;
  align-items: center;
  gap: 12px;
  cursor: pointer;
  transition: transform var(--transition), opacity var(--transition);
  box-shadow: 0 4px 15px rgba(59, 130, 246, 0.3);
}

.pwa-banner:hover {
  transform: scale(1.01);
}

.pwa-banner svg {
  width: 28px;
  height: 28px;
  flex-shrink: 0;
}

.pwa-banner-text {
  flex: 1;
}

.pwa-banner-text strong {
  display: block;
  font-size: 0.9rem;
}

.pwa-banner-text span {
  font-size: 0.78rem;
  opacity: 0.85;
}

.pwa-banner-close {
  background: none;
  border: none;
  color: white;
  opacity: 0.7;
  cursor: pointer;
  padding: 4px;
  font-size: 1.2rem;
  line-height: 1;
}

.pwa-banner-close:hover {
  opacity: 1;
}

/* Greeting Flow */
.greeting-flow {
  margin-bottom: 24px;
}

.greeting-flow h2 {
  font-size: 1.3rem;
  font-weight: 700;
  margin-bottom: 2px;
}

.greeting-sub {
  color: var(--text-secondary);
  font-size: 0.95rem;
  margin-bottom: 16px;
}

/* Setup Prompt */
.setup-prompt {
  margin-bottom: 16px;
}

.setup-prompt-link {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 14px 18px;
  background: linear-gradient(135deg, rgba(59, 130, 246, 0.08), rgba(139, 92, 246, 0.08));
  border: 1px dashed var(--blue-400);
  border-radius: var(--radius-sm);
  color: var(--blue-500);
  font-size: 0.9rem;
  font-weight: 600;
  text-decoration: none;
  cursor: pointer;
  transition: background var(--transition), transform var(--transition);
}

.setup-prompt-link:hover {
  background: linear-gradient(135deg, rgba(59, 130, 246, 0.14), rgba(139, 92, 246, 0.14));
  transform: translateY(-1px);
}

.setup-prompt-link svg {
  flex-shrink: 0;
  opacity: 0.8;
}

/* Category context panel — appears between greeting + search box when
   a Quick-Action card is clicked. Replaces the old "auto-fire a sample
   query" behavior with a guided descriptor of what Qbit can help with
   in that category. (Scott #31.) */
.category-context {
  margin: 0 auto 16px;
  max-width: 680px;
  padding: 18px 20px;
  background: linear-gradient(135deg, rgba(59,130,246,0.08), rgba(139,92,246,0.06));
  border: 1px solid rgba(59,130,246,0.20);
  border-radius: 14px;
  box-shadow: 0 2px 10px rgba(59,130,246,0.06);
  text-align: left;
  animation: category-context-in .22s ease-out;
}
[data-theme="dark"] .category-context {
  background: linear-gradient(135deg, rgba(59,130,246,0.15), rgba(139,92,246,0.10));
  border-color: rgba(59,130,246,0.35);
}
@keyframes category-context-in {
  from { opacity: 0; transform: translateY(-6px); }
  to   { opacity: 1; transform: none; }
}
.category-context-head {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 8px;
}
.category-context-icon {
  font-size: 22px;
  line-height: 1;
  flex-shrink: 0;
}
.category-context-title {
  flex: 1;
  font-size: 1.05rem;
  font-weight: 700;
  color: var(--text);
  letter-spacing: -0.01em;
}
.category-context-close {
  width: 28px; height: 28px;
  border: none;
  background: transparent;
  color: var(--text-muted);
  font-size: 22px;
  line-height: 1;
  cursor: pointer;
  border-radius: 6px;
  display: flex; align-items: center; justify-content: center;
  transition: background .12s, color .12s;
}
.category-context-close:hover {
  background: rgba(0,0,0,0.06);
  color: var(--text);
}
[data-theme="dark"] .category-context-close:hover {
  background: rgba(255,255,255,0.08);
}
.category-context-body {
  font-size: 0.95rem;
  line-height: 1.55;
  color: var(--text);
}
.category-context-body b, .category-context-body strong { color: var(--text); }

/* "Or try this:" + suggested prompt — restructured (Scott #37):
   the caption is now a separate label sitting ABOVE the button, and the
   button gets a full-width row with the prompt allowed to wrap. Solves
   the ellipsis-truncation problem the inline-pill version had when
   prompts ran longer than ~50 characters. */
.category-context-try-wrap {
  margin-top: 14px;
  display: flex;
  flex-direction: column;
  /* gap removed (was 6px) — label sits flush against the prompt button
     below. Per Scott: prior spacing made the label feel detached. */
  gap: 0;
}
.category-context-try-label {
  font-size: 0.78rem;
  font-weight: 700;
  color: var(--blue-500);
  letter-spacing: 0.02em;
  text-transform: uppercase;
  /* Tighten line-height so the label baseline sits closer to the button
     above it. Default 1.5 left ~6px of visual whitespace under the text;
     1.2 cuts it nearly in half. Combined with gap:0 this lands the label
     directly hugging the button — no daylight in between. */
  line-height: 1.2;
  margin-bottom: 4px;
}
.category-context-try {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  padding: 12px 16px;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 12px;
  font-family: inherit;
  font-size: 0.92rem;
  color: var(--text);
  cursor: pointer;
  text-align: left;
  transition: border-color .15s, background .15s, transform .08s;
}
.category-context-try:hover {
  border-color: var(--blue-400);
  background: rgba(59,130,246,0.04);
}
.category-context-try:active { transform: scale(0.99); }
.category-context-try-prompt {
  flex: 1;
  /* Allow the prompt to wrap onto multiple lines if needed — no more
     ellipsis truncation. line-height kept tight for compact appearance. */
  line-height: 1.45;
  color: var(--text);
  word-break: break-word;
}
.category-context-try-arrow {
  flex-shrink: 0;
  font-size: 1.1rem;
  color: var(--blue-500);
  transition: transform .15s;
}
.category-context-try:hover .category-context-try-arrow {
  transform: translateX(3px);
}

/* Subtle "active" feedback on the card the user clicked, so they remember
   which category they activated. */
.action-card.is-active-category {
  outline: 2px solid var(--blue-400);
  outline-offset: -1px;
  background: rgba(59,130,246,0.04);
}

/* Search / Input Area */
.search-area {
  position: relative;
  /* Was 28px before the Image/Video options row was added beneath
     the bubble. Tightened to 6px so the options row sits closer to
     its bubble, matching Jimmy's DevTools-tuned target spacing
     (2026-05-11). */
  margin-bottom: 6px;
}

.search-input {
  width: 100%;
  /* Left padding accommodates the SINGLE + attach button (38w at
     left:6px). 52px gives a small breathing gap before placeholder
     text starts. Was 78px when we had two attach icons (paperclip +
     doc); collapsed to 52px on 2026-05-14 when the icons merged
     into one + menu (Scott + Jimmy). */
  /* Right padding clears TWO stacked controls: the morph slot (voice
     waveform ↔ send arrow, right:6) + mic dictate (right:52) — 100px
     total (2026-07-13, two-button morph; was 146px for three). */
  padding: 16px 100px 16px 52px;
  border: 2px solid var(--border);
  border-radius: 28px;
  background: var(--bg-card);
  color: var(--text);
  font-size: 1rem;
  outline: none;
  transition: border-color var(--transition), box-shadow var(--transition);
  font-family: inherit;
  /* Auto-growing <textarea> (was a single-line <input>, 2026-07-04): starts
     as one row and grows with the query — JS caps height at 140px (~5 lines)
     then it scrolls — so users can see their whole prompt instead of a
     one-line window. Mirrors .chat-input. The round +/send buttons stay
     vertically centered (same as the chat box). */
  line-height: 1.45;
  min-height: 56px;
  max-height: 140px;
  resize: none;
  overflow-y: auto;
  vertical-align: bottom;
  box-sizing: border-box;
}

.search-input::placeholder {
  color: var(--text-muted);
}

.search-input:focus {
  border-color: var(--blue-400);
  box-shadow: 0 0 0 4px rgba(59, 130, 246, 0.12);
}

.search-btn {
  position: absolute;
  right: 6px;
  top: 50%;
  transform: translateY(-50%);
  width: 42px;
  height: 42px;
  border: none;
  border-radius: 50%;
  background: linear-gradient(135deg, var(--blue-500), var(--purple-500));
  color: white;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: transform var(--transition), box-shadow var(--transition);
}

.search-btn:hover {
  transform: translateY(-50%) scale(1.05);
  box-shadow: 0 4px 12px rgba(59, 130, 246, 0.4);
}

.search-btn svg {
  width: 20px;
  height: 20px;
}

/* ===== Unified "+" attach button + popover menu =====
   Replaced the previous paperclip + doc-icon pair on 2026-05-14
   (Scott + Jimmy). One iOS-Messages-style + button on the left edge
   of the input opens a small menu offering Photo / Document. Same
   markup on the homepage and the chat input row, same CSS.

   The wrapping div is anchored INSIDE .search-area / .chat-input-row
   so position:absolute on .attach-plus-btn aligns relative to the
   input. The popover .attach-menu is also position:absolute,
   anchored to the wrap so it floats above the button. */
.attach-menu-wrap {
  position: static;       /* anchor stays on the parent (.search-area / .chat-input-row) */
}

.attach-plus-btn {
  position: absolute;
  left: 6px;
  top: 50%;
  transform: translateY(-50%);
  width: 38px;
  height: 38px;
  border: none;
  border-radius: 50%;
  background: var(--bg-secondary, #f3f4f6);
  color: var(--text-muted);
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background var(--transition), color var(--transition), transform 120ms;
  z-index: 2;             /* above .search-input border */
  padding: 0;
}
.attach-plus-btn:hover,
.attach-plus-btn:focus-visible {
  background: rgba(59, 130, 246, 0.12);
  color: var(--blue-500);
  outline: none;
}
.attach-plus-btn:active {
  transform: translateY(-50%) scale(0.94);
}
.attach-plus-btn[aria-expanded="true"] {
  background: var(--blue-500);
  color: white;
}
.attach-plus-btn svg {
  width: 18px;
  height: 18px;
}

/* The popover menu — opens above the + button. iOS-Messages style:
   white card, rounded corners, soft shadow, dividers between items.
   Uses position:absolute relative to .search-area / .chat-input-row
   (whichever is the nearest positioned ancestor). */
.attach-menu {
  position: absolute;
  bottom: calc(100% + 8px);   /* sits 8px above the input row */
  left: 6px;
  min-width: 180px;
  background: var(--bg-card, white);
  border: 1px solid var(--border);
  border-radius: 14px;
  box-shadow: 0 6px 20px rgba(0,0,0,0.12), 0 1px 3px rgba(0,0,0,0.05);
  padding: 4px;
  z-index: 30;
  animation: attach-menu-in 140ms ease-out;
}
.attach-menu[hidden] {
  display: none;
}

@keyframes attach-menu-in {
  from { opacity: 0; transform: translateY(4px); }
  to   { opacity: 1; transform: translateY(0); }
}

.attach-menu-item {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  padding: 10px 14px;
  border: none;
  background: transparent;
  border-radius: 10px;
  color: var(--text);
  font-size: 0.95rem;
  font-family: inherit;
  cursor: pointer;
  text-align: left;
  transition: background 100ms;
}
.attach-menu-item:hover,
.attach-menu-item:focus-visible {
  background: var(--bg-secondary, #f3f4f6);
  outline: none;
}
.attach-menu-item:active {
  background: rgba(59, 130, 246, 0.10);
}
.attach-menu-icon {
  width: 18px;
  height: 18px;
  flex-shrink: 0;
  color: var(--text-muted);
}
.attach-menu-item:hover .attach-menu-icon,
.attach-menu-item:focus-visible .attach-menu-icon {
  color: var(--blue-500);
}
.attach-menu-label {
  font-weight: 500;
}
.attach-menu-divider {
  height: 1px;
  background: var(--border);
  margin: 2px 4px;
}

/* The attached-image strip sits ABOVE the search bar — a row of small
   thumbnails with X buttons for removal. Hidden by default (the
   [hidden] attribute does the work; this just makes the visible state
   look right). */
.search-attachments {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-bottom: 10px;
}
.search-attachment-chip {
  position: relative;
  width: 56px;
  height: 56px;
  border-radius: 10px;
  overflow: hidden;
  border: 1px solid var(--border);
  background: var(--bg-card);
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06);
}
.search-attachment-chip img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
.search-attachment-remove {
  position: absolute;
  top: 2px;
  right: 2px;
  width: 18px;
  height: 18px;
  border: none;
  border-radius: 50%;
  background: rgba(0, 0, 0, 0.65);
  color: white;
  font-size: 14px;
  line-height: 1;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  transition: background var(--transition);
}
.search-attachment-remove:hover {
  background: rgba(220, 38, 38, 0.9); /* red-600 */
}

/* ===== Chat page (page-chat) =====
   Immersive full-viewport conversation experience. Sits as a fixed
   overlay between the global app-header and the viewport bottom.
   The global header (.app-header) stays visible above; the global
   page-trademark footer is hidden behind this overlay (intentional —
   chat is a focused experience, no marketing chrome).

   Why position: fixed? The earlier attempt used `min-height: calc(...)`
   in normal flow and ran into two problems we kept fighting:
     (1) The chat-page-header tried `position: sticky; top: 0`, which
         collided with the global app-header (also sticky at top:0)
         and hid behind it (lower z-index).
     (2) The chat-input-area used `position: sticky; bottom: 0`,
         but sticky's contract is "stop sticking when the parent's
         bottom edge enters view." The global trademark footer below
         the chat page yanked the parent bottom into view as the
         user scrolled, so the input would un-stick and float
         mid-page.

   Fixing the parent to the viewport (top: 64px to clear the global
   header, bottom: 0) gives the inner flex column DETERMINISTIC
   dimensions. Thread scrolls inside a known-size container — no
   sticky tricks needed, no flex-basis-auto trap from prior rounds. */
#page-chat.active {
  display: flex !important;       /* override default .page block */
  flex-direction: column;
  position: fixed;
  /* 64px clearance matches the visible height of .app-header
     (12px top padding + ~40px content + 12px bottom padding). */
  top: 64px;
  /* Lift the overlay's bottom 40px so the global page-trademark
     footer (pinned via :has() rule below) sits visibly beneath
     the chat surface. Without the lift, the trademark would
     overlap the bottom of the input bar. */
  bottom: 40px;
  /* Match the .app-shell column width so the chat visually aligns
     with the global Qbit header above it. Phone uses the same 480px
     the shell uses; tablet+ expands to 760px (matches the responsive
     shell rules added 2026-05-16). The chat overlay doesn't go FULL
     width on tablet portrait even though the shell does, because
     conversations read better as a centered narrow column — the
     individual .qbit-turn already caps at 760px anyway. */
  left: 50%;
  transform: translateX(-50%);
  width: 100%;
  max-width: 480px;
  padding: 0;
  margin: 0;
  background: var(--bg);
  z-index: 5;                     /* under app-header (z:100), above body */
}
@media (min-width: 600px) and (orientation: portrait), (min-width: 900px) {
  #page-chat.active {
    max-width: 760px;
  }
}

/* Pin the global trademark footer to the viewport bottom WHILE THE
   CHAT PAGE IS ACTIVE. On every other page the trademark sits at
   the bottom of the scrolled content (its natural document-flow
   position); only the chat page — which uses a fixed-position
   overlay — needs the trademark to be force-pinned so it shows
   below the overlay's bottom edge. :has() lets us scope this
   without touching the trademark's CSS for other pages.
   Width matches the .app-shell column (480px centered) so the
   trademark text is below the centered chat overlay, not floating
   off to the side. */
body:has(#page-chat.active) .page-trademark {
  position: fixed;
  bottom: 0;
  left: 50%;
  transform: translateX(-50%);
  width: 100%;
  max-width: 480px;
  z-index: 6;                     /* above chat overlay (z:5) */
  background: var(--bg);
  padding: 8px 16px 12px;
}
/* Mirror the chat overlay's responsive max-width so the trademark
   strip stays centered under the chat surface, not floating to a
   smaller width than the chat above it (Jimmy 2026-05-16). */
@media (min-width: 600px) and (orientation: portrait), (min-width: 900px) {
  body:has(#page-chat.active) .page-trademark {
    max-width: 760px;
  }
}
/* Hide page-footer-links on the chat page — the trademark is
   the only piece we need to keep visible; the Privacy/Terms/About
   links would crowd the small bottom strip. */
body:has(#page-chat.active) .page-footer-links {
  display: none;
}

/* Hide the global footer (Privacy/Terms/About links, trademark, version) on the
   DMs/Groups page. With a short contact list it otherwise floats in the middle of
   the screen above the bottom nav, which reads as a broken layout. (Scott/Jimmy
   2026-06-16) */
body:has(#page-messages.active) .page-footer-links,
body:has(#page-messages.active) .page-trademark,
body:has(#page-messages.active) .app-version {
  display: none;
}

.chat-page {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  width: 100%;
  /* No max-width — full viewport. Each section below centers its
     own content at 760px via calc-padding. */
  margin: 0;
  min-height: 0;                  /* allow children to shrink for scroll */
  overflow: hidden;               /* outer wrapper; thread handles its own scroll */
}

/* Sub-header inside the chat page. No sticky needed — the parent
   #page-chat is itself fixed, so this naturally sits at the top.
   The whole chat overlay is constrained to .app-shell's 480px
   width, so simple padding suffices (no calc-centering needed). */
.chat-page-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 10px 16px;
  background: var(--bg-card);
  border-bottom: 1px solid var(--border);
  flex-shrink: 0;                 /* never crushed by the thread below */
}
.chat-page-title {
  font-size: 0.95rem;
  font-weight: 600;
  color: var(--text);
  letter-spacing: -0.01em;
}
.chat-back-btn,
.chat-new-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px 12px;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: transparent;
  color: var(--text-secondary);
  font-size: 0.85rem;
  font-family: inherit;
  cursor: pointer;
  transition: background var(--transition), color var(--transition), border-color var(--transition);
}
.chat-back-btn:hover,
.chat-new-btn:hover,
.chat-back-btn:focus-visible,
.chat-new-btn:focus-visible {
  background: rgba(59, 130, 246, 0.08);
  color: var(--blue-500);
  border-color: var(--blue-300, var(--border));
  outline: none;
}
.chat-back-label,
.chat-new-label {
  /* Hide labels on narrow viewports — keep just the icons to save
     horizontal space on phones. */
}
@media (max-width: 480px) {
  .chat-back-label,
  .chat-new-label {
    display: none;
  }
  .chat-back-btn,
  .chat-new-btn {
    padding: 8px 10px;
  }
}

/* Thread — scrolls inside the chat-page flex column. The parent
   #page-chat is position:fixed with definite top + bottom, so the
   flex layout has DETERMINISTIC available space and `flex: 1 1 0
   + min-height: 0` lets the thread shrink correctly when content
   overflows.
   --
   Centering pattern: thread is FULL WIDTH so the scrollbar sits at
   the right edge of the viewport (where users expect it on chat
   apps). Inner turns are centered with `align-items: center` and
   `max-width: 760px` on each .qbit-turn (see further down). This
   way long-line wrapping looks like a chat-app reading column,
   not a banner stretched edge-to-edge.
   --
   overflow-y:scroll (not auto) so the scrollbar gutter is always
   visible — macOS hides auto-mode bars until hover. */
.chat-thread {
  flex: 1 1 0;
  min-height: 0;
  overflow-y: scroll;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain;   /* don't leak wheel scroll out to body */
  display: flex;
  flex-direction: column;
  align-items: center;            /* center inner .qbit-turn cards */
  gap: 18px;
  padding: 18px 16px;
  background: var(--bg);

  /* Custom scrollbar styling — match the rest of the brand. */
  scrollbar-width: thin;
  scrollbar-color: var(--border) transparent;
}

/* Each turn (Q + A bubble pair) is centered in the wide thread at
   a comfortable reading width. Width:100% lets it shrink on mobile;
   max-width caps it at 760px on desktop. */
.chat-thread .qbit-turn {
  width: 100%;
  max-width: 760px;
}
.chat-thread::-webkit-scrollbar {
  width: 8px;
}
.chat-thread::-webkit-scrollbar-track {
  background: transparent;
}
.chat-thread::-webkit-scrollbar-thumb {
  background: var(--border);
  border-radius: 4px;
}
.chat-thread::-webkit-scrollbar-thumb:hover {
  background: var(--text-muted);
}

/* Bottom input bar. Sits at the bottom of the flex column —
   no sticky / fixed needed since the parent is itself fixed and
   the column has bounded height. flex-shrink: 0 keeps it from
   collapsing under a long thread. */
.chat-input-area {
  flex-shrink: 0;
  background: var(--bg-card);
  border-top: 1px solid var(--border);
  padding: 10px 14px env(safe-area-inset-bottom, 10px);
  /* Subtle top shadow so the input visually separates from
     scrolled-up content above. */
  box-shadow: 0 -4px 12px rgba(0, 0, 0, 0.04);
}

/* ─── Chat → Image Options disclosure ────────────────────
   Lives below the input row. Collapsed by default — quiet for
   text-only users. When the user opens it and picks a non-default
   style or aspect, those preferences are saved to localStorage and
   automatically woven into future image-generation requests via
   decorateQueryWithImageOptions() in app.js. */
.chat-image-options {
  margin-top: 6px;
  font-size: 13px;
}

/* Wraps the Image + Video options disclosures so they sit on a single
   row instead of stacking. Used on both the homepage hero and the
   chat page. Falls back to a single column under ~360px (very narrow
   phone) so the labels never clip. When either disclosure expands,
   align-items: flex-start keeps the unopened sibling pinned to the
   top of the row instead of vertically centering.

   Disclosures take their natural width (flex: 0 1 auto) so the pills
   sit close together with the | separator between them — instead of
   stretching across the full row width. The bubble→pill vertical gap
   is unchanged from the pre-row layout (disclosures keep their own
   6px margin-top). */
/* Image + Video options layout (refactored 2026-05-11).
   ─────────────────────────────────────────────────────
   We replaced the original <details>/<summary> structure with
   <button> toggles + sibling panel wrappers because <details>
   intrinsic-sizing in flex/inline-block contexts produced a
   cascade of layout glitches (separator wrapping, pill jumping
   to a new line on open, floating panels) that no CSS solution
   could fully resolve.
   See the JS init in app.js for the toggle behavior. */

.chat-options-area {
  /* Wraps the pill row + both panel wrappers. Just a block container
     so the panels (which live as siblings BELOW the row) push other
     content down naturally when shown. Spacing relative to the input
     bubble is owned by .search-area's margin-bottom (Jimmy's
     DevTools-tuned fix 2026-05-11). */
}

/* Flex row — left group (Image options | Video options) + right
   group (Query History pushed by margin-left: auto). Earlier
   attempts at flex regressed because of <details> intrinsic-sizing
   quirks; now that all three pills are <button> elements, flex
   behaves cleanly. align-items: center keeps all three pills on
   the same horizontal baseline regardless of internal content.
   width: 100% so margin-left: auto on the Query History pill has
   actual room to absorb — without explicit width, the flex
   container shrink-wraps to content and there's nothing for auto
   margin to consume. */
.chat-options-row {
  display: flex;
  align-items: center;
  width: 100%;
  /* Never wrap the row — Image / Video / History pills stay on ONE
     line regardless of platform. iOS Safari + San Francisco fits
     fine; Android Chrome + Roboto used to wrap because Roboto's
     glyphs are slightly wider, breaking each two-word label across
     two lines (Scott reported via Android screenshot 2026-05-14). */
  flex-wrap: nowrap;
}


/* The pill toggles. They reuse .chat-image-options-summary styling
   for the visual look. These rules normalize button defaults
   (background, border, font, line-height) so the button looks
   identical to the old <summary>-based pill — no extra vertical
   bloat from the user-agent button stylesheet. */
.chat-options-toggle {
  background: none;
  border: none;
  font: inherit;
  margin: 0;
  line-height: 1;
  /* The .chat-image-options-summary class on the same element provides
     padding, color, hover, etc. Don't override here. */
}
.chat-options-toggle:focus-visible {
  outline: 2px solid var(--brand, #6366f1);
  outline-offset: 2px;
  border-radius: 12px;
}

/* Query History pill — sits at the far right of the chat-options-row.
   Same .chat-image-options-summary + .chat-options-toggle styling as
   the other two pills. margin-left: auto absorbs the leftover row
   width and pushes this pill to the right edge while keeping the
   shared baseline (flex container handles vertical alignment).

   MUST appear AFTER .chat-options-toggle in this stylesheet — both
   selectors are class-only (0,1,0 specificity), and .chat-options-toggle
   sets `margin: 0` blanket-style. CSS cascade picks the later rule
   when specificity is tied, so this rule has to come last to win.
   Bit me 2026-05-12 — order it wrong and Query History clusters with
   the left group instead of pushing right. */
.chat-options-history-link {
  margin-left: auto;
}

/* Pipe separator between the option pills. aria-hidden in HTML so
   screen readers don't announce a stray "|". margin-bottom matches
   .search-area's bottom rhythm (Jimmy's DevTools-tuned fix). */
.chat-options-separator {
  display: inline-block;
  color: var(--text-muted);
  user-select: none;
  padding: 4px 4px;
  vertical-align: middle;
  margin-bottom: 6px;
}

/* The panel wrapper — sibling of the row, rendered below it. When
   hidden it's display: none; when shown it's display: block and
   pushes content below the .chat-options-area down naturally. */
.chat-options-panel-wrap {
  margin-top: 4px;
}
.chat-options-panel-wrap[hidden] {
  display: none;
}

/* Active-state dot/summary on the new toggle button (replaces the
   <details>-based selectors below for the new structure). */
.chat-options-toggle.chat-image-options-active .chat-image-options-dot {
  display: inline-block;
}
.chat-options-toggle.chat-image-options-active .chat-image-options-active-summary {
  display: inline;
}
/* When the toggle is open (panel showing), suppress the inline summary
   to avoid duplicating info — the dropdowns themselves show it. */
.chat-options-toggle[aria-expanded="true"] .chat-image-options-active-summary {
  display: none;
}
/* Open-state visual on the toggle pill — same treatment the old
   <details>[open] summary used to get. */
.chat-options-toggle[aria-expanded="true"] {
  background: var(--bg-soft, #f3f4f6);
  color: var(--text);
}
.chat-image-options-summary {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 8px;
  border-radius: 12px;
  cursor: pointer;
  color: var(--text-muted);
  list-style: none;
  user-select: none;
  transition: background 120ms, color 120ms;
}
.chat-image-options-summary:hover {
  background: var(--bg-soft, #f3f4f6);
  color: var(--text);
}
.chat-image-options-icon {
  width: 14px;
  height: 14px;
  flex-shrink: 0;
}
.chat-image-options-label {
  font-weight: 500;
  font-size: 12.5px;
  /* Lock each label to a single line. Without this, Android Chrome
     wraps "Image options" → "Image" / "options" because Roboto is
     wider than iOS's San Francisco at the same px (Scott's Android
     screenshot 2026-05-14). */
  white-space: nowrap;
}

/* Tighten the pill padding on narrow viewports (under 380px) so all
   three pills + separator fit comfortably on one line on small
   Android phones (Galaxy A-series, Pixel 6a, etc.). On wider screens
   the original 4px 8px padding stays for visual breathing room. */
@media (max-width: 380px) {
  .chat-image-options-summary {
    padding: 4px 6px;
    gap: 4px;
  }
  .chat-image-options-label {
    font-size: 12px;
  }
  .chat-options-separator {
    padding: 4px 2px;
  }
}
/* Active-state dot — only visible when the toggle has the
   .chat-image-options-active class (toggled by JS in
   refreshImageOptionsArea / refreshVideoOptionsArea). */
.chat-image-options-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: linear-gradient(135deg, #3b82f6, #8b5cf6);
  display: none;
  flex-shrink: 0;
}
/* Tiny inline summary of what's active, e.g. "Watercolor · Landscape".
   Visibility controlled by the .chat-options-toggle.* rules above. */
.chat-image-options-active-summary {
  font-size: 11.5px;
  color: var(--text-muted);
  margin-left: 4px;
  display: none;
}

/* The expanded panel — dropdowns + reset button on one row. */
.chat-image-options-panel {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  align-items: end;
  margin: 8px 0 4px 0;
  padding: 10px 12px;
  background: var(--bg-soft, #f3f4f6);
  border-radius: 10px;
}
.chat-image-options-field {
  display: flex;
  flex-direction: column;
  gap: 3px;
  flex: 1 1 140px;
  min-width: 130px;
}
.chat-image-options-field-label {
  font-size: 10.5px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .05em;
  color: var(--text-muted);
}
.chat-image-options-field select {
  width: 100%;
  /* Split padding so the global select-chevron rule's padding-right
     (36px for chevron clearance) survives — same reasoning as the
     ticket-form fix. background-color (NOT shorthand) so the global
     chevron background-image survives too. */
  padding: 7px 32px 7px 10px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background-color: var(--bg-card);
  color: var(--text);
  font-size: 13px;
  font-family: inherit;
  cursor: pointer;
}
.chat-image-options-field select:focus-visible {
  outline: 2px solid var(--blue-500, #3b82f6);
  outline-offset: 1px;
}
.chat-image-options-reset {
  padding: 7px 12px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--bg-card);
  color: var(--text-muted);
  font-size: 12px;
  font-weight: 500;
  cursor: pointer;
  transition: background 120ms, color 120ms;
  flex-shrink: 0;
}
.chat-image-options-reset:hover {
  background: var(--bg-card);
  color: var(--text);
  border-color: var(--text-muted);
}
.chat-image-options-hint {
  font-size: 11px;
  color: var(--text-muted);
  margin: 4px 4px 0 4px;
  font-style: italic;
}

.chat-attachments {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-bottom: 8px;
}
.chat-input-row {
  position: relative;
  display: flex;
  align-items: center;
}
.chat-attach-btn {
  position: absolute;
  left: 6px;
  top: 50%;
  transform: translateY(-50%);
  width: 36px;
  height: 36px;
  border: none;
  border-radius: 50%;
  background: transparent;
  color: var(--text-muted);
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background var(--transition), color var(--transition);
  z-index: 1;
}
.chat-attach-btn:hover,
.chat-attach-btn:focus-visible {
  background: rgba(59, 130, 246, 0.10);
  color: var(--blue-500);
  outline: none;
}
.chat-attach-btn svg {
  width: 18px;
  height: 18px;
}

/* ============================================================
 * DOC-UPLOAD BUTTONS — sit just to the right of the image-attach
 * paperclip on both surfaces. Distinct icon (document with +) so
 * the user can tell them apart at a glance. Same hover treatment
 * as the image-attach for visual consistency.
 * ============================================================ */
.search-doc-attach-btn {
  position: absolute;
  /* Position pulled in from 42px → 32px (2026-05-12, per Jimmy) so the
     two buttons read as a related pair rather than visually-separated
     controls. The 10px button overlap doesn't affect click targets —
     each icon sits within its own un-overlapped center zone, and
     z-index ordering routes any edge-case clicks to the doc-attach. */
  left: 32px;
  top: 50%;
  transform: translateY(-50%);
  width: 38px;
  height: 38px;
  border: none;
  border-radius: 50%;
  background: transparent;
  color: var(--text-muted);
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background var(--transition), color var(--transition);
  z-index: 1;
}
.search-doc-attach-btn:hover,
.search-doc-attach-btn:focus-visible {
  background: rgba(124, 58, 237, 0.10);  /* purple tint to differentiate from image-attach blue */
  color: var(--purple-600, #7c3aed);
  outline: none;
}
.search-doc-attach-btn svg { width: 18px; height: 18px; }

/* Chat-page variant — overrides the base .chat-attach-btn left: 6px so
   the second button sits to the right of the image-attach. Inherits
   everything else (size, color, hover) from .chat-attach-btn.
   Same 42px → 32px tightening as .search-doc-attach-btn above (2026-05-12). */
.chat-doc-attach-btn {
  left: 32px;
}
.chat-doc-attach-btn:hover,
.chat-doc-attach-btn:focus-visible {
  background: rgba(124, 58, 237, 0.10);
  color: var(--purple-600, #7c3aed);
}

/* ============================================================
 * DOC CHIP STRIP — sits above the input row, shows in-flight and
 * recently-completed doc attachments. Distinct from .chat-attachments
 * (image-thumb strip) which lives just above. One row each, both
 * stacking neatly when present.
 * ============================================================ */
.search-doc-attachments {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-bottom: 8px;
}
.chat-doc-chip {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 6px 8px 6px 10px;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--bg-card, #fff);
  max-width: 320px;
  font-size: 12px;
  position: relative;
  /* Subtle accent bar on the left to read as "attached to this turn"
     rather than just floating UI noise. */
  border-left: 3px solid var(--purple-500, #8b5cf6);
}
.chat-doc-chip-loading { border-left-color: var(--blue-400); }
.chat-doc-chip-ready   { border-left-color: var(--green-500); }
.chat-doc-chip-failed  { border-left-color: #dc2626; background: rgba(220, 38, 38, 0.04); }

.chat-doc-chip-icon {
  display: inline-flex;
  width: 22px;
  height: 22px;
  align-items: center;
  justify-content: center;
  color: var(--text-muted);
  flex-shrink: 0;
}
.chat-doc-chip-icon svg { width: 18px; height: 18px; }
.chat-doc-chip-text {
  display: flex;
  flex-direction: column;
  gap: 1px;
  min-width: 0;          /* lets the inner name truncate */
}
.chat-doc-chip-name {
  font-weight: 600;
  color: var(--text);
  font-size: 12px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.chat-doc-chip-meta {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 11px;
  color: var(--text-muted);
}
.chat-doc-chip-mode {
  /* Mode pill — soft purple for chat, soft green for KB. The text says
     "This chat" or "Saved → Folder Name" so color is just secondary. */
  display: inline-flex;
  padding: 1px 6px;
  border-radius: 6px;
  background: rgba(124, 58, 237, 0.08);
  color: var(--purple-600, #7c3aed);
}
.chat-doc-chip-status { color: var(--text-muted); }
.chat-doc-chip-error  { color: #dc2626; cursor: help; }
.chat-doc-chip-remove {
  border: none;
  background: transparent;
  color: var(--text-muted);
  font-size: 18px;
  line-height: 1;
  cursor: pointer;
  padding: 0 4px;
  border-radius: 4px;
  margin-left: 4px;
  flex-shrink: 0;
}
.chat-doc-chip-remove:hover {
  background: rgba(0, 0, 0, 0.08);
  color: var(--text);
}

/* ============================================================
 * DOC-UPLOAD CHOOSER MODAL — singleton, opened by either attacher
 * after file pick. Mirrors .folder-modal patterns for visual
 * consistency without inheriting from it (different layout needs).
 * ============================================================ */
.doc-chooser-modal {
  position: fixed;
  inset: 0;
  z-index: 1000;
  display: flex;
  align-items: center;
  justify-content: center;
}
.doc-chooser-backdrop {
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, 0.5);
}
.doc-chooser-panel {
  position: relative;
  background: var(--bg, #fff);
  border-radius: 12px;
  box-shadow: var(--shadow-lg, 0 10px 25px rgba(0, 0, 0, 0.3));
  width: 92%;
  max-width: 480px;
  max-height: 90vh;
  display: flex;
  flex-direction: column;
}
.doc-chooser-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 16px 20px;
  border-bottom: 1px solid var(--border);
}
.doc-chooser-header h2 { margin: 0; font-size: 1.15rem; font-weight: 600; }
.doc-chooser-close {
  background: none; border: none; padding: 0 4px;
  font-size: 28px; line-height: 1; cursor: pointer;
  color: var(--text-muted);
}
.doc-chooser-close:hover { color: var(--text); }
.doc-chooser-body {
  padding: 18px 20px;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.doc-chooser-files {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 10px 12px;
  background: var(--bg-secondary);
  border-radius: 8px;
  max-height: 200px;
  overflow-y: auto;
}
.doc-chooser-file {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 13px;
}
.doc-chooser-file-icon {
  display: inline-flex;
  width: 20px;
  height: 20px;
  align-items: center;
  justify-content: center;
  color: var(--text-muted);
  flex-shrink: 0;
}
.doc-chooser-file-icon svg { width: 16px; height: 16px; }
.doc-chooser-file-name {
  flex: 1;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  color: var(--text);
}
.doc-chooser-file-size {
  color: var(--text-muted);
  font-size: 11px;
  flex-shrink: 0;
}

.doc-chooser-prompt {
  font-size: 13px;
  font-weight: 600;
  color: var(--text);
}

.doc-chooser-modes {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.doc-chooser-mode {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  padding: 12px 14px;
  border: 1.5px solid var(--border);
  border-radius: 10px;
  cursor: pointer;
  transition: border-color 0.12s, background 0.12s;
}
.doc-chooser-mode:hover { background: var(--bg-secondary); }
.doc-chooser-mode input[type="radio"] {
  margin-top: 2px;
  flex-shrink: 0;
  accent-color: var(--blue-500);
}
.doc-chooser-mode:has(input[type="radio"]:checked) {
  border-color: var(--blue-500);
  background: rgba(59, 130, 246, 0.05);
}
.doc-chooser-mode-text {
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.doc-chooser-mode-title {
  font-size: 14px;
  font-weight: 600;
  color: var(--text);
}
.doc-chooser-mode-sub {
  font-size: 12px;
  color: var(--text-muted);
  line-height: 1.4;
}

.doc-chooser-folder-field {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.doc-chooser-label {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-muted);
}
.doc-chooser-folder-field select {
  padding: 10px 12px;
  border: 1px solid var(--border);
  border-radius: 8px;
  font-size: 14px;
  font-family: inherit;
  background: var(--bg-card, #fff);
  color: var(--text);
}

.doc-chooser-error {
  padding: 10px 12px;
  border-radius: 8px;
  background: rgba(220, 38, 38, 0.08);
  color: #dc2626;
  font-size: 13px;
}

.doc-chooser-actions {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
  padding: 12px 20px 16px;
  border-top: 1px solid var(--border);
}
.chat-input {
  width: 100%;
  /* Left padding accommodates the SINGLE + attach button (38w at
     left:6px). 50px gives a small breathing gap before placeholder
     text starts. Was 76px when we had two attach icons (paperclip +
     doc); collapsed to 50px on 2026-05-14 when the icons merged into
     one + menu (Scott + Jimmy). */
  /* Right padding clears TWO stacked controls: the morph slot (voice
     waveform ↔ send arrow) + mic dictate — 100px total (2026-07-13,
     two-button morph; was 146px for three). */
  padding: 14px 100px 14px 50px;
  border: 2px solid var(--border);
  border-radius: 28px;
  background: var(--bg-card);
  color: var(--text);
  font-size: 1rem;
  outline: none;
  transition: border-color var(--transition), box-shadow var(--transition);
  font-family: inherit;
  /* #chat-input is now a <textarea> (tester #47229): multi-line capable, but
     it starts as one row and auto-grows (JS caps height at 140px, then it
     scrolls). No drag handle; line-height tuned for readable wrapped text.
     min-height keeps the single-line pill the same size as the old input. */
  line-height: 1.45;
  min-height: 54px;
  max-height: 140px;
  resize: none;
  overflow-y: auto;
  vertical-align: bottom;
}
.chat-input::placeholder {
  color: var(--text-muted);
}
.chat-input:focus {
  border-color: var(--blue-400);
  box-shadow: 0 0 0 4px rgba(59, 130, 246, 0.12);
}
.chat-send-btn {
  position: absolute;
  right: 6px;
  top: 50%;
  transform: translateY(-50%);
  width: 42px;
  height: 42px;
  border: none;
  border-radius: 50%;
  background: linear-gradient(135deg, var(--blue-500), var(--purple-500));
  color: white;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: transform var(--transition), box-shadow var(--transition);
}
.chat-send-btn:hover {
  transform: translateY(-50%) scale(1.05);
  box-shadow: 0 4px 12px rgba(59, 130, 246, 0.4);
}
.chat-send-btn svg {
  width: 18px;
  height: 18px;
}

/* Mic button (speak-to-type) — sits just left of the send button, same
   round 42px footprint but neutral (secondary action next to the primary
   send). Records → /api/voice/transcribe → drops text in the input.
   (Voice frontend, 2026-06-23) */
.chat-mic-btn {
  position: absolute;
  right: 52px;            /* just left of the 42px send button at right:6px */
  top: 50%;
  transform: translateY(-50%);
  width: 42px;
  height: 42px;
  border: none;
  border-radius: 50%;
  background: rgba(127, 119, 221, 0.12);
  color: var(--text-muted);
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: transform var(--transition), background var(--transition), color var(--transition);
}
.chat-mic-btn:hover {
  transform: translateY(-50%) scale(1.05);
  color: var(--text);
  background: rgba(127, 119, 221, 0.2);
}
.chat-mic-btn svg {
  width: 19px;
  height: 19px;
}
/* Recording: red pulse so it's obvious the mic is live. */
.chat-mic-btn.recording {
  background: #ef4444;
  color: #fff;
  animation: chat-mic-pulse 1.2s ease-in-out infinite;
}
/* Busy = uploading/transcribing after stop. */
.chat-mic-btn.busy {
  opacity: 0.55;
  cursor: default;
}
@keyframes chat-mic-pulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(239, 68, 68, 0.5); }
  50%      { box-shadow: 0 0 0 6px rgba(239, 68, 68, 0); }
}

/* A single turn = user's question bubble + Qbit's answer bubble. The
   visual distinction between the two is subtle but consistent: the
   question is right-aligned with a tinted background, the answer is
   left-aligned with no background (lets it feel like Qbit "speaking"). */
.qbit-turn {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.qbit-turn-question {
  align-self: flex-end;
  max-width: 85%;
  padding: 10px 14px;
  background: linear-gradient(135deg, rgba(59, 130, 246, 0.10), rgba(139, 92, 246, 0.10));
  border: 1px solid rgba(59, 130, 246, 0.20);
  border-radius: 14px 14px 4px 14px;
  font-size: 0.95rem;
  line-height: 1.45;
  color: var(--text);
  word-wrap: break-word;
}
.qbit-turn-question-text {
  /* nothing extra; just gives JS a stable target for text content */
}

/* Image thumbnails inside a question bubble (multimodal turns). Small,
   rounded, capped so a 4-image attachment doesn't dominate the bubble. */
.qbit-turn-images {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-bottom: 8px;
}
.qbit-turn-images img {
  width: 80px;
  height: 80px;
  object-fit: cover;
  border-radius: 8px;
  border: 1px solid var(--border);
  display: block;
}

/* Long unbreakable tokens (raw URLs, hashes) must WRAP, never push the
   layout past the viewport (Jimmy 2026-07-13: a manuals.plus URL with a
   64-char hash forced the whole conversation page into horizontal
   overflow on mobile). overflow-wrap inherits to everything inside a
   turn — prose, bubbles, list items, table cells, code. */
.qbit-turn {
  overflow-wrap: anywhere;
  word-break: break-word;
  min-width: 0;
}

.qbit-turn-answer {
  align-self: flex-start;
  max-width: 92%;
  font-size: 1rem;
  line-height: 1.55;
  color: var(--text);
  /* No background — Qbit "speaks" plainly to the user. */
}
.qbit-turn-answer p {
  margin: 0 0 10px 0;
}
.qbit-turn-answer p:last-child {
  margin-bottom: 0;
}
.qbit-turn-answer code {
  background: var(--border);
  padding: 1px 6px;
  border-radius: 4px;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.9em;
}

/* ============================================================
 * Broca's Area — 🔊 Play / ⏸ Stop control on spoken-output turns.
 * Appears beneath the answer text when metadata.speakAloud=true.
 * Uses the browser's built-in SpeechSynthesis (no audio file
 * transfer, no extra vendor cost). See attachSpeakAloudButton()
 * + togglePlayAloud() in app.js.
 * ============================================================ */
.speak-aloud-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-top: 10px;
  padding: 6px 12px;
  border: 1px solid var(--border);
  border-radius: 999px;        /* pill */
  background: var(--bg-secondary);
  color: var(--text);
  font-size: 0.85rem;
  font-family: inherit;
  cursor: pointer;
  transition: background var(--transition), border-color var(--transition), color var(--transition);
}

.speak-aloud-btn:hover {
  background: var(--bg);
  border-color: var(--blue-400);
  color: var(--blue-500);
}

.speak-aloud-btn[data-state="playing"] {
  background: var(--blue-100, rgba(59,130,246,0.12));
  border-color: var(--blue-500);
  color: var(--blue-600, #2563eb);
}

.speak-aloud-icon {
  font-size: 1rem;
  line-height: 1;
}

.speak-aloud-label {
  font-weight: 500;
}

/* Markdown headings inside an answer bubble. The formatter maps
   markdown # → <h2>, ## → <h3>, ### → <h4> (skipping h1 since the
   panel header is already the page-level heading). Sized to feel
   like section labels within Qbit's reply, not big banner-style
   titles — answers shouldn't look louder than the question above. */
.qbit-turn-answer h2 {
  font-size: 1.15rem;
  font-weight: 700;
  margin: 14px 0 6px;
  color: var(--text);
  letter-spacing: -0.01em;
}
.qbit-turn-answer h3 {
  font-size: 1.02rem;
  font-weight: 700;
  margin: 12px 0 4px;
  color: var(--text);
}
.qbit-turn-answer h4 {
  font-size: 0.95rem;
  font-weight: 600;
  margin: 10px 0 4px;
  color: var(--text-secondary);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
.qbit-turn-answer h2:first-child,
.qbit-turn-answer h3:first-child,
.qbit-turn-answer h4:first-child {
  margin-top: 0;
}

/* Markdown lists. Tight indent on the left so they don't wrap
   awkwardly inside the bubble's max-width. */
.qbit-turn-answer ul,
.qbit-turn-answer ol {
  margin: 6px 0 12px;
  padding-left: 22px;
}
.qbit-turn-answer li {
  margin: 3px 0;
  line-height: 1.5;
}
.qbit-turn-answer ul ul,
.qbit-turn-answer ol ol,
.qbit-turn-answer ul ol,
.qbit-turn-answer ol ul {
  margin: 4px 0;
}

/* Horizontal rule — section divider for multi-day itineraries etc.
   Subtle, not a thick page-break. */
.qbit-turn-answer hr {
  border: none;
  border-top: 1px solid var(--border);
  margin: 14px 0;
  height: 0;
}

/* Markdown links — Qbit is now instructed to drop links to official
   websites whenever it mentions a real place or business. Style them
   so they're obviously clickable without screaming for attention. */
.qbit-turn-answer a {
  color: var(--blue-500);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 2px;
  word-break: break-word;
  transition: color var(--transition);
}
.qbit-turn-answer a:hover,
.qbit-turn-answer a:focus-visible {
  color: var(--purple-500);
  text-decoration-thickness: 2px;
  outline: none;
}

/* Markdown tables — typically used by Qbit for label/value pairs in
   travel itineraries, comparison rows, etc. Compact, readable, and
   tucked inside the answer bubble's max-width without overflowing. */
/* Wrapper gives a wide table (many columns) a horizontal scroll on
   narrow screens instead of letting its columns crush to one character
   per line. (Squashed-table fix 2026-06-23) */
.qbit-md-table-wrap {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  max-width: 100%;
  margin: 10px 0;
}
/* Fenced ``` blocks from answers (2026-07-08) — verbatim text panel.
 * Qbit doesn't write code, but models fence sample emails / poems /
 * recipes; render them as a quiet monospace card instead of leaking
 * literal backticks. Wraps long lines; scrolls only if a single word
 * overflows. */
.qbit-md-pre {
  background: var(--bg-subtle, rgba(0, 0, 0, 0.045));
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 10px 12px;
  margin: 10px 0;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 0.85em;
  line-height: 1.5;
  white-space: pre-wrap;
  overflow-wrap: break-word;
  overflow-x: auto;
  max-width: 100%;
}
.qbit-md-table {
  border-collapse: collapse;
  font-size: 0.92rem;
  /* width:auto + min-width:100% — small tables still fill the bubble,
     but a wide one is allowed to exceed it and scroll (via the wrap
     above) rather than squashing every column. */
  width: auto;
  min-width: 100%;
}
.qbit-md-table th,
.qbit-md-table td {
  text-align: left;
  padding: 6px 10px;
  border-bottom: 1px solid var(--border);
  vertical-align: top;
  /* Wrap long content gracefully, but only break WITHIN a word when a
     single token (e.g. a long URL) is too wide for its column — never
     break normal words letter-by-letter. word-break:break-word did the
     latter, which is what squashed "SNAKES" into S/N/A/K/E/S on phones. */
  overflow-wrap: break-word;
  word-break: normal;
}
.qbit-md-table th {
  font-weight: 700;
  font-size: 0.85rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--text-secondary);
  background: rgba(59, 130, 246, 0.04);
  border-bottom: 2px solid var(--border);
}
.qbit-md-table tr:last-child td {
  border-bottom: none;
}
/* When the model produces a label/value table (empty header row,
   first column = bold labels, second column = values), give the
   first column a hint of weight so it reads as a label. */
.qbit-md-table tbody td:first-child {
  font-weight: 600;
  white-space: nowrap;
  padding-right: 14px;
}
.qbit-turn-loading {
  /* While we're waiting for the API, the answer bubble holds the
     thinking spinner. No extra styling needed — the .qbit-thinking
     animation does the visual work. */
}

/* ===== Reply area =====
   Persistent follow-up input pinned at the bottom of the panel. Has
   its own paperclip + thumbnail strip (paired to replyAttacher in JS).
   The send button is the same purple gradient as the top search bar's
   send button so users feel like the controls match. */
.qbit-reply-area {
  flex-shrink: 0;
  border-top: 1px solid var(--border);
  padding: 10px 14px;
  background: var(--bg);
}
.qbit-reply-attachments {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-bottom: 8px;
}
/* Reply chips reuse the .search-attachment-chip styling — same DOM
   shape, same look. The factory in app.js renders both with the
   same template. */

.qbit-reply-row {
  position: relative;
  display: flex;
  align-items: center;
  gap: 0;
}
.qbit-reply-attach-btn {
  position: absolute;
  left: 6px;
  top: 50%;
  transform: translateY(-50%);
  width: 34px;
  height: 34px;
  border: none;
  border-radius: 50%;
  background: transparent;
  color: var(--text-muted);
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background var(--transition), color var(--transition);
  z-index: 1;
}
.qbit-reply-attach-btn:hover,
.qbit-reply-attach-btn:focus-visible {
  background: rgba(59, 130, 246, 0.10);
  color: var(--blue-500);
  outline: none;
}
.qbit-reply-attach-btn svg {
  width: 16px;
  height: 16px;
}

.qbit-reply-input {
  width: 100%;
  padding: 12px 48px 12px 46px;
  border: 1.5px solid var(--border);
  border-radius: 24px;
  background: var(--bg-card);
  color: var(--text);
  font-size: 0.95rem;
  outline: none;
  transition: border-color var(--transition), box-shadow var(--transition);
  font-family: inherit;
}
.qbit-reply-input::placeholder {
  color: var(--text-muted);
}
.qbit-reply-input:focus {
  border-color: var(--blue-400);
  box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.10);
}

.qbit-reply-send {
  position: absolute;
  right: 4px;
  top: 50%;
  transform: translateY(-50%);
  width: 36px;
  height: 36px;
  border: none;
  border-radius: 50%;
  background: linear-gradient(135deg, var(--blue-500), var(--purple-500));
  color: white;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: transform var(--transition), box-shadow var(--transition);
}
.qbit-reply-send:hover {
  transform: translateY(-50%) scale(1.05);
  box-shadow: 0 4px 10px rgba(59, 130, 246, 0.35);
}
.qbit-reply-send svg {
  width: 16px;
  height: 16px;
}

/* ===== "Qbit is thinking…" indicator =====
   The little Qbit creature bobbing in place while he works — replaced
   the walking-paws motif app-wide (de-pawed 2026-07-07 per Scott). Used
   by the home chat AND the DM/group thinking bubble (messaging.js). */
.qbit-thinking {
  display: flex;
  align-items: center;
  gap: 10px;
  color: var(--text-secondary);
  padding: 4px 0;
}
.qbit-thinking em {
  font-style: italic;
  font-size: 0.92em;
}

/* The creature img — small enough to live inside an answer bubble's
   spinner area. A gentle bob with a hint of lean reads as "working"
   without being distracting; the 1.1s cycle loops until the answer
   replaces the indicator. */
.qbit-thinking-qbit {
  width: 26px;
  height: 26px;
  object-fit: contain;
  animation: qbit-think-bob 1.1s infinite ease-in-out;
  transform-origin: center bottom;
}

@keyframes qbit-think-bob {
  0%, 100% { transform: translateY(0) rotate(0deg); }
  25%      { transform: translateY(-3px) rotate(-3deg); }
  50%      { transform: translateY(0) rotate(0deg); }
  75%      { transform: translateY(-3px) rotate(3deg); }
}

.qbit-error {
  background: rgba(239, 68, 68, 0.08);
  border: 1px solid rgba(239, 68, 68, 0.3);
  color: #b91c1c;
  padding: 10px 12px;
  border-radius: var(--radius-sm);
  font-size: 0.92rem;
}

/* ============================================================
 * Quick Actions header (Jimmy 2026-05-16)
 * ============================================================
 * Holds the section title + the Edit pencil button. Sits above
 * the .quick-actions grid. The Edit button toggles edit mode on
 * the cards (× delete + ↑↓ reorder + "Add shortcut" button).
 * ============================================================ */
.quick-actions-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin: 18px 0 10px;
}
.quick-actions-title {
  font-size: 0.95rem;
  font-weight: 700;
  color: var(--text);
  margin: 0;
}
.quick-actions-edit-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px 12px;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: var(--bg-card);
  color: var(--text-muted);
  font-size: 0.82rem;
  font-weight: 500;
  cursor: pointer;
  transition: background .15s, color .15s, border-color .15s;
}
.quick-actions-edit-btn:hover {
  color: var(--text);
  border-color: var(--text-muted);
}
.quick-actions-edit-btn-active {
  background: var(--blue-500);
  border-color: var(--blue-500);
  color: #fff;
}
.quick-actions-edit-btn-active:hover {
  background: var(--blue-600);
  border-color: var(--blue-600);
  color: #fff;
}
.quick-actions-edit-btn svg {
  flex-shrink: 0;
}

/* Quick Actions — responsive grid (Jimmy 2026-05-16).
 * Phone keeps the original 2-col layout so muscle memory + on-screen
 * card sizes stay the same. Tablet portrait gets 3 columns to make
 * use of the extra horizontal space without making cards comically
 * wide. Tablet landscape + desktop get 4 columns — the 8 default
 * shortcuts fit cleanly in 2 rows of 4 instead of 4 rows of 2. */
.quick-actions {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 12px;
  margin-bottom: 28px;
}
@media (min-width: 600px) and (orientation: portrait) {
  .quick-actions {
    grid-template-columns: repeat(3, 1fr);
  }
}
@media (min-width: 900px) {
  .quick-actions {
    grid-template-columns: repeat(4, 1fr);
  }
}

/* Edit mode — visual cue that the grid is "in edit". Cards get
   per-card delete + reorder controls overlaid; click-to-open
   is disabled so the user doesn't accidentally fire showCategoryContext
   when they meant to hit the × or ↑↓. */
.quick-actions.quick-actions-editing .action-card {
  cursor: default;
}
.quick-actions.quick-actions-editing .action-card:hover {
  transform: none;
  box-shadow: var(--shadow);
  border-color: var(--blue-400);
  border-style: dashed;
}
.action-card.action-card-editing {
  position: relative;
}

.action-card-delete {
  position: absolute;
  top: 6px;
  right: 6px;
  width: 24px;
  height: 24px;
  border-radius: 50%;
  border: 1px solid var(--border);
  background: var(--bg);
  color: var(--text-muted);
  font-size: 16px;
  line-height: 1;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background .15s, color .15s, border-color .15s;
}
.action-card-delete:hover {
  background: rgba(239, 68, 68, 0.10);
  border-color: #ef4444;
  color: #ef4444;
}

.action-card-reorder {
  position: absolute;
  bottom: 6px;
  left: 6px;
  display: flex;
  gap: 2px;
}
.action-card-reorder-btn {
  width: 22px;
  height: 22px;
  border-radius: 6px;
  border: 1px solid var(--border);
  background: var(--bg);
  color: var(--text-muted);
  font-size: 12px;
  line-height: 1;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background .15s, color .15s, border-color .15s;
}
.action-card-reorder-btn:hover {
  color: var(--blue-500);
  border-color: var(--blue-400);
  background: rgba(59, 130, 246, 0.08);
}
.action-card-reorder-spacer {
  width: 22px;
  height: 22px;
}

/* "+ Add shortcut" card — same visual footprint as the regular cards
   but uses a dashed border to signal "this is a slot, not a real card". */
.action-card-add {
  background: transparent;
  border: 2px dashed var(--border);
  cursor: pointer;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 18px 16px;
  border-radius: var(--radius-sm);
  font-family: inherit;
  color: var(--text-muted);
  transition: border-color .15s, color .15s, background .15s;
}
.action-card-add:hover {
  border-color: var(--blue-400);
  color: var(--blue-500);
  background: rgba(59, 130, 246, 0.04);
}
.action-card-add-icon {
  width: 44px;
  height: 44px;
  border-radius: 12px;
  background: rgba(59, 130, 246, 0.10);
  color: var(--blue-500);
  display: flex;
  align-items: center;
  justify-content: center;
  margin: 0 auto 10px;
  font-size: 1.6rem;
  font-weight: 300;
}
.action-card-add h3 {
  font-size: 0.85rem;
  font-weight: 600;
  margin: 0 0 2px;
  color: inherit;
}
.action-card-add p {
  font-size: 0.75rem;
  color: inherit;
  opacity: 0.85;
  margin: 0;
}

/* ============================================================
 * Add Shortcut picker modal
 * ============================================================
 * Opens when the user clicks "+ Add shortcut" in edit mode. Shows
 * a grid of preset cards they haven't already added. Each item
 * appends to state.homeCards on click.
 * ============================================================ */
.add-shortcut-modal {
  position: fixed;
  inset: 0;
  z-index: 9999;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 16px;
}
.add-shortcut-overlay {
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, 0.45);
  cursor: pointer;
}
.add-shortcut-card {
  position: relative;
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: 16px;
  padding: 20px;
  max-width: 520px;
  width: 100%;
  max-height: 80vh;
  overflow-y: auto;
  box-shadow: var(--shadow-lg);
}
.add-shortcut-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 8px;
}
.add-shortcut-header h3 {
  margin: 0;
  font-size: 1.1rem;
  font-weight: 700;
  color: var(--text);
}
.add-shortcut-close {
  width: 28px;
  height: 28px;
  border: none;
  background: transparent;
  color: var(--text-muted);
  font-size: 22px;
  line-height: 1;
  cursor: pointer;
  border-radius: 8px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.add-shortcut-close:hover {
  color: var(--text);
  background: var(--bg);
}
.add-shortcut-hint {
  font-size: 0.85rem;
  color: var(--text-muted);
  margin-bottom: 16px;
}
.add-shortcut-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 8px;
}
.add-shortcut-item {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--bg);
  cursor: pointer;
  text-align: left;
  font-family: inherit;
  transition: border-color .15s, background .15s, transform .1s;
}
.add-shortcut-item:hover {
  border-color: var(--blue-400);
  background: rgba(59, 130, 246, 0.04);
}
.add-shortcut-item:active {
  transform: scale(0.99);
}
.add-shortcut-icon {
  width: 40px;
  height: 40px;
  border-radius: 10px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1.25rem;
  flex-shrink: 0;
}
.add-shortcut-text {
  flex: 1;
  min-width: 0;
}
.add-shortcut-title {
  font-size: 0.95rem;
  font-weight: 600;
  color: var(--text);
  margin-bottom: 2px;
}
.add-shortcut-sub {
  font-size: 0.8rem;
  color: var(--text-muted);
}

/* On wider viewports show 2 columns in the picker for faster scanning. */
@media (min-width: 640px) {
  .add-shortcut-grid {
    grid-template-columns: 1fr 1fr;
  }
}

/* ============================================================
 * Category-context Games list view (Jimmy 2026-05-16)
 * ============================================================
 * When the user clicks Games & Puzzles, the context panel shows
 * 8 clickable game ideas instead of one descriptor + "Or try this:".
 * Each item is a full-width row that submits its game prompt on click.
 * ============================================================ */
.category-context-lead {
  font-size: 0.9rem;
  color: var(--text-secondary);
  margin: 0 0 12px;
  line-height: 1.5;
}
.category-games-list {
  display: grid;
  grid-template-columns: 1fr;
  gap: 6px;
}
.category-game-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 10px 14px;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--bg);
  cursor: pointer;
  text-align: left;
  font-family: inherit;
  font-size: 0.92rem;
  color: var(--text);
  transition: border-color .15s, background .15s, transform .1s;
}
.category-game-item:hover {
  border-color: var(--blue-400);
  background: rgba(59, 130, 246, 0.06);
}
.category-game-item:active {
  transform: scale(0.99);
}
.category-game-label {
  flex: 1;
  min-width: 0;
}
.category-game-arrow {
  color: var(--text-muted);
  font-weight: 600;
  flex-shrink: 0;
}
@media (min-width: 640px) {
  .category-games-list {
    grid-template-columns: 1fr 1fr;
  }
}

.action-card {
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 18px 16px;
  cursor: pointer;
  transition: transform var(--transition), box-shadow var(--transition), border-color var(--transition);
  text-align: center;
}

.action-card:hover {
  transform: translateY(-2px);
  box-shadow: var(--shadow-lg);
  border-color: var(--blue-400);
}

.action-icon {
  width: 44px;
  height: 44px;
  border-radius: 12px;
  display: flex;
  align-items: center;
  justify-content: center;
  margin: 0 auto 10px;
  font-size: 1.4rem;
}

/* Per-card icon tints. Each card type gets a soft 12%-alpha colored
   tile so the grid reads as visually distinct at a glance. The 10 new
   presets added 2026-05-16 (Jimmy) get their own colors to keep the
   "Add Shortcut" picker visually balanced. */
.action-icon.search    { background: rgba(59, 130, 246, 0.12); }
.action-icon.write     { background: rgba(139, 92, 246, 0.12); }
.action-icon.calendar  { background: rgba(236, 72, 153, 0.12); } /* legacy — kept for backward compat */
.action-icon.health    { background: rgba(236, 72, 153, 0.12); } /* replaces calendar */
.action-icon.travel    { background: rgba(16, 185, 129, 0.12); }
.action-icon.research  { background: rgba(245, 158, 11, 0.12); }
.action-icon.homework  { background: rgba(99, 102, 241, 0.12); }
.action-icon.games     { background: rgba(239, 68, 68, 0.12); }
.action-icon.private   { background: rgba(20, 184, 166, 0.12); }
/* New 2026-05-16 presets */
.action-icon.cooking   { background: rgba(234, 88, 12, 0.12); }   /* warm orange */
.action-icon.finance   { background: rgba(34, 197, 94, 0.12); }   /* green */
.action-icon.fitness   { background: rgba(220, 38, 38, 0.12); }   /* red */
.action-icon.shopping  { background: rgba(168, 85, 247, 0.12); }  /* purple */
.action-icon.career    { background: rgba(71, 85, 105, 0.12); }   /* slate */
.action-icon.parenting { background: rgba(251, 146, 60, 0.12); }  /* peach */
.action-icon.techhelp  { background: rgba(14, 165, 233, 0.12); }  /* sky */
.action-icon.diy       { background: rgba(132, 204, 22, 0.12); }  /* lime */
.action-icon.pets      { background: rgba(217, 119, 6, 0.12); }   /* amber */
.action-icon.events    { background: rgba(244, 63, 94, 0.12); }   /* rose */
/* New 2026-05-17 (Scott) — picked hues distinct from every existing
   icon-tint above. Fuchsia leans creative for arts; deep emerald is
   the universal "field" green for sports; deep violet reads as
   showbiz/curtain for entertainment. */
.action-icon.arts          { background: rgba(217, 70, 239, 0.12); }  /* fuchsia */
.action-icon.sports        { background: rgba(5, 150, 105, 0.12); }   /* emerald-deep */
.action-icon.entertainment { background: rgba(124, 58, 237, 0.12); }  /* violet-deep */

.action-card h3 {
  font-size: 0.85rem;
  font-weight: 600;
  margin-bottom: 2px;
  /* Balance + nowrap fallback so longer titles like
     "Homework & Tutoring" don't orphan a single word on a narrow
     Android (Scott reported "Tutoring" wrapping alone on Galaxy
     2026-05-14). Balance evens out the line lengths if wrap is
     truly unavoidable; the narrow-viewport rule below shrinks
     the font enough that wrap is no longer needed at all. */
  text-wrap: balance;
}

.action-card p {
  font-size: 0.75rem;
  color: var(--text-secondary);
  text-wrap: balance;
}

/* Narrow Android viewports — shrink card title + sub fonts so every
   title (even "Homework & Tutoring") fits comfortably on one line.
   At 360-400px viewport with the 2-column grid, content per card
   is ~125px; 0.78rem Roboto fits 19 chars cleanly there. */
@media (max-width: 400px) {
  .action-card h3 {
    font-size: 0.78rem;
  }
  .action-card p {
    font-size: 0.7rem;
  }
}

/* Share Card */
.share-card {
  background: linear-gradient(135deg, #eef2ff, #f0e6ff);
  border: 1px solid var(--blue-200);
  border-radius: var(--radius);
  padding: 20px;
  margin-bottom: 28px;
  cursor: pointer;
  transition: transform var(--transition), box-shadow var(--transition);
}

[data-theme="dark"] .share-card {
  background: linear-gradient(135deg, rgba(59, 130, 246, 0.1), rgba(139, 92, 246, 0.1));
  border-color: var(--border);
}

.share-card:hover {
  transform: translateY(-2px);
  box-shadow: var(--shadow-lg);
}

.share-card-content {
  display: flex;
  align-items: center;
  /* Tighter horizontal gap (was 14px) to give the subtitle enough
     room to fit on one line at this card's typical desktop width.
     Combined with the smaller avatar below — Scott #14. */
  gap: 10px;
  /* Tighter gap between the text/avatar block and the gradient Share
     button below (Scott #13). 14px → 8px. */
  margin-bottom: 8px;
}

/* Natural block flow inside the text column — h3 directly above p with
   the small margin defined on .share-card-text h3. Earlier we forced a
   space-around spread to "center" the subtitle vertically, but that
   made the headline ↔ subtitle gap feel inflated. Scott prefers tight. */
.share-card-text {
  flex: 1;
  min-width: 0;
}

.share-qbit-wrapper {
  position: relative;
  flex-shrink: 0;
}

.share-card-avatar {
  /* Down from 80px so the subtitle fits on one line at desktop width
     without wrapping (Scott #14). 64px still reads as a clearly
     recognizable Qbit waving — verified visually. */
  width: 64px;
  height: 64px;
  object-fit: contain;
  flex-shrink: 0;
  filter: drop-shadow(0 2px 6px rgba(59, 130, 246, 0.25));
}

.share-speech-bubble {
  position: absolute;
  /* Default = -10px. The original calibration that looks great on
     iPhone Pro Max, iPad, and any viewport ≥460px wide where the
     headline ("Know someone who'd love Qbit?") fits on one line.
     Narrower viewports get -16px via the media query below — that's
     where the headline wraps to 2 lines and the centered text column
     pushes upward into the bubble's vertical band on Android.
     left value scales proportionally with the avatar (was 58px for
     the 80px avatar, now 46px for the 64px avatar — same ~72% offset). */
  top: -10px;
  left: 46px;
  background: var(--green-400);
  color: #1a2e05;
  font-size: 0.6rem;
  font-weight: 700;
  padding: 3px 7px;
  border-radius: 12px 12px 12px 2px;
  white-space: nowrap;
  box-shadow: 0 2px 6px rgba(0,0,0,0.15);
  /* Defensive z-index — if the headline ever does wrap UP into the
     bubble's band on some future viewport, the bubble stays on top
     (legible) instead of being half-occluded by the text. */
  z-index: 2;
}

/* Narrow phones (under 460px — Galaxy S, Pixel, iPhone in portrait,
   most Android non-tablets) — lift the bubble HIGHER so it can't
   overlap the headline when it wraps to 2 lines. The lift was
   originally applied to all viewports on 2026-05-14 but it left the
   bubble floating too far above the card on larger iPhone screens
   (Jimmy reported), so it's scoped here to phones only. */
@media (max-width: 460px) {
  .share-speech-bubble {
    top: -16px;
  }
}

/* Very narrow Android viewports — also nudge the bubble slightly LEFT
   so it sits more squarely over the avatar and its right edge doesn't
   reach into the headline column. */
@media (max-width: 400px) {
  .share-speech-bubble {
    left: 38px;
    font-size: 0.58rem;
  }
}

.share-card-text h3 {
  font-size: 0.95rem;
  font-weight: 600;
  margin-bottom: 2px;
}

.share-card-text p {
  font-size: 0.82rem;
  color: var(--text-secondary);
  line-height: 1.4;
}

.share-card-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 10px 0;
  background: linear-gradient(135deg, var(--blue-500), var(--purple-500));
  color: white;
  border-radius: 24px;
  font-size: 0.88rem;
  font-weight: 600;
  transition: opacity var(--transition);
}

.share-card:hover .share-card-btn {
  opacity: 0.9;
}

/* Menu Share Link */
.menu-share-link {
  color: var(--blue-500) !important;
}

/* ===== About Page ===== */
.about-hero {
  text-align: center;
  padding: 32px 0 24px;
}

.about-hero img {
  width: 80px;
  height: 80px;
  object-fit: contain;
  margin-bottom: 12px;
  filter: drop-shadow(0 4px 12px rgba(59, 130, 246, 0.3));
}

.about-hero h1 {
  font-size: 1.4rem;
  font-weight: 700;
  margin-bottom: 4px;
}

.about-hero .subtitle {
  color: var(--blue-500);
  font-weight: 600;
  font-size: 0.95rem;
  margin-bottom: 16px;
}

.about-description {
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 20px;
  margin-bottom: 24px;
  box-shadow: var(--shadow);
  font-size: 0.92rem;
  line-height: 1.7;
  color: var(--text-secondary);
}

/* App version line at the BOTTOM of the global footer (under the
   trademark). Smaller than the rest, deliberately understated —
   it's metadata for support, not content for users. Sits with a
   tight 4px gap above (matching trademark→links spacing) and
   inherits the trademark's iOS safe-area bottom padding via the
   @supports rule below.
   Auto-populated at boot from the live SW cache name — see
   renderAppVersion() in app.js. Scott UX update 2026-05-15:
   moved from above Privacy/Terms to BELOW Trademark so it
   reads as least-prominent metadata, not visual competition for
   the primary footer links. */
.app-version {
  text-align: center;
  font-size: 0.65rem;       /* slightly smaller than other footer lines */
  /* Same color as the other footer lines — opacity removed 2026-05-15
     (Scott UX) so the version line reads as a peer of Privacy/Terms
     and Trademark instead of looking dimmed/disabled. */
  color: var(--text-muted);
  letter-spacing: 0.03em;
  /* 2px top to match the other footer lines' 2px (uniform 4px gap
     between every line). Generous bottom padding handles the iOS
     safe-area inset since this is the lowest element on the page. */
  padding: 2px 16px 16px;
  font-variant-numeric: tabular-nums;
  user-select: text;        /* let support reps double-click + copy */
}

@supports (padding-bottom: env(safe-area-inset-bottom)) {
  .app-version {
    padding-bottom: calc(16px + env(safe-area-inset-bottom));
  }
}

/* (No adjacent-sibling override needed — .page-footer-links no longer
   has its own -40px pull-up. The .app-version above does the pull
   for the entire footer block, and Privacy line just sits naturally
   below it.) */

.about-description p + p {
  margin-top: 12px;
}

.faq-section h2 {
  font-size: 1.1rem;
  font-weight: 700;
  margin-bottom: 14px;
  padding-left: 4px;
}

.faq-item {
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  margin-bottom: 10px;
  overflow: hidden;
  box-shadow: var(--shadow);
  transition: border-color var(--transition);
}

.faq-item:hover {
  border-color: var(--blue-400);
}

.faq-question {
  padding: 16px 18px;
  font-weight: 600;
  font-size: 0.9rem;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  user-select: none;
}

.faq-question::after {
  content: '';
  width: 8px;
  height: 8px;
  border-right: 2px solid var(--text-muted);
  border-bottom: 2px solid var(--text-muted);
  transform: rotate(45deg);
  transition: transform 0.2s;
  flex-shrink: 0;
}

.faq-item.open .faq-question::after {
  transform: rotate(-135deg);
}

.faq-answer {
  max-height: 0;
  overflow: hidden;
  transition: max-height 0.3s ease;
}

.faq-answer-inner {
  padding: 0 18px 16px;
  font-size: 0.88rem;
  color: var(--text-secondary);
  line-height: 1.6;
}

.faq-item.open .faq-answer {
  max-height: 200px;
}

/* Inline contextual links inside FAQ answers (e.g. "buy credit packs in
   Settings ›" → deep-links to Settings → Ads section). Looks like a
   real text link with brand-colored underline + chevron, no external-
   link styling. The chevron is included in the HTML (›) so it animates
   with hover. */
/* Inline links in Tips & FAQ. Rewritten 2026-05-19 (Jimmy spotted
   inconsistencies): three issues combined to make the same link
   class look different in different contexts —
     (1) no :visited rule → browser default purple after first click
     (2) border-bottom: dotted renders crammed-into-solid at small
         font sizes and visibly spaced at larger ones, so the same
         class looked solid in the tip-text and dotted in the FAQ
     (3) some browsers strip the border-bottom on :visited links
         unless we re-declare it
   Switched to text-decoration: underline for crisp consistent
   rendering at any font size + explicit :visited override. */
.faq-inline-link {
  color: var(--blue-500);
  font-weight: 600;
  text-decoration: underline;
  text-decoration-color: var(--blue-400);
  text-decoration-thickness: 1px;
  text-underline-offset: 2px;
  cursor: pointer;
  transition: color var(--transition), text-decoration-color var(--transition);
  white-space: nowrap;
}

.faq-inline-link:visited {
  /* Override browser default purple — keep visited links the same
     brand blue so the UI doesn't look like a 1998 web page after a
     user has clicked around. */
  color: var(--blue-500);
  text-decoration-color: var(--blue-400);
}

.faq-inline-link:hover,
.faq-inline-link:visited:hover {
  color: var(--purple-500);
  text-decoration-color: var(--purple-500);
}

.faq-inline-link:focus-visible {
  outline: 2px solid var(--blue-400);
  outline-offset: 2px;
  border-radius: 2px;
}

/* ===== Settings Page ===== */
.settings-header {
  padding: 28px 0 20px;
}

.settings-header h1 {
  font-size: 1.4rem;
  font-weight: 700;
}

.settings-header p {
  color: var(--text-secondary);
  font-size: 0.88rem;
  margin-top: 4px;
}

.settings-group {
  margin-bottom: 24px;
}

/* ===== Responsive Settings + My Profile layout (Jimmy 2026-05-16) =====
 * On tablet+ both pages become a 2-column CSS grid so compact sections
 * (Voice, Preferences, Notifications, Personalization, Privacy & Data,
 * Account) can pair side-by-side instead of stretching across the whole
 * tablet/desktop width awkwardly.
 *
 * Large/complex sections (Profile, Ads, Family Insights, My Important
 * Dates, Special Things, Social & Web, Connected Apps, page headers,
 * Metrics & Advanced link) carry the .settings-group-full class and
 * span ALL columns regardless of breakpoint — they read better as
 * full-width cards because their content is wide-form.
 *
 * Grid only kicks in at the same breakpoint as the .app-shell expansion
 * (tablet portrait 600px+ OR tablet landscape/desktop 900px+) so phone
 * users still see the original single-column stack. The auto-flow:dense
 * tweak lets full-width spans not leave awkward gaps when they sit
 * between two half-width cards.
 * ===================================================== */
/* Settings + My Profile stay SINGLE COLUMN on tablet+ (Jimmy 2026-05-16
   second pass). The 2-col side-by-side grid I built initially felt
   overengineered — mismatched-height pairs looked awkward, single-
   toggle Notifications next to multi-control Personalization read as
   visually unbalanced, and the orphaned 5th section had nothing to
   pair with. Single column reads as a clean settings flow regardless
   of viewport width; the .app-shell expansion + Quick Actions grid
   are the actual responsive wins on the Home page.

   The .settings-group-full class additions in HTML are harmless
   when no parent grid exists (just an extra class with no rules
   targeting it now) — leaving them in case we want to bring back
   pairing in the future. */

/* Advanced Settings link at bottom of Settings page */
.settings-advanced-link {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 16px 18px;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 14px;
  cursor: pointer;
  text-decoration: none;
  color: inherit;
  margin-top: 8px;
  margin-bottom: 24px;
  transition: border-color var(--transition), background var(--transition);
}

/* When .settings-advanced-link sits INSIDE a .settings-group (used as
   that section's content card — e.g. "My Documents" between Ads and
   Preferences), the default 8px+24px margins create extra vertical
   space the other section cards don't have, so the My Documents
   section reads as visually inconsistent. Zero those margins out so
   the link uses the same spacing rhythm as a regular .settings-card:
   the 10px padding-bottom on .settings-group-title sets the gap above,
   and the .settings-group's margin-bottom: 24px sets the gap below.

   The standalone use case (the "Metrics & Advanced Settings" link at
   the bottom of Settings, NOT inside a .settings-group) keeps its
   original margins — they're appropriate there as a footer affordance. */
.settings-group > .settings-advanced-link {
  margin-top: 0;
  margin-bottom: 0;
}

.settings-advanced-link:hover {
  border-color: var(--blue-500);
  background: var(--blue-50);
}

[data-theme="dark"] .settings-advanced-link:hover {
  background: rgba(59, 130, 246, 0.08);
}

.settings-advanced-info {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.settings-advanced-title {
  font-size: 0.96rem;
  font-weight: 600;
  color: var(--text);
  /* Flex so an inline count badge (e.g. "My Documents [12]" on My Profile)
     sits to the right of the title text with a 8px gap, not pushed to a
     new line. align-items:center vertically aligns the badge against the
     title's text baseline-y. Added 2026-05-17 (Scott). */
  display: flex;
  align-items: center;
  gap: 8px;
}

/* Generic count badge — used wherever a section title needs "N of these"
   visual context (My Documents [12], Your Connections [3], etc.).
   Identical visual to the legacy .connections-badge; this version is the
   canonical class going forward. Existing .connections-badge stays in
   place for backwards compatibility — both render identically. */
.count-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 22px;
  height: 18px;
  padding: 0 6px;
  border-radius: 9px;
  background: var(--blue-500);
  color: #fff;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0;
  text-transform: none;
}

.settings-advanced-sub {
  font-size: 0.78rem;
  color: var(--text-secondary);
  line-height: 1.3;
}

.settings-advanced-chevron {
  font-size: 1.4rem;
  color: var(--text-muted);
  flex-shrink: 0;
  line-height: 1;
}

.settings-group-title {
  font-size: 0.75rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-muted);
  padding: 0 4px 10px;
}

/* ---- Collapsible card row (Scott 2026-05-30, fixes v521 + v523) ----
   When the toggle button lives INSIDE a .settings-card (so the
   collapsed state still reads as a card row rather than a bare
   label floating on the page background), use
   `.qbit-code-collapse-toggle` instead of the generic group-title
   variant. The button fills the card edge-to-edge and behaves like
   a tappable card row.

   v523 bug fix: the parent .qbit-code-block > .settings-card rule
   adds `padding: 16px 18px` to the card — combined with the
   toggle's own 16/18 padding that was producing a 32/36 double
   pad, so "Connect with People" sat way further indented than the
   "Your connections" title in the card below it. Solution: when
   the block is collapsible, zero out the card's padding and let
   the toggle button + collapsible-body each manage their own
   padding (same total visual padding, no doubling). */
.qbit-code-block.qbit-code-block-collapsible > .settings-card {
  padding: 0;
}
.qbit-code-collapse-toggle {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  padding: 16px 18px;
  background: transparent;
  border: none;
  cursor: pointer;
  text-align: left;
  font: inherit;
  color: var(--text);
  border-radius: var(--radius);
  transition: background-color 0.12s ease;
}
.qbit-code-collapse-toggle:hover {
  background-color: rgba(0, 0, 0, 0.04);
}
.qbit-code-collapse-text {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.qbit-code-collapse-title {
  font-size: 0.96rem;
  font-weight: 600;
  color: var(--text);
  line-height: 1.3;
}
.qbit-code-collapse-hint {
  font-size: 0.82rem;
  font-weight: 400;
  color: var(--text-muted);
  line-height: 1.4;
  white-space: normal;
}
.settings-group-title-chevron {
  flex-shrink: 0;
  transition: transform 0.18s ease;
  color: var(--text-muted);
}
/* Collapsed state — chevron points right, body is hidden.
   Selector goes through the wrapping .settings-card to reach the
   toggle + body siblings. */
[data-collapsed="true"] > .settings-card > .qbit-code-collapse-toggle .settings-group-title-chevron {
  transform: rotate(-90deg);
}
[data-collapsed="true"] > .settings-card > .collapsible-body {
  display: none;
}
/* Expanded body — give it its own card-interior padding since the
   wrapping card's padding was zeroed out above. Top is 0 because
   the toggle button's bottom padding already supplies that gap. */
[data-collapsed="false"] > .settings-card > .qbit-code-collapsible-body {
  padding: 0 18px 18px 18px;
}
/* When EXPANDED, kill the toggle header's hover/tap shade so it doesn't leave
   a stuck shaded bar across the top of the open section (the :hover background
   sticks after a tap on touch devices). Collapsed state keeps its hover cue.
   (Jimmy 2026-06-10) */
[data-collapsed="false"] > .settings-card > .qbit-code-collapse-toggle,
[data-collapsed="false"] > .settings-card > .qbit-code-collapse-toggle:hover {
  background-color: transparent;
}

.settings-card {
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
  box-shadow: var(--shadow);
}

/* Save Changes button — auto-injected by JS into every settings-card
   that contains an editable input. Auto-save still happens on change;
   this button is purely the "I see that it saved" reassurance every
   user has been trained to expect from settings pages.

   Click → 1.5s green "Saved!" pulse → revert. Disabled during pulse. */
.settings-save-btn {
  display: block;
  width: calc(100% - 36px);
  margin: 0 18px 16px 18px;
  padding: 10px 16px;
  background: linear-gradient(135deg, var(--blue-500), var(--purple-500, #8b5cf6));
  color: #ffffff;
  border: none;
  border-radius: 10px;
  font-size: 0.92rem;
  font-weight: 700;
  cursor: pointer;
  letter-spacing: 0.01em;
  box-shadow: 0 2px 8px rgba(59,130,246,0.20);
  transition: transform 0.15s ease, box-shadow 0.15s ease, background 0.25s ease;
}
.settings-save-btn:hover:not(:disabled) {
  transform: translateY(-1px);
  box-shadow: 0 4px 14px rgba(59,130,246,0.30);
}
.settings-save-btn:active:not(:disabled) {
  transform: translateY(0);
}
.settings-save-btn:disabled {
  cursor: default;
  opacity: 1; /* override default disabled fade — we manage state via .is-saved */
}
/* Confirmed state — bright green, no gradient. Communicates success. */
.settings-save-btn.is-saved {
  background: linear-gradient(135deg, #16a34a, #22c55e);
  box-shadow: 0 2px 10px rgba(34,197,94,0.30);
}

.setting-item {
  padding: 16px 18px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  border-bottom: 1px solid var(--border);
}

.setting-item:last-child {
  border-bottom: none;
}

/* The auto-injected Save Changes button (.settings-save-btn) sits as the
   actual last-child inside .settings-card, which means the LAST visible
   .setting-item still gets a border-bottom — surfacing as a thin line
   right above the gradient button. Strip it explicitly. (Scott noticed.)
   :has() is supported in Chrome 105+, Safari 15.4+, Firefox 121+. */
.setting-item:has(+ .settings-save-btn) {
  border-bottom: none;
}

/* Genius Credits Balance row in the Ads section — gold gradient pill so
   the number reads as the *valuable* / treasure-like object on this card.
   Per Scott: gold (not brand blue) makes credits feel earned/owned in a
   way that brand blue doesn't. Updates live (no refresh) after a query
   or a purchase. */
.settings-credits-balance {
  /* Two-tone gold: deeper amber on the lower-right, lighter honey on the
     upper-left, brushed-metal feel without being garish. */
  background: linear-gradient(135deg, #fde68a, #f59e0b);
  color: #422006; /* dark espresso for max contrast on gold */
  font-weight: 800;
  font-size: 1.05rem;
  padding: 6px 16px;
  border-radius: 999px;
  letter-spacing: 0.01em;
  box-shadow: 0 2px 8px rgba(245, 158, 11, 0.30), inset 0 1px 0 rgba(255,255,255,0.5);
  min-width: 56px;
  text-align: center;
  font-variant-numeric: tabular-nums;
  /* Brief flash when the value changes — applied via .credits-flash class */
  transition: transform 0.18s ease, box-shadow 0.18s ease;
}
.settings-credits-balance.credits-flash {
  animation: credits-flash 0.7s ease-out 1;
}
@keyframes credits-flash {
  0%   { transform: scale(1);    box-shadow: 0 2px 8px rgba(245,158,11,0.30), inset 0 1px 0 rgba(255,255,255,0.5); }
  35%  { transform: scale(1.08); box-shadow: 0 4px 16px rgba(245,158,11,0.65), inset 0 1px 0 rgba(255,255,255,0.7); }
  100% { transform: scale(1);    box-shadow: 0 2px 8px rgba(245,158,11,0.30), inset 0 1px 0 rgba(255,255,255,0.5); }
}

.setting-info {
  flex: 1;
  min-width: 0;
}

.setting-label {
  font-size: 0.92rem;
  font-weight: 500;
}

/* Genius Credits Balance label — matched to .settings-advanced-title
   (0.96rem / 600) so it reads as a section heading rather than just
   another row label. The Credits row is functionally a CTA ("buy more
   credits") not a setting toggle, so the heavier weight is appropriate. */
#settings-credits-row .setting-label {
  font-size: 0.96rem;
  font-weight: 600;
}

.setting-desc {
  font-size: 0.78rem;
  color: var(--text-secondary);
  margin-top: 2px;
}

/* Lock badge shown next to a setting label when that setting is gated.
   Currently used by SafeSearch (locks until age-verified 18+). */
.setting-lock-badge {
  display: inline-flex;
  align-items: center;
  margin-left: 8px;
  padding: 2px 8px;
  border-radius: 10px;
  font-size: 0.65rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  background: var(--bg-secondary, #f1f5f9);
  color: var(--text-secondary);
  vertical-align: middle;
}
/* The class rule above has the same specificity as [hidden]{display:none}
   from the user-agent stylesheet — and author styles win on a tie, which
   means setting `badge.hidden = true` in JS had no visual effect. Result:
   the "🔒 Locked" badge persisted even after the user verified 18+ and
   the toggle was unlocked. Explicit override fixes it. (Scott bug.) */
.setting-lock-badge[hidden] { display: none; }
[data-theme="dark"] .setting-lock-badge {
  background: rgba(148, 163, 184, 0.15);
  color: #94a3b8;
}

/* When a row is locked, dim the toggle and stop pointer events on it.
   The label/click handler still receives clicks so we can show the
   "why is this locked?" explanation. */
.setting-item.is-locked .toggle {
  opacity: 0.55;
  cursor: not-allowed;
}
.setting-item.is-locked .toggle input {
  pointer-events: none;
}

/* Inline link inside a setting description — used by the SafeSearch lock
   to jump to the My Birthday field. Underline + brand blue so it reads
   as a real link rather than body text. */
.setting-link {
  color: var(--blue-500);
  font-weight: 600;
  text-decoration: underline;
  text-underline-offset: 2px;
  cursor: pointer;
}
.setting-link:hover { color: var(--blue-600); }

/* Pulse-highlight animation. Applied briefly to a target row when the
   user clicks "jump to birthday" — gives a clear visual cue about
   which field needs filling out. ~1.5s total, plays once. */
@keyframes pulse-attention {
  0%   { box-shadow: 0 0 0 0 rgba(59, 130, 246, 0.55); }
  40%  { box-shadow: 0 0 0 10px rgba(59, 130, 246, 0); }
  100% { box-shadow: 0 0 0 0 rgba(59, 130, 246, 0); }
}
.pulse-attention {
  border-radius: 12px;
  animation: pulse-attention 1.5s ease-out 1;
}

.setting-input {
  width: 100%;
  max-width: 180px;
  padding: 8px 12px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--bg);
  color: var(--text);
  font-size: 0.88rem;
  font-family: inherit;
  outline: none;
  transition: border-color var(--transition);
}

.setting-input:focus {
  border-color: var(--blue-400);
}

.setting-item-full {
  flex-direction: column;
  align-items: stretch;
  gap: 10px;
}

.setting-input-full {
  max-width: none !important;
  width: 100%;
}

.location-inputs {
  display: flex;
  gap: 8px;
  width: 100%;
}

.location-input {
  max-width: none !important;
  min-width: 0;
  flex: 1;
  padding: 8px 10px !important;
  font-size: 0.85rem !important;
}

.setting-textarea {
  width: 100%;
  padding: 10px 14px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--bg);
  color: var(--text);
  font-size: 0.88rem;
  font-family: inherit;
  outline: none;
  resize: vertical;
  min-height: 60px;
  line-height: 1.5;
  transition: border-color var(--transition);
}

.setting-textarea::placeholder {
  color: var(--text-muted);
}

.setting-textarea:focus {
  border-color: var(--blue-400);
}

/* Radio Buttons */
.radio-group {
  display: flex;
  flex-direction: column;
  gap: 10px;
  width: 100%;
}

.radio-option {
  display: flex;
  align-items: center;
  gap: 10px;
  cursor: pointer;
  font-size: 0.88rem;
  padding: 4px 0;
}

.radio-option input[type="radio"] {
  display: none;
}

.radio-mark {
  width: 20px;
  height: 20px;
  border: 2px solid var(--gray-300);
  border-radius: 50%;
  flex-shrink: 0;
  position: relative;
  transition: border-color 0.2s;
}

[data-theme="dark"] .radio-mark {
  border-color: var(--gray-600);
}

.radio-option input:checked ~ .radio-mark {
  border-color: var(--blue-500);
}

.radio-option input:checked ~ .radio-mark::after {
  content: '';
  position: absolute;
  top: 3px;
  left: 3px;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: var(--blue-500);
}

.radio-text {
  color: var(--text-secondary);
  line-height: 1.4;
}

/* ===== Credit Packs ===== */
.credit-packs {
  padding: 16px 18px 12px;
  border-top: 1px solid var(--border);
}

.credit-packs-title {
  font-size: 0.88rem;
  font-weight: 600;
  color: var(--blue-600);
  margin-bottom: 14px;
  text-align: center;
}

[data-theme="dark"] .credit-packs-title {
  color: var(--blue-400);
}

.credit-pack-options {
  display: flex;
  gap: 10px;
  margin-bottom: 14px;
}

.credit-pack-option {
  flex: 1;
  cursor: pointer;
}

.credit-pack-option input[type="radio"] {
  display: none;
}

.credit-pack-card {
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 12px 8px;
  border: 2px solid var(--border);
  border-radius: 12px;
  background: var(--bg);
  transition: border-color var(--transition), background var(--transition);
  text-align: center;
  gap: 3px;
  position: relative;
}

.credit-pack-option input:checked + .credit-pack-card {
  border-color: var(--blue-500);
  background: var(--blue-50);
}

[data-theme="dark"] .credit-pack-option input:checked + .credit-pack-card {
  background: rgba(59, 130, 246, 0.1);
}

.credit-pack-badge {
  position: absolute;
  top: -9px;
  left: 50%;
  transform: translateX(-50%);
  background: var(--blue-500);
  color: white;
  font-size: 0.62rem;
  font-weight: 600;
  padding: 2px 8px;
  border-radius: 10px;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  white-space: nowrap;
}

.credit-pack-best .credit-pack-badge {
  background: #7C3AED;
}

.credit-pack-price {
  font-size: 1.2rem;
  font-weight: 700;
  color: var(--text);
}

.credit-pack-amount {
  font-size: 0.7rem;
  color: var(--text-secondary);
  line-height: 1.3;
}

.credit-pack-total {
  font-size: 0.72rem;
  font-weight: 600;
  color: var(--blue-600);
}

[data-theme="dark"] .credit-pack-total {
  color: var(--blue-400);
}

.credit-usage-info {
  padding: 10px 12px;
  background: var(--bg-secondary);
  border-radius: 10px;
  margin-bottom: 14px;
}

.credit-usage-row {
  font-size: 0.78rem;
  color: var(--text-secondary);
  padding: 0;
  line-height: 1.35;
}

.credit-usage-row strong {
  color: var(--text);
}

/* Credit purchases paused notice (replaces the buy controls while
   Qbit migrates to a new payment provider). Adult Settings → Ads and
   the per-child "Add credits" region share this look. */
.credits-buy-paused,
.child-settings-add-credits-paused {
  padding: 14px 16px;
  background: var(--bg-secondary);
  border-radius: 12px;
  border: 1px solid var(--border, rgba(0, 0, 0, 0.08));
}
.credits-buy-paused-title {
  font-size: 0.95rem;
  font-weight: 700;
  color: var(--text);
  margin-bottom: 6px;
}
.credits-buy-paused-body,
.child-settings-add-credits-paused {
  font-size: 0.85rem;
  line-height: 1.45;
  color: var(--text-secondary);
}
/* When used as the injected <p> inside the child pack-tiles slot it is
   both the container and the text, so it carries its own margin reset. */
.child-settings-add-credits-paused {
  margin: 0;
}

.credit-go-btn {
  width: 100%;
  padding: 12px;
  background: linear-gradient(135deg, var(--blue-500), var(--purple-500));
  color: white;
  border: none;
  border-radius: 10px;
  font-size: 0.95rem;
  font-weight: 700;
  font-family: inherit;
  cursor: pointer;
  margin-bottom: 12px;
  transition: transform var(--transition), box-shadow var(--transition), opacity var(--transition);
  letter-spacing: 0.3px;
}

.credit-go-btn:hover {
  transform: translateY(-1px);
  box-shadow: 0 6px 16px rgba(59, 130, 246, 0.35);
}

.credit-go-btn:disabled {
  opacity: 0.6;
  cursor: wait;
  transform: none;
  box-shadow: none;
}

.credit-replenish-row {
  border-bottom: none !important;
  padding: 10px 0 !important;
}

.credit-disclaimer {
  font-size: 0.7rem;
  color: var(--text-muted);
  font-style: italic;
  text-align: center;
  padding-top: 4px;
}

.setting-select {
  padding: 8px 12px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--bg);
  color: var(--text);
  font-size: 0.88rem;
  font-family: inherit;
  outline: none;
  cursor: pointer;
}

/* ===== Unified dropdown arrow =====
   Browser-default <select> arrows have inconsistent placement across
   Chrome / Safari / Firefox and on dark themes the contrast often
   makes them disappear into the input border. Replace with a custom
   SVG chevron, positioned with explicit padding so the arrow has
   breathing room from the right edge AND the option text never runs
   into it.
   The mid-gray stroke (#7b8794) reads cleanly on both light and dark
   backgrounds — same approach we use for the support-row chevron. */
.setting-select,
.custom-social-platform {
  appearance: none;
  -webkit-appearance: none;
  -moz-appearance: none;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%237b8794' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'><polyline points='6 9 12 15 18 9'/></svg>");
  background-repeat: no-repeat;
  background-position: right 14px center;
  background-size: 14px 14px;
  padding-right: 36px;            /* leave room for the chevron */
}
/* Hide the legacy IE/Edge-Legacy arrow if any browser still ships one. */
.setting-select::-ms-expand,
.custom-social-platform::-ms-expand {
  display: none;
}

/* Toggle Switch */
.toggle {
  position: relative;
  width: 48px;
  height: 28px;
  flex-shrink: 0;
}

.toggle input {
  opacity: 0;
  width: 0;
  height: 0;
}

.toggle-slider {
  position: absolute;
  inset: 0;
  background: var(--gray-300);
  border-radius: 28px;
  cursor: pointer;
  transition: background 0.2s;
}

[data-theme="dark"] .toggle-slider {
  background: var(--gray-600);
}

.toggle-slider::before {
  content: '';
  position: absolute;
  width: 22px;
  height: 22px;
  left: 3px;
  bottom: 3px;
  background: white;
  border-radius: 50%;
  transition: transform 0.2s;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
}

.toggle input:checked + .toggle-slider {
  background: var(--blue-500);
}

.toggle input:checked + .toggle-slider::before {
  transform: translateX(20px);
}

/* Important Dates */
.dates-hint {
  padding: 12px 18px;
  font-size: 0.88rem;             /* was 0.82rem — matches qbit-code-hint */
  color: var(--text-secondary);   /* was --text-muted — readable, not faded */
  line-height: 1.5;
  font-style: italic;             /* kept — intentional flavor for dates copy */
  /* Removed border-bottom (Scott #16) — the italic styling + muted color
     already separate the hint from the data below, no divider needed.
     Matches the cleaner look of the other settings cards. */
}

/* ── Birthday card (Jimmy's restructure 2026-05-15) ──
   2-column CSS Grid:
     ┌─────────┬──────────────────────────┐
     │         │      My Birthday!        │
     │  Qbit   │   Let Qbit celebrate...  │   ← right col stacks
     │  icon   ├──────────────────────────┤      text on top,
     │         │   [Mon] [Day] [Year]     │      dropdowns below
     └─────────┴──────────────────────────┘
   - Icon col is `auto` width so the image dictates the column size
     (icon stays visually square + full-height of the card)
   - Text col is `1fr` so it absorbs the rest of the width
   - Text col is itself a flex column with centered content
   - Dropdowns inside the birthday card override the default fixed
     widths and flex:1 each so they spread across the full text-col
*/
/* SPECIFICITY NOTE (Jimmy's iPhone bug 2026-05-15):
   This selector chains BOTH classes (`.date-entry.date-entry-birthday`)
   to bump specificity from (0,1,0) to (0,2,0). Without that bump, the
   plain `.date-entry { display: flex; flex-wrap: wrap }` rule further
   down in this file wins on cascade order (equal specificity → later
   rule wins), which silently turned this card into a flex container.
   Android rendered the flex layout in a way that *happened* to look
   OK because both children fit on one line; iPhone's narrower viewport
   wrapped the text column below the icon, breaking the design.
   Always qualify with both classes here so grid wins unambiguously. */
.date-entry.date-entry-birthday {
  background: linear-gradient(135deg, #f5f3ff, #ede9fe, #e8e0ff);
  border: 1px solid #a78bfa;
  border-radius: 14px;
  /* margin: 12px 18px — only the 18px horizontal matters for the
     alignment Jimmy asked for. Matches .settings-save-btn's 18px
     horizontal inset so Birthday card and Save Changes button line
     up vertically, framing the My Important Dates section cleanly. */
  margin: 12px 18px;
  padding: 14px;
  display: grid;
  grid-template-columns: auto 1fr;
  align-items: stretch;
  gap: 14px;
  position: relative;
  overflow: hidden;
}

[data-theme="dark"] .date-entry.date-entry-birthday {
  background: linear-gradient(135deg, #2d1f3d, #1f2a4a, #2d1f3d);
  border-color: #a855f7;
}

/* Left column: bigger Qbit icon, vertically centered in its square. */
.birthday-icon-col {
  display: flex;
  align-items: center;
  justify-content: center;
}

.birthday-icon-img {
  /* Bumped from 64px → 96px (Jimmy 2026-05-15 — wanted Qbit bigger
     and visually dominant in the icon column). Stays square via
     equal width/height; object-fit prevents distortion if the source
     image ever changes aspect ratio. */
  width: 96px;
  height: 96px;
  object-fit: contain;
  filter: drop-shadow(0 2px 4px rgba(0,0,0,0.1));
  flex-shrink: 0;
}

/* Right column: stacks label+sub on top, dropdowns below, both
   centered/full-width within the column. justify-content:center
   gives even vertical spacing so a slightly taller icon stays
   visually balanced. */
.birthday-text-col {
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 10px;
  min-width: 0;        /* lets the column shrink properly on narrow */
}

.birthday-info {
  display: flex;
  flex-direction: column;
  align-items: center; /* centers the label + sub-text horizontally */
  text-align: center;
  gap: 2px;
}

.birthday-label {
  font-size: 1.05rem;
  font-weight: 700;
  color: #7c3aed;
  letter-spacing: 0.01em;
}

[data-theme="dark"] .birthday-label {
  color: #c4b5fd;
}

.birthday-sub {
  font-size: 0.78rem;
  color: #6d28d9;
  opacity: 0.7;
}

[data-theme="dark"] .birthday-sub {
  color: #c4b5fd;
  opacity: 0.6;
}

/* Inside the birthday card, the dropdowns spread to fill the full
   text-col width (overrides the fixed-width caps used in normal
   important-date rows). Each select flex:1 with max-width:none so
   they share the column space evenly regardless of viewport width. */
.date-entry-birthday .date-dropdowns {
  width: 100%;
  gap: 6px;
}
.date-entry-birthday .date-dropdowns select {
  flex: 1 1 0;
  max-width: none;
  min-width: 0;
}

/* Narrow viewports: shrink Qbit icon slightly so the text column
   has more room for the 3 dropdowns. Below ~360px screens the
   icon at 96px would crowd the right column. */
@media (max-width: 380px) {
  /* Same specificity bump as the main rule above — must beat the
     plain `.date-entry` gap/padding declarations. */
  .date-entry.date-entry-birthday {
    gap: 10px;
    padding: 12px;
  }
  .birthday-icon-img {
    width: 76px;
    height: 76px;
  }
}

/* (Previously: .birthday-date-input + .birthday-trigger rules. Removed
   2026-05-15 when the native date input was replaced with 3 locale-
   ordered dropdowns. The birthday-context purple-tinted dropdown
   styling now lives in the `.date-entry-birthday .date-dropdowns`
   selectors further down in this file.) */

.dates-section {
  margin-top: 0;
}

/* ── Important Date rows (Jimmy 2026-05-15) ──
   Layout (always, at every viewport width):
     ┌─────────────────────────────────────────────┐
     │ [Description.............................]│  ← title on top
     │ [Mon ▾][Day ▾][Year ▾]         [↑][↓][×] │  ← pickers + actions
     └─────────────────────────────────────────────┘
   Text input gets flex-basis:100% so it always wraps to its own row;
   HTML order puts the input FIRST so it lands on row 1, with the
   dropdowns + reorder controls + remove × flowing onto row 2.
*/
.date-entry {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  row-gap: 8px;
  align-items: center;
  padding: 10px 18px;
  border-bottom: 1px solid var(--border);
}

/* Description text input always takes its own full-width row.
   Combined with the JS render order (text-input-first in HTML),
   this puts the label visually on top of the dropdowns. */
.date-entry input[type="text"] {
  flex: 1 1 100%;
}

/* Push action buttons (reorder ↑↓ + delete ×) to the right edge
   (Jimmy 2026-05-15). The first sibling after .date-dropdowns gets
   margin-left:auto, which absorbs all leftover space on row 2 and
   carries every subsequent element (the delete × in visible rows,
   or just the × in draft rows that have no reorder controls) along
   with it. Using `>` so the rule doesn't leak into the birthday
   card's nested .date-dropdowns inside .birthday-text-col. */
.date-entry > .date-dropdowns + * {
  margin-left: auto;
}

.date-entry:last-child {
  border-bottom: none;
}

/* ── Wide-screen layout (≥540px) — Jimmy 2026-05-16 ──
   Single-row grid: description on the left absorbing remaining space,
   then dropdowns, reorder pill, and delete × all flush right. Keeps
   each row to one line on wide screens for a clean, scannable list.

     ┌──────────────────────────────────────────────────────────┐
     │ [Description.........]   [Mo][Day][Year]   [↑↓]   [×]   │
     └──────────────────────────────────────────────────────────┘

   The 4-column track gives reorder its own slot when present
   (visible-real rows) — it collapses to 0-width when absent (draft
   rows) so dropdowns + remove stay anchored to the right edge either
   way. The birthday card is unaffected — it uses its own
   higher-specificity grid (.date-entry.date-entry-birthday).
*/
@media (min-width: 540px) {
  .date-entry {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto auto auto;
    grid-template-areas: "description dropdowns reorder remove";
    column-gap: 8px;
    align-items: center;
  }
  /* Reset the mobile flex sizing — irrelevant under grid, but keeps
     intent obvious to anyone reading the rules. */
  .date-entry > input[type="text"] {
    flex: unset;
    grid-area: description;
    align-self: center;
  }
  .date-entry > .date-dropdowns {
    grid-area: dropdowns;
    justify-self: end;
  }
  .date-entry > .remove-date-btn {
    grid-area: remove;
    justify-self: end;
    /* Override the mobile rule's margin-left:auto (flex trick irrelevant
       under grid layout — justify-self does the alignment). */
    margin-left: 0;
  }
  .date-entry > .list-reorder-controls {
    grid-area: reorder;
    justify-self: end;
    margin-left: 0;
  }
  /* Same neutralizer for any other sibling after dropdowns. */
  .date-entry > .date-dropdowns + * {
    margin-left: 0;
  }
}

.date-entry input {
  flex: 1;
  padding: 8px 12px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--bg);
  color: var(--text);
  font-size: 0.85rem;
  font-family: inherit;
  outline: none;
}

.date-entry input:focus {
  border-color: var(--blue-400);
}

/* ============================================================
 * Date Dropdowns (Month / Day / Year)
 * ============================================================
 * Replaces the old <input type="date"> + invisible-overlay trick
 * (Scott's UX request 2026-05-15) — native date pickers are awful
 * on mobile for selecting a year decades in the past. Three native
 * <select>s use the OS-native picker on every device and reorder
 * to match the user's locale. See renderDateDropdowns() in app.js.
 *
 * Replaces these now-deleted blocks: .date-trigger-wrap,
 * .date-overlay-input, .date-trigger (and its hover/focus rules),
 * .date-trigger-value, plus the .date-entry input[type="date"]
 * rule above.
 * ============================================================ */
.date-dropdowns {
  display: flex;
  gap: 4px;
  align-items: center;
  flex-shrink: 0;
}

.date-dropdowns select {
  padding: 8px 4px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--bg);
  color: var(--text);
  font-size: 0.85rem;
  font-family: inherit;
  cursor: pointer;
  outline: none;
  transition: border-color var(--transition);
  /* Empty selects (placeholder visible) read muted; once a value is
     picked the option text itself uses --text, so we get a free
     "empty vs filled" distinction without extra JS. */
}

.date-dropdowns select:focus,
.date-dropdowns select:hover {
  border-color: var(--blue-400);
}

/* Per-part width caps. Tuned for SHORT localized month names
   ("Sep" / "9月" / "sept.") — much narrower than full names which
   bloated the row on mobile (Jimmy's screenshot 2026-05-15). Day
   is narrowest (1-2 chars), Year medium (4 chars), Month slightly
   wider to absorb the longest abbreviations across locales. */
.date-dropdowns .date-dd-day   { min-width: 56px; max-width: 64px; }
.date-dropdowns .date-dd-year  { min-width: 72px; max-width: 82px; }
.date-dropdowns .date-dd-month { min-width: 76px; max-width: 92px; }

/* Inside an important-date row, the dropdowns keep their fixed
   footprint and the description input absorbs remaining space. */
.date-entry .date-dropdowns {
  flex-shrink: 0;
}

/* Narrow viewports — just tighten the dropdown chrome a touch.
   The text-on-its-own-row layout is now the base rule (always-on)
   so the previous mobile-only wrap/order patches are gone. */
@media (max-width: 600px) {
  .date-dropdowns .date-dd-month { max-width: 88px; }
  .date-dropdowns select { padding: 6px 4px; font-size: 0.82rem; }
}

/* Birthday card variant — purple-tinted to match the gradient. The
   .date-entry-birthday parent already supplies the purple background;
   we just match the dropdown chrome to it. */
.date-entry-birthday .date-dropdowns select {
  border: 1.5px solid #a78bfa;
  background: rgba(255, 255, 255, 0.7);
}

[data-theme="dark"] .date-entry-birthday .date-dropdowns select {
  border-color: #a855f7;
  /* OPAQUE dark-violet fill (not translucent). The old rgba(255,255,255,0.08)
     was ~transparent, so it relied entirely on color-scheme:dark to darken the
     native <select> chrome. On engines that ignore color-scheme for native
     controls (older Chromium, some Android WebViews, Linux/GTK themes) the
     widget's default LIGHT background bled through → white bg under the light
     --text = white-on-white until interacted with (tester ticket 2026-07-02).
     Every OTHER select is safe because line ~96 gives it a SOLID --bg-card;
     this birthday variant was the lone exception that overrode it with a
     translucent value. A solid fill + explicit color restores readable
     contrast in every engine, color-scheme honored or not. Sits a touch
     lighter than the #2d1f3d/#1f2a4a card gradient so it still reads as an
     input; ~11:1 contrast against --text. */
  background: #352a52;
  color: var(--text);
}

/* (Previously: a mobile-stacking patch using .birthday-left as flex
   basis. Removed 2026-05-15 — the new birthday-card grid layout
   handles every viewport natively. The .birthday-icon-col +
   .birthday-text-col split above replaces this entirely.) */

.add-date-btn {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 12px 18px;
  color: var(--blue-500);
  font-size: 0.88rem;
  font-weight: 500;
  cursor: pointer;
  background: none;
  border: none;
  width: 100%;
  font-family: inherit;
  transition: background var(--transition);
}

.add-date-btn:hover {
  background: var(--bg-secondary);
}

/* ===== Legacy Important-Dates archive =====
   When we migrated dates from `profile.dates` (free-text labels in
   the user-profile blob) to the dedicated `qbit-important-dates`
   table, a handful of older entries don't have matching rows in the
   API list. Hiding them entirely scared users into thinking they
   were deleted (Jimmy reported this 2026-05-13), so we expose them
   as an opt-in disclosure at the bottom of the Important Dates card:
   a closed-by-default <details> labeled "N previous dates from
   before the upgrade" with per-row Restore + Dismiss buttons.

   Visually muted so it doesn't compete with the live list above. */
.dates-legacy-archive {
  margin: 8px 18px 12px;
  border: 1px dashed var(--border);
  border-radius: 10px;
  background: var(--bg-secondary);
  overflow: hidden;
}

.dates-legacy-archive[open] {
  background: var(--bg);
}

.dates-legacy-summary {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 14px;
  cursor: pointer;
  list-style: none;
  user-select: none;
  font-size: 0.85rem;
  color: var(--text-muted);
}

/* Remove default disclosure-triangle on webkit so we can use our own icon */
.dates-legacy-summary::-webkit-details-marker { display: none; }
.dates-legacy-summary::marker { content: ''; }

.dates-legacy-icon {
  font-size: 1rem;
  flex-shrink: 0;
}

.dates-legacy-summary-text {
  flex: 1;
  font-weight: 500;
}

.dates-legacy-summary-hint {
  font-size: 0.75rem;
  opacity: 0.7;
}

.dates-legacy-archive[open] .dates-legacy-summary-hint::after {
  content: ' (collapse)';
}

.dates-legacy-body {
  padding: 4px 14px 12px;
  border-top: 1px solid var(--border);
}

.dates-legacy-hint {
  margin: 8px 0 10px;
  font-size: 0.78rem;
  font-style: italic;
  color: var(--text-muted);
  line-height: 1.45;
}

.dates-legacy-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 0;
  border-top: 1px solid var(--border);
  font-size: 0.85rem;
}

.dates-legacy-row:first-of-type {
  border-top: none;
}

.dates-legacy-cal {
  font-size: 1rem;
  opacity: 0.6;
  flex-shrink: 0;
}

.dates-legacy-date {
  flex: 0 0 auto;
  min-width: 90px;
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}

.dates-legacy-label {
  flex: 1;
  min-width: 0;
  color: var(--text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.dates-legacy-restore {
  flex: 0 0 auto;
  padding: 5px 10px;
  border: 1px solid var(--blue-400);
  border-radius: 6px;
  background: transparent;
  color: var(--blue-500);
  font-size: 0.78rem;
  font-weight: 500;
  font-family: inherit;
  cursor: pointer;
  transition: background var(--transition), color var(--transition);
}

.dates-legacy-restore:hover {
  background: var(--blue-500);
  color: white;
}

.dates-legacy-dismiss {
  flex: 0 0 auto;
  width: 24px;
  height: 24px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: none;
  border: 1px solid transparent;
  border-radius: 50%;
  color: var(--text-muted);
  font-size: 1rem;
  line-height: 1;
  cursor: pointer;
  padding: 0;
  opacity: 0.6;
  transition: opacity .15s, color .15s, border-color .15s, background .15s;
}

.dates-legacy-dismiss:hover {
  opacity: 1;
  color: #ef4444;
  border-color: #ef4444;
  background: rgba(239, 68, 68, 0.06);
}

/* ===== Custom Socials — "+ Add Another" rows =====
   Each user-added social link is a flex row: [platform select]
   [optional Other-name input][URL input][× remove]. The Other-name
   input is hidden by the `hidden` attribute when not in use, and
   the row layout naturally collapses to "select | url | ×" — no
   layout shift workaround needed.
   Each row has its OWN horizontal padding (instead of the list
   container) so the row's border-top spans the full card width
   and visually aligns with the .setting-item separators above it.
   Earlier version put padding on the list, which inset the borders
   and made them look truncated next to the edge-to-edge .setting-item
   borders — caught by Jimmy 2026-05-02. */
.custom-socials-list {
  display: flex;
  flex-direction: column;
}

.custom-social-row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 18px;
  border-top: 1px solid var(--border);
}

.custom-social-platform {
  flex: 0 0 130px;
  padding: 8px 10px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm, 8px);
  background: var(--bg);
  color: var(--text);
  font-family: inherit;
  font-size: 0.88rem;
  outline: none;
  cursor: pointer;
}
.custom-social-platform:focus {
  border-color: var(--blue-500);
  box-shadow: 0 0 0 3px var(--blue-100, rgba(59,130,246,.15));
}

.custom-social-other-name {
  flex: 0 0 130px;
  padding: 8px 10px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm, 8px);
  background: var(--bg);
  color: var(--text);
  font-family: inherit;
  font-size: 0.88rem;
  outline: none;
}
.custom-social-other-name:focus {
  border-color: var(--blue-500);
  box-shadow: 0 0 0 3px var(--blue-100, rgba(59,130,246,.15));
}
.custom-social-other-name[hidden] { display: none; }

.custom-social-url {
  flex: 1 1 auto;
  min-width: 0;             /* lets the input shrink instead of overflowing */
  padding: 8px 10px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm, 8px);
  background: var(--bg);
  color: var(--text);
  font-family: inherit;
  font-size: 0.88rem;
  outline: none;
}
.custom-social-url:focus {
  border-color: var(--blue-500);
  box-shadow: 0 0 0 3px var(--blue-100, rgba(59,130,246,.15));
}

.custom-social-remove {
  flex: 0 0 auto;
  width: 28px;
  height: 28px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: none;
  border: 1px solid var(--border);
  border-radius: 50%;
  color: var(--text-muted);
  font-size: 1.1rem;
  line-height: 1;
  cursor: pointer;
  padding: 0;
  transition: color .15s, border-color .15s, background .15s;
}
.custom-social-remove:hover {
  color: #ef4444;
  border-color: #ef4444;
  background: rgba(239, 68, 68, 0.06);
}

.add-social-btn {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 12px 18px;
  color: var(--blue-500);
  font-size: 0.88rem;
  font-weight: 500;
  cursor: pointer;
  background: none;
  border: none;
  width: 100%;
  font-family: inherit;
  transition: background var(--transition);
}
.add-social-btn:hover { background: var(--bg-secondary); }

/* ===== Hardcoded socials — hide button =====
   Tiny × in the TOP-RIGHT corner of an EMPTY hardcoded social row,
   letting the user collapse fields they don't use. Hidden by default
   (the JS adds .has-hide-button when the URL is empty). When clicked,
   the row hides and the platform reappears as a "Restore X" option
   in the "+ Add Another" dropdown below.

   Position-absolute (anchored to the row, which is position: relative)
   instead of an inline flex item — keeps the × out of the input row's
   tab order visually and survives narrow-viewport flex-wrap (where an
   inline × ends up awkwardly stacked under the input).

   Visually subtle — we don't want to draw the eye away from the URL
   input itself. Becomes salient only on row hover. */
.setting-item-social {
  position: relative;
}
.setting-social-hide {
  position: absolute;
  top: 8px;
  right: 8px;
  width: 24px;
  height: 24px;
  display: none;             /* hidden by default; JS toggles via .has-hide-button */
  align-items: center;
  justify-content: center;
  background: none;
  border: 1px solid transparent;
  border-radius: 50%;
  color: var(--text-muted);
  font-size: 1rem;
  line-height: 1;
  cursor: pointer;
  padding: 0;
  opacity: 0.6;
  transition: opacity .15s, color .15s, border-color .15s, background .15s;
  /* Sit above the input visually so a tap on the corner of an
     overlapping input still hits the × (matches modal-close pattern). */
  z-index: 1;
}
.setting-item-social.has-hide-button .setting-social-hide {
  display: flex;
}
.setting-item-social:hover .setting-social-hide {
  opacity: 1;
}
.setting-social-hide:hover {
  color: #ef4444;
  border-color: #ef4444;
  background: rgba(239, 68, 68, 0.06);
  opacity: 1;
}

/* Hide-row visibility — toggled by the JS .is-hidden class so we can
   re-show the row with a single removeClass when the user picks the
   platform from the Add Another dropdown. Don't use [hidden] because
   the .setting-item layout depends on display:flex which the [hidden]
   attribute would override with display:none. */
.setting-item-social.is-hidden {
  display: none !important;
}

/* Mobile — stack vertically so the URL input has room. The platform
   select + name input stay on one line; the URL goes below; the ×
   sits at the right of the name input row. */
@media (max-width: 540px) {
  .custom-social-row {
    flex-wrap: wrap;
  }
  .custom-social-platform { flex: 1 1 0; }
  .custom-social-url {
    order: 99;        /* pushes URL onto its own row */
    flex: 1 1 100%;
  }
  .custom-social-other-name { flex: 1 1 0; }
}

.remove-date-btn {
  background: none;
  border: none;
  color: var(--text-muted);
  cursor: pointer;
  padding: 4px;
  font-size: 1.1rem;
  line-height: 1;
  border-radius: 6px;
  transition: color var(--transition), background var(--transition);
}

.remove-date-btn:hover {
  color: #ef4444;
  background: rgba(239, 68, 68, 0.1);
}

/* ===== Terms & Privacy Pages ===== */
.legal-page {
  padding-top: 32px;
}

.legal-page h1 {
  font-size: 1.4rem;
  font-weight: 700;
  margin-bottom: 20px;
}

.legal-content {
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 24px 20px;
  box-shadow: var(--shadow);
}

.legal-content h2 {
  font-size: 1.05rem;
  font-weight: 600;
  margin: 20px 0 8px;
  color: var(--text);
}

.legal-content h2:first-child {
  margin-top: 0;
}

.legal-content p {
  font-size: 0.88rem;
  color: var(--text-secondary);
  line-height: 1.7;
  margin-bottom: 10px;
}

/* The legal pages had NO links until counsel's 2026-07-24 Privacy Notice, so
   anchors here were never styled and fell back to the browser default
   (#0000EE). That reads as unstyled next to the rest of the app. This is the
   ONLY rule added for the new document: same blue the message bubbles already
   use, so the links look like they belong without touching the page's layout,
   type or spacing. */
.legal-content a {
  color: var(--blue-600, #2563eb);
  text-decoration: underline;
  word-break: break-word;
}

.legal-content ul {
  padding-left: 20px;
  margin-bottom: 12px;
}

/* Sub-lists: counsel's indented "o" bullets (the cookie opt-out links, the
   state Attorney General lines). The browser default already gives a hollow
   circle marker and the inherited padding-left steps the indent, which is
   what the .docx shows. Only the spacing needs taming so the sub-list sits
   tight under the sentence that introduces it instead of floating. */
.legal-content ul ul {
  margin-top: 6px;
  margin-bottom: 8px;
}

.legal-content li {
  font-size: 0.88rem;
  color: var(--text-secondary);
  line-height: 1.7;
  margin-bottom: 4px;
}

.legal-updated {
  font-size: 0.78rem;
  color: var(--text-muted);
  margin-bottom: 16px;
  font-style: italic;
}

.legal-content h3 {
  font-size: 0.96rem;
  font-weight: 600;
  margin-top: 16px;
  margin-bottom: 8px;
  color: var(--text);
}

.legal-content p.legal-emphasis {
  background: var(--bg-secondary);
  border-left: 3px solid var(--blue-500);
  padding: 12px 14px;
  border-radius: 6px;
  font-size: 0.82rem;
  margin: 12px 0;
}

[data-theme="dark"] .legal-content p.legal-emphasis {
  background: rgba(59, 130, 246, 0.08);
}

.legal-table-wrap {
  overflow-x: auto;
  margin: 14px 0;
  -webkit-overflow-scrolling: touch;
  border-radius: 8px;
  border: 1px solid var(--border);
}

.legal-table {
  width: 100%;
  min-width: 540px;
  border-collapse: collapse;
  font-size: 0.78rem;
  background: var(--bg);
}

.legal-table th,
.legal-table td {
  text-align: left;
  padding: 10px 12px;
  border-bottom: 1px solid var(--border);
  vertical-align: top;
}

.legal-table th {
  background: var(--bg-secondary);
  font-weight: 600;
  color: var(--text);
  font-size: 0.76rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.legal-table tr:last-child td {
  border-bottom: none;
}

.legal-table td:not(:last-child) {
  border-right: 1px solid var(--border);
}

.legal-table th:not(:last-child) {
  border-right: 1px solid var(--border);
}

/* ===== Share Modal ===== */
.share-modal-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.5);
  z-index: 1000;   /* above the bottom tab bar (850) so the sheet + its share
                      buttons aren't sliced by it (Scott 2026-06-13) */
  display: flex;
  align-items: flex-end;
  justify-content: center;
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.3s, visibility 0.3s;
}

.share-modal-overlay.open {
  opacity: 1;
  visibility: visible;
}

.share-modal {
  background: var(--bg);
  border-radius: 20px 20px 0 0;
  padding: 24px 20px calc(32px + env(safe-area-inset-bottom, 0px));   /* clear the home indicator */
  width: 100%;
  max-width: 480px;
  transform: translateY(100%);
  transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}

.share-modal-overlay.open .share-modal {
  transform: translateY(0);
}

.share-modal-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 4px;
}

.share-modal-header h3 {
  font-size: 1.1rem;
  font-weight: 700;
}

.share-modal-close {
  background: none;
  border: none;
  font-size: 1.5rem;
  color: var(--text-muted);
  cursor: pointer;
  padding: 4px 8px;
  line-height: 1;
  border-radius: 8px;
}

.share-modal-close:hover {
  background: var(--bg-secondary);
}

.share-modal-desc {
  color: var(--text-secondary);
  font-size: 0.88rem;
  margin-bottom: 20px;
}

.share-options {
  display: grid;
  /* 5 columns: Mail | Gmail | Messages | Copy | More.
     The Gmail option was added v381 as a fallback for desktop users
     whose OS has no default mailto: handler.

     minmax(0, 1fr) — NOT plain 1fr. A grid item's default min-width is
     `auto` ("never shrink below my content"), so the longer labels
     ("Messages", "More…") refused to shrink, stole width from the other
     columns, and pushed the row past the screen edge: measured 33px of
     overflow on a 320px iPhone, with columns 43/52/78/49/59 instead of
     equal fifths. That overflow is what clipped the right side of the
     sheet — the "More" option and the Send button — on Jimmy's iPhone
     (2026-07-23). minmax(0, 1fr) lets the columns shrink: true equal
     fifths, zero overflow. */
  grid-template-columns: repeat(5, minmax(0, 1fr));
  gap: 8px;
}

.share-option {
  display: flex;
  flex-direction: column;
  align-items: center;
  /* min-width:0 lets the flex/grid item actually honor its column width
     instead of being floored at its content size — the companion to the
     minmax(0,1fr) above. */
  min-width: 0;
  gap: 6px;
  padding: 14px 8px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--bg-card);
  color: var(--text);
  text-decoration: none;
  font-size: 0.78rem;
  font-weight: 500;
  cursor: pointer;
  transition: background var(--transition), transform var(--transition);
  font-family: inherit;
}

.share-option:hover {
  background: var(--bg-secondary);
  transform: translateY(-2px);
}


/* Label guard (2026-07-23): with equal fifths, the longest label
   ("Messages") is ~60px against a ~50px column on a 320px iPhone. Shrink
   the type a touch on very narrow screens so the word stays whole, and
   ellipsize as a last resort so a label can never bleed into its
   neighbour. On a normal modern iPhone (~390pt) everything fits at full
   size and neither rule fires. */
.share-option > span:last-child {
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
@media (max-width: 360px) {
  .share-option > span:last-child { font-size: 0.66rem; }
}

.share-option-icon {
  font-size: 1.6rem;
  line-height: 1;
}

/* ===== Scrollbar ===== */
::-webkit-scrollbar {
  width: 4px;
}

::-webkit-scrollbar-track {
  background: transparent;
}

::-webkit-scrollbar-thumb {
  background: var(--gray-300);
  border-radius: 4px;
}

[data-theme="dark"] ::-webkit-scrollbar-thumb {
  background: var(--gray-700);
}

/* ===== Safe Area (notch devices) ===== */
@supports (padding-top: env(safe-area-inset-top)) {
  .app-header {
    padding-top: calc(12px + env(safe-area-inset-top));
  }
  .page {
    padding-bottom: calc(100px + env(safe-area-inset-bottom));
  }
}

/* =========================================
   INSTALL MODAL (Add to Home Screen)
   ========================================= */
.install-modal {
  position: fixed;
  inset: 0;
  z-index: 2100;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 16px;
}

.install-modal-overlay {
  position: absolute;
  inset: 0;
  background: rgba(11, 20, 40, 0.7);
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
}

.install-modal-content {
  position: relative;
  background: var(--bg);
  border-radius: 22px;
  max-width: 440px;
  width: 100%;
  max-height: 92vh;
  overflow-y: auto;
  padding: 28px 24px 22px;
  box-shadow: 0 30px 80px rgba(0, 0, 0, 0.4);
  animation: auth-slide-up 0.32s cubic-bezier(0.22, 1.4, 0.36, 1);
}

.install-modal-close {
  position: absolute;
  top: 12px;
  right: 14px;
  background: none;
  border: none;
  color: var(--text-muted);
  font-size: 1.6rem;
  cursor: pointer;
  padding: 4px 10px;
  line-height: 1;
  border-radius: 50%;
  transition: background var(--transition);
}

.install-modal-close:hover {
  background: var(--bg-secondary);
  color: var(--text);
}

.install-modal-header {
  text-align: center;
  margin-bottom: 22px;
}

.install-modal-icon {
  width: 64px;
  height: 64px;
  border-radius: 14px;
  margin-bottom: 10px;
  box-shadow: 0 6px 18px rgba(59, 130, 246, 0.3);
}

.install-modal-header h2 {
  font-size: 1.28rem;
  margin: 0 0 4px;
  color: var(--text);
}

.install-modal-header p {
  font-size: 0.88rem;
  color: var(--text-secondary);
  margin: 0;
}

/* Steps */
.install-step {
  display: flex;
  gap: 14px;
  padding: 14px;
  margin-bottom: 10px;
  background: var(--bg-secondary);
  border-radius: 12px;
  align-items: flex-start;
}

.install-step-num {
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: var(--blue-500);
  color: white;
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: 700;
  font-size: 0.95rem;
  flex-shrink: 0;
}

.install-step-content {
  flex: 1;
  min-width: 0;
}

.install-step-title {
  font-size: 0.95rem;
  color: var(--text);
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
  line-height: 1.4;
}

.install-step-title strong {
  font-weight: 700;
}

.install-share-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 7px;
  padding: 4px 5px;
  color: var(--blue-500);
  vertical-align: middle;
  margin: 0 2px;
}

[data-theme="dark"] .install-share-icon {
  color: var(--blue-400);
}

.install-step-desc {
  font-size: 0.8rem;
  color: var(--text-secondary);
  margin-top: 4px;
  line-height: 1.4;
}

/* Success note at bottom of iOS flow */
.install-success-note {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 12px 14px;
  background: linear-gradient(135deg, rgba(34, 197, 94, 0.1), rgba(34, 197, 94, 0.05));
  border: 1px solid rgba(34, 197, 94, 0.3);
  border-radius: 11px;
  font-size: 0.84rem;
  color: var(--text-secondary);
  margin-top: 14px;
  line-height: 1.4;
}

.install-success-icon {
  font-size: 1.1rem;
  flex-shrink: 0;
}

/* Done button */
.install-modal-done {
  width: 100%;
  margin-top: 16px;
  padding: 13px;
  background: var(--blue-500);
  color: white;
  border: none;
  border-radius: 11px;
  font-family: inherit;
  font-size: 0.96rem;
  font-weight: 600;
  cursor: pointer;
  transition: background var(--transition);
}

.install-modal-done:hover {
  background: var(--blue-600);
}

/* =========================================
   AUTH MODAL (Signup / Login)
   ========================================= */
/* ===== Password Modal =====
   Mirrors auth-modal styling for visual consistency. Two flows
   (change vs set-first-password) share the same DOM; JS toggles
   the "Current password" field's visibility based on user state.
   Animation, overlay, close-button, header treatment all match
   auth-modal so the two feel like siblings rather than strangers. */
.password-modal {
  position: fixed;
  inset: 0;
  z-index: 2000;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 16px;
}

.password-modal-overlay {
  position: absolute;
  inset: 0;
  background: rgba(11, 20, 40, 0.7);
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
}

.password-modal-content {
  position: relative;
  background: var(--bg);
  border-radius: 22px;
  max-width: 440px;
  width: 100%;
  max-height: 92vh;
  overflow-y: auto;
  padding: 24px 22px 20px;
  box-shadow: 0 30px 80px rgba(0, 0, 0, 0.4);
  animation: auth-slide-up 0.32s cubic-bezier(0.22, 1.4, 0.36, 1);
}

.password-modal-close {
  position: absolute;
  top: 12px;
  right: 14px;
  background: none;
  border: none;
  color: var(--text-muted);
  font-size: 1.6rem;
  cursor: pointer;
  padding: 4px 10px;
  line-height: 1;
  border-radius: 50%;
  transition: background var(--transition);
}
.password-modal-close:hover {
  background: var(--bg-secondary);
  color: var(--text);
}

.password-modal-header {
  text-align: center;
  margin-bottom: 18px;
}
.password-modal-avatar {
  width: 64px;
  height: 64px;
  margin-bottom: 10px;
  border-radius: 50%;
  object-fit: cover;
}
.password-modal-header h2 {
  font-size: 1.28rem;
  margin: 0 0 6px;
  color: var(--text);
}
.password-modal-header p {
  font-size: 0.88rem;
  color: var(--text-muted);
  margin: 0;
  line-height: 1.45;
}

/* Form fields — mirror .auth-field spacing/typography */
.password-form {
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.password-field {
  display: flex;
  flex-direction: column;
  gap: 5px;
}
.password-field label {
  font-size: 0.82rem;
  color: var(--text-muted);
  font-weight: 500;
}
.password-field input[type="password"] {
  width: 100%;
  padding: 10px 12px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm, 10px);
  background: var(--bg);
  color: var(--text);
  font-family: inherit;
  font-size: 0.95rem;
  outline: none;
  transition: border-color .15s, box-shadow .15s;
}
.password-field input[type="password"]:focus {
  border-color: var(--blue-500);
  box-shadow: 0 0 0 3px var(--blue-100, rgba(59,130,246,.15));
}

.password-error {
  background: rgba(239, 68, 68, 0.08);
  color: #b91c1c;
  border-left: 3px solid #ef4444;
  padding: 8px 12px;
  border-radius: 6px;
  font-size: 0.85rem;
  line-height: 1.4;
}

[data-theme="dark"] .password-error {
  background: rgba(239, 68, 68, 0.15);
  color: #fecaca;
}

.password-actions {
  display: flex;
  gap: 10px;
  margin-top: 4px;
}
.password-btn {
  flex: 1;
  padding: 11px 16px;
  border-radius: 10px;
  font-family: inherit;
  font-size: 0.92rem;
  font-weight: 600;
  cursor: pointer;
  border: none;
  transition: background .15s, transform .08s, opacity .15s;
}
.password-btn:active { transform: scale(0.98); }
.password-btn:disabled { opacity: 0.6; cursor: not-allowed; }
.password-btn-cancel {
  background: var(--bg-secondary);
  color: var(--text);
}
.password-btn-cancel:hover { background: var(--bg-tertiary, var(--border)); }
.password-btn-submit {
  background: var(--blue-500);
  color: #fff;
}
.password-btn-submit:hover:not(:disabled) {
  background: var(--blue-600, var(--blue-500));
  filter: brightness(1.05);
}

.auth-modal {
  position: fixed;
  inset: 0;
  z-index: 2000;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 16px;
}

.auth-modal-overlay {
  position: absolute;
  inset: 0;
  background: rgba(11, 20, 40, 0.7);
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
}

.auth-modal-content {
  position: relative;
  background: var(--bg);
  border-radius: 22px;
  max-width: 440px;
  width: 100%;
  max-height: 92vh;
  overflow-y: auto;
  padding: 24px 22px 20px;
  box-shadow: 0 30px 80px rgba(0, 0, 0, 0.4);
  animation: auth-slide-up 0.32s cubic-bezier(0.22, 1.4, 0.36, 1);
}

@keyframes auth-slide-up {
  from { opacity: 0; transform: translateY(20px) scale(0.97); }
  to   { opacity: 1; transform: translateY(0)    scale(1); }
}

.auth-modal-close {
  position: absolute;
  top: 12px;
  right: 14px;
  background: none;
  border: none;
  color: var(--text-muted);
  font-size: 1.6rem;
  cursor: pointer;
  padding: 4px 10px;
  line-height: 1;
  border-radius: 50%;
  transition: background var(--transition);
}

.auth-modal-close:hover {
  background: var(--bg-secondary);
  color: var(--text);
}

/* Header */
.auth-modal-header {
  text-align: center;
  margin-bottom: 18px;
}

.auth-modal-avatar {
  width: 70px;
  height: 70px;
  margin-bottom: 10px;
}

.auth-modal-header h2 {
  font-size: 1.32rem;
  margin: 0 0 4px;
  color: var(--text);
}

.auth-modal-header p {
  font-size: 0.88rem;
  color: var(--text-secondary);
  margin: 0;
}

/* Welcome bonus banner */
.auth-bonus-banner {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 14px;
  background: linear-gradient(135deg, #fef3c7, #fde68a);
  border: 1px solid #fbbf24;
  border-radius: 12px;
  margin-bottom: 18px;
}

[data-theme="dark"] .auth-bonus-banner {
  background: linear-gradient(135deg, rgba(251, 191, 36, 0.18), rgba(251, 191, 36, 0.08));
  border-color: rgba(251, 191, 36, 0.5);
}

.auth-bonus-icon {
  font-size: 1.4rem;
  flex-shrink: 0;
}

.auth-bonus-text {
  font-size: 0.84rem;
  color: #78350f;
  line-height: 1.4;
}

[data-theme="dark"] .auth-bonus-text {
  color: #fbbf24;
}

.auth-bonus-text strong {
  font-weight: 700;
}

/* "Show password" toggle — wraps every input[type="password"] at boot.
   The wrap fills its parent's width; the input takes the full width
   minus extra right padding to clear the eye button; the button
   absolutely positions to the right of the field. Works for all 3
   password inputs (signup, login, future change-password flow). */
.password-wrap {
  position: relative;
  display: block;
  width: 100%;
}
.password-wrap input {
  width: 100%;
  padding-right: 42px;
}
.password-toggle {
  position: absolute;
  right: 6px;
  top: 50%;
  transform: translateY(-50%);
  background: transparent;
  border: none;
  cursor: pointer;
  padding: 6px;
  color: var(--text-muted);
  border-radius: 6px;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: color .15s, background .15s;
}
.password-toggle:hover { color: var(--text); background: var(--bg-secondary, rgba(0,0,0,.05)); }
.password-toggle:active { transform: translateY(-50%) scale(0.95); }
.password-toggle:focus-visible { outline: 2px solid var(--blue-500); outline-offset: 2px; }

/* OAuth row */
.auth-oauth-row {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-bottom: 16px;
}

.auth-oauth-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  padding: 11px 14px;
  border: 1px solid var(--border);
  border-radius: 11px;
  background: var(--bg);
  color: var(--text);
  font-family: inherit;
  font-size: 0.92rem;
  font-weight: 500;
  cursor: pointer;
  position: relative;
  opacity: 1;
  transition: opacity var(--transition), background var(--transition), border-color var(--transition);
}

/* Enabled OAuth buttons (e.g., Google) get a hover state */
.auth-oauth-btn:hover:not([disabled]) {
  background: var(--bg-secondary, #f8fafc);
  border-color: var(--blue-400);
}

.auth-oauth-btn:active:not([disabled]) {
  opacity: 0.85;
}

/* Disabled OAuth buttons get the muted look + not-allowed cursor.
   No buttons are currently disabled (Apple's "Soon" badge was removed
   2026-05-12); rule kept for any future temporarily-disabled state. */
.auth-oauth-btn[disabled] {
  cursor: not-allowed;
  opacity: 0.55;
}

.auth-oauth-apple svg { color: var(--text); }
.auth-oauth-email svg { color: var(--text); }

/* Email button: third button in the OAuth row. Clicking expands the
   email/password form below. Hidden once the form is expanded so the
   user only sees one path forward. */
.auth-oauth-email {
  /* Inherits .auth-oauth-btn baseline styles. Nothing extra needed —
     this rule exists as a documentation hook in case we want to tweak
     later (e.g. weight the email path slightly differently from OAuth). */
}

/* When the modal is in its default "collapsed" state, hide the divider
   and both forms. Only the 3 buttons (Apple/Google/Email) are shown.
   The "Already have a Qbit? Sign in" toggle row stays visible so a
   returning user landing on the signup screen can still switch modes. */
.auth-email-collapsed .auth-divider,
.auth-email-collapsed .auth-form {
  display: none !important;
}

/* When the user has clicked "Sign up/in with Email" (collapsed class
   removed), hide the email button itself — they've expressed their
   intent, no need to keep nudging them. Apple/Google stay visible
   in case they change their mind. */
.auth-modal:not(.auth-email-collapsed) .auth-oauth-email {
  display: none;
}

/* Divider */
.auth-divider {
  display: flex;
  align-items: center;
  margin: 14px 0 16px;
  color: var(--text-muted);
  font-size: 0.78rem;
}

.auth-divider::before,
.auth-divider::after {
  content: '';
  flex: 1;
  height: 1px;
  background: var(--border);
}

.auth-divider span {
  padding: 0 12px;
}

/* Form */
.auth-form {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.auth-field {
  display: flex;
  flex-direction: column;
  gap: 5px;
}

.auth-field label {
  font-size: 0.78rem;
  font-weight: 500;
  color: var(--text-secondary);
  margin-left: 2px;
}

.auth-field input {
  padding: 11px 13px;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--bg);
  color: var(--text);
  font-size: 0.95rem;
  font-family: inherit;
  outline: none;
  transition: border-color var(--transition);
}

.auth-field input:focus {
  border-color: var(--blue-400);
}

/* ── Boot splash (Jimmy 2026-07-13) ─────────────────────────────
   Covers the app.js parse/boot window for signed-in reopens so the
   signed-out header never flashes. Shown synchronously by the inline
   script right after the markup (class boot-splash-visible), removed
   by the end-of-boot hook. Signed-out visitors: the div is removed
   before first paint, so display:none is just belt-and-braces. */
#boot-splash {
  display: none;
  position: fixed;
  inset: 0;
  z-index: 100000;
  background: var(--bg);
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 14px;
  padding: 24px;
  text-align: center;
  opacity: 1;
  transition: opacity 0.4s ease;
}
#boot-splash.boot-splash-visible { display: flex; }
#boot-splash.boot-splash-leaving { opacity: 0; pointer-events: none; }

.boot-splash-mascot {
  width: 96px;
  height: 96px;
  object-fit: contain;
  animation: bootSplashBob 1.6s ease-in-out infinite;
}
@keyframes bootSplashBob {
  0%, 100% { transform: translateY(0); }
  50%      { transform: translateY(-7px); }
}

/* (.boot-splash-title/-sub removed 2026-07-13 — Scott: mascot only,
   the splash is too brief to read text.) */

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

/* Age verification row */
.auth-age-row {
  display: flex;
  align-items: flex-start;
  gap: 9px;
  font-size: 0.82rem;
  color: var(--text-secondary);
  cursor: pointer;
  padding: 4px 0 2px;
  line-height: 1.4;
}

/* ── Registration consent block (legal, 2026-07-13) ─────────────
   Sits ABOVE the OAuth row so the 18+/ToS/Privacy checkbox gates
   email AND Google AND Apple signup. Hidden in login mode —
   returning users already agreed at registration. */
.auth-consent {
  margin: 2px 0 12px;
  padding: 10px 12px 8px;
  border: 1px solid var(--border);
  border-radius: 12px;
  background: var(--bg-secondary);
}

.auth-modal.is-login .auth-consent { display: none; }

.auth-under18-note {
  margin: 6px 0 0 27px;   /* aligns under the label text: 18px box + 9px gap */
  font-size: 0.74rem;
  color: var(--text-secondary);
  opacity: 0.85;
  line-height: 1.35;
}

.auth-consent .auth-error { margin: 8px 0 0; }

/* Attention pulse when the user tries to continue without checking —
   two gentle border flashes; reduced-motion users get a static
   highlight instead. */
@keyframes authConsentPulse {
  0%, 100% { border-color: var(--border); }
  25%, 75% { border-color: #dc2626; }
}
.auth-consent-attention { animation: authConsentPulse 0.9s ease-in-out 2; }
@media (prefers-reduced-motion: reduce) {
  .auth-consent-attention { animation: none; border-color: #dc2626; }
}

.auth-age-row input[type="checkbox"] {
  width: 18px;
  height: 18px;
  margin-top: 1px;
  accent-color: var(--blue-500);
  flex-shrink: 0;
  cursor: pointer;
}

.auth-tos-link {
  color: var(--blue-500);
  text-decoration: underline;
  cursor: pointer;
  font-weight: 500;
}

[data-theme="dark"] .auth-tos-link {
  color: var(--blue-400);
}

/* Submit button */
.auth-submit-btn {
  margin-top: 6px;
  padding: 13px;
  background: var(--blue-500);
  color: white;
  border: none;
  border-radius: 11px;
  font-family: inherit;
  font-size: 0.96rem;
  font-weight: 600;
  cursor: pointer;
  transition: background var(--transition);
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
}

.auth-submit-btn:hover:not(:disabled) {
  background: var(--blue-600);
}

.auth-submit-btn:disabled {
  background: var(--gray-400);
  cursor: not-allowed;
}

.auth-submit-spinner {
  font-size: 1rem;
  animation: auth-spin 1.2s linear infinite;
}

@keyframes auth-spin {
  to { transform: rotate(360deg); }
}

/* Error */
.auth-error {
  background: rgba(239, 68, 68, 0.1);
  border: 1px solid rgba(239, 68, 68, 0.3);
  color: #dc2626;
  padding: 9px 12px;
  border-radius: 8px;
  font-size: 0.84rem;
  line-height: 1.4;
}

[data-theme="dark"] .auth-error {
  color: #fca5a5;
}

/* Toggle (signup <-> login) */
.auth-toggle {
  text-align: center;
  margin-top: 16px;
  font-size: 0.85rem;
  color: var(--text-secondary);
}

.auth-toggle-btn {
  background: none;
  border: none;
  color: var(--blue-500);
  font-weight: 600;
  cursor: pointer;
  font-family: inherit;
  font-size: inherit;
  padding: 0 4px;
  text-decoration: underline;
}

[data-theme="dark"] .auth-toggle-btn {
  color: var(--blue-400);
}

/* Kid sign-in entry (installed-PWA logged-out → child code sign-in). */
.auth-kid-row { text-align: center; margin-top: 12px; }
.auth-kid-signin-btn {
  background: var(--bg-secondary, #f1f1f7);
  border: 1px solid var(--border, #e5e5ee);
  border-radius: 999px;
  color: var(--text, #1a1a22);
  font-weight: 600;
  font-size: 0.85rem;
  font-family: inherit;
  cursor: pointer;
  padding: 9px 16px;
  width: 100%;
}
.auth-kid-signin-btn:active { opacity: 0.85; }

/* (.auth-legal footer removed 2026-07-13 — the consent checkbox is the
   one statement of agreement on the modal.) */

/* Hide bonus banner in login mode (the welcome gift only applies to
   new accounts, so showing it during sign-in would be misleading). */
.auth-modal.is-login .auth-bonus-banner {
  display: none;
}

/* OAuth buttons (Google/Apple) intentionally show in BOTH signup AND login
   mode. Returning users who originally signed up via Google need to sign
   back in the same way — hiding the buttons in login mode left them
   stranded with only an email/password form. */

/* Forgot-password link — small, right-aligned under the password field.
   Plain text style so it doesn't compete with the Sign In submit button. */
.auth-forgot-row {
  display: flex;
  justify-content: flex-end;
  margin: -4px 0 4px 0;
}
.auth-forgot-link {
  background: none;
  border: none;
  padding: 4px 2px;
  font-size: 0.85rem;
  color: var(--blue-500);
  cursor: pointer;
  text-decoration: none;
}
.auth-forgot-link:hover {
  text-decoration: underline;
}

/* Global trademark footer (visible on every page) */
.page-trademark {
  text-align: center;
  font-size: 0.7rem;
  color: var(--text-muted);
  /* 2px top + 2px bottom — combined with the matching 2px padding
     on .page-footer-links above and .app-version below, gives the
     three footer lines a uniform 4px gap each (Scott UX 2026-05-15). */
  padding: 2px 16px 2px;
  letter-spacing: 0.02em;
  position: relative;
  z-index: 1;
}

/* =========================================
   MY QBIT PAGE STYLES
   ========================================= */


/* Identity row inside the credits card (consolidated section) */
.credits-identity-row {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 14px;
}

.credits-identity-avatar {
  width: 44px;
  height: 44px;
  border-radius: 50%;
  background: rgba(255,255,255,0.25);
  color: white;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1.2rem;
  font-weight: 600;
  flex-shrink: 0;
  border: 2px solid rgba(255,255,255,0.3);
}

.credits-identity-info {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 5px;
}

.credits-identity-name {
  font-size: 1.05rem;
  font-weight: 600;
  color: white;
  line-height: 1.2;
}

.plan-badge {
  background: rgba(255,255,255,0.2);
  color: white;
  padding: 3px 9px;
  border-radius: 10px;
  font-weight: 600;
  font-size: 0.66rem;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  align-self: flex-start;
  border: 1px solid rgba(255,255,255,0.3);
}

.plan-badge-genius {
  background: linear-gradient(135deg, #fbbf24, #f59e0b);
  color: #1f2937;
  border-color: rgba(251, 191, 36, 0.6);
  font-weight: 700;
}

.credits-card-divider {
  height: 1px;
  background: rgba(255,255,255,0.2);
  margin: 0 0 18px;
}

/* Credits Card */
.myqbit-credits-card {
  background: linear-gradient(135deg, #1e3a8a, #2563eb);
  color: white;
  padding: 22px 20px;
  border-radius: 16px;
  margin-bottom: 12px;
  position: relative;
  overflow: hidden;
}

.credits-card-label {
  font-size: 0.74rem;
  opacity: 0.9;
  text-transform: uppercase;
  letter-spacing: 0.6px;
  font-weight: 600;
  line-height: 1.3;
}

.credits-card-number {
  font-size: 2.6rem;
  font-weight: 700;
  margin-top: 4px;
  margin-bottom: 14px;
  line-height: 1;
}

.credits-card-bar {
  height: 6px;
  background: rgba(255,255,255,0.2);
  border-radius: 3px;
  overflow: hidden;
  margin-bottom: 10px;
}

.credits-card-bar-fill {
  height: 100%;
  background: linear-gradient(90deg, #34d399, #60a5fa);
  border-radius: 3px;
  transition: width 0.3s;
}

.credits-card-meta {
  display: flex;
  justify-content: space-between;
  font-size: 0.78rem;
  opacity: 0.9;
  margin-bottom: 16px;
}

.credits-card-sparkline {
  display: flex;
  align-items: flex-end;
  gap: 6px;
  height: 50px;
  margin-bottom: 6px;
}

.sparkline-bar {
  flex: 1;
  background: rgba(255,255,255,0.4);
  border-radius: 3px 3px 0 0;
  min-height: 4px;
  transition: height 0.3s;
}

.sparkline-today {
  background: #34d399;
}

.credits-card-sparkline-label {
  font-size: 0.7rem;
  opacity: 0.7;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  margin-bottom: 16px;
}

.credits-buy-btn {
  width: 100%;
  padding: 12px;
  background: rgba(255,255,255,0.2);
  color: white;
  border: 1px solid rgba(255,255,255,0.3);
  border-radius: 10px;
  font-size: 0.95rem;
  font-weight: 600;
  font-family: inherit;
  cursor: pointer;
  transition: background var(--transition);
}

.credits-buy-btn:hover {
  background: rgba(255,255,255,0.3);
}

/* Collapsible cards (Usage Breakdown / Query History) */
.collapse-card {
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 14px;
  overflow: hidden;
  transition: box-shadow var(--transition);
}

/* Profile collapse-card (Scott 2026-05-17) — the Profile section moved
   from Settings to My Profile and now lives inside a collapse-card. The
   .settings-card inside needs its OWN border/shadow/bg stripped so we
   don't get visual double-nesting (outer collapse-card border + inner
   settings-card border = two parallel rectangles 1px apart). The inner
   .settings-card stays in the markup because it carries data-save-redirect
   and is what the Save Changes auto-injector binds to. */
.profile-collapse-card > .collapse-content > .settings-card {
  background: transparent;
  border: none;
  border-radius: 0;
  box-shadow: none;
}

/* Match the 24px bottom margin every .settings-group on the My Profile
   page uses. Without this, the Profile collapsible visually touches
   the next section ("Your Qbit Code") with no breathing room — Scott
   reported the gap was tighter than every other section-to-section
   gap on the page (2026-05-17). */
.profile-collapse-card {
  margin-bottom: 24px;
}

/* Spacing between stacked collapsibles (but not after a title) */
.collapse-card + .collapse-card {
  margin-top: 10px;
}

/* Spacing between a credits card / settings card / advanced-link and a
   following collapsible. The advanced-link case is the "View Query History"
   link sitting above "Usage Details" on Metrics & Advanced (2026-06-16) —
   without it the two cards touch with no gap. */
.myqbit-credits-card + .collapse-card,
.settings-advanced-link + .collapse-card,
.settings-card + .collapse-card {
  margin-top: 10px;
}

.collapse-card[open] {
  box-shadow: 0 2px 12px rgba(0,0,0,0.04);
}

.collapse-summary {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 16px 18px;
  padding-right: 42px;
  cursor: pointer;
  list-style: none;
  user-select: none;
  -webkit-user-select: none;
  position: relative;
}

.collapse-summary::-webkit-details-marker {
  display: none;
}

/* UI STANDARD (Jimmy 2026-07-09, brand/UI-UX-STANDARDS.md): in-place
   expanders use a DOWN chevron when closed, rotating UP when open.
   Right chevrons are reserved for navigation. The old '+'/'−' marker
   read as a third affordance and collided with "+ Add …" action
   buttons. This one rule restyles all 15 collapse-cards. */
.collapse-summary::after {
  content: '';
  position: absolute;
  right: 18px;
  top: 50%;
  width: 8px;
  height: 8px;
  border-right: 2px solid var(--text-muted);
  border-bottom: 2px solid var(--text-muted);
  transform: translateY(-70%) rotate(45deg);   /* chevron-down = closed */
  transition: transform 0.15s ease;
  line-height: 1;
}

.collapse-card[open] .collapse-summary::after {
  transform: translateY(-30%) rotate(-135deg); /* chevron-up = open */
}

.collapse-summary-text {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.collapse-summary-title {
  font-size: 0.96rem;
  font-weight: 600;
  color: var(--text);
}

.collapse-summary-sub {
  font-size: 0.78rem;
  color: var(--text-secondary);
}

.collapse-content {
  border-top: 1px solid var(--border);
  padding: 4px 0;
  animation: collapse-fade-in 0.25s ease;
}

.collapse-content-flush {
  padding: 0;
}

@keyframes collapse-fade-in {
  from { opacity: 0; transform: translateY(-4px); }
  to { opacity: 1; transform: translateY(0); }
}

/* Deep-link highlight pulse (Scott 2026-05-17) — applied for ~1.6s when
   the user arrives at a section via a data-scroll-to deep link. Helps
   their eye land on the target, especially when the target is a small
   element (a single toggle row) inside a busy section. Uses box-shadow
   instead of background so it works on any element type without
   disturbing the existing card backgrounds — and outline-style so it
   reads as "this is where you were sent" rather than "this is selected." */
.deep-link-pulse {
  animation: deep-link-pulse-anim 1.6s ease-out 1;
  border-radius: 10px;
}
@keyframes deep-link-pulse-anim {
  0%   { box-shadow: 0 0 0 0   rgba(59, 130, 246, 0.55); }
  20%  { box-shadow: 0 0 0 6px rgba(59, 130, 246, 0.30); }
  100% { box-shadow: 0 0 0 0   rgba(59, 130, 246, 0); }
}

/* Usage Breakdown legacy title (no longer used but kept for safety) */
.usage-breakdown-title {
  padding: 14px 18px 8px;
  font-size: 0.78rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  color: var(--text-secondary);
}

.usage-row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 18px;
  border-bottom: 1px solid var(--border);
}

.usage-row:last-child {
  border-bottom: none;
}

.usage-icon {
  font-size: 1.2rem;
}

.usage-label {
  flex: 1;
  font-size: 0.9rem;
  color: var(--text);
}

.usage-value {
  color: var(--text-secondary);
}

.usage-value strong {
  color: var(--text);
  font-size: 1rem;
}

/* History */
/* Query History page: the rows looked too narrow inside the standard 20px
   .page gutter (Jimmy 2026-06-16). Break the list out to the full device
   width by cancelling that side gutter — the rows' own 16px padding keeps the
   text inset, and the page header + Back button keep the normal 20px inset. */
#page-history #query-history-section {
  margin-left: -20px;
  margin-right: -20px;
}

.history-search {
  padding: 12px 14px;
  margin: 12px 14px;
  background: var(--bg-secondary);
  border-radius: 10px;
}

.history-search input {
  width: 100%;
  border: none;
  background: transparent;
  outline: none;
  font-family: inherit;
  font-size: 0.88rem;
  color: var(--text);
}

.history-search input::placeholder {
  color: var(--text-muted);
}

.history-filters {
  display: flex;
  gap: 6px;
  padding: 0 14px 10px;
  overflow-x: auto;
}

.history-chip {
  padding: 6px 14px;
  border: 1px solid var(--border);
  background: var(--bg);
  color: var(--text-secondary);
  border-radius: 20px;
  font-size: 0.8rem;
  font-family: inherit;
  cursor: pointer;
  white-space: nowrap;
  transition: all var(--transition);
}

.history-chip.active {
  background: var(--blue-500);
  color: white;
  border-color: var(--blue-500);
}

.history-empty {
  padding: 40px 20px;
  text-align: center;
  color: var(--text-muted);
}

/* Empty-state SVG illustration removed 2026-05-15 (Scott UX) — the
   icon was floating too high above the text and washed out at 0.4
   opacity. Cleaner empty state without it. The .history-empty
   container already provides ample padding so the text reads as
   centered and intentional on its own. */

.history-empty p {
  margin: 4px 0;
  font-size: 0.92rem;
  color: var(--text-secondary);
}

.history-empty-sub {
  font-size: 0.8rem !important;
  color: var(--text-muted) !important;
}

/* ===== Query History rows (real list) ===== */
/* ── Conversation thread cards (History grouping, Emmett #47230) ─────────────
   A conversation (2+ turns sharing a conversationId) collapses into one card:
   a clickable head (title + "N messages · when" + Continue) over a hidden list
   of its turns. Single-turn / legacy rows still render bare. */
.history-thread { border-bottom: 1px solid var(--border); }
.history-thread-head {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 14px 16px;
  cursor: pointer;
  transition: background var(--transition);
}
.history-thread-head:hover { background: var(--bg-soft); }
.history-thread-chevron {
  flex: 0 0 auto;
  color: var(--text-muted);
  font-size: 0.9rem;
  transition: transform var(--transition);
}
.history-thread-chevron-open { transform: rotate(90deg); }
.history-thread-titlewrap { flex: 1 1 auto; min-width: 0; }
.history-thread-title {
  font-weight: 600;
  color: var(--text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.history-thread-meta { font-size: 0.8rem; color: var(--text-muted); margin-top: 2px; }
.history-thread-continue {
  flex: 0 0 auto;
  border: 1px solid var(--blue-400);
  background: transparent;
  color: var(--blue-400);
  border-radius: 999px;
  padding: 6px 14px;
  font-size: 0.85rem;
  font-weight: 600;
  cursor: pointer;
  transition: background var(--transition), color var(--transition);
}
.history-thread-continue:hover { background: var(--blue-400); color: #fff; }
.history-thread-turns { border-top: 1px solid var(--border); background: var(--bg-soft); }
.history-thread-turns .history-row { padding-left: 30px; }
.history-thread-turns .history-row:last-child { border-bottom: none; }

.history-row {
  padding: 14px 16px;
  border-bottom: 1px solid var(--border);
  text-align: left;
  transition: background var(--transition);
  /* Click-to-replay landed 2026-05-03 — rows are now interactive
     (role="button" tabindex="0" in app.js). Cursor + active state
     make that affordance obvious. */
  cursor: pointer;
}

.history-row:last-child {
  /* Thicker closing line marks the end of the list (Jimmy 2026-07-10). */
  border-bottom: 2px solid var(--border);
}

.history-row:hover {
  background: rgba(59, 130, 246, 0.04);
}
.history-row:focus-visible {
  outline: none;
  background: rgba(59, 130, 246, 0.08);
  box-shadow: inset 3px 0 0 var(--blue-500);
}
.history-row:active {
  background: rgba(59, 130, 246, 0.10);
}

.history-row-meta {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 4px;
  font-size: 0.72rem;
  color: var(--text-muted);
}

.history-row-time {
  /* Lives in the bottom action row beside the thumbs (Jimmy
     2026-07-10) — self-styled since the meta-line parent is gone. */
  letter-spacing: 0.02em;
  font-size: 0.72rem;
  color: var(--text-muted);
  margin-left: 4px;
}

.history-row-credit {
  display: inline-block;
  padding: 1px 7px;
  border-radius: 999px;
  background: linear-gradient(135deg, var(--blue-500), var(--purple-500));
  color: #fff;
  font-weight: 700;
  font-size: 0.68rem;
  letter-spacing: 0.02em;
}

.history-row-credit-free {
  background: var(--border);
  color: var(--text-muted);
}

/* Per-row action buttons on Query History rows.
   Three of them, in this right-side cluster:
     [Share] [Download] [×]
   All share .history-row-action / .history-row-delete styling for
   consistency. Margin-left:auto on the FIRST button pushes the whole
   cluster to the right edge of the flex .history-row-meta.
   Click handlers in app.js call e.stopPropagation() so the parent
   .history-row's "open this conversation" handler doesn't fire when
   any of these are clicked. (Scott UX 2026-05-15.) */
.history-row-action,
.history-row-delete {
  width: 24px;
  height: 24px;
  border: 1px solid transparent;
  border-radius: 50%;
  background: transparent;
  color: var(--text-muted);
  cursor: pointer;
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  opacity: 0.55;
  flex-shrink: 0;
  transition: opacity .15s, color .15s, border-color .15s, background .15s;
}

/* The credit pill left the History card entirely (Jimmy 2026-07-10) —
   it now renders per-answer inside the Conversation turn-actions
   cluster (class turn-credit reuses the pill visuals above). */
.turn-credit {
  margin-right: 4px;
  flex-shrink: 0;
}

/* Share / Download / Print — neutral hover (Qbit blue) for non-
   destructive actions. Sets them apart visually from the destructive
   × button. Print added 2026-05-15 (Scott UX) and inherits the same
   .history-row-action styling — no Print-specific override needed. */
.history-row-action:hover,
.history-row-action:focus-visible {
  opacity: 1;
  color: var(--blue-500);
  border-color: var(--blue-400);
  background: rgba(59, 130, 246, 0.08);
  outline: none;
}

/* ============================================================
 * Per-turn action cluster on the Conversation page
 * ============================================================
 * Mirror of the per-row cluster above, but lives BELOW each
 * Qbit answer in the chat thread. Same icons (Share / Download
 * / Print), same hover treatment, same accessibility — just
 * a different parent container.
 * Skips the × delete button on purpose: deleting a single live
 * conversation turn breaks context. (Scott UX 2026-05-15.)
 * ============================================================ */
.turn-actions {
  display: flex;
  align-items: center;
  gap: 4px;
  margin-top: 10px;
  /* Dashed divider removed (Jimmy 2026-07-10) — same no-lines-inside-
     cards rule as .history-row-bottom. */
  opacity: 0.85;
}
.turn-action {
  width: 28px;
  height: 28px;
  border: 1px solid transparent;
  border-radius: 50%;
  background: transparent;
  color: var(--text-muted);
  cursor: pointer;
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  transition: opacity .15s, color .15s, border-color .15s, background .15s;
}
.turn-action:hover,
.turn-action:focus-visible {
  color: var(--blue-500);
  border-color: var(--blue-400);
  background: rgba(59, 130, 246, 0.08);
  outline: none;
}

/* ── Card-icon standard (Jimmy 2026-07-10) ──────────────────────
   Canon = the My Documents row buttons (.docs-row-btn): every card
   action icon is a 24px CIRCLED button with a visible border and
   card background at full opacity — not a bare faded glyph. Applied
   here to History rows, chat-turn clusters, and feedback thumbs so
   the whole app reads identically. State styles (blue hover, red
   destructive hover, .feedback-active) keep their higher-specificity
   rules and win as before. */
.history-row-action,
.history-row-delete,
.turn-action,
.feedback-btn {
  width: 24px;
  height: 24px;
  border: 1px solid var(--border);
  background: var(--bg-card);
  opacity: 1;
}

/* (.history-row-delete inherits all sizing from .history-row-action
   above — same 24×24 button, same SVG icon size. The font-size
   override that used to live here was for the × text character;
   now that delete uses the same SVG-icon system as Share + Download
   no override is needed.) */
/* Delete keeps the red destructive hover. */
.history-row-delete:hover,
.history-row-delete:focus-visible {
  opacity: 1;
  color: #ef4444;
  border-color: #ef4444;
  background: rgba(239, 68, 68, 0.08);
  outline: none;
}

/* ============================================================
 * Feedback cluster (thumbs up/down + comment expander)
 * (added 2026-05-16 — Phase 1 of the feedback mechanism)
 * ============================================================
 * Renders on every Qbit answer (live chat turn + Query History row).
 * Two thumb buttons on the left + an inline expander region below
 * them where the comment textarea slides in after the user rates.
 *
 * Sizing matches the existing .turn-action / .history-row-action
 * pattern (16×16 SVG inside a ~28px button) so the cluster reads
 * as visually-coherent with its neighbors.
 *
 * Inside a chat turn the cluster sits to the LEFT of the existing
 * Share/Download/Print group (see .turn-actions-share-group rule
 * below). Inside a History row it sits in its own line under the
 * answer snippet so the expander has room to open downward.
 * ============================================================ */
.feedback-cluster {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 4px;
  margin-top: 8px;
}
/* Inside the flex action rows the legacy 8px top margin shoved the
   thumbs down and made the neighboring date read top-aligned
   (Jimmy 2026-07-10) — zero it where the cluster is a flex item. */
.history-row-bottom .feedback-cluster,
.turn-actions .feedback-cluster {
  margin-top: 0;
}

/* Sizing + idle state mirror .history-row-action exactly — 24×24
   button, transparent chrome, muted-gray icon at 55% opacity. Keeps
   the thumbs visually in the same icon family as Share/Download/
   Print/Delete on the History row (Jimmy 2026-05-16 — the previous
   28×28 + solid-color-fill made the thumbs pop out and look like
   a different control). */
.feedback-btn {
  width: 24px;
  height: 24px;
  border: 1px solid transparent;
  border-radius: 50%;
  background: transparent;
  color: var(--text-muted);
  cursor: pointer;
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  opacity: 0.55;
  flex-shrink: 0;
  transition: opacity .15s, color .15s, border-color .15s, background .15s;
}
/* Thumb feedback icon inside the 24px round button (de-pawed 2026-07-07 —
   inline SVG thumbs replaced the paw PNGs). The SVG strokes currentColor,
   so the button's color rules (below) paint the icon along with the
   colored border + shaded background. */
.feedback-btn .feedback-icon { width: 18px; height: 18px; display: block; }

.feedback-btn:hover,
.feedback-btn:focus-visible {
  opacity: 1;
  outline: none;
}

/* Green/red semantics for the feedback thumbs (Jimmy 2026-06-16, replacing
   the earlier all-blue). Up = green, matched to the emerald "Ready" status
   badge (#047857 text on rgba(5,150,105,..)). Down = red, the app's danger
   color (#dc2626 / rgba(220,38,38,..)). currentColor tints the SVG thumb
   itself; the color reinforces the helpful/unhelpful meaning. */
.feedback-btn-up:hover,
.feedback-btn-up:focus-visible {
  color: #047857;
  border-color: rgba(5, 150, 105, 0.45);
  background: rgba(5, 150, 105, 0.10);
}
.feedback-btn-down:hover,
.feedback-btn-down:focus-visible {
  color: #dc2626;
  border-color: rgba(220, 38, 38, 0.40);
  background: rgba(220, 38, 38, 0.08);
}

/* Active (rated) state — persistent, full opacity, a touch stronger than
   hover. The up fill at 0.15 matches the Ready badge's shading exactly. */
.feedback-btn-up.feedback-active {
  opacity: 1;
  color: #047857;
  border-color: rgba(5, 150, 105, 0.55);
  background: rgba(5, 150, 105, 0.15);
}
.feedback-btn-down.feedback-active {
  opacity: 1;
  color: #dc2626;
  border-color: rgba(220, 38, 38, 0.50);
  background: rgba(220, 38, 38, 0.12);
}

/* Inline expander — appears below the thumb pair when the user
   rates. Takes full row width via flex-basis:100%. The textarea
   styling matches the rest of the app's form inputs. */
.feedback-expand {
  flex-basis: 100%;
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-top: 4px;
}

.feedback-comment {
  width: 100%;
  min-height: 56px;
  padding: 8px 10px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--bg);
  color: var(--text);
  font: inherit;
  font-size: 0.88rem;
  resize: vertical;
  outline: none;
  transition: border-color var(--transition);
}
.feedback-comment:focus {
  border-color: var(--blue-400);
}

.feedback-expand-actions {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
}

.feedback-skip,
.feedback-submit {
  padding: 6px 14px;
  border-radius: 8px;
  font-size: 0.82rem;
  font-weight: 600;
  font-family: inherit;
  cursor: pointer;
  border: 1px solid var(--border);
  background: var(--bg);
  color: var(--text-muted);
  transition: background .15s, color .15s, border-color .15s;
}
.feedback-skip:hover {
  color: var(--text);
  border-color: var(--text-muted);
}
.feedback-submit {
  color: #fff;
  background: var(--blue-500);
  border-color: var(--blue-500);
}
.feedback-submit:hover {
  background: var(--blue-600);
  border-color: var(--blue-600);
}

/* Inside a chat turn, the feedback cluster sits LEFT of the existing
   Share/Download/Print group. Push the share group to the right with
   margin-left:auto so the visual layout is:
     [👍 👎]                              [Share Download Print]
   On narrow viewports the .feedback-cluster's own flex-wrap:wrap
   lets the comment expander span full width without affecting the
   share group's position. */
.turn-actions {
  flex-wrap: wrap;
}
.turn-actions-share-group {
  display: flex;
  align-items: center;
  gap: 4px;
  margin-left: auto;
}

/* Bottom action row inside a Query History row — wraps the feedback
   cluster (left) and the Share/Download/Print group (right). Mirrors
   the .turn-actions layout on chat turns so the two surfaces feel
   like one design system. The dashed top-border that used to sit on
   the .feedback-cluster alone now lives on this wrapper so the divider
   spans the full bottom row regardless of whether thumbs are visible. */
.history-row-bottom {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px;
  margin-top: 8px;
  /* 8px title→actions gap (Jimmy 2026-07-10) — was 4px while a date
     line sat between them; with the two-line card the action row
     needs its own breathing room. No divider lines inside cards. */
}

/* Share/Download/Print group sits on the RIGHT of the bottom row.
   margin-left:auto absorbs all leftover horizontal space, pushing
   the group to the right edge even when the feedback cluster on
   the left is hidden (legacy items without a timestamp). */
.history-row-share-group {
  display: flex;
  align-items: center;
  gap: 4px;
  margin-left: auto;
}

.history-row-q {
  font-size: 0.92rem;
  color: var(--text);
  font-weight: 600;
  line-height: 1.4;
  margin-bottom: 4px;
  /* Truncate at 2 lines if very long */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.history-row-a {
  font-size: 0.82rem;
  color: var(--text-secondary);
  line-height: 1.45;
  /* Truncate at 2 lines */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* Font Selection */
.font-preview {
  width: 100%;
  padding: 14px 16px;
  background: var(--bg-secondary);
  border-radius: 10px;
  font-size: 0.92rem;
  line-height: 1.5;
  color: var(--text);
  margin-bottom: 12px;
  transition: font-family 0.2s, font-size 0.2s, line-height 0.2s;
}

.font-options {
  display: flex;
  gap: 8px;
  width: 100%;
}

.font-option {
  flex: 1;
  position: relative;
  cursor: pointer;
}

.font-option input[type="radio"] {
  position: absolute;
  opacity: 0;
}

.font-option span {
  display: block;
  padding: 9px 8px;
  text-align: center;
  border: 2px solid var(--border);
  border-radius: 10px;
  font-size: 0.78rem;
  color: var(--text-secondary);
  transition: all var(--transition);
}

.font-option input:checked + span {
  border-color: var(--blue-500);
  background: var(--blue-50);
  color: var(--blue-700);
  font-weight: 600;
}

[data-theme="dark"] .font-option input:checked + span {
  background: rgba(59, 130, 246, 0.15);
  color: var(--blue-400);
}

.font-size-control {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
}

.font-size-btn {
  width: 36px;
  height: 36px;
  border: 1px solid var(--border);
  background: var(--bg);
  color: var(--text);
  border-radius: 8px;
  font-family: inherit;
  font-weight: 600;
  cursor: pointer;
}

.font-size-control input[type="range"] {
  flex: 1;
  accent-color: var(--blue-500);
}

.line-spacing-options {
  display: flex;
  gap: 8px;
  width: 100%;
}

.line-chip {
  flex: 1;
  padding: 9px;
  border: 2px solid var(--border);
  background: var(--bg);
  color: var(--text-secondary);
  border-radius: 10px;
  font-family: inherit;
  font-size: 0.82rem;
  cursor: pointer;
  transition: all var(--transition);
}

.line-chip.active {
  border-color: var(--blue-500);
  background: var(--blue-50);
  color: var(--blue-700);
  font-weight: 600;
}

[data-theme="dark"] .line-chip.active {
  background: rgba(59, 130, 246, 0.15);
  color: var(--blue-400);
}

/* Health Coming Soon */
.health-coming-soon {
  position: relative;
  padding: 24px 20px;
  background: linear-gradient(135deg, #fef3c7, #fde68a);
  border: 1px dashed #f59e0b;
}

[data-theme="dark"] .health-coming-soon {
  background: linear-gradient(135deg, rgba(251, 191, 36, 0.12), rgba(251, 191, 36, 0.04));
  border-color: rgba(251, 191, 36, 0.5);
}

.coming-soon-badge {
  position: absolute;
  top: 12px;
  right: 12px;
  background: #f59e0b;
  color: white;
  padding: 4px 10px;
  border-radius: 12px;
  font-size: 0.68rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.5px;
}

.health-preview {
  text-align: center;
  padding: 8px 0;
}

.health-icon {
  font-size: 2.2rem;
  margin-bottom: 8px;
}

.health-preview h3 {
  font-size: 1.08rem;
  margin: 0 0 8px;
  color: var(--text);
}

.health-preview p {
  font-size: 0.86rem;
  color: var(--text-secondary);
  line-height: 1.5;
  margin: 6px 0;
}

.health-features {
  list-style: none;
  padding: 12px 0;
  margin: 12px 0;
  text-align: left;
}

.health-features li {
  padding: 5px 0 5px 22px;
  position: relative;
  color: var(--text-secondary);
  font-size: 0.84rem;
}

.health-features li::before {
  content: '✓';
  position: absolute;
  left: 0;
  color: #f59e0b;
  font-weight: 700;
}

.health-disclaimer {
  background: rgba(255,255,255,0.5);
  border-radius: 8px;
  padding: 10px;
  font-size: 0.76rem !important;
  font-style: italic;
  margin-top: 14px !important;
}

[data-theme="dark"] .health-disclaimer {
  background: rgba(0,0,0,0.2);
}

/* Settings info note — small explanatory bar shown at the top of cards
   that need to communicate context (e.g. "Quiet times are managed by your
   device's Do Not Disturb"). Subtle background, distinct from setting items. */
.settings-info-note {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 12px 18px;
  background: linear-gradient(135deg, rgba(59,130,246,0.06), rgba(139,92,246,0.06));
  border-bottom: 1px solid var(--border);
  font-size: 0.85rem;
  line-height: 1.45;
  color: var(--text-muted);
}
.settings-info-note strong {
  color: var(--text);
}
.settings-info-icon {
  font-size: 1.1rem;
  line-height: 1;
  flex-shrink: 0;
  margin-top: 1px;
}

/* Quiet Hours (UI removed; styles kept for any orphan markup, can be
   pruned once we're confident nothing else uses them) */
.quiet-hours-config {
  padding: 14px 18px;
  background: var(--bg-secondary);
  border-bottom: 1px solid var(--border);
}

.quiet-hours-row {
  display: flex;
  gap: 20px;
  margin-bottom: 18px;
  flex-wrap: wrap;
}

.quiet-hours-time {
  flex: 1 1 140px;
  min-width: 140px;
  display: flex;
  flex-direction: column;
}

.quiet-hours-time label {
  display: block;
  font-size: 0.74rem;
  color: var(--text-secondary);
  margin-bottom: 6px;
  text-transform: uppercase;
  letter-spacing: 0.5px;
}

.quiet-hours-time input {
  width: 100%;
  box-sizing: border-box;
  padding: 10px 14px;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--bg);
  color: var(--text);
  font-family: inherit;
  font-size: 0.95rem;
  -webkit-appearance: none;
  appearance: none;
}

.quiet-hours-time input:focus {
  outline: none;
  border-color: var(--blue-500);
}

.quiet-hours-days label {
  display: block;
  font-size: 0.74rem;
  color: var(--text-secondary);
  margin-bottom: 6px;
  text-transform: uppercase;
  letter-spacing: 0.5px;
}

.day-chips {
  display: flex;
  gap: 6px;
  margin-bottom: 14px;
}

.day-chip {
  flex: 1;
  height: 36px;
  border: 1px solid var(--border);
  background: var(--bg);
  color: var(--text-secondary);
  border-radius: 50%;
  font-family: inherit;
  font-size: 0.8rem;
  font-weight: 600;
  cursor: pointer;
  transition: all var(--transition);
}

.day-chip.active {
  background: var(--blue-500);
  color: white;
  border-color: var(--blue-500);
}

.setting-item-thin {
  padding: 10px 0 !important;
  border-bottom: none !important;
}

/* Connected Apps */
.connected-section-label {
  padding: 12px 18px 6px;
  font-size: 0.72rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.6px;
  color: var(--text-secondary);
}

.connected-app {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 18px;
  border-bottom: 1px solid var(--border);
}

.connected-app:last-child {
  border-bottom: none;
}

.connected-app-icon {
  width: 38px;
  height: 38px;
  border-radius: 10px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1.2rem;
  background: var(--bg-secondary);
  flex-shrink: 0;
}

/* Per-provider icon backgrounds. Productivity providers added 2026-05-16
   (Jimmy) ahead of the OAuth wiring phase. Brand colors approximate the
   provider's marketing palette so the icons read as "official-ish" even
   though they're emoji-based; will swap to real SVG logos when we ship
   real connections. Netflix + Hulu rules deleted alongside the removed
   cards (no public API → not connectable; misleading to imply otherwise). */
.connected-icon-google-gmail     { background: linear-gradient(135deg, #ea4335, #c5221f); color: white; }
.connected-icon-microsoft-outlook{ background: linear-gradient(135deg, #0078d4, #005a9e); color: white; }
.connected-icon-apple-calendar   { background: linear-gradient(135deg, #ff3b30, #d70015); color: white; }
.connected-icon-apple-music      { background: linear-gradient(135deg, #fa57c1, #b249f8); color: white; }
.connected-icon-spotify          { background: #1db954; color: white; }
.connected-icon-pandora          { background: linear-gradient(135deg, #00a0ee, #005c9c); color: white; }
.connected-icon-youtube          { background: #ff0000; color: white; }
/* Providers added 2026-07-01 (Apple Music/Calendar, Pandora, YouTube Music
   removed — no usable public API). Brand-ish colors, white glyph. */
/* Google Calendar + Drive now use Google's official marks (2026-07-25).
   Neither may sit on one of our own coloured gradients — that misrepresents
   the mark and the colours fight it. The two need DIFFERENT treatments
   because the artwork is shaped differently, so don't "unify" them:

   Calendar's mark is a full-bleed square app icon that already supplies its
   own background, so it becomes the tile — filled edge to edge and clipped
   by the tile's own radius, exactly like the coloured tiles around it.

   Drive's mark is an open triangle on transparency, so it needs a surface to
   sit on: a neutral white tile with a hairline, inset so the mark can breathe.
   White in dark mode too — it's drawn for a light surface. */
.connected-icon-google-calendar {
  background: transparent;
  overflow: hidden;
  padding: 0;
}
.connected-icon-google-calendar img { display: block; width: 100%; height: 100%; }

.connected-icon-google-drive {
  background: #fff;
  border: 1px solid var(--border, #DEE4EC);
}
[data-theme="dark"] .connected-icon-google-drive { background: #fff; }
.connected-icon-google-drive img { display: block; width: 24px; height: 24px; }
.connected-icon-dropbox          { background: #0061ff; color: white; }
.connected-icon-slack            { background: #4a154b; color: white; }
.connected-icon-fitbit           { background: linear-gradient(135deg, #00b0b9, #007a80); color: white; }
.connected-icon-oura             { background: linear-gradient(135deg, #4b4b4b, #111111); color: white; }
.connected-icon-withings         { background: linear-gradient(135deg, #00b2a9, #007f79); color: white; }
.connected-icon-garmin           { background: linear-gradient(135deg, #007cc3, #005a8e); color: white; }
.connected-icon-google-home      { background: linear-gradient(135deg, #4285f4, #34a853); color: white; }

/* "Coming soon" footer note inside the Connected Apps card. Soft
   info-tile styling — distinguishes itself from the list items above
   without screaming for attention. Phase B (real OAuth wiring) removes
   this once "+ Connect" buttons actually work. */
.connected-apps-note {
  margin-top: 14px;
  padding: 12px 14px;
  background: rgba(59, 130, 246, 0.06);
  border: 1px solid rgba(59, 130, 246, 0.25);
  border-radius: 10px;
  font-size: 0.82rem;
  line-height: 1.5;
  color: var(--text-secondary);
}
.connected-apps-note strong {
  color: var(--text);
  font-weight: 600;
}

.connected-app-info {
  flex: 1;
  min-width: 0;
}

.connected-app-name {
  font-size: 0.94rem;
  font-weight: 500;
  color: var(--text);
}

.connected-app-status {
  font-size: 0.76rem;
  color: var(--text-secondary);
  margin-top: 2px;
  display: flex;
  align-items: center;
  gap: 4px;
}

.status-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  display: inline-block;
}

.status-green { background: #10b981; }
.status-red { background: #ef4444; }
.status-amber { background: #f59e0b; }

.connected-app-action {
  background: none;
  border: none;
  color: var(--text-muted);
  font-size: 1.4rem;
  cursor: pointer;
  padding: 4px 10px;
}

.connect-btn {
  padding: 7px 14px;
  background: var(--blue-500);
  color: white;
  border: none;
  border-radius: 16px;
  font-size: 0.8rem;
  font-weight: 600;
  font-family: inherit;
  cursor: pointer;
  transition: background var(--transition);
}

.connect-btn:hover {
  background: var(--blue-600);
}

/* Connected state — quieter than "+ Connect" on purpose. The live action
   here is Disconnect, which shouldn't shout for attention the way an
   invitation to connect does. (Calendar 2026-07-20) */
.connect-btn.is-connected {
  background: transparent;
  color: var(--text-secondary, #5B6675);
  border: 1px solid var(--border, #DEE4EC);
}
.connect-btn.is-connected:hover {
  background: var(--bg-subtle, rgba(0, 0, 0, 0.04));
  color: var(--text-primary, #0F1620);
}

/* Flag still off → the button is inert. Make that legible rather than
   leaving a live-looking control that does nothing when tapped. */
.connect-btn:disabled {
  opacity: 0.45;
  cursor: default;
}
.connect-btn:disabled:hover {
  background: var(--blue-500);
}

/* Family Insights */
.family-intro {
  display: flex;
  gap: 12px;
  padding: 14px 18px;
  background: linear-gradient(135deg, #f0f9ff, #e0f2fe);
  border-bottom: 1px solid var(--border);
}

[data-theme="dark"] .family-intro {
  background: linear-gradient(135deg, rgba(14, 165, 233, 0.12), rgba(14, 165, 233, 0.04));
}

.family-intro-icon {
  font-size: 1.6rem;
  flex-shrink: 0;
}

.family-intro-text {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.family-intro-text strong {
  font-size: 0.9rem;
  color: var(--text);
}

.family-intro-text span {
  font-size: 0.78rem;
  color: var(--text-secondary);
  line-height: 1.4;
}

.family-config {
  border-top: 1px solid var(--border);
}

.family-verify-btn {
  margin-top: 8px;
  padding: 8px 14px;
  background: var(--blue-500);
  color: white;
  border: none;
  border-radius: 8px;
  font-size: 0.84rem;
  font-weight: 600;
  font-family: inherit;
  cursor: pointer;
  align-self: flex-start;
}

.frequency-options {
  display: flex;
  gap: 8px;
  width: 100%;
}

.frequency-option {
  flex: 1;
  position: relative;
  cursor: pointer;
}

.frequency-option input[type="radio"] {
  position: absolute;
  opacity: 0;
}

.frequency-option span {
  display: block;
  padding: 9px;
  text-align: center;
  border: 2px solid var(--border);
  border-radius: 10px;
  font-size: 0.84rem;
  color: var(--text-secondary);
  transition: all var(--transition);
}

.frequency-option input:checked + span {
  border-color: var(--blue-500);
  background: var(--blue-50);
  color: var(--blue-700);
  font-weight: 600;
}

[data-theme="dark"] .frequency-option input:checked + span {
  background: rgba(59, 130, 246, 0.15);
  color: var(--blue-400);
}

.family-includes {
  padding: 14px 18px;
  background: var(--bg-secondary);
  border-top: 1px solid var(--border);
  border-bottom: 1px solid var(--border);
}

.family-includes-title {
  font-size: 0.74rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  color: var(--text-secondary);
  margin-bottom: 10px;
}

.family-include-row {
  display: flex;
  align-items: center;
  justify-content: space-between; /* text left, .toggle switch right —
                                     checkbox→switch pass (Jimmy 2026-07-09) */
  gap: 10px;
  padding: 6px 0;
  font-size: 0.88rem;
  color: var(--text);
  cursor: pointer;
}

.family-include-row input[type="checkbox"] {
  accent-color: var(--blue-500);
  width: 18px;
  height: 18px;
}

.family-include-row em {
  font-style: italic;
  color: var(--text-muted);
  font-size: 0.78rem;
}

/* (The .family-sample-btn "Send me a sample" button was removed
   2026-05-18 per Scott — we're not building the sample-summary
   preview feature.) */

/* Sub-note for the Full conversation transcripts checkbox — explains
   what the setting does AND the teen-trust trade-off so the parent
   makes an informed choice. Visually demoted from the main label
   (smaller, muted) but big enough to be unmissable. Scott's copy. */
.family-include-note {
  display: block;
  font-size: 0.78rem;
  color: var(--text-secondary);
  line-height: 1.45;
  margin-top: 4px;
  font-weight: 400;
}

/* ============================================================
 * Family Insights — child sub-accounts (Phase 1, 2026-05-18)
 * ============================================================
 * The "Your child sub-accounts" section that lives inside the
 * Family Insights settings card. Renders a list of children the
 * parent has provisioned + an expand-on-click "Add child" form.
 *
 * Phase 1 only ships the account-model UI. Children show with a
 * "⏳ Awaiting verification (Phase 2)" pill until VPC lands —
 * see family-child-status-pending styles below.
 * ============================================================ */
/* Alerts defaults — SCOTT-B (2026-05-31). Sits ABOVE the
   children-accounts section as the parent's default config
   that new children inherit. Uses the same visual rhythm as
   .family-children-section so the page stays cohesive. */
.family-alerts-defaults-section {
  margin-top: 22px;
  padding: 0 18px 14px;
  border-bottom: 1px solid var(--border);
}
/* Inside its own collapsible section (Jimmy 2026-07-09) the alerts block
   sheds the old in-panel chrome — the section provides the spacing, and
   the stray bottom border read as clutter. */
#family-alerts-config .family-alerts-defaults-section {
  margin-top: 0;
  padding: 0;
  border-bottom: none;
}
.family-alerts-defaults-header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  margin-bottom: 4px;
  padding-top: 12px;
}
.family-alerts-defaults-title {
  font-size: 1.05rem;
  font-weight: 700;
  color: var(--text);
}
.family-alerts-defaults-desc {
  font-size: 0.85rem;
  color: var(--text-secondary);
  margin: 0 0 12px;
  line-height: 1.45;
}
.family-alerts-defaults-body {
  /* Re-uses .child-settings-safety-config-row for the toggle rows
     so the visual style matches the per-child Configure panel. */
  background: var(--bg-secondary);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 8px 12px;
}
.family-alerts-defaults-loading {
  padding: 8px 0;
  color: var(--text-secondary);
  font-size: 0.85rem;
  font-style: italic;
}
.family-alerts-defaults-status {
  margin-top: 8px;
  min-height: 1.2em;
  font-size: 0.82rem;
  color: var(--text-secondary);
}
.family-alerts-defaults-status.ok    { color: #15803d; }
.family-alerts-defaults-status.error { color: #b91c1c; }

.family-children-section {
  /* No border-top — the .family-includes section above already has a
     border-bottom, and stacking two borders + the margin between
     them reads as a doubled line / visual collision (Jimmy 2026-05-18).
     Margin alone provides the breathing room.
     padding-bottom bumped 4px → 18px 2026-05-27 so the last child
     card has the same breathing room below it as the section title
     has above it (Jimmy: "match the rest of the margins"). */
  margin-top: 22px;
  padding: 0 18px 18px;
}

/* Children section moved to its own card at the TOP of the Parent-Child
   page (Scott 2026-07-09). The 22px margin-top above was spacing it off
   .family-includes in the old nested layout; as the first block of a
   standalone card it reads as dead space, so tighten it. */
.settings-card > .family-children-section {
  margin-top: 14px;
}

/* "Set up a child account" intro title is a live link into the add-child
   flow (Scott 2026-07-09) — clicking scrolls up to the children card and
   opens the "+ Add child" form. Blue reads as tappable; underline on
   hover confirms it. The <strong> keeps its weight, inherits the color. */
.family-intro-setup-link {
  color: var(--blue-500);
  text-decoration: none;
  cursor: pointer;
}
.family-intro-setup-link:hover,
.family-intro-setup-link:focus-visible {
  text-decoration: underline;
}
.family-intro-setup-link strong {
  color: inherit;
}

.family-children-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 10px;
}

.family-children-title {
  font-size: 0.92rem;
  font-weight: 700;
  color: var(--text);
}

.family-add-child-btn {
  padding: 6px 12px;
  background: var(--blue-500);
  color: white;
  border: none;
  border-radius: 8px;
  font-size: 0.82rem;
  font-weight: 600;
  font-family: inherit;
  cursor: pointer;
  transition: background var(--transition);
}

.family-add-child-btn:hover {
  background: var(--blue-600);
}

.family-add-child-btn:disabled {
  background: var(--gray-300);
  cursor: not-allowed;
}

[data-theme="dark"] .family-add-child-btn:disabled {
  background: var(--gray-700);
}

/* Children list — empty state, loading, or rendered rows */
.family-children-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
  min-height: 24px;
}

/* Card outer — single horizontal row holding everything (Jimmy
   2026-05-27 "much cleaner way"): avatar · name · age · pill ·
   optional action btn · chevron. The whole card is the click
   target for opening per-child Settings (iOS list-row pattern);
   the chevron is the visual affordance. role=button + tabindex
   set in the render for keyboard a11y. */
.family-child-card {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 12px 14px;
  background: var(--bg-secondary);
  border: 1px solid var(--border);
  border-radius: 10px;
  cursor: pointer;
  transition: background 0.12s, border-color 0.12s, transform 0.05s;
  -webkit-tap-highlight-color: transparent;
}
.family-child-card:hover {
  background: var(--bg-hover, rgba(59, 130, 246, 0.04));
  border-color: rgba(59, 130, 246, 0.35);
}
.family-child-card:active {
  transform: scale(0.997);
}
.family-child-card:focus-visible {
  outline: 2px solid var(--blue-500);
  outline-offset: 2px;
}

.family-child-avatar {
  width: 36px;
  height: 36px;
  border-radius: 50%;
  background: var(--blue-500);
  color: white;
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: 700;
  font-size: 0.95rem;
  flex-shrink: 0;
}

/* Name takes whatever space remains after the fixed-width siblings
   (avatar, age band, pill, action btn, chevron) claim theirs.
   Ellipsis-truncates if the name is too long. */
.family-child-name {
  font-weight: 600;
  font-size: 0.95rem;
  color: var(--text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
  flex: 1 1 auto;
}

.family-child-meta {
  font-size: 0.78rem;
  color: var(--text-secondary);
  flex-shrink: 0;
}

/* Chevron — visual cue that the card opens to a detail page.
   Subdued color so it doesn't compete with the action button or
   pill for attention. */
.family-child-chevron {
  flex-shrink: 0;
  color: var(--text-secondary);
  opacity: 0.6;
}
.family-child-card:hover .family-child-chevron {
  opacity: 1;
  color: var(--blue-500);
}

.family-child-status-pill {
  font-size: 0.72rem;
  padding: 3px 8px;
  border-radius: 999px;
  font-weight: 600;
  white-space: nowrap;
}

.family-child-status-pending {
  background: rgba(234, 179, 8, 0.15);
  color: rgb(180, 130, 8);
}

[data-theme="dark"] .family-child-status-pending {
  background: rgba(234, 179, 8, 0.2);
  color: rgb(252, 211, 77);
}

.family-child-status-active {
  background: rgba(34, 197, 94, 0.15);
  color: rgb(22, 130, 60);
}

[data-theme="dark"] .family-child-status-active {
  background: rgba(34, 197, 94, 0.2);
  color: rgb(134, 239, 172);
}

/* Phase 4 (2026-05-23): Suspended/paused state — neutral grey to
   signal "not active right now, but not an error/warning either."
   Distinct from pending (amber) and active (green). */
.family-child-status-suspended {
  background: rgba(107, 114, 128, 0.18);
  color: rgb(75, 85, 99);
}

[data-theme="dark"] .family-child-status-suspended {
  background: rgba(156, 163, 175, 0.22);
  color: rgb(209, 213, 219);
}

/* Phase 2 VPC states — blue while parent action is needed
   (init_sent: check email), amber while we're waiting on the
   24hr grace timer to elapse (link_clicked + second_sent). */
.family-child-status-init-sent {
  background: rgba(59, 130, 246, 0.15);
  color: rgb(29, 78, 216);
}

[data-theme="dark"] .family-child-status-init-sent {
  background: rgba(59, 130, 246, 0.2);
  color: rgb(147, 197, 253);
}

.family-child-status-grace {
  background: rgba(234, 179, 8, 0.12);
  color: rgb(146, 105, 8);
  border: 1px dashed rgba(234, 179, 8, 0.5);
  padding: 2px 7px;   /* compensate for the 1px border */
}

[data-theme="dark"] .family-child-status-grace {
  background: rgba(234, 179, 8, 0.18);
  color: rgb(252, 211, 77);
  border-color: rgba(234, 179, 8, 0.45);
}

.family-child-remove {
  background: none;
  border: none;
  color: var(--text-muted);
  cursor: pointer;
  padding: 4px 6px;
  border-radius: 6px;
  font-size: 1.1rem;
  line-height: 1;
}

.family-child-remove:hover {
  color: rgb(220, 38, 38);
  background: rgba(220, 38, 38, 0.08);
}

/* Per-row action button (Send verification / Resend email) — sits
   between the status pill and the × remove button. Different styles
   for the two variants: "Send" is a primary CTA (the consent has
   never been started), "Resend" is secondary (parent already
   consented, we're just delivering another email). */
.family-child-action {
  padding: 4px 10px;
  border-radius: 8px;
  font-size: 0.75rem;
  font-weight: 600;
  font-family: inherit;
  cursor: pointer;
  border: 1px solid transparent;
  white-space: nowrap;
  /* Inline-flex so an icon (e.g. the new SVG cog on Settings —
     2026-05-27) sits vertically centered with the label text, gap
     between them. Plain text-only buttons render identically. */
  display: inline-flex;
  align-items: center;
  gap: 5px;
}

.family-child-action-icon {
  flex-shrink: 0;
  /* Slight optical lift so a stroke-based icon aligns with the
     visual baseline of the text. */
  margin-top: -1px;
}

.family-child-action-start {
  background: var(--blue-500);
  color: white;
}

.family-child-action-start:hover {
  background: var(--blue-600);
}

.family-child-action-resend {
  background: transparent;
  color: var(--blue-600);
  border-color: var(--blue-500);
}

.family-child-action-resend:hover {
  background: rgba(59, 130, 246, 0.08);
}

[data-theme="dark"] .family-child-action-resend {
  color: var(--blue-400);
  border-color: var(--blue-400);
}

.family-child-action:disabled {
  opacity: 0.6;
  cursor: not-allowed;
}

.family-children-empty,
.family-children-loading,
.family-children-error {
  font-size: 0.82rem;
  color: var(--text-secondary);
  text-align: center;
  padding: 12px 8px;
  font-style: italic;
}

.family-children-error {
  color: rgb(220, 38, 38);
  font-style: normal;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  padding: 16px 12px;
  background: rgba(220, 38, 38, 0.04);
  border: 1px solid rgba(220, 38, 38, 0.2);
  border-radius: 8px;
}
.family-children-error-msg {
  text-align: center;
  font-size: 0.9rem;
}
.family-children-error-status {
  margin-top: 4px;
  font-size: 0.74rem;
  color: var(--text-secondary);
  font-weight: 500;
  letter-spacing: 0.02em;
}
.family-children-retry-btn {
  appearance: none;
  border: none;
  background: rgb(220, 38, 38);
  color: #fff;
  font-weight: 600;
  font-size: 0.86rem;
  padding: 8px 18px;
  border-radius: 6px;
  cursor: pointer;
  transition: background 0.12s;
}
.family-children-retry-btn:hover { background: rgb(185, 28, 28); }
.family-children-retry-btn:active { transform: scale(0.98); }

/* Add-child form (expanded on button click).
   Intentionally NO background, border, or border-radius (Jimmy
   2026-05-18) — the previous "card within a card" look created
   a visible doubled bottom edge inside the children section. The
   form just stacks fields naturally now; the individual inputs
   keep their own borders for definition. */
.family-add-child-form {
  margin-top: 10px;
  padding: 4px 0 0;
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.family-add-child-field {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.family-add-child-field label {
  font-size: 0.82rem;
  font-weight: 600;
  color: var(--text);
}

.family-add-child-field input[type="text"],
.family-add-child-field textarea {
  padding: 8px 10px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--bg);
  color: var(--text);
  font-size: 0.88rem;
  font-family: inherit;
  resize: vertical;
}

.family-add-child-field input[type="text"]:focus,
.family-add-child-field textarea:focus {
  outline: none;
  border-color: var(--blue-500);
}

/* The DOB picker uses the shared .date-dropdowns helper (3 selects:
   Month / Day / Year). Same styling as My Birthday + Important Dates
   — no extra CSS here. The .family-dob-hint sub-label below covers
   the "why we ask" explanation. */

/* ============================================================
 * Direct Notice modal — COPPA §312.4 disclosure (Phase 2, v392)
 * ============================================================
 * Pops after a successful "Create child" in Family Insights and
 * must be acknowledged with an affirmative consent click before
 * the VPC verification email goes out. Heavy on text (5 sections
 * + intro + VPC reminder), so the body scrolls while the header
 * and consent footer stay sticky for mobile + small-laptop screens.
 * ============================================================ */
.direct-notice-overlay {
  position: fixed;
  inset: 0;
  background: rgba(15, 23, 42, 0.6);
  z-index: 1000;       /* above the bottom tab bar (850) too, so the sticky
                          consent footer is never sliced by it (2026-06-13) */
  display: flex;
  align-items: center;
  justify-content: center;
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.2s, visibility 0.2s;
  padding: 16px;
}

.direct-notice-overlay.open {
  opacity: 1;
  visibility: visible;
}

.direct-notice-modal {
  background: var(--bg);
  border-radius: 16px;
  width: 100%;
  max-width: 560px;
  max-height: 90vh;
  display: flex;
  flex-direction: column;
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
  transform: translateY(8px) scale(0.98);
  transition: transform 0.25s cubic-bezier(0.34, 1.56, 0.64, 1);
  overflow: hidden;
}

.direct-notice-overlay.open .direct-notice-modal {
  transform: translateY(0) scale(1);
}

/* Sticky header */
.direct-notice-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 18px 22px;
  border-bottom: 1px solid var(--border);
  background: var(--bg);
  flex-shrink: 0;
}

.direct-notice-header h2 {
  margin: 0;
  font-size: 1.05rem;
  font-weight: 800;
  color: var(--text);
  line-height: 1.3;
}

.direct-notice-close {
  background: none;
  border: none;
  font-size: 1.6rem;
  line-height: 1;
  color: var(--text-muted);
  cursor: pointer;
  padding: 4px 10px;
  border-radius: 8px;
  flex-shrink: 0;
}

.direct-notice-close:hover {
  background: var(--bg-secondary);
  color: var(--text);
}

/* Scrollable body */
.direct-notice-body {
  flex: 1 1 auto;
  overflow-y: auto;
  padding: 18px 22px 8px;
  -webkit-overflow-scrolling: touch;
}

.direct-notice-intro {
  margin: 0 0 18px;
  font-size: 0.92rem;
  line-height: 1.55;
  color: var(--text);
}

.direct-notice-child-name {
  color: var(--blue-600);
}

[data-theme="dark"] .direct-notice-child-name {
  color: var(--blue-400);
}

.direct-notice-section {
  margin: 0 0 18px;
  padding-bottom: 14px;
  border-bottom: 1px dashed var(--border);
}

.direct-notice-section:last-of-type {
  border-bottom: none;
}

.direct-notice-section h3 {
  margin: 0 0 8px;
  font-size: 0.95rem;
  font-weight: 700;
  color: var(--text);
}

.direct-notice-section p,
.direct-notice-section li {
  font-size: 0.88rem;
  line-height: 1.55;
  color: var(--text);
}

.direct-notice-section ul {
  margin: 0;
  padding-left: 22px;
}

.direct-notice-section li {
  margin-bottom: 6px;
}

.direct-notice-section li:last-child {
  margin-bottom: 0;
}

.direct-notice-collect-not {
  margin: 10px 0 0;
  padding: 10px 12px;
  background: var(--bg-secondary);
  border-left: 3px solid var(--blue-500);
  border-radius: 6px;
  font-size: 0.85rem;
  line-height: 1.5;
}

.direct-notice-contact-card {
  margin-top: 10px;
  padding: 12px 14px;
  background: var(--bg-secondary);
  border: 1px solid var(--border);
  border-radius: 8px;
  font-size: 0.86rem;
  line-height: 1.55;
}

.direct-notice-contact-line {
  display: block;
}

.direct-notice-contact-line + .direct-notice-contact-line {
  margin-top: 4px;
}

.direct-notice-vpc-note {
  margin: 0 0 8px;
  padding: 12px 14px;
  background: rgba(59, 130, 246, 0.08);
  border-radius: 8px;
}

.direct-notice-vpc-note p {
  margin: 0;
  font-size: 0.85rem;
  line-height: 1.55;
  color: var(--text);
}

/* Sticky footer with Cancel + Consent buttons */
.direct-notice-footer {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 14px 22px 18px;
  border-top: 1px solid var(--border);
  background: var(--bg);
  flex-shrink: 0;
}

.direct-notice-decline,
.direct-notice-consent {
  padding: 12px 16px;
  border-radius: 10px;
  font-size: 0.92rem;
  font-weight: 600;
  font-family: inherit;
  cursor: pointer;
  line-height: 1.35;
  text-align: center;
}

.direct-notice-decline {
  background: var(--bg);
  color: var(--text);
  border: 1px solid var(--border);
  order: 2;     /* visually below the primary consent button */
}

.direct-notice-decline:hover {
  background: var(--bg-secondary);
}

.direct-notice-consent {
  background: linear-gradient(135deg, var(--blue-500), #8b5cf6);
  color: white;
  border: none;
  font-weight: 700;
  order: 1;     /* primary action on top */
  box-shadow: 0 4px 12px rgba(59, 130, 246, 0.3);
}

.direct-notice-consent:hover {
  filter: brightness(1.05);
}

.direct-notice-consent:disabled {
  opacity: 0.6;
  cursor: not-allowed;
}

.direct-notice-consent strong {
  font-weight: 800;
}

/* Slightly more breathing room on desktop — the modal can grow up
   to 600px and use horizontal padding more generously. */
@media (min-width: 600px) {
  .direct-notice-modal {
    max-width: 600px;
  }
  .direct-notice-footer {
    flex-direction: row;
    justify-content: flex-end;
  }
  .direct-notice-decline {
    order: 1;
    flex: 0 0 auto;
    min-width: 100px;
  }
  .direct-notice-consent {
    order: 2;
    flex: 1 1 auto;
  }
}

/* ============================================================
   Device Pairing modal — Parent/Child Phase 2b-3 (2026-05-22)
   ============================================================
   Borrows the .direct-notice-overlay layout almost verbatim
   (fixed-position scrim, centered card, drop-shadow), but with
   tighter padding because the contents are visually heavier
   (giant 6-digit code + QR). Closes via X / Done / Esc — overlay
   click is intentionally NOT a dismiss trigger so a parent can
   set the phone down for the child to read without losing the
   modal to an accidental backdrop tap. */
.device-pairing-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.55);
  display: none;
  align-items: center;
  justify-content: center;
  z-index: 10000;
  padding: 16px;
  opacity: 0;
  transition: opacity 0.18s ease;
}
.device-pairing-overlay.open {
  display: flex;
  opacity: 1;
}
.device-pairing-modal {
  background: var(--bg, #fff);
  border-radius: 18px;
  width: 100%;
  max-width: 420px;
  max-height: 92vh;
  overflow-y: auto;
  position: relative;
  padding: 28px 22px 22px;
  box-shadow: 0 24px 72px rgba(0, 0, 0, 0.35);
  transform: translateY(8px);
  transition: transform 0.18s ease;
}
.device-pairing-overlay.open .device-pairing-modal {
  transform: translateY(0);
}
.device-pairing-close {
  position: absolute;
  top: 10px;
  right: 12px;
  background: transparent;
  border: 0;
  font-size: 1.6rem;
  line-height: 1;
  cursor: pointer;
  color: var(--text-muted, #8a8a8a);
  padding: 4px 10px;
  border-radius: 8px;
}
.device-pairing-close:hover {
  background: var(--bg-secondary, #f4f4f6);
  color: var(--text, #111);
}
.device-pairing-header {
  text-align: center;
  margin-bottom: 18px;
}
.device-pairing-title {
  font-size: 1.18rem;
  font-weight: 700;
  margin: 0 0 8px;
  color: var(--text, #111);
}
.device-pairing-subtitle {
  font-size: 0.92rem;
  line-height: 1.45;
  color: var(--text-secondary, #5a5a66);
  margin: 0;
}
.device-pairing-subtitle strong {
  color: var(--text, #111);
  font-weight: 700;
}
.device-pairing-body {
  min-height: 280px;
}
.device-pairing-loading {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 14px;
  padding: 48px 16px;
  color: var(--text-secondary, #5a5a66);
}
.device-pairing-spinner {
  width: 28px;
  height: 28px;
  border: 3px solid var(--border, #e6e6ea);
  border-top-color: var(--blue-500, #4c6ef5);
  border-radius: 50%;
  animation: device-pairing-spin 0.85s linear infinite;
}
@keyframes device-pairing-spin {
  to { transform: rotate(360deg); }
}
.device-pairing-code-row {
  display: flex;
  justify-content: center;
  margin: 4px 0 18px;
}
.device-pairing-code {
  /* Giant 6-digit display — has to be legible across a room when
     the parent shows their phone to the child. Monospaced so the
     digit widths are even (system-ui sans-serif would jitter on
     1's). Letter-spacing widens it for camera-readability too. */
  font-family: 'SF Mono', 'Roboto Mono', Menlo, Consolas, monospace;
  font-size: 2.6rem;
  font-weight: 700;
  letter-spacing: 0.18em;
  color: var(--text, #111);
  background: linear-gradient(135deg, rgba(76, 110, 245, 0.08), rgba(168, 85, 247, 0.08));
  padding: 14px 22px;
  border-radius: 14px;
  border: 1px solid var(--border, #e6e6ea);
  user-select: all;
}
.device-pairing-qr-row {
  display: flex;
  justify-content: center;
  margin-bottom: 18px;
}
.device-pairing-qr {
  width: 180px;
  height: 180px;
  background: #fff;
  border-radius: 12px;
  border: 1px solid var(--border, #e6e6ea);
  padding: 10px;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}
.device-pairing-qr svg {
  width: 100%;
  height: auto;
  display: block;
}
.device-pairing-meta {
  text-align: center;
  font-size: 0.82rem;
  color: var(--text-secondary, #5a5a66);
  margin-bottom: 4px;
}
.device-pairing-expires-label {
  margin-right: 4px;
}
.device-pairing-countdown {
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  color: var(--text, #111);
}
.device-pairing-countdown.is-expiring {
  /* Pulse the countdown red in the final 60 seconds so the parent
     notices and rotates the code if the child hasn't claimed yet. */
  color: var(--red-600, #dc2626);
}
.device-pairing-error {
  text-align: center;
  padding: 32px 16px;
}
.device-pairing-error-msg {
  color: var(--text-secondary, #5a5a66);
  font-size: 0.92rem;
  margin: 0;
}
.device-pairing-actions {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-top: 16px;
}
.device-pairing-regenerate,
.device-pairing-done {
  font-size: 0.95rem;
  font-weight: 600;
  padding: 12px 16px;
  border-radius: 12px;
  cursor: pointer;
  border: 0;
  width: 100%;
  transition: background 0.15s ease, transform 0.05s ease;
}
.device-pairing-regenerate {
  background: var(--bg-secondary, #f4f4f6);
  color: var(--text, #111);
}
.device-pairing-regenerate:hover {
  background: var(--border, #e6e6ea);
}
.device-pairing-regenerate:disabled {
  opacity: 0.55;
  cursor: not-allowed;
}
.device-pairing-done {
  background: linear-gradient(135deg, var(--blue-500, #4c6ef5), var(--purple-500, #a855f7));
  color: #fff;
}
.device-pairing-done:hover {
  filter: brightness(1.05);
}
.device-pairing-done:active {
  transform: scale(0.99);
}

/* Desktop: side-by-side action buttons (matches the DirectNotice
   convention — secondary on left, primary on right). */
@media (min-width: 480px) {
  .device-pairing-actions {
    flex-direction: row;
  }
  .device-pairing-regenerate {
    order: 1;
    flex: 0 0 auto;
    min-width: 160px;
  }
  .device-pairing-done {
    order: 2;
    flex: 1 1 auto;
  }
}

/* "Sign-in code" button on Active child rows (next to status pill) */
.family-child-action-pair {
  font-size: 0.78rem;
  font-weight: 600;
  padding: 6px 10px;
  border-radius: 8px;
  background: linear-gradient(135deg, var(--blue-500, #4c6ef5), var(--purple-500, #a855f7));
  color: #fff;
  border: 0;
  cursor: pointer;
  white-space: nowrap;
}
.family-child-action-pair:hover {
  filter: brightness(1.08);
}
/* "Sign-in password" button — same shape as -pair but a subtler
   outline treatment so the two action buttons don't visually
   compete. Only renders for 13-17 active rows. */
.family-child-action-password {
  font-size: 0.78rem;
  font-weight: 600;
  padding: 6px 10px;
  border-radius: 8px;
  background: var(--bg, #fff);
  color: var(--text, #111);
  border: 1.5px solid var(--border, #e6e6ea);
  cursor: pointer;
  white-space: nowrap;
}
.family-child-action-password:hover {
  background: var(--bg-secondary, #f4f4f6);
  border-color: var(--text-secondary, #5a5a66);
}

/* ============================================================
   Child Password modal — Phase 2b-3 Pass 2 (2026-05-22)
   ============================================================
   Reuses the .device-pairing layout DNA but with a form-card
   feel rather than the giant-code look. Three text inputs
   stacked vertically, a Save/Cancel row, and a quietly
   destructive "Revoke password" button at the bottom that's
   gated behind a confirm(). */
.child-password-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.55);
  display: none;
  align-items: center;
  justify-content: center;
  z-index: 10000;
  padding: 16px;
  opacity: 0;
  transition: opacity 0.18s ease;
}
.child-password-overlay.open {
  display: flex;
  opacity: 1;
}
.child-password-modal {
  background: var(--bg, #fff);
  border-radius: 18px;
  width: 100%;
  max-width: 440px;
  max-height: 92vh;
  overflow-y: auto;
  position: relative;
  padding: 26px 22px 18px;
  box-shadow: 0 24px 72px rgba(0, 0, 0, 0.35);
  transform: translateY(8px);
  transition: transform 0.18s ease;
}
.child-password-overlay.open .child-password-modal {
  transform: translateY(0);
}
.child-password-close {
  position: absolute;
  top: 10px;
  right: 12px;
  background: transparent;
  border: 0;
  font-size: 1.6rem;
  line-height: 1;
  cursor: pointer;
  color: var(--text-muted, #8a8a8a);
  padding: 4px 10px;
  border-radius: 8px;
}
.child-password-close:hover {
  background: var(--bg-secondary, #f4f4f6);
  color: var(--text, #111);
}
.child-password-header {
  text-align: center;
  margin-bottom: 18px;
}
.child-password-title {
  font-size: 1.18rem;
  font-weight: 700;
  margin: 0 0 8px;
  color: var(--text, #111);
}
.child-password-subtitle {
  font-size: 0.88rem;
  line-height: 1.45;
  color: var(--text-secondary, #5a5a66);
  margin: 0;
}
.child-password-form {
  display: flex;
  flex-direction: column;
  gap: 14px;
}
.child-password-field {
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.child-password-field label {
  font-size: 0.84rem;
  font-weight: 600;
  color: var(--text, #111);
}
.child-password-field input {
  font-size: 0.96rem;
  padding: 10px 12px;
  border-radius: 10px;
  border: 1px solid var(--border, #e6e6ea);
  background: var(--bg, #fff);
  color: var(--text, #111);
  outline: none;
  transition: border-color 0.12s ease;
}
.child-password-field input:focus {
  border-color: var(--blue-500, #4c6ef5);
}
.child-password-hint {
  font-size: 0.76rem;
  color: var(--text-secondary, #5a5a66);
  margin-top: 2px;
  line-height: 1.4;
}
.child-password-error {
  background: rgba(220, 38, 38, 0.08);
  border: 1px solid rgba(220, 38, 38, 0.3);
  color: var(--red-700, #b91c1c);
  font-size: 0.86rem;
  padding: 10px 12px;
  border-radius: 10px;
  margin-top: 4px;
}
.child-password-actions {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-top: 10px;
}
.child-password-cancel,
.child-password-submit {
  font-size: 0.95rem;
  font-weight: 600;
  padding: 12px 16px;
  border-radius: 12px;
  cursor: pointer;
  border: 0;
  width: 100%;
  transition: background 0.15s ease, transform 0.05s ease;
}
.child-password-cancel {
  background: var(--bg-secondary, #f4f4f6);
  color: var(--text, #111);
}
.child-password-cancel:hover {
  background: var(--border, #e6e6ea);
}
.child-password-submit {
  background: linear-gradient(135deg, var(--blue-500, #4c6ef5), var(--purple-500, #a855f7));
  color: #fff;
}
.child-password-submit:hover {
  filter: brightness(1.05);
}
.child-password-submit:active {
  transform: scale(0.99);
}
.child-password-submit:disabled,
.child-password-cancel:disabled {
  opacity: 0.55;
  cursor: not-allowed;
}
/* Revoke button: visually quieter than primary actions but still
   recognizably destructive (red text). Sits below the main form
   actions, separated by a hairline to make accidental clicks
   require deliberate eye-down + tap. */
.child-password-revoke-row {
  margin-top: 18px;
  padding-top: 14px;
  border-top: 1px solid var(--border, #e6e6ea);
  text-align: center;
}
.child-password-revoke {
  background: transparent;
  border: 0;
  color: var(--red-600, #dc2626);
  font-size: 0.84rem;
  font-weight: 600;
  cursor: pointer;
  padding: 6px 10px;
  border-radius: 8px;
}
.child-password-revoke:hover {
  background: rgba(220, 38, 38, 0.08);
}
.child-password-revoke:disabled {
  opacity: 0.55;
  cursor: not-allowed;
}

/* Desktop: side-by-side Cancel + Save (Cancel on left, Save on right
   — matches Family form + DeleteAccount button-order convention).
   2026-05-27 (Jimmy): explicitly reset width to auto here so flex
   sizing takes over. Without this the base rule's `width: 100%`
   (needed for the mobile column layout) leaks into row mode and
   pushes the Save button off the right edge of the modal — same
   pattern as the bug fixed on the device-pairing modal. */
@media (min-width: 480px) {
  .child-password-actions {
    flex-direction: row;
  }
  .child-password-cancel {
    order: 1;
    flex: 0 0 auto;
    width: auto;
    min-width: 110px;
  }
  .child-password-submit {
    order: 2;
    flex: 1 1 auto;
    width: auto;
  }
}

/* ============================================================
   Child sign-in screen — Phase 2b-4 (2026-05-22)
   ============================================================
   Full-bleed takeover for the public /family/signin and
   /family/pair?t=... URLs. Z-indexes over the entire SPA chrome
   so the kid sees ONLY this screen. Hidden by default; shown by
   ChildSignin.show() in app.js when the boot handler matches the
   URL.

   On mobile (< 600px): full-bleed white canvas, no card border.
   On desktop: centered card on a soft gradient background so the
   page doesn't feel naked in a wide browser window. */
.child-signin-screen {
  position: fixed;
  inset: 0;
  background: linear-gradient(160deg, #f5f7ff 0%, #faf5ff 100%);
  z-index: 9999;
  display: none;
  align-items: center;
  justify-content: center;
  padding: 16px;
  overflow-y: auto;
}
.child-signin-screen.open {
  display: flex;
}
.child-signin-card {
  background: #fff;
  width: 100%;
  max-width: 440px;
  padding: 32px 24px 28px;
  box-shadow: 0 24px 72px rgba(0, 0, 0, 0.08);
}
@media (min-width: 600px) {
  .child-signin-card {
    border-radius: 22px;
  }
}
.child-signin-header {
  text-align: center;
  margin-bottom: 24px;
}
.child-signin-mascot {
  width: 88px;
  height: 88px;
  object-fit: contain;
  margin: 0 auto 12px;
  display: block;
}
.child-signin-title {
  font-size: 1.6rem;
  font-weight: 800;
  margin: 0 0 8px;
  background: linear-gradient(135deg, var(--blue-500, #4c6ef5), var(--purple-500, #a855f7));
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
}
.child-signin-subtitle {
  font-size: 0.96rem;
  line-height: 1.5;
  color: var(--text-secondary, #5a5a66);
  margin: 0;
}

/* Code form — 6 separate one-character inputs styled like a
   one-time-code field. ChildSignin module handles auto-advance,
   paste support, and backspace navigation. */
.child-signin-code-form {
  display: flex;
  flex-direction: column;
  gap: 16px;
}
.child-signin-code-inputs {
  display: flex;
  gap: 6px;
  justify-content: center;
  align-items: center;
}
.child-signin-code-input {
  width: 42px;
  height: 56px;
  text-align: center;
  font-size: 1.6rem;
  font-weight: 700;
  font-family: 'SF Mono', 'Roboto Mono', Menlo, Consolas, monospace;
  border-radius: 12px;
  border: 1.5px solid var(--border, #e6e6ea);
  background: var(--bg, #fff);
  color: var(--text, #111);
  outline: none;
  transition: border-color 0.12s ease, transform 0.05s ease;
}
.child-signin-code-input:focus {
  border-color: var(--blue-500, #4c6ef5);
  transform: scale(1.03);
}
.child-signin-code-input.is-filled {
  background: linear-gradient(135deg, rgba(76, 110, 245, 0.06), rgba(168, 85, 247, 0.06));
}
.child-signin-code-sep {
  font-size: 1.5rem;
  color: var(--text-muted, #c0c0c8);
  margin: 0 2px;
}
.child-signin-submit {
  font-size: 1rem;
  font-weight: 700;
  padding: 14px 16px;
  border-radius: 14px;
  cursor: pointer;
  border: 0;
  width: 100%;
  background: linear-gradient(135deg, var(--blue-500, #4c6ef5), var(--purple-500, #a855f7));
  color: #fff;
  transition: filter 0.12s ease, transform 0.05s ease;
}
.child-signin-submit:hover {
  filter: brightness(1.05);
}
.child-signin-submit:active {
  transform: scale(0.99);
}
.child-signin-submit:disabled {
  opacity: 0.55;
  cursor: not-allowed;
}
.child-signin-error {
  background: rgba(220, 38, 38, 0.08);
  border: 1px solid rgba(220, 38, 38, 0.3);
  color: var(--red-700, #b91c1c);
  font-size: 0.9rem;
  padding: 10px 12px;
  border-radius: 10px;
  margin-top: 12px;
  text-align: center;
}

/* "Or use email and password" toggle row. Sits between the code
   form and the (hidden) password form. */
.child-signin-pw-toggle-row {
  margin: 22px 0 0;
  text-align: center;
}
.child-signin-pw-toggle {
  background: transparent;
  border: 0;
  color: var(--blue-500, #4c6ef5);
  font-size: 0.92rem;
  font-weight: 600;
  cursor: pointer;
  padding: 6px 10px;
  border-radius: 8px;
}
.child-signin-pw-toggle:hover {
  background: var(--bg-secondary, #f4f4f6);
  text-decoration: underline;
}

/* Email + password form — collapsed by default; revealed when the
   toggle is clicked. */
.child-signin-pw-form {
  display: flex;
  flex-direction: column;
  gap: 14px;
  margin-top: 14px;
  padding-top: 18px;
  border-top: 1px solid var(--border, #e6e6ea);
}
.child-signin-pw-field {
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.child-signin-pw-field label {
  font-size: 0.84rem;
  font-weight: 600;
  color: var(--text, #111);
}
.child-signin-pw-field input {
  font-size: 0.96rem;
  padding: 11px 12px;
  border-radius: 10px;
  border: 1px solid var(--border, #e6e6ea);
  background: var(--bg, #fff);
  color: var(--text, #111);
  outline: none;
  transition: border-color 0.12s ease;
}
.child-signin-pw-field input:focus {
  border-color: var(--blue-500, #4c6ef5);
}

/* Footer help line — small, soft color so it doesn't compete with
   the primary form actions but is reachable if a kid scans down. */
.child-signin-help {
  margin-top: 26px;
  padding-top: 18px;
  border-top: 1px solid var(--border, #e6e6ea);
  text-align: center;
  font-size: 0.82rem;
  color: var(--text-secondary, #5a5a66);
  line-height: 1.45;
}


/* (The .family-dob-hint sub-hint under the DOB input was removed
   2026-05-18 per Scott — the "why we ask" explanation was deemed
   unnecessary noise on the form. The same reasoning is still
   captured inside the Direct Notice modal that pops after
   "Create child" anyway, so the parent sees it at the moment it
   actually matters.) */

.family-add-child-actions {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
  margin-top: 4px;
}

.family-add-child-cancel,
.family-add-child-submit {
  padding: 8px 14px;
  border-radius: 8px;
  font-size: 0.85rem;
  font-weight: 600;
  font-family: inherit;
  cursor: pointer;
  border: 1px solid var(--border);
}

.family-add-child-cancel {
  background: var(--bg);
  color: var(--text);
}

.family-add-child-submit {
  background: var(--blue-500);
  color: white;
  border-color: var(--blue-500);
}

.family-add-child-submit:hover {
  background: var(--blue-600);
}

.family-add-child-submit:disabled {
  background: var(--gray-300);
  border-color: var(--gray-300);
  cursor: not-allowed;
}

[data-theme="dark"] .family-add-child-submit:disabled {
  background: var(--gray-700);
  border-color: var(--gray-700);
}

/* Privacy Links */
.setting-link-btn {
  background: none;
  border: none;
  color: var(--blue-500);
  font-size: 0.88rem;
  font-weight: 500;
  font-family: inherit;
  cursor: pointer;
  padding: 4px 8px;
}

.setting-link-btn.signout-btn {
  color: var(--text-secondary);
}

/* Back to Settings — matches the Save Changes button in COLOR (gradient
   blue→purple + white text) per Scott. Keeps its own dimensions so it
   sits cleanly above Delete account at full width without the
   .settings-save-btn's card-relative inset. */
.back-to-settings-btn {
  width: 100%;
  margin-top: 16px;
  padding: 12px;
  background: linear-gradient(135deg, var(--blue-500), var(--purple-500, #8b5cf6));
  border: none;
  color: #ffffff;
  border-radius: 10px;
  font-size: 0.92rem;
  font-weight: 700;
  font-family: inherit;
  letter-spacing: 0.01em;
  cursor: pointer;
  box-shadow: 0 2px 8px rgba(59, 130, 246, 0.20);
  transition: transform 0.15s ease, box-shadow 0.15s ease;
}
.back-to-settings-btn:hover {
  transform: translateY(-1px);
  box-shadow: 0 4px 14px rgba(59, 130, 246, 0.30);
}
.back-to-settings-btn:active { transform: translateY(1px); }

/* Delete Account — sits BELOW Back to Settings. Margin reduced so the
   two buttons read as a stacked pair rather than separate sections. */
.delete-account-btn {
  width: 100%;
  margin-top: 8px;
  padding: 12px;
  background: none;
  border: 1px solid var(--border);
  color: var(--text-muted);
  border-radius: 10px;
  font-size: 0.86rem;
  font-family: inherit;
  cursor: pointer;
  transition: all var(--transition);
}

.delete-account-btn:hover {
  color: #dc2626;
  border-color: #dc2626;
}

/* Delete Modal */
/* ============================================================
   Qbit Toast Modal — branded replacement for native alert()
   ============================================================ */
.qbit-toast {
  position: fixed;
  inset: 0;
  z-index: 1100;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
}
.qbit-toast-overlay {
  position: absolute;
  inset: 0;
  background: rgba(0,0,0,0.55);
  backdrop-filter: blur(2px);
}
.qbit-toast-content {
  position: relative;
  background: var(--bg);
  border-radius: 20px;
  max-width: 380px;
  width: 100%;
  padding: 28px 24px 22px 24px;
  text-align: center;
  box-shadow: 0 20px 60px rgba(0,0,0,0.3);
  animation: qbit-toast-pop 0.28s cubic-bezier(0.34, 1.56, 0.64, 1);
}
@keyframes qbit-toast-pop {
  from { transform: scale(0.9) translateY(8px); opacity: 0; }
  to   { transform: scale(1)   translateY(0);   opacity: 1; }
}
.qbit-toast-avatar {
  display: block;
  width: 88px;
  height: 88px;
  margin: 0 auto 12px auto;
  filter: drop-shadow(0 6px 14px rgba(59,130,246,0.25));
}
.qbit-toast-title {
  font-size: 0.78rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  /* Slightly darker than text-muted for better legibility — sits between
     muted and full text color so it reads as a subtle eyebrow label. */
  color: #4b5563;
  margin-bottom: 10px;
}
[data-theme="dark"] .qbit-toast-title {
  color: #9ca3af;
}
.qbit-toast-message {
  font-size: 1.05rem;
  line-height: 1.45;
  color: var(--text);
  margin-bottom: 22px;
  font-weight: 500;
}
.qbit-toast-btn {
  display: inline-block;
  padding: 12px 36px;
  background: linear-gradient(135deg, var(--blue-500), var(--purple-500, #8b5cf6));
  color: #ffffff;
  border: none;
  border-radius: 999px;
  font-size: 0.95rem;
  font-weight: 700;
  cursor: pointer;
  letter-spacing: 0.01em;
  box-shadow: 0 4px 14px rgba(59,130,246,0.30);
  transition: transform 0.15s ease, box-shadow 0.15s ease;
}
.qbit-toast-btn:hover {
  transform: translateY(-1px);
  box-shadow: 0 6px 18px rgba(59,130,246,0.40);
}
.qbit-toast-btn:active {
  transform: translateY(0);
}

.delete-modal {
  position: fixed;
  inset: 0;
  z-index: 1000;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
}

.delete-modal-overlay {
  position: absolute;
  inset: 0;
  background: rgba(0,0,0,0.6);
}

.delete-modal-content {
  position: relative;
  background: var(--bg);
  border-radius: 16px;
  max-width: 440px;
  width: 100%;
  max-height: 90vh;
  overflow-y: auto;
  box-shadow: 0 20px 60px rgba(0,0,0,0.3);
}

.delete-modal-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 18px 20px;
  border-bottom: 1px solid var(--border);
}

.delete-modal-header h2 {
  margin: 0;
  font-size: 1.2rem;
  color: var(--text);
}

.delete-modal-close {
  background: none;
  border: none;
  color: var(--text-muted);
  font-size: 1.6rem;
  cursor: pointer;
  padding: 0 4px;
}

.delete-modal-body {
  padding: 18px 20px;
}

.delete-warning {
  padding: 14px;
  background: #fef2f2;
  border: 1px solid #fecaca;
  border-radius: 10px;
  margin-bottom: 16px;
  display: flex;
  flex-direction: column;
  gap: 6px;
}

[data-theme="dark"] .delete-warning {
  background: rgba(239, 68, 68, 0.1);
  border-color: rgba(239, 68, 68, 0.3);
}

.delete-warning strong {
  color: #dc2626;
  font-size: 0.92rem;
}

.delete-warning span {
  color: var(--text-secondary);
  font-size: 0.82rem;
  line-height: 1.5;
}

.delete-list-title {
  font-size: 0.84rem;
  font-weight: 600;
  color: var(--text);
  margin-bottom: 8px;
}

.delete-list {
  margin: 0 0 16px;
  padding-left: 20px;
  color: var(--text-secondary);
  font-size: 0.86rem;
  line-height: 1.6;
}

.delete-export {
  margin-bottom: 16px;
}

.delete-export-btn {
  width: 100%;
  padding: 10px;
  background: var(--blue-50);
  border: 1px solid var(--blue-500);
  color: var(--blue-600);
  border-radius: 8px;
  font-size: 0.86rem;
  font-weight: 600;
  font-family: inherit;
  cursor: pointer;
}

[data-theme="dark"] .delete-export-btn {
  background: rgba(59, 130, 246, 0.1);
  color: var(--blue-400);
}

.delete-confirm-row label {
  display: block;
  font-size: 0.84rem;
  color: var(--text-secondary);
  margin-bottom: 6px;
}

.delete-confirm-row input {
  width: 100%;
  padding: 10px 12px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--bg);
  color: var(--text);
  font-family: inherit;
  font-size: 0.92rem;
}

.delete-modal-footer {
  display: flex;
  gap: 10px;
  padding: 14px 20px 18px;
  border-top: 1px solid var(--border);
}

.delete-cancel-btn,
.delete-confirm-btn {
  flex: 1;
  padding: 11px;
  border-radius: 10px;
  font-size: 0.9rem;
  font-weight: 600;
  font-family: inherit;
  cursor: pointer;
}

.delete-cancel-btn {
  background: var(--bg-secondary);
  border: 1px solid var(--border);
  color: var(--text);
}

.delete-confirm-btn {
  background: #dc2626;
  border: none;
  color: white;
}

.delete-confirm-btn:disabled {
  background: var(--gray-300);
  cursor: not-allowed;
}

[data-theme="dark"] .delete-confirm-btn:disabled {
  background: var(--gray-600);
}

/* =========================================
   LEARN PAGE STYLES
   ========================================= */

/* Checklist */
.checklist-header {
  padding: 14px 18px 12px;
  border-bottom: 1px solid var(--border);
}

/* Celebration line — shown only when all 5 tasks are complete.
   Tap-to-expand: clicking it re-shows the checklist items below
   (and the celebration tucks back away). Default-hidden; the
   .is-complete class on the parent card flips the visibility. */
.checklist-celebration {
  display: none;          /* hidden until card.is-complete */
  width: 100%;
  text-align: left;
  background: linear-gradient(135deg, rgba(34,197,94,0.10), rgba(59,130,246,0.08));
  border: none;
  padding: 16px 18px;
  font: inherit;
  color: var(--text);
  cursor: pointer;
  display: none; /* explicitly hidden — flipped by .is-complete below */
  align-items: center;
  gap: 12px;
  transition: background var(--transition);
}
.checklist-celebration:hover {
  background: linear-gradient(135deg, rgba(34,197,94,0.16), rgba(59,130,246,0.12));
}
.checklist-celebration-icon {
  font-size: 1.4rem;
  line-height: 1;
  flex-shrink: 0;
}
.checklist-celebration-text {
  flex: 1;
  font-size: 0.92rem;
  font-weight: 600;
  color: var(--text);
}
.checklist-celebration-hint {
  font-size: 0.75rem;
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  font-weight: 600;
  flex-shrink: 0;
}

/* When all 5 are complete: collapse the items + progress bar, show
   only the celebration line. The user can tap the celebration to
   expand back (we add .is-expanded then to override the collapse). */
#checklist-card.is-complete .checklist-celebration {
  display: flex;
}
#checklist-card.is-complete:not(.is-expanded) .checklist-header,
#checklist-card.is-complete:not(.is-expanded) .checklist-item {
  display: none;
}

/* "Close & collapse" button — sits at the bottom of the items list
   as a quiet way to re-tuck the section after the user has expanded
   the celebration line to peek at completed items. Visibility is
   driven by the `hidden` HTML attribute (toggled in JS), NOT by CSS
   selectors — relying on the global `[hidden] { display: none !important }`
   rule keeps the button reliably invisible across cache states.
   Per Jimmy 2026-05-12. */
.checklist-collapse-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin: 8px 14px 14px;
  padding: 8px 14px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--bg-card, #fff);
  color: var(--text-muted);
  font-size: 0.82rem;
  font-weight: 500;
  cursor: pointer;
  transition: background var(--transition), color var(--transition), border-color var(--transition);
}
.checklist-collapse-btn:hover {
  background: var(--bg-secondary);
  color: var(--text);
  border-color: var(--text-muted);
}
.checklist-collapse-btn svg { flex-shrink: 0; }

.checklist-progress-text {
  font-size: 0.82rem;
  color: var(--text-secondary);
  margin-bottom: 8px;
}

.checklist-progress-text strong {
  color: var(--blue-600);
}

[data-theme="dark"] .checklist-progress-text strong {
  color: var(--blue-400);
}

.checklist-progress-bar {
  height: 6px;
  background: var(--bg-secondary);
  border-radius: 3px;
  overflow: hidden;
}

.checklist-progress-fill {
  height: 100%;
  background: linear-gradient(90deg, var(--blue-400), var(--blue-600));
  border-radius: 3px;
  transition: width 0.4s;
}

.checklist-item {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 18px;
  border-bottom: 1px solid var(--border);
  cursor: pointer;
  transition: background var(--transition);
}

.checklist-item:last-child {
  border-bottom: none;
}

.checklist-item:hover {
  background: var(--bg-secondary);
}

.checklist-check {
  width: 22px;
  height: 22px;
  border: 2px solid var(--border);
  border-radius: 50%;
  flex-shrink: 0;
  position: relative;
  transition: all var(--transition);
}

.checklist-item.completed .checklist-check {
  background: var(--blue-500);
  border-color: var(--blue-500);
}

.checklist-item.completed .checklist-check::after {
  content: '✓';
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  color: white;
  font-size: 0.8rem;
  font-weight: 700;
}

.checklist-text {
  font-size: 0.9rem;
  color: var(--text);
  transition: color var(--transition);
}

.checklist-item.completed .checklist-text {
  color: var(--text-muted);
  text-decoration: line-through;
}

/* Prompt cards */
.prompt-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
  margin-bottom: 10px;
}

.prompt-card {
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 14px;
  padding: 14px 12px;
  text-align: left;
  cursor: pointer;
  transition: all var(--transition);
  display: flex;
  flex-direction: column;
  gap: 8px;
  font-family: inherit;
  min-height: 100px;
}

.prompt-card:hover {
  border-color: var(--blue-500);
  background: var(--blue-50);
}

[data-theme="dark"] .prompt-card:hover {
  background: rgba(59, 130, 246, 0.08);
}

.prompt-card-tag {
  font-size: 0.65rem;
  font-weight: 700;
  color: var(--blue-600);
  letter-spacing: 0.6px;
}

[data-theme="dark"] .prompt-card-tag {
  color: var(--blue-400);
}

.prompt-card-text {
  font-size: 0.84rem;
  color: var(--text);
  line-height: 1.4;
  font-style: italic;
}

/* Tutorials */
.tutorial-row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 18px;
  border-bottom: 1px solid var(--border);
}

.tutorial-row:last-child {
  border-bottom: none;
}

.tutorial-thumb {
  width: 44px;
  height: 44px;
  border-radius: 8px;
  background: linear-gradient(135deg, var(--blue-400), var(--blue-600));
  color: white;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.9rem;
  flex-shrink: 0;
}

.tutorial-info {
  flex: 1;
}

.tutorial-title {
  font-size: 0.92rem;
  font-weight: 500;
  color: var(--text);
}

.tutorial-meta {
  font-size: 0.76rem;
  color: var(--text-muted);
  margin-top: 2px;
}

/* Tips */
.tip-row {
  display: flex;
  gap: 12px;
  padding: 14px 18px;
  border-bottom: 1px solid var(--border);
}

.tip-row:last-child {
  border-bottom: none;
}

.tip-icon {
  font-size: 1.4rem;
  flex-shrink: 0;
  margin-top: 2px;
}

.tip-content {
  flex: 1;
}

.tip-title {
  font-size: 0.92rem;
  font-weight: 600;
  color: var(--text);
  margin-bottom: 4px;
}

.tip-text {
  font-size: 0.84rem;
  color: var(--text-secondary);
  line-height: 1.5;
}

/* FAQ — each item is its own self-contained card */
.faq-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.faq-item {
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 12px;
  overflow: hidden;
  transition: border-color var(--transition), box-shadow var(--transition);
}

.faq-item[open] {
  border-color: var(--blue-300, #93c5fd);
  box-shadow: 0 1px 6px rgba(0,0,0,0.04);
}

[data-theme="dark"] .faq-item[open] {
  border-color: rgba(96, 165, 250, 0.4);
}

.faq-item summary {
  padding: 14px 16px;
  font-size: 0.92rem;
  font-weight: 500;
  color: var(--text);
  cursor: pointer;
  list-style: none;
  position: relative;
  padding-right: 42px;
  user-select: none;
  -webkit-user-select: none;
}

.faq-item summary::-webkit-details-marker {
  display: none;
}

/* Same expander standard as .collapse-summary (brand/UI-UX-STANDARDS.md):
   chevron-down closed, chevron-up open. */
.faq-item summary::after {
  content: '';
  position: absolute;
  right: 16px;
  top: 50%;
  width: 8px;
  height: 8px;
  border-right: 2px solid var(--text-muted);
  border-bottom: 2px solid var(--text-muted);
  transform: translateY(-70%) rotate(45deg);   /* chevron-down = closed */
  transition: transform 0.15s ease;
  line-height: 1;
}

.faq-item[open] summary::after {
  transform: translateY(-30%) rotate(-135deg); /* chevron-up = open */
}

.faq-item p {
  padding: 4px 16px 14px;
  margin: 0;
  font-size: 0.86rem;
  color: var(--text-secondary);
  line-height: 1.6;
  border-top: 1px solid var(--border);
  padding-top: 14px;
  margin-top: 0;
}

/* Support row */
.support-row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 14px 18px;
  text-decoration: none;
  color: inherit;
  cursor: pointer;
  border-bottom: 1px solid var(--border);
}

.support-row:last-child {
  border-bottom: none;
}

.support-row:hover {
  background: var(--bg-secondary);
}

/* Ask Qbit inline input */
.support-ask-qbit {
  padding: 14px 18px;
  border-bottom: 1px solid var(--border);
}

.support-ask-header {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 12px;
}

.support-ask-form {
  display: flex;
  align-items: center;
  gap: 8px;
}

.support-ask-input {
  flex: 1;
  width: 100%;
  padding: 8px 12px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--bg);
  color: var(--text);
  font-size: 0.88rem;
  font-family: inherit;
  outline: none;
  transition: border-color var(--transition);
  min-width: 0;
}

.support-ask-input:focus {
  border-color: var(--blue-400);
}

.support-ask-input::placeholder {
  color: var(--text-muted);
}

.support-ask-btn {
  width: 36px;
  height: 36px;
  border-radius: 50%;
  background: var(--blue-500);
  color: white;
  border: none;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  flex-shrink: 0;
  transition: background var(--transition), transform var(--transition);
}

.support-ask-btn:hover {
  background: var(--blue-600);
}

.support-ask-btn:active {
  transform: scale(0.92);
}

.support-ask-btn:disabled {
  background: var(--gray-300);
  cursor: not-allowed;
}

[data-theme="dark"] .support-ask-btn:disabled {
  background: var(--gray-600);
}

.support-icon {
  font-size: 1.6rem;
}

.support-info {
  flex: 1;
}

.support-title {
  font-size: 0.94rem;
  font-weight: 500;
  color: var(--text);
}

.support-meta {
  font-size: 0.78rem;
  color: var(--text-muted);
  margin-top: 2px;
}

.support-chevron {
  color: var(--text-muted);
  font-size: 1.3rem;
}

/* Count badge for "Your submitted tickets" row — only visible when the
   user has at least one ticket. Sits inline with the title so it looks
   like a soft indicator rather than a notification dot. */
.support-count-badge {
  display: inline-block;
  background: var(--brand, #6366f1);
  color: white;
  border-radius: 999px;
  padding: 1px 8px;
  font-size: 0.72rem;
  font-weight: 600;
  margin-left: 8px;
  vertical-align: 1px;
  min-width: 18px;
  text-align: center;
}

/* Font preview variants */
.font-preview.font-sans { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; }
.font-preview.font-serif { font-family: Georgia, "Times New Roman", serif; }
.font-preview.font-dyslexic { font-family: "Comic Sans MS", "OpenDyslexic", sans-serif; letter-spacing: 0.04em; }

/* ===== Home Tips & Ideas button =====
   Sits below the Share card. Deep-links to Tips & Tricks on Setup & Support. */
.home-tips-link {
  display: flex;
  align-items: center;
  gap: 14px;
  margin-top: 12px;
  padding: 16px 18px;
  background: linear-gradient(135deg, rgba(59, 130, 246, 0.08), rgba(139, 92, 246, 0.08));
  border: 1px dashed var(--blue-400);
  border-radius: var(--radius-sm);
  color: var(--text);
  text-decoration: none;
  cursor: pointer;
  transition: transform var(--transition), background var(--transition), box-shadow var(--transition);
}

.home-tips-link:hover {
  transform: translateY(-1px);
  background: linear-gradient(135deg, rgba(59, 130, 246, 0.14), rgba(139, 92, 246, 0.14));
  box-shadow: 0 4px 14px rgba(59, 130, 246, 0.18);
}

.home-tips-icon {
  flex-shrink: 0;
  font-size: 1.6rem;
  line-height: 1;
}

.home-tips-text {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 2px;
  line-height: 1.3;
}

.home-tips-text strong {
  font-size: 0.98rem;
  font-weight: 700;
  color: var(--text);
}

.home-tips-sub {
  font-size: 0.82rem;
  color: var(--text-secondary);
}

.home-tips-chev {
  flex-shrink: 0;
  color: var(--blue-500);
  opacity: 0.7;
}

/* ===== Checklist — clickable items with chevron ===== */
.checklist-item {
  cursor: pointer;
  transition: background var(--transition);
  position: relative;
}

.checklist-item:hover:not(.complete) {
  background: rgba(59, 130, 246, 0.06);
}

.checklist-item:focus-visible {
  outline: 2px solid var(--blue-400);
  outline-offset: -2px;
  border-radius: 6px;
}

.checklist-chev {
  margin-left: auto;
  color: var(--text-muted);
  font-size: 1.1rem;
  opacity: 0.5;
  transition: opacity var(--transition), transform var(--transition);
}

.checklist-item:hover:not(.complete) .checklist-chev {
  opacity: 1;
  color: var(--blue-500);
  transform: translateX(2px);
}

.checklist-item.complete .checklist-chev {
  display: none;
}

/* ===== Query History — Delete history button ===== */
.history-footer {
  display: flex;
  justify-content: center;
  /* padding-top was 12px; tightened to 0 (2026-05-13, per Scott) — the
     element directly above (either a .history-row or the .docs-show-more-btn)
     already supplies 14px of its own bottom padding, so the prior
     12+14=26px combined gap read as too airy. Going to 0 leaves a
     clean 14px gap above the Delete button in every state. */
  padding: 0 16px 4px;
}

.history-delete-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 8px 14px;
  background: transparent;
  border: 1px solid var(--border);
  border-radius: 999px;
  color: var(--text-secondary);
  font-size: 0.82rem;
  font-weight: 500;
  font-family: inherit;
  cursor: pointer;
  transition: background var(--transition), color var(--transition), border-color var(--transition);
}

.history-delete-btn:hover {
  background: rgba(239, 68, 68, 0.08);
  color: #b91c1c;
  border-color: rgba(239, 68, 68, 0.3);
}

.history-delete-btn svg {
  flex-shrink: 0;
}

/* ===== Global footer links =====
   Sit just above .page-trademark on every page. Same font color/size as
   the trademark line — visually a single muted footer block, just with
   the legal links above the trademark notice. Visible to OAuth verifiers
   (Google, Apple) so they can crawl Privacy/Terms straight from the
   home page HTML.

   The margin-top: -40px keeps the whole two-line footer block tight to
   the bottom of the page content (this offset used to live on
   .page-trademark; moved here so it doesn't pull the trademark UP over
   the links). */
.page-footer-links {
  text-align: center;
  font-size: 0.7rem;
  color: var(--text-muted);
  letter-spacing: 0.02em;
  /* padding-bottom: 2px (with .page-trademark + .app-version each
     using 2px top + 2px bottom) gives a uniform 4px total gap between
     all three footer lines (Scott UX 2026-05-15). The 16px top
     padding stays — combined with margin-top:-40px it keeps the
     whole footer block tight against page content above. */
  padding: 16px 16px 2px;
  margin-top: -40px;
  position: relative;
  z-index: 1;
}

.page-footer-links a {
  color: var(--text-muted);
  text-decoration: none;
  padding: 2px 4px;
  transition: color var(--transition);
}

.page-footer-links a:hover {
  color: var(--blue-500);
}

.page-footer-links span {
  user-select: none;
  margin: 0 2px;
}

/* ===== My Documents (RAG knowledge base) =====
   Settings → My Documents card. Shows the upload button + a list of
   the user's uploaded docs with status pills. */
/* ===== Shared-query public viewer (/s/<token>, 2026-07-08) =====
   Public page — must read well signed-out, in both themes. The
   question renders as the sharer's "bubble", the answer as clean
   long-form text, and one CTA closes the loop (signup or open). */
/* Shared-query landing — the "cute template" (Jimmy 2026-07-10):
 * gradient hero card with the bobbing Qbit creature + sharer's name,
 * the question as the title, the answer, and a gradient install CTA. */
.shared-header {
  text-align: center;
  background: linear-gradient(135deg, rgba(59, 130, 246, 0.14), rgba(139, 92, 246, 0.14));
  border: 1px solid rgba(59, 130, 246, 0.22);
  border-radius: 20px;
  padding: 22px 18px 18px;
  margin-bottom: 16px;
}
.shared-qbit-img {
  width: 72px; height: 72px; object-fit: contain;
  margin: 0 auto 10px; display: block;
  animation: shared-bob 3s ease-in-out infinite;
}
@keyframes shared-bob {
  0%, 100% { transform: translateY(0); }
  50%      { transform: translateY(-6px); }
}
@media (prefers-reduced-motion: reduce) {
  .shared-qbit-img { animation: none; }
}
.shared-header h1 { margin: 0; font-size: 1.35rem; }
.shared-by {
  color: var(--text);
  font-size: 1.02rem;
  font-weight: 600;
  margin-top: 8px;
}
.shared-status { text-align: center; color: var(--text-secondary); padding: 18px 0; }
.shared-question-card {
  background: rgba(59, 130, 246, 0.10);
  border: 1px solid rgba(59, 130, 246, 0.25);
  border-radius: 16px;
  padding: 15px 17px;
  margin: 4px 0 14px;
}
.shared-question { font-weight: 700; font-size: 1.06rem; margin: 0; white-space: pre-wrap; }
.shared-answer-card {
  background: var(--bg-card, rgba(148, 163, 184, 0.06));
  border: 1px solid var(--border-color, rgba(148, 163, 184, 0.25));
  border-radius: 16px;
  padding: 16px 18px;
}
.shared-answer p { margin: 0 0 12px; line-height: 1.55; }
.shared-answer p:last-child { margin-bottom: 0; }
.shared-cta-row { text-align: center; margin: 22px 0 4px; }
.shared-cta {
  border: none;
  background: linear-gradient(135deg, #3b82f6, #8b5cf6);
  color: #fff;
  font-family: inherit;
  font-size: 1.05rem;
  font-weight: 700;
  padding: 14px 34px;
  border-radius: 999px;
  cursor: pointer;
  box-shadow: 0 4px 14px rgba(59, 130, 246, 0.35);
  -webkit-tap-highlight-color: transparent;
}
.shared-cta:not(:disabled):hover { filter: brightness(1.05); }
.shared-cta:active { transform: translateY(1px); }
.shared-tagline {
  text-align: center;
  color: var(--text-secondary);
  font-size: 0.9rem;
  margin: 10px 0 8px;
}

/* Docs & Photos pill switcher retired (Jimmy 2026-07-11) — both pages
   use the lean stacked header and My Photos rides the slide menu. */

.docs-hint {
  /* Borderless since 2026-07-11 (card wrapper retired) — flush left
     with the page title, no interior card padding. */
  padding: 4px 0 8px 0;
  font-size: 0.88rem;             /* was 0.85rem — matches qbit-code-hint */
  line-height: 1.5;
  color: var(--text-secondary);   /* was --text-muted — readable, not faded */
}
/* Credits cost note relocated to the bottom of My Documents, below the
   trust rows (Scott 2026-07-09). No border-top — the last trust row
   already draws a bottom separator, and stacking a second line read as
   a doubled bar (Jimmy 2026-07-09). Margin alone spaces it. */
.kb-cost-note {
  margin-top: 4px;
}

/* "Upload a photo" on My Photos (Jimmy 2026-07-09) — mirrors the Docs
   page's upload button (same .btn-primary.docs-upload-btn classes); the
   status line narrates uploading → reviewing → done/blocked. */
.qphoto-upload-row {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 6px;
  margin: 0 0 14px 0;
}
.qphoto-upload-status {
  min-height: 1.2em;
  font-size: 0.85rem;
  color: var(--text-secondary);
}
.docs-limits {
  display: block;
  margin-top: 6px;
  font-size: 0.75rem;
  color: var(--text-muted);
  opacity: 0.85;
}
.docs-actions {
  padding: 8px 18px 14px 18px;
}
.docs-upload-btn {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 10px 18px;
  background: linear-gradient(135deg, var(--blue-500), var(--purple-500, #8b5cf6));
  color: #ffffff;
  border: none;
  border-radius: 999px;
  font-size: 0.9rem;
  font-weight: 700;
  cursor: pointer;
  letter-spacing: 0.01em;
  box-shadow: 0 2px 8px rgba(59,130,246,0.25);
  transition: transform 0.15s ease, box-shadow 0.15s ease;
}
.docs-upload-btn:hover {
  transform: translateY(-1px);
  box-shadow: 0 4px 14px rgba(59,130,246,0.35);
}
.docs-upload-btn:active { transform: translateY(0); }

/* The list sits flat below the controls card (2026-07-10). */
.docs-list { margin-top: 8px; }
.docs-list {
  border-top: 1px solid var(--border);
}
.docs-empty {
  padding: 22px 18px;
  text-align: center;
  font-size: 0.88rem;
  color: var(--text-muted);
  line-height: 1.55;
}
.docs-empty.docs-error { color: #dc2626; }
.docs-empty.docs-loading { color: var(--text-muted); }

/* ============================================================
 * MEMORY — Phase 2 review queue + approved-memories list.
 * Lives on the Settings page (two sections). Visual language
 * mirrors My Documents (settings-card + soft padding + tinted
 * row backgrounds) so the page feels cohesive.
 * ============================================================ */

/* ── Pending review queue ─────────────────────────────────── */
.memory-review-hint {
  padding: 14px 18px 4px;
  font-size: 0.92rem;
  color: var(--text);
  line-height: 1.5;
}
.memory-review-hint strong { color: var(--text); }
.memory-review-list {
  padding: 6px 0 6px;
}
.memory-review-row {
  padding: 14px 18px;
  border-top: 1px solid var(--border);
  position: relative;
}
.memory-review-row:first-child { border-top: 1px solid var(--border); }
.memory-review-row-busy { opacity: 0.55; pointer-events: none; }
.memory-review-content {
  font-size: 0.95rem;
  color: var(--text);
  line-height: 1.45;
}
.memory-review-content-date {
  display: flex;
  align-items: flex-start;
  gap: 12px;
}
.memory-review-date-icon { font-size: 1.6rem; line-height: 1; flex-shrink: 0; }
.memory-review-date-headline { font-size: 0.95rem; line-height: 1.4; }
.memory-review-note {
  margin-top: 4px;
  font-size: 0.8rem;
  color: var(--text-muted);
  font-style: italic;
}
.memory-review-meta {
  margin-top: 4px;
  font-size: 0.75rem;
  color: var(--text-muted);
}
.memory-review-actions {
  margin-top: 10px;
  display: flex;
  gap: 8px;
  justify-content: flex-end;
}
.memory-review-reject {
  padding: 6px 14px;
  font-size: 0.85rem;
}
.memory-review-approve {
  padding: 6px 14px;
  font-size: 0.85rem;
  font-weight: 600;
}

/* ============================================================
 * Voice Settings card — controls Broca's spoken playback.
 * Browser SpeechSynthesis under the hood; preferences live in
 * localStorage and survive every change instantly (no Save
 * Changes button — sliders + dropdown are live). See
 * initVoiceSettings() + VoicePrefs in app.js.
 * ============================================================ */
.voice-hint {
  padding: 14px 18px 8px;
  font-size: 0.88rem;
  color: var(--text-secondary);   /* was --text-muted — matches qbit-code-hint */
  line-height: 1.5;
}

/* Each row is a setting-item but with the slider/dropdown spanning
   full width on the right column. The shared .setting-item layout
   already handles label+control alignment; .voice-row is just an
   anchor in case we ever need voice-specific tweaks. */
.voice-row .setting-input {
  font-size: 0.92rem;
}

/* Speed + Pitch sliders — match the rest of the app's controls */
.voice-slider {
  -webkit-appearance: none;
  appearance: none;
  width: 100%;
  max-width: 280px;
  height: 4px;
  background: var(--border);
  border-radius: 999px;
  outline: none;
  cursor: pointer;
}

.voice-slider::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background: var(--blue-500);
  border: 2px solid white;
  box-shadow: 0 1px 4px rgba(0,0,0,0.18);
  cursor: pointer;
  transition: transform var(--transition);
}
.voice-slider::-webkit-slider-thumb:hover {
  transform: scale(1.1);
}

.voice-slider::-moz-range-thumb {
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background: var(--blue-500);
  border: 2px solid white;
  box-shadow: 0 1px 4px rgba(0,0,0,0.18);
  cursor: pointer;
}

.voice-test-btn {
  margin: 8px 18px 18px;
  display: inline-flex;
  align-items: center;
  gap: 8px;
}

.voice-test-icon {
  font-size: 1.05rem;
  line-height: 1;
}

/* ── Approved memories list ─────────────────────────────── */
.memories-hint {
  padding: 14px 18px 4px;
  font-size: 0.88rem;
  color: var(--text-secondary);   /* was --text-muted — matches qbit-code-hint */
  line-height: 1.5;
}
.memories-add-form {
  display: flex;
  gap: 8px;
  padding: 6px 18px 12px;
}
.memories-add-form input[type="text"] {
  flex: 1;
  padding: 8px 12px;
  border: 1px solid var(--border);
  border-radius: 8px;
  font-size: 0.9rem;
  background: var(--bg-card, #fff);
  color: var(--text);
}
.memories-add-form input[type="text"]:focus {
  outline: none;
  border-color: var(--blue-500);
  box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.15);
}
.memories-add-form button {
  flex-shrink: 0;
  padding: 8px 16px;
  font-size: 0.9rem;
}

.memories-list { padding: 0; }
.memories-empty {
  padding: 14px 18px 18px;
  font-size: 0.88rem;
  color: var(--text-muted);
  line-height: 1.5;
}
.memories-empty em { color: var(--text); font-style: italic; }
.memories-row {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  padding: 12px 18px;
  border-top: 1px solid var(--border);
}
.memories-row-content {
  flex: 1;
  font-size: 0.92rem;
  color: var(--text);
  line-height: 1.45;
}
.memories-row-side {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-shrink: 0;
}
.memories-row-source {
  font-size: 0.7rem;
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  font-weight: 600;
}
.memories-row-delete {
  width: 24px;
  height: 24px;
  border: none;
  border-radius: 50%;
  background: transparent;
  color: var(--text-muted);
  font-size: 18px;
  line-height: 1;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
}
.memories-row-delete:hover {
  background: rgba(220, 38, 38, 0.10);
  color: #dc2626;
}

/* ============================================================
 * Reorder controls (↑/↓) — shared across Memories, Important
 * Dates, and Custom Socials. One pair of buttons per list row.
 * Disabled state on the first row's ↑ and last row's ↓ so the
 * user gets visual feedback that there's nowhere to move.
 *
 * Why arrows instead of drag-and-drop:
 *   - Touch drag fights with vertical scroll on mobile
 *   - Arrows work with keyboard out of the box (real <button>s)
 *   - Discoverability is immediate — nothing hidden behind a
 *     long-press or hover-only handle
 * ============================================================ */
.list-reorder-controls {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  flex-shrink: 0;
  /* Subtle pill-style container so the pair reads as a single
     control rather than two stray buttons. */
  background: var(--bg-secondary);
  border-radius: 6px;
  padding: 1px;
}

.list-reorder-btn {
  width: 22px;
  height: 22px;
  border: none;
  border-radius: 5px;
  background: transparent;
  color: var(--text-muted);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  transition: background var(--transition), color var(--transition);
}

.list-reorder-btn:hover:not(:disabled) {
  background: var(--bg);
  color: var(--blue-500);
}

.list-reorder-btn:active:not(:disabled) {
  background: var(--blue-100, rgba(59,130,246,0.12));
  color: var(--blue-600, #2563eb);
}

.list-reorder-btn:disabled {
  opacity: 0.25;
  cursor: not-allowed;
}

.list-reorder-btn svg {
  display: block;
  width: 10px;
  height: 10px;
}

/* ============================================================
 * FOLDERS — chip bar, modal, swatches, move-to popover
 * ============================================================ */
.docs-folder-bar {
  /* Borderless since 2026-07-11: no card around it and no closing line
     under the chips — the rail sits flush left on the page. */
  margin: 0;
  padding: 6px 0 6px 0;
}
.docs-folder-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  align-items: center;
}
.docs-folder-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px 12px;
  border-radius: 20px;
  border: 1px solid var(--border);
  background: var(--bg-card, #fff);
  color: var(--text);
  font-size: 13px;
  font-weight: 500;
  cursor: pointer;
  transition: background 0.12s ease, border-color 0.12s ease, transform 0.06s ease;
  /* Tighter than a normal button — chips read better small */
  line-height: 1.2;
}
.docs-folder-chip:hover {
  background: var(--bg-secondary);
}
.docs-folder-chip:active { transform: scale(0.97); }
.docs-folder-chip-icon {
  display: inline-flex;
  width: 14px;
  height: 14px;
  align-items: center;
  justify-content: center;
}
.docs-folder-chip-icon svg { width: 14px; height: 14px; }
.docs-folder-chip-label { white-space: nowrap; }
.docs-folder-chip-count {
  display: inline-block;
  min-width: 18px;
  text-align: center;
  padding: 0 6px;
  border-radius: 10px;
  background: var(--gray-100);
  color: var(--text-muted);
  font-size: 11px;
  font-weight: 600;
}

/* Selected chip — color-tinted background based on the folder's color */
.docs-folder-chip-selected {
  border-color: transparent;
  color: white;
}
.docs-folder-chip-selected .docs-folder-chip-count {
  background: rgba(255, 255, 255, 0.25);
  color: white;
}

/* Per-color tint when selected. Each color's hex matches the swatch
   palette below so the chip and swatch read as the same family. */
.docs-folder-chip-gray.docs-folder-chip-selected   { background: #6b7280; }
.docs-folder-chip-blue.docs-folder-chip-selected   { background: #3b82f6; }
.docs-folder-chip-green.docs-folder-chip-selected  { background: #16a34a; }
.docs-folder-chip-amber.docs-folder-chip-selected  { background: #f59e0b; }
.docs-folder-chip-red.docs-folder-chip-selected    { background: #dc2626; }
.docs-folder-chip-purple.docs-folder-chip-selected { background: #7c3aed; }
.docs-folder-chip-teal.docs-folder-chip-selected   { background: #0d9488; }
.docs-folder-chip-pink.docs-folder-chip-selected   { background: #db2777; }

/* "+ New folder" chip — dashed border to read as an action, not a folder */
.docs-folder-chip-new {
  border-style: dashed;
  color: var(--text-muted);
}
.docs-folder-chip-new .docs-folder-chip-icon {
  font-size: 16px;
  font-weight: 600;
  line-height: 1;
}

/* ===== Folder editor modal ===== */
.folder-modal {
  position: fixed;
  inset: 0;
  z-index: 1000;
  display: flex;
  align-items: center;
  justify-content: center;
}
.folder-modal-backdrop {
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, 0.5);
}
.folder-modal-panel {
  position: relative;
  background: var(--bg, #fff);
  border-radius: 12px;
  box-shadow: var(--shadow-lg, 0 10px 25px rgba(0, 0, 0, 0.3));
  width: 90%;
  max-width: 460px;
  max-height: 90vh;
  overflow-y: auto;
}
.folder-modal-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 16px 20px;
  border-bottom: 1px solid var(--border);
}
.folder-modal-header h2 { margin: 0; font-size: 1.15rem; font-weight: 600; }
.folder-modal-close {
  background: none;
  border: none;
  font-size: 28px;
  line-height: 1;
  cursor: pointer;
  color: var(--text-muted);
  padding: 0 4px;
}
.folder-modal-close:hover { color: var(--text); }
.folder-modal-form {
  padding: 18px 20px 20px;
  display: flex;
  flex-direction: column;
  gap: 16px;
}
.folder-modal-field { display: flex; flex-direction: column; gap: 6px; }
.folder-modal-label {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-muted);
}
.folder-modal-label-hint {
  text-transform: none;
  letter-spacing: 0;
  font-weight: 400;
  font-size: 11px;
  color: var(--text-muted);
}
.folder-modal-form input[type="text"],
.folder-modal-form textarea,
.folder-modal-form select {
  padding: 10px 12px;
  border: 1px solid var(--border);
  border-radius: 8px;
  font-size: 14px;
  font-family: inherit;
  background: var(--bg-card, #fff);
  color: var(--text);
}
.folder-modal-form input[type="text"]:focus,
.folder-modal-form textarea:focus,
.folder-modal-form select:focus {
  outline: none;
  border-color: var(--blue-500);
  box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.15);
}
.folder-modal-form select { width: 100%; appearance: auto; }

/* Color swatches — circular dots */
.folder-modal-swatches {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}
.folder-modal-swatch {
  width: 28px;
  height: 28px;
  border-radius: 50%;
  border: 2px solid transparent;
  cursor: pointer;
  padding: 0;
  transition: transform 0.08s ease, border-color 0.12s ease;
}
.folder-modal-swatch:hover { transform: scale(1.1); }
.folder-modal-swatch-selected {
  border-color: var(--text);
  box-shadow: 0 0 0 2px var(--bg);
}
.folder-modal-swatch-gray   { background: #6b7280; }
.folder-modal-swatch-blue   { background: #3b82f6; }
.folder-modal-swatch-green  { background: #16a34a; }
.folder-modal-swatch-amber  { background: #f59e0b; }
.folder-modal-swatch-red    { background: #dc2626; }
.folder-modal-swatch-purple { background: #7c3aed; }
.folder-modal-swatch-teal   { background: #0d9488; }
.folder-modal-swatch-pink   { background: #db2777; }

/* Icon picker — grid of square buttons */
.folder-modal-icons {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: 8px;
}
.folder-modal-icon-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 38px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--bg-card, #fff);
  color: var(--text-muted);
  cursor: pointer;
  transition: border-color 0.12s, color 0.12s, background 0.12s;
}
.folder-modal-icon-btn:hover { color: var(--text); background: var(--bg-secondary); }
.folder-modal-icon-btn-selected {
  border-color: var(--blue-500);
  color: var(--blue-500);
  background: rgba(59, 130, 246, 0.08);
}
.folder-modal-icon-btn svg { width: 18px; height: 18px; }

.folder-modal-error {
  padding: 10px 12px;
  border-radius: 8px;
  background: rgba(220, 38, 38, 0.08);
  color: #dc2626;
  font-size: 13px;
}

.folder-modal-actions {
  display: flex;
  gap: 8px;
  justify-content: flex-end;
  padding-top: 6px;
  border-top: 1px solid var(--border);
  margin-top: 4px;
  padding-top: 16px;
}
.folder-modal-delete {
  margin-right: auto;  /* push delete to the LEFT, save/cancel right */
}

/* ===== Move-to-folder popover ===== */
.docs-move-menu {
  background: var(--bg, #fff);
  border: 1px solid var(--border);
  border-radius: 8px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
  min-width: 200px;
  max-width: 240px;
  padding: 6px;
  z-index: 999;
}
.docs-move-menu-header {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-muted);
  padding: 6px 8px;
}
.docs-move-menu-item {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 8px 10px;
  border: none;
  background: transparent;
  color: var(--text);
  font-size: 13px;
  text-align: left;
  border-radius: 6px;
  cursor: pointer;
}
.docs-move-menu-item:hover { background: var(--bg-secondary); }
.docs-move-menu-icon {
  display: inline-flex;
  width: 16px;
  height: 16px;
  color: var(--text-muted);
}
.docs-move-menu-icon svg { width: 16px; height: 16px; }
/* Color the icon by folder color, like the chip selection state */
.docs-move-menu-item-blue   .docs-move-menu-icon { color: #3b82f6; }
.docs-move-menu-item-green  .docs-move-menu-icon { color: #16a34a; }
.docs-move-menu-item-amber  .docs-move-menu-icon { color: #f59e0b; }
.docs-move-menu-item-red    .docs-move-menu-icon { color: #dc2626; }
.docs-move-menu-item-purple .docs-move-menu-icon { color: #7c3aed; }
.docs-move-menu-item-teal   .docs-move-menu-icon { color: #0d9488; }
.docs-move-menu-item-pink   .docs-move-menu-icon { color: #db2777; }
.docs-move-menu-item-new {
  border-top: 1px solid var(--border);
  margin-top: 4px;
  padding-top: 10px;
  color: var(--text-muted);
}
.docs-move-menu-item-new .docs-move-menu-icon { font-size: 14px; font-weight: 600; }
.docs-move-menu-empty {
  padding: 8px 10px;
  font-size: 12px;
  color: var(--text-muted);
  font-style: italic;
}
/* Nested folders in the move-to menu — indent per depth (sub-folders 2026-07-08) */
.docs-move-menu-depth-2 { padding-left: 26px; }
.docs-move-menu-depth-3 { padding-left: 42px; }
.docs-move-menu-depth-4 { padding-left: 58px; }

/* ============================================================
 * Folder view prefix — breadcrumb + sub-folder rows (2026-07-08)
 * Rendered at the top of #docs-list when a real folder is open.
 * ============================================================ */
.docs-breadcrumb {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
  padding: 10px 14px;
  border-bottom: 1px solid var(--border);
  background: var(--bg-subtle, rgba(0, 0, 0, 0.02));
}
.docs-crumb-back {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--bg-card, #fff);
  color: var(--text);
  cursor: pointer;
  flex-shrink: 0;
}
.docs-crumb-back:hover { border-color: var(--blue-500); color: var(--blue-500); }
.docs-crumb {
  border: none;
  background: transparent;
  padding: 2px 4px;
  font-size: 13px;
  font-family: inherit;
  color: var(--blue-500);
  cursor: pointer;
  border-radius: 6px;
  max-width: 38vw;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.docs-crumb:hover { text-decoration: underline; }
.docs-crumb-current {
  color: var(--text);
  font-weight: 600;
  cursor: default;
  padding: 2px 4px;
  font-size: 13px;
  max-width: 38vw;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.docs-crumb-sep { color: var(--text-muted); font-size: 12px; }

.docs-subfolder-row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 18px;
  border-bottom: 1px solid var(--border);
  cursor: pointer;
  user-select: none;
}
.docs-subfolder-row:hover,
.docs-subfolder-row:focus-visible { background: var(--bg-subtle, rgba(0, 0, 0, 0.03)); outline: none; }
.docs-subfolder-icon {
  display: inline-flex;
  width: 22px;
  height: 22px;
  flex-shrink: 0;
}
.docs-subfolder-icon svg { width: 22px; height: 22px; }
/* Icon tint per folder color — same palette as the chips */
.docs-subfolder-gray   .docs-subfolder-icon { color: #6b7280; }
.docs-subfolder-blue   .docs-subfolder-icon { color: #3b82f6; }
.docs-subfolder-green  .docs-subfolder-icon { color: #16a34a; }
.docs-subfolder-amber  .docs-subfolder-icon { color: #d97706; }
.docs-subfolder-red    .docs-subfolder-icon { color: #dc2626; }
.docs-subfolder-purple .docs-subfolder-icon { color: #7c3aed; }
.docs-subfolder-teal   .docs-subfolder-icon { color: #0d9488; }
.docs-subfolder-pink   .docs-subfolder-icon { color: #db2777; }
.docs-subfolder-name {
  flex: 1;
  min-width: 0;
  font-size: 14px;
  font-weight: 600;
  color: var(--text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.docs-subfolder-count {
  font-size: 12px;
  color: var(--text-muted);
  background: var(--bg-subtle, rgba(0, 0, 0, 0.05));
  border-radius: 999px;
  padding: 1px 8px;
  flex-shrink: 0;
}
.docs-crumb-edit {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: none;
  background: transparent;
  color: var(--text-muted);
  padding: 3px;
  margin-left: 2px;
  border-radius: 6px;
  cursor: pointer;
  flex: 0 0 auto;
}
.docs-crumb-edit:hover { color: var(--blue-500); background: var(--bg-subtle, rgba(0, 0, 0, 0.05)); }

.docs-subfolder-edit {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  border: none;
  border-radius: 6px;
  background: transparent;
  color: var(--text-muted);
  cursor: pointer;
  flex-shrink: 0;
}
.docs-subfolder-edit:hover { color: var(--blue-500); background: var(--bg-subtle, rgba(0, 0, 0, 0.05)); }
.docs-subfolder-chevron { color: var(--text-muted); flex-shrink: 0; }

.docs-subfolder-new {
  display: flex;
  align-items: center;
  gap: 8px;
  width: calc(100% - 28px);
  margin: 10px 14px;
  padding: 9px 12px;
  border: 1px dashed var(--border);
  border-radius: 10px;
  background: transparent;
  color: var(--text-muted);
  font-size: 13px;
  font-family: inherit;
  cursor: pointer;
}
.docs-subfolder-new:hover { border-color: var(--blue-500); color: var(--blue-500); }
.docs-subfolder-new-plus { font-size: 15px; font-weight: 700; line-height: 1; }

/* Drag-a-doc filing (2026-07-08) — grab cursor only where a mouse exists
 * so touch scrolling is untouched; dragged row dims; any valid target
 * (chip / sub-folder row / crumb) shows a dashed ring while hovered. */
@media (hover: hover) {
  .docs-row[draggable="true"] { cursor: grab; }
  .docs-row[draggable="true"]:active { cursor: grabbing; }
}
/* Blue hover tint on document rows — mirrors .history-row:hover
   (Jimmy 2026-07-10, card-anatomy standard). Ready rows are
   click-to-open, so they get the pointer. */
.docs-row:hover { background: rgba(59, 130, 246, 0.05); }
.docs-row-ready { cursor: pointer; }
.docs-row-dragging { opacity: 0.45; }
.docs-drop-target {
  outline: 2px dashed var(--blue-500);
  outline-offset: 2px;
  background: rgba(59, 130, 246, 0.08) !important;
  border-radius: 10px;
}
/* In-list reorder insertion line (Jimmy 2026-07-10): shows where the
   dragged doc will land — top or bottom edge of the hovered row. */
.docs-row-insert-before { box-shadow: inset 0 3px 0 0 var(--blue-500, #3b82f6); }
.docs-row-insert-after  { box-shadow: inset 0 -3px 0 0 var(--blue-500, #3b82f6); }

.docs-row {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  padding: 14px 18px;
  border-bottom: 1px solid var(--border);
}
.docs-row:last-child { border-bottom: 2px solid var(--border); }

.docs-row-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 8px; /* matches History's title→actions gap (Jimmy 2026-07-10) */ }
/* Top row of the .docs-row-main column — file name on the left,
   status pill aligned to the right. Scott edit 2026-05-30:
   restructure so the action buttons can move out of the right-hand
   stack and into the bottom row alongside the size/date meta. */
.docs-row-top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}
.docs-row-name {
  font-size: 0.92rem;
  font-weight: 600;
  color: var(--text);
  word-break: break-word;
  flex: 1;
  min-width: 0;
}
/* Bottom row — size + date on the left, action icons on the right,
   same line. Scott edit 2026-05-30: previously the icons stacked
   vertically on .docs-row-side which left awkward dead space. */
.docs-row-bottom {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}
.docs-row-meta {
  font-size: 0.75rem;
  color: var(--text-muted);
  flex: 1;
  min-width: 0;
  display: flex;
  align-items: center;
  gap: 6px;
}
/* Doc-type glyph rides the meta line (Jimmy 2026-07-10) — the left
   icon column is retired so titles align flush left like query cards. */
.docs-row-meta-icon { display: inline-flex; flex-shrink: 0; color: var(--text-muted); }
.docs-row-meta-icon svg { width: 15px; height: 15px; }
.docs-row-error {
  margin-top: 6px;
  padding: 6px 8px;
  background: rgba(220, 38, 38, 0.06);
  border-left: 2px solid #dc2626;
  border-radius: 3px;
  font-size: 0.78rem;
  color: #b91c1c;
  line-height: 1.4;
}
.docs-row-warn {
  margin-top: 6px;
  padding: 6px 8px;
  background: rgba(217, 119, 6, 0.06);
  border-left: 2px solid #d97706;
  border-radius: 3px;
  font-size: 0.78rem;
  color: #b45309;
  line-height: 1.4;
}
/* Progress hint shown on in-flight rows for files >2MB. Honest
   expectation-setting: tells the user the work is genuinely going to
   take a bit + they can navigate away without losing progress. */
.docs-row-hint {
  margin-top: 6px;
  padding: 6px 10px;
  background: linear-gradient(135deg, rgba(59,130,246,0.06), rgba(139,92,246,0.06));
  border-left: 2px solid var(--blue-500, #3b82f6);
  border-radius: 3px;
  font-size: 0.78rem;
  color: var(--text-muted);
  line-height: 1.45;
  font-style: italic;
}
/* Elapsed-time pill next to the in-progress status label. Updates
   naturally every 3s as the polling cycle re-renders. Sits inline
   with the status text so the visual hierarchy stays calm — it's
   a glanceable detail, not a focal point. */
.docs-row-elapsed {
  display: inline-block;
  margin-left: 8px;
  padding: 1px 7px;
  font-family: 'SF Mono', Menlo, Consolas, monospace;
  font-size: 0.68rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  color: var(--text-muted);
  background: rgba(0,0,0,0.04);
  border-radius: 10px;
  vertical-align: middle;
}

/* .docs-row-side was deleted 2026-05-30 (Scott) — the status pill is
   now part of .docs-row-top (in-line with the file name) and the
   action buttons are part of .docs-row-bottom (in-line with the
   meta). Class still exists in case of cached old DOM, but it's
   no longer used by new renders. */
.docs-row-status {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 2px 9px;
  border-radius: 999px;
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.02em;
  background: var(--bg-secondary);
  color: var(--text-muted);
  white-space: nowrap;
}
.docs-row-status-ready    { background: rgba(5, 150, 105, 0.15); color: #047857; }
.docs-row-status-failed   { background: rgba(220, 38, 38, 0.12);  color: #b91c1c; }
.docs-row-status-pending,
.docs-row-status-queued,
.docs-row-status-ingesting { background: rgba(59, 130, 246, 0.12); color: #1d4ed8; }

/* Tiny inline spinner for in-progress states. */
.docs-spinner {
  display: inline-block;
  width: 8px;
  height: 8px;
  border: 1.5px solid currentColor;
  border-top-color: transparent;
  border-radius: 50%;
  animation: docs-spin 0.7s linear infinite;
  vertical-align: middle;
}
@keyframes docs-spin { to { transform: rotate(360deg); } }

.docs-row-actions {
  display: flex;
  gap: 4px;
}
.docs-row-btn {
  width: 24px;
  height: 24px;
  border-radius: 50%;
  border: 1px solid var(--border);
  background: var(--bg-card);
  color: var(--text-muted);
  cursor: pointer;
  font-size: 0.95rem;
  line-height: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background var(--transition), color var(--transition), border-color var(--transition);
}
.docs-row-btn:hover {
  background: var(--bg-secondary);
  color: var(--text);
}
.docs-row-btn-danger:hover {
  background: rgba(220, 38, 38, 0.10);
  color: #b91c1c;
  border-color: rgba(220, 38, 38, 0.30);
}

/* Show More — sits below the last visible doc row when paginated.
   Reveals another 10 rows per click. Subtle/text-link feel rather
   than a heavy CTA: the page already has the gradient "Upload"
   button up top + "Back to Home" at bottom — Show More shouldn't
   compete. Hover bumps brightness so it still feels interactive. */
.docs-show-more-btn {
  display: block;
  width: 100%;
  padding: 14px 18px;
  background: transparent;
  border: none;
  border-top: 1px solid var(--border);
  color: var(--blue-500);
  font-size: 0.92rem;
  font-weight: 600;
  cursor: pointer;
  font-family: inherit;
  text-align: center;
  letter-spacing: 0.01em;
  transition: background var(--transition), color var(--transition);
}
.docs-show-more-btn:hover {
  background: rgba(59, 130, 246, 0.06);
  color: var(--blue-600, #2563eb);
}
.docs-show-more-btn:active {
  background: rgba(59, 130, 246, 0.10);
}
/* The "(X more · Y hidden)" caption — smaller + muted so it reads
   as supplementary info rather than part of the main label. */
.docs-show-more-count {
  font-size: 0.78rem;
  font-weight: 500;
  color: var(--text-muted);
  margin-left: 4px;
}

/* ─── Help & Tickets — customer-side support inbox ──────── */
#page-tickets .page-subtitle {
  color: var(--text-muted);
  font-size: 0.95rem;
  margin: 4px 0 20px 0;
}
.tickets-list-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 12px;
  gap: 12px;
}
.tickets-list-header h2 { margin: 0; font-size: 1.1rem; }
.tickets-list { display: flex; flex-direction: column; gap: 0; }
.tickets-empty {
  padding: 24px 16px;
  text-align: center;
  color: var(--text-muted);
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: 12px;
}
.tickets-empty-sub { font-size: 0.85rem; margin-top: 6px; }
.tickets-error { color: #b91c1c; }
/* Ticket-row layout — restructured 2026-05-19 (Jimmy spotted awkward
   line-wrapping on the AWAITING YOUR REPLY card).
   Original was a horizontal flex row (title-meta-preview on left,
   status badge on right). Long status labels ("AWAITING YOUR REPLY")
   ate enough horizontal width that the meta line wrapped mid-phrase
   — "2" alone at the end of line 1, "messages" on line 2. Cards with
   shorter status labels ("RESOLVED", "CLOSED") didn't wrap, so the
   list looked inconsistent.
   Now: column layout with the status badge sitting at top-right
   above the title. Main content (title, meta, preview) gets full
   card width regardless of status-label length. Every card uses
   the same layout, so they all look consistent. */
.tickets-row {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 14px 16px;
  /* Flat separator rows (Jimmy 2026-07-10) — the Query History list is
     the SEPARATION canon: no per-card borders/radii, just a 1px line
     between rows. */
  border-bottom: 1px solid var(--border);
  cursor: pointer;
  transition: background 120ms;
}
.tickets-row:last-child { border-bottom: 2px solid var(--border); }
.tickets-row:hover {
  background: rgba(59, 130, 246, 0.05);
}
.tickets-row:focus-visible {
  outline: none;
  background: rgba(59, 130, 246, 0.08);
  box-shadow: inset 3px 0 0 var(--blue-500);
}
.tickets-row-main {
  min-width: 0;
}
.tickets-row-subject {
  font-weight: 600;
  font-size: 0.95rem;
  color: var(--text);
}
.tickets-row-meta {
  font-size: 0.78rem;
  color: var(--text-muted);
  margin-top: 3px;
}
.tickets-row-preview {
  font-size: 0.82rem;
  color: var(--text-secondary);
  margin-top: 6px;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
/* Title line — subject on the left, status pill + delete on the right,
   all on ONE row in line with the title (Jimmy 2026-07-10; the pill
   previously floated alone above the title on its own line). */
.tickets-row-titleline {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
}
.tickets-row-titleline .tickets-row-subject { min-width: 0; }
.tickets-row-side {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-shrink: 0;
}
.tickets-row-delete {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px; height: 24px;
  padding: 0;
  border: 1px solid var(--border);
  border-radius: 50%;
  background: var(--bg-card);
  color: var(--text-muted);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.tickets-row-delete svg { width: 15px; height: 15px; }
.tickets-row-delete:hover, .tickets-row-delete:focus-visible {
  color: #dc2626;
  background: rgba(220, 38, 38, 0.08);
  outline: none;
}

/* Status badges — color per status */
.tickets-status {
  display: inline-block;
  font-size: 10.5px; font-weight: 700;
  text-transform: uppercase; letter-spacing: .04em;
  padding: 3px 9px; border-radius: 12px;
}
.tickets-status-open { background: rgba(59,130,246,0.10); color: #2563eb; }
.tickets-status-in_progress { background: rgba(139,92,246,0.10); color: #7c3aed; }
.tickets-status-waiting_on_customer { background: rgba(217,119,6,0.10); color: #b45309; }
.tickets-status-resolved { background: rgba(22,163,74,0.10); color: #16a34a; }
.tickets-status-closed { background: rgba(107,114,128,0.10); color: #6b7280; }

/* Card wrapper for SUBMIT + DETAIL views */
.tickets-card {
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 20px 22px;
}
/* Heading inside the SUBMIT card wraps to two lines on mobile
   ("Send the humans at Qbit a / question, comment or suggestion").
   Default browser line-height (~1.4) leaves an airy gap; tighten to
   1.15 so the two lines read as a single phrase. Bold + ~20px size
   wants more aggressive line-height tightening than body copy would.
   margin-bottom restores breathing room between the heading and the
   first form label (the global `* { margin: 0 }` reset zeroes out
   the browser-default h2 bottom margin). Per Scott 2026-05-12. */
.tickets-card h2 {
  line-height: 1.15;
  margin-bottom: 16px;
}
.tickets-back-btn {
  background: none; border: none;
  color: var(--blue-500, #3b82f6);
  font-size: 13px; font-weight: 500;
  cursor: pointer; padding: 0; margin-bottom: 14px;
}
.tickets-back-btn:hover { text-decoration: underline; }

/* SUBMIT form */
.tickets-form { display: flex; flex-direction: column; gap: 14px; }
.tickets-form-field { display: flex; flex-direction: column; gap: 5px; }
.tickets-form-label {
  font-size: 11px; font-weight: 700;
  text-transform: uppercase; letter-spacing: .04em;
  color: var(--text-muted);
}
.tickets-form-field input,
.tickets-form-field select,
.tickets-form-field textarea {
  width: 100%;
  /* Padding split into individual sides (not shorthand) so the global
     `select` chevron rule's padding-right:36px isn't clobbered.
     Background uses background-COLOR (not shorthand `background:`)
     so the global select chevron's background-image survives —
     `background:` shorthand resets ALL background properties including
     image, which was wiping the chevron entirely on 2026-05-09. */
  padding-top: 10px;
  padding-bottom: 10px;
  padding-left: 12px;
  padding-right: 12px;
  border: 1px solid var(--border); border-radius: 8px;
  background-color: var(--bg-card); color: var(--text);
  font-size: 14px; font-family: inherit;
}
/* Selects need extra right-padding for the custom chevron — overrides
   the 12px above without affecting input/textarea. */
.tickets-form-field select { padding-right: 36px; }
.tickets-form-field textarea { resize: vertical; }
.tickets-form-actions {
  display: flex; justify-content: flex-end; gap: 8px; margin-top: 4px;
}
.tickets-form-hint {
  font-size: 12px; color: var(--text-muted);
  font-style: italic; margin: 6px 0 0 0;
}

/* DETAIL view */
.tickets-detail-header { margin-bottom: 14px; }
/* Detail title line — subject left, status pill right, matching the
   list cards (Jimmy 2026-07-10). */
.tickets-detail-titleline {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin-bottom: 6px;
}
.tickets-detail-titleline .tickets-status { flex-shrink: 0; }
.tickets-detail-subject {
  font-size: 1.15rem; font-weight: 700; color: var(--text);
  min-width: 0;
}
.tickets-detail-meta {
  font-size: 12px; color: var(--text-muted);
  display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
}
.tickets-detail-meta-sep { color: var(--text-muted); opacity: 0.5; }

.tickets-thread {
  display: flex; flex-direction: column; gap: 12px;
  margin: 16px 0;
  max-height: 60vh; overflow-y: auto;
  padding-right: 4px;
}
.tickets-msg {
  padding: 12px 14px;
  border-radius: 10px;
  border: 1px solid var(--border);
}
.tickets-msg-customer {
  background: var(--bg-card);
  margin-left: 24px;
}
.tickets-msg-staff {
  background: linear-gradient(135deg, rgba(59,130,246,0.06), rgba(139,92,246,0.06));
  border-color: rgba(59,130,246,0.20);
  margin-right: 24px;
}
.tickets-msg-meta {
  display: flex; justify-content: space-between;
  font-size: 12px; color: var(--text-muted);
  margin-bottom: 6px;
}
.tickets-msg-meta strong { color: var(--text); font-weight: 600; }
.tickets-msg-time { font-size: 11px; }
.tickets-msg-body { font-size: 14px; line-height: 1.5; color: var(--text); }

.tickets-reply-area { margin-top: 10px; }
.tickets-reply-area textarea {
  width: 100%; padding: 10px 12px;
  border: 1px solid var(--border); border-radius: 8px;
  background: var(--bg-card); color: var(--text);
  font-size: 14px; font-family: inherit; resize: vertical;
}
.tickets-detail-reply-actions {
  display: flex; justify-content: space-between; gap: 8px; margin-top: 8px;
}

/* ─── Branded buttons (.btn / .btn-primary / .btn-secondary) ─────
   Customer-side button system. Mirrors the search-btn / chat-send-btn
   visual language: blue→purple gradient for primary actions, clean
   border-only style for secondary. Used by the Tickets submit form
   (Cancel + Submit ticket) and any future modals/forms.
   The admin dashboard has its own .btn classes — these are scoped
   to the customer app via load-order; both can coexist without conflict
   since each side loads its own stylesheet. */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 10px 22px;
  border-radius: 999px;     /* fully rounded — matches the Qbit pill aesthetic */
  font-size: 14px;
  font-weight: 600;
  font-family: inherit;
  cursor: pointer;
  border: 1px solid transparent;
  transition: transform 120ms ease, box-shadow 180ms ease, background 180ms ease, color 180ms ease;
  white-space: nowrap;
  text-decoration: none;
}
.btn:active { transform: translateY(1px); }
.btn:disabled {
  opacity: 0.55;
  cursor: not-allowed;
  transform: none;
  box-shadow: none;
}

/* Primary — the Qbit brand gradient button. The defining customer-facing
   action. Used wherever the user is committing/submitting something. */
.btn-primary {
  background: linear-gradient(135deg, var(--blue-500, #3b82f6), var(--purple-500, #8b5cf6));
  color: #ffffff;
  box-shadow: 0 2px 8px rgba(59, 130, 246, 0.25);
}
.btn-primary:hover:not(:disabled) {
  transform: translateY(-1px);
  box-shadow: 0 6px 16px rgba(59, 130, 246, 0.35);
}
.btn-primary:focus-visible {
  outline: 3px solid rgba(139, 92, 246, 0.40);
  outline-offset: 2px;
}

/* Secondary — outline style with brand-tinted border. Used for the
   "less important" sibling action (Cancel, Back, etc.). Keeps the
   visual hierarchy clear: primary is the action, secondary is the
   escape hatch. */
.btn-secondary {
  background: var(--bg-card, #ffffff);
  color: var(--text, #1f2937);
  border-color: var(--border, #e5e7eb);
}
.btn-secondary:hover:not(:disabled) {
  background: var(--bg-soft, #f3f4f6);
  border-color: var(--blue-500, #3b82f6);
  color: var(--blue-500, #3b82f6);
}
.btn-secondary:focus-visible {
  outline: 3px solid rgba(59, 130, 246, 0.25);
  outline-offset: 2px;
}

/* ─── Global select chevron ─────────────────────────────────────
   Replace the browser's native dropdown chevron — which renders
   flush against the right edge of the box — with a custom SVG
   that sits with proper breathing room. Applies app-wide so every
   <select> looks consistent.

   Pattern: hide the native arrow via appearance:none, then draw
   our own via background-image. The SVG is a 12px chevron-down
   in a muted gray, positioned 14px from the right edge. We bump
   right-padding to 36px to leave room for it without overlapping
   the option text.

   (2026-05-09 — Jimmy noticed the cramped default chevron on the
   ticket-submit Category dropdown; this is the proper fix that
   tidies every select in the customer app at once.) */
select {
  appearance: none;
  -webkit-appearance: none;
  -moz-appearance: none;
  background-image: url("data:image/svg+xml;charset=UTF-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%236b7280' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 14px center;
  background-size: 12px;
  /* Room for the chevron — text won't overlap. The 14px chevron
     position + 12px chevron width + 10px buffer = 36px total.
     !important here is intentional belt-and-suspenders: any future
     container that uses `padding: ... ...` shorthand would otherwise
     silently clobber this and re-cramp the chevron. We hit this exact
     issue 2026-05-09 with .tickets-form-field — fixing it once at
     the global level prevents the same regression elsewhere. */
  padding-right: 36px !important;
}

/* Browser's IE-style native arrow on Firefox needs an extra reset */
select::-ms-expand { display: none; }

/* ─── Chat message media (images + videos) ──────────────────
   Rendered by formatAnswer() when the model embeds a media URL
   via the standard ![alt](url) markdown syntax. The chat tool's
   generate_image / generate_video both produce this shape. */
.chat-msg-image,
.chat-msg-video {
  display: block;
  max-width: 100%;
  width: auto;
  max-height: 480px;
  margin: 8px 0;
  border-radius: 12px;
  background: var(--bg-soft, #f3f4f6);
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
}
.chat-msg-video {
  /* Videos with the mp4 mimetype + controls render at their native
     aspect ratio inside the max-width/max-height bounds — same shape
     as images so the chat thread doesn't get jarringly tall when a
     vertical 9:16 video shows up. */
  background: #000;     /* matches before-poster letterboxing */
  outline: none;
}
.chat-msg-video:focus-visible {
  outline: 2px solid var(--blue-500, #3b82f6);
  outline-offset: 2px;
}
/* Generated-audio player — full-width bar (native <audio controls>
   shape), not the boxed image/video treatment. (Audio modality 2026-06-23) */
.chat-msg-audio {
  display: block;
  width: 100%;
  max-width: 420px;
  margin: 8px 0;
}

/* On the chat page, give media a slightly tighter top margin so it
   reads as part of the assistant's message, not a separate block. */
.chat-msg-assistant .chat-msg-image,
.chat-msg-assistant .chat-msg-video,
.chat-msg-assistant .chat-msg-audio {
  margin-top: 4px;
}

/* Save / Share action row under a generated image (Scott 2026-07-04).
   The image keeps its normal block treatment; the row sits directly
   beneath it, left-aligned to the image's edge. (Delete arrives with
   the Gallery — Phase 2.) */
.chat-image-block {
  display: block;
  margin: 8px 0;
}
.chat-image-block .chat-msg-image {
  margin: 0;   /* the wrapper owns the vertical margin now */
}
.chat-image-actions {
  display: flex;
  gap: 8px;
  margin-top: 6px;
}
.chat-image-action {
  width: 34px;
  height: 34px;
  border-radius: 9px;
  border: 1px solid var(--border);
  background: var(--bg-card);
  color: var(--text-secondary);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transition: color var(--transition), border-color var(--transition), background var(--transition);
}
.chat-image-action:hover,
.chat-image-action:focus-visible {
  color: var(--text);
  border-color: var(--blue-400);
  background: var(--bg-soft);
  outline: none;
}
.chat-image-action svg { width: 17px; height: 17px; }
/* "Saved to Qbit" confirmed state on the Save-to-Qbit action button. */
.chat-image-action.is-saved { color: #22c55e; border-color: #22c55e; }

/* ── "What Qbit knows" dossier card (Child Settings, M1b 2026-07-06) ── */
.child-settings-dossier {
  border: 1px solid var(--border);
  border-radius: 12px;
  background: var(--surface);
  padding: 14px 16px;
  color: var(--text);
  white-space: pre-wrap;      /* the dossier uses labeled line sections */
  line-height: 1.5;
  font-size: 0.95em;
  opacity: 0.75;              /* muted while showing the empty placeholder */
}
.child-settings-dossier.has-content { opacity: 1; }

/* Per-fact rows (Scott 2026-07-26) — each fact the parent can delete gets its
   own row with a remove control. The container keeps white-space:pre-wrap for
   the empty-state string; rows override it since each line is its own node. */
.child-settings-dossier .dossier-heading,
.child-settings-dossier .dossier-fact { white-space: normal; }
.child-settings-dossier .dossier-heading {
  font-weight: 600;
  margin: 10px 0 4px;
  opacity: 0.8;
}
.child-settings-dossier .dossier-heading:first-child { margin-top: 0; }
.child-settings-dossier .dossier-fact {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  padding: 4px 0;
}
.child-settings-dossier .dossier-fact-text { flex: 1; min-width: 0; }
.child-settings-dossier .dossier-fact.is-removing { opacity: 0.45; }
.dossier-fact-remove {
  flex-shrink: 0;
  width: 26px;
  height: 26px;
  border: 1px solid var(--border);
  border-radius: 50%;
  background: transparent;
  color: var(--text-secondary);
  font-size: 1.05rem;
  line-height: 1;
  cursor: pointer;
  /* Deleting something about your own child shouldn't feel loud, so this
     stays quiet until you reach for it. */
  opacity: 0.55;
  transition: opacity .15s ease, color .15s ease, border-color .15s ease;
}
.dossier-fact-remove:hover:not(:disabled) {
  opacity: 1;
  color: var(--danger, #d7263d);
  border-color: var(--danger, #d7263d);
}
.dossier-fact-remove:disabled { cursor: default; opacity: 0.3; }

/* ── Streaming answer cursor (2026-07-05) — blinking bar at the end of the
   partial text while Qbit is still generating. Removed automatically when
   the final answer render replaces the partial. ── */
.qbit-stream-cursor {
  display: inline-block;
  width: 2px;
  height: 1.05em;
  margin-left: 2px;
  vertical-align: -0.15em;
  background: currentColor;
  opacity: 0.8;
  animation: qbit-cursor-blink 0.9s steps(2) infinite;
}
@keyframes qbit-cursor-blink {
  0%, 49% { opacity: 0.8; }
  50%, 100% { opacity: 0; }
}

/* ── My Photos gallery ("Save to Qbit") — Scott 2026-07-05 ── */
.qphoto-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: 12px;
  padding: 4px 0 8px;
}
.qphoto-tile {
  position: relative;
  margin: 0;
  border: 1px solid var(--border);
  border-radius: 14px;
  overflow: hidden;
  background: var(--surface);
  aspect-ratio: 1 / 1;
}
.qphoto-media { width: 100%; height: 100%; object-fit: cover; display: block; background: var(--surface); }
.qphoto-actions {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  display: flex;
  gap: 6px;
  justify-content: flex-end;
  padding: 8px;
  background: linear-gradient(to top, rgba(0, 0, 0, 0.55), rgba(0, 0, 0, 0));
  opacity: 0;
  transition: opacity 0.15s ease;
}
.qphoto-tile:hover .qphoto-actions,
.qphoto-tile:focus-within .qphoto-actions { opacity: 1; }
@media (hover: none) { .qphoto-actions { opacity: 1; } }  /* touch: always show */
.qphoto-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px; height: 34px;
  border-radius: 9px;
  border: none;
  background: rgba(255, 255, 255, 0.92);
  color: #1e293b;
  cursor: pointer;
}
.qphoto-btn svg { width: 17px; height: 17px; }
.qphoto-btn:hover, .qphoto-btn:focus-visible { background: #fff; }
.qphoto-btn-danger { color: #dc2626; }
.qphoto-empty, .qphoto-status {
  grid-column: 1 / -1;
  text-align: center;
  color: var(--text);
  opacity: 0.75;
  padding: 40px 16px;
}
.qphoto-empty-sub { font-size: 0.9em; opacity: 0.8; margin-top: 6px; }
.qphoto-tile img.qphoto-media { cursor: zoom-in; }
/* Fullscreen photo viewer (tap a saved image to enlarge). */
.qphoto-lightbox {
  position: fixed; inset: 0; z-index: 9999;
  background: rgba(0, 0, 0, 0.9);
  display: flex; align-items: center; justify-content: center;
  padding: 24px;
}
.qphoto-lightbox-img { max-width: 96vw; max-height: 92vh; object-fit: contain; border-radius: 8px; }
.qphoto-lightbox-close {
  position: absolute; top: 14px; right: 18px;
  width: 40px; height: 40px; border-radius: 50%;
  border: none; background: rgba(255, 255, 255, 0.16); color: #fff;
  font-size: 26px; line-height: 1; cursor: pointer;
}
.qphoto-lightbox-close:hover { background: rgba(255, 255, 255, 0.3); }

/* ============================================================
 * Your Qbit Code section (Scott's universal connection primitive)
 * 2026-05-16
 * ============================================================
 * Lives at the top of My Profile. Two-column layout on tablet+:
 * QR code on the left (~220px), share link + buttons on the right.
 * Single column on phone — QR centered, controls below.
 *
 * Padding note: `.settings-card` itself has no padding (the usual
 * `.setting-item` rows bring their own 16/18 padding). Our Phase 0
 * cards put hint text + QR + subheads directly inside, so we scope
 * the inner padding to these specific cards by ID — leaves the
 * rows-pattern cards untouched.
 * ============================================================ */
/* Card padding for the QR block AND the requests-list wrapper.
   Originally ID-scoped to #qbit-code-section (the My Profile QR
   instance) and #connections-section-profile. Generalized 2026-05-19
   to use the .qbit-code-block class so the duplicate QR block on
   the Your Connections page picks it up too (Jimmy spotted the
   QR-too-large + right-column-overflow caused by the new block's
   .settings-card having zero padding). */
.qbit-code-block > .settings-card,
#qbit-code-section > .settings-card,
#connections-section-profile > .settings-card {
  padding: 16px 18px;
}
.qbit-code-hint {
  font-size: 0.88rem;
  color: var(--text-secondary);
  margin-bottom: 14px;
  line-height: 1.5;
}
/* Default (mobile): show the top hint, hide the in-column one. */
.qbit-code-hint-mobile  { display: block; }
.qbit-code-hint-desktop { display: none; }
.qbit-code-wrap {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 18px;
}
@media (min-width: 540px) {
  .qbit-code-wrap {
    flex-direction: row;
    /* stretch so the QR-wrap grows vertically to match the side column.
       The QR itself is square (aspect-ratio: 1/1) but we let its outer
       wrap take the full section height so there's no orphan space below
       the QR when the side column (now with hint) is taller. */
    align-items: stretch;
    gap: 24px;
  }
  /* Swap which hint is visible — desktop hint lives inside the side
     column, above SHARE LINK. */
  .qbit-code-hint-mobile  { display: none; }
  .qbit-code-hint-desktop {
    display: block;
    margin-bottom: 0;        /* let .qbit-code-side's gap:10 handle spacing */
  }
  /* Push the share-link cluster (label + input row + Share via + Regenerate)
     to the bottom of the side column. The hint sits at the top, the cluster
     bottom-aligns with the QR's bottom edge, and the leftover space falls
     naturally between them as breathing room. Single rule does the work of
     both visual gap + bottom alignment because the side column stretches
     to match the QR's height (via parent's align-items: stretch). */
  .qbit-code-url-label {
    margin-top: auto;
  }
}
.qbit-code-qr-wrap {
  flex: 0 0 auto;
  /* On wide screens, this wrap stretches vertically (via parent's
     align-items: stretch) so we can vertically-center the square QR
     inside it when the side column is taller than the QR's intrinsic
     size — keeps the QR optically aligned. */
  display: flex;
  align-items: center;
  justify-content: center;
}
/* Default (loading): themed background that blends into the card, so
   the placeholder doesn't stand out as a stark white island in dark
   mode. Only when the QR actually renders does the background flip
   to white — QR codes need white to be camera-readable (Jimmy's
   feedback 2026-05-16). The `.qbit-code-qr-loaded` class is added
   by renderQbitCodeQR() right after the SVG goes in. */
.qbit-code-qr {
  width: 220px;
  height: 220px;
  padding: 12px;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 14px;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--text-muted);
  transition: background .2s, color .2s;
}
/* On tablet+, grow the QR so it visually matches the now-taller side
   column (which now also holds the hint copy above SHARE LINK). The
   wrap is set to stretch vertically; the QR stays square + centered. */
@media (min-width: 540px) {
  .qbit-code-qr {
    width: 280px;
    height: 280px;
  }
}
.qbit-code-qr.qbit-code-qr-loaded {
  background: #fff;        /* required white field for QR camera readability */
  color: #0a1628;          /* dark QR modules against the white field */
}
.qbit-code-loading {
  color: var(--text-muted);
  font-size: 0.85rem;
}

/* Sample/placeholder QR — shown on first paint of My Profile before the
   real share-link round-trip completes (or indefinitely if the backend
   Connections endpoints aren't deployed yet). Renders a real-looking QR
   in muted gray with a "SAMPLE" watermark across the center so it reads
   instantly as "preview, not active." */
.qbit-code-sample-stack {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 100%;
}
.qbit-code-sample-watermark {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%) rotate(-18deg);
  pointer-events: none;
  user-select: none;
  font-size: 1.55rem;
  font-weight: 800;
  letter-spacing: 0.22em;
  color: #fff;
  background: rgba(59, 130, 246, 0.92);  /* Qbit blue, mostly opaque */
  padding: 6px 18px 6px 22px;            /* extra right-pad to balance letter-spacing */
  border-radius: 8px;
  box-shadow: 0 2px 10px rgba(15, 23, 42, 0.28),
              0 0 0 1px rgba(255, 255, 255, 0.6) inset;
}
.qbit-code-side {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 10px;
  width: 100%;
}
.qbit-code-url-label {
  font-size: 0.78rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--text-muted);
  font-weight: 600;
}
.qbit-code-url-row {
  display: flex;
  gap: 6px;
}
/* Invite by email/phone row (Scott/Jimmy 2026-06-14) — sits under Share via… */
.qbit-code-invite-label {
  margin: 14px 0 6px; font-size: 0.78rem; font-weight: 600; color: var(--text-secondary);
}
.qbit-code-invite-row { display: flex; gap: 6px; }
.qbit-code-invite-target {
  flex: 1; min-width: 0; padding: 8px 12px; border: 1px solid var(--border);
  border-radius: 8px; background: var(--bg); color: var(--text);
  font-family: inherit; font-size: 0.82rem; outline: none;
}
.qbit-code-invite-btn {
  flex-shrink: 0; padding: 8px 16px; border: none; border-radius: 8px; cursor: pointer;
  background: var(--blue-500); color: #fff; font-family: inherit; font-size: 0.82rem; font-weight: 600;
}
.qbit-code-invite-btn:disabled { opacity: 0.6; cursor: default; }
.qbit-code-invite-status { margin-top: 8px; font-size: 0.8rem; color: var(--blue-600, #2563eb); min-height: 1em; }
.qbit-code-url {
  flex: 1;
  min-width: 0;
  padding: 8px 12px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--bg);
  color: var(--text);
  font-family: 'SF Mono', Menlo, Consolas, monospace;
  font-size: 0.78rem;
  outline: none;
}
.qbit-code-copy-btn,
.qbit-code-share-btn,
.qbit-code-regenerate-btn {
  padding: 8px 14px;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: var(--bg-card);
  color: var(--text);
  font-size: 0.85rem;
  font-family: inherit;
  font-weight: 500;
  cursor: pointer;
  transition: background .15s, color .15s, border-color .15s;
}
.qbit-code-copy-btn:hover {
  color: var(--blue-500);
  border-color: var(--blue-400);
  background: rgba(59, 130, 246, 0.06);
}
/* "Share via…" + "Scan a code to connect" use the Qbit brand button look —
   same blue→purple gradient as the "Back to My Profile" button (Jimmy
   2026-06-10). "Generate new link" stays a subtle dashed link (it's a
   caution action) and "Copy" stays a small inline control. */
.qbit-code-share-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  width: 100%;
  padding: 12px;
  background: linear-gradient(135deg, var(--blue-500), var(--purple-500, #8b5cf6));
  border: none;
  color: #ffffff;
  border-radius: 10px;
  font-size: 0.92rem;
  font-weight: 700;
  letter-spacing: 0.01em;
  box-shadow: 0 2px 8px rgba(59, 130, 246, 0.20);
  transition: transform 0.15s ease, box-shadow 0.15s ease;
}
.qbit-code-share-btn:hover {
  transform: translateY(-1px);
  box-shadow: 0 4px 14px rgba(59, 130, 246, 0.30);
}
.qbit-code-share-btn:active { transform: translateY(1px); }
.qbit-code-regenerate-btn {
  color: var(--text-muted);
  font-size: 0.78rem;
  background: transparent;
  border-style: dashed;
}
.qbit-code-regenerate-btn:hover {
  color: #dc2626;
  border-color: #dc2626;
  background: rgba(220, 38, 38, 0.05);
}
.qbit-code-regenerate-btn:disabled {
  opacity: 0.6;
  cursor: wait;
}

/* ── Scan-to-connect (in-app QR scanner) ─────────────────────────
   The blue "Scan a code to connect" button + the fullscreen camera
   modal it opens. Existing users scan in-app and connect instantly
   instead of being bounced to sign-up by their phone's native camera
   (Scott 2026-06-10). */
.qbit-scan-open-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  width: 100%;
  padding: 12px;
  border: none;
  border-radius: 10px;
  background: linear-gradient(135deg, var(--blue-500), var(--purple-500, #8b5cf6));
  color: #ffffff;
  font-family: inherit;
  font-size: 0.92rem;
  font-weight: 700;
  letter-spacing: 0.01em;
  cursor: pointer;
  box-shadow: 0 2px 8px rgba(59, 130, 246, 0.20);
  transition: transform 0.15s ease, box-shadow 0.15s ease;
}
.qbit-scan-open-btn:hover {
  transform: translateY(-1px);
  box-shadow: 0 4px 14px rgba(59, 130, 246, 0.30);
}
.qbit-scan-open-btn:active { transform: translateY(1px); }
/* Scan block now sits at the BOTTOM of Connect with People (under the QR +
   share/generate controls). A hairline above the hint separates "scan THEIR
   code" from the "share MY code" controls above it. (Jimmy 2026-06-10) */
.qbit-scan-open-hint {
  margin: 16px 2px 10px;
  padding-top: 16px;
  border-top: 1px solid var(--border);
  font-size: 0.78rem;
  color: var(--text-muted);
  line-height: 1.4;
}
/* Fullscreen scanner overlay */
.qbit-scan-modal {
  position: fixed;
  inset: 0;
  z-index: 100000;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(0, 0, 0, 0.92);
  padding: 20px;
}
.qbit-scan-modal[hidden] { display: none; }
.qbit-scan-sheet {
  position: relative;
  width: 100%;
  max-width: 420px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
  color: #fff;
  text-align: center;
}
.qbit-scan-title { font-size: 1.1rem; font-weight: 700; margin: 0; }
.qbit-scan-close {
  position: absolute;
  top: -6px;
  right: -6px;
  width: 38px;
  height: 38px;
  border-radius: 50%;
  border: none;
  background: rgba(255, 255, 255, 0.15);
  color: #fff;
  font-size: 1.5rem;
  line-height: 1;
  cursor: pointer;
}
.qbit-scan-close:hover { background: rgba(255, 255, 255, 0.28); }
.qbit-scan-video-wrap {
  position: relative;
  width: 100%;
  aspect-ratio: 1 / 1;
  border-radius: 16px;
  overflow: hidden;
  background: #000;
}
.qbit-scan-video-wrap video {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
.qbit-scan-reticle {
  position: absolute;
  inset: 14%;
  border: 3px solid rgba(255, 255, 255, 0.85);
  border-radius: 14px;
  box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.25);
  pointer-events: none;
}
.qbit-scan-status {
  font-size: 0.9rem;
  color: rgba(255, 255, 255, 0.9);
  min-height: 1.2em;
  margin: 0;
  line-height: 1.4;
}
.qbit-scan-status.is-error { color: #fca5a5; }
.qbit-scan-status.is-success { color: #86efac; font-weight: 600; }

/* (The v377 .qbit-code-quick-share-row / -btn pair was removed in
   v378 when QbitShare unified all share entry points into a single
   modal — Email + Messages now live inside the modal itself.) */

/* ============================================================
 * Connections section (pending requests + established list)
 * ============================================================ */
/* Section subhead inside a card. Standardized 2026-05-16 to match
   the .settings-advanced-title style used by the navigation link cards
   ("My Documents", "Metrics & Advanced Settings") so every in-card
   section title looks the same — same color, weight, and size —
   regardless of whether the card is content (Connections) or a link
   (My Documents). Jimmy 2026-05-16. */
.connections-subhead {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 4px 0 8px;
  font-size: 0.96rem;            /* matches .settings-advanced-title */
  font-weight: 600;              /* matches .settings-advanced-title */
  color: var(--text);            /* matches .settings-advanced-title — bold dark */
  /* Removed: text-transform uppercase + letter-spacing — those were the
     "small label" cues that made this look like a tag instead of a title. */
}
.connections-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 22px;
  height: 18px;
  padding: 0 6px;
  border-radius: 9px;
  background: var(--blue-500);
  color: #fff;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0;
  text-transform: none;
}
.connections-requests-wrap {
  margin-bottom: 16px;
  padding-bottom: 12px;
  border-bottom: 1px dashed var(--border);
}
.connection-request-row,
.connection-row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 12px;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--bg);
  margin-bottom: 8px;
  flex-wrap: wrap;
}
.connection-request-info,
.connection-info {
  flex: 1;
  min-width: 200px;
}
.connection-request-name,
.connection-name {
  font-weight: 600;
  color: var(--text);
  font-size: 0.92rem;
}
.connection-request-meta,
.connection-meta {
  font-size: 0.78rem;
  color: var(--text-muted);
  margin-top: 2px;
}

/* Per-connection notes — Bundle 2 / C1 (2026-05-27 Jimmy).
   Private free-text the viewer can attach to each connection.
   Collapsed by default via <details>; auto-expanded when a note
   already exists. The status span shows "Saving…" / "Saved" /
   "Couldn't save" after debounced auto-save fires. */
.connection-notes {
  margin-top: 8px;
}
.connection-notes-summary {
  display: flex;
  align-items: center;
  gap: 8px;
  cursor: pointer;
  font-size: 0.78rem;
  color: var(--text-secondary);
  user-select: none;
  padding: 4px 0;
  list-style: none; /* hide native disclosure triangle on some browsers */
}
.connection-notes-summary::-webkit-details-marker { display: none; }
/* Disclosure marker: "+" when collapsed, "−" when open (Jimmy
   2026-05-27). Plus reads as "add a note" which is the obvious
   action; minus reads as "close this open panel." Kept in a fixed-
   width inline-block so the label doesn't shift when it swaps. */
.connection-notes-summary::before {
  content: '+';
  display: inline-block;
  width: 0.9em;
  text-align: center;
  font-size: 1rem;
  line-height: 1;
  font-weight: 600;
  color: var(--text-muted);
}
.connection-notes[open] > .connection-notes-summary::before {
  content: '−'; /* U+2212 MINUS SIGN — not a hyphen */
}
.connection-notes-summary-label {
  font-weight: 600;
}
.connection-notes-summary-status {
  margin-left: auto;
  font-size: 0.72rem;
  font-weight: 500;
  color: var(--text-muted);
  transition: opacity 0.2s ease;
}
.connection-notes-summary-status.notes-status-saving { color: var(--text-muted); }
.connection-notes-summary-status.notes-status-saved  { color: #16a34a; } /* green */
.connection-notes-summary-status.notes-status-error  { color: #dc2626; } /* red */
.connection-notes-input {
  display: block;
  width: 100%;
  margin-top: 6px;
  padding: 8px 10px;
  font-size: 0.86rem;
  font-family: inherit;
  line-height: 1.45;
  color: var(--text);
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 8px;
  resize: vertical;
  min-height: 64px;
  outline: none;
  transition: border-color 0.12s ease;
}
.connection-notes-input:focus {
  border-color: var(--blue-500);
}
.connection-notes-hint {
  margin-top: 4px;
  font-size: 0.7rem;
  color: var(--text-muted);
  font-style: italic;
}
.connection-request-msg {
  font-size: 0.82rem;
  color: var(--text-secondary);
  font-style: italic;
  margin-top: 6px;
  padding-left: 8px;
  border-left: 2px solid var(--border);
}
.connection-request-actions {
  display: flex;
  gap: 6px;
  flex-shrink: 0;
}
.connection-accept-btn,
.connection-decline-btn,
.connection-remove-btn {
  padding: 6px 12px;
  font-size: 0.82rem;
  font-weight: 600;
  border-radius: 8px;
  cursor: pointer;
  font-family: inherit;
  border: 1px solid var(--border);
}
.connection-accept-btn {
  background: var(--blue-500);
  border-color: var(--blue-500);
  color: #fff;
}
.connection-accept-btn:hover { background: var(--blue-600); border-color: var(--blue-600); }
.connection-decline-btn,
.connection-remove-btn {
  background: var(--bg-card);
  color: var(--text-muted);
}
.connection-decline-btn:hover,
.connection-remove-btn:hover {
  color: #dc2626;
  border-color: #dc2626;
  background: rgba(220, 38, 38, 0.05);
}
/* Empty-state descriptor — standardized 2026-05-16 to match
   .settings-advanced-sub so the descriptor under "Your connections"
   looks identical to the one under "My Documents" / "Metrics & Advanced
   Settings". Same size, same gray, no italic. */
.connections-empty {
  padding: 4px 0 8px;            /* tightened — no longer centered, lives directly under subhead */
  color: var(--text-secondary);  /* matches .settings-advanced-sub */
  font-size: 0.82rem;            /* matches .settings-advanced-sub (0.78–0.85rem range) */
  line-height: 1.4;
  /* Removed: text-align center + italic — those were the empty-state-marker
     cues that made this look different from a normal descriptor. */
}

/* ================================================================
   Family VPC modal — Parent verification UI (Phase 2, 2026-05-23)
   ================================================================
   Pops over the Direct Notice modal after parent clicks "I consent".
   Borrows the device-pairing modal pattern (fixed-position scrim,
   centered card, no overlay-click-to-close so a half-completed
   verification step doesn't get dismissed by an accidental tap).

   The method picker / provider-slot / loading sub-states below are
   legacy: the verification provider was removed, so the live modal
   only renders the "verification temporarily unavailable" notice
   (reusing the error group). The other groups are kept as empty
   hidden slots for JS null-safety.
   ================================================================ */
.vpc-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.55);
  display: none;
  align-items: center;
  justify-content: center;
  z-index: 10000;
  padding: 16px;
  opacity: 0;
  transition: opacity 0.18s ease;
}
.vpc-overlay.open {
  display: flex;
  opacity: 1;
}
.vpc-modal {
  background: var(--bg, #fff);
  border-radius: 18px;
  width: 100%;
  max-width: 560px;       /* room for the ID-verification WebSDK iframe (2026-07-08) */
  max-height: 92vh;
  overflow-y: auto;
  position: relative;
  padding: 28px 22px 22px;
  box-shadow: 0 24px 72px rgba(0, 0, 0, 0.35);
  transform: translateY(8px);
  transition: transform 0.18s ease;
}
.vpc-overlay.open .vpc-modal {
  transform: translateY(0);
}
.vpc-close {
  position: absolute;
  top: 10px;
  right: 12px;
  background: transparent;
  border: 0;
  font-size: 1.6rem;
  line-height: 1;
  cursor: pointer;
  color: var(--text-muted, #8a8a8a);
  padding: 4px 10px;
  border-radius: 8px;
}
.vpc-close:hover {
  background: var(--bg-secondary, #f4f4f6);
  color: var(--text, #111);
}
.vpc-header {
  text-align: center;
  margin-bottom: 18px;
}
.vpc-title {
  font-size: 1.18rem;
  font-weight: 700;
  margin: 0 0 8px;
  color: var(--text, #111);
}
.vpc-subtitle {
  font-size: 0.92rem;
  line-height: 1.45;
  color: var(--text-secondary, #5a5a66);
  margin: 0;
}
.vpc-body {
  min-height: 240px;
}

/* --- Method picker: two big stacked buttons --- */
.vpc-picker {
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 6px 0;
}
.vpc-method {
  display: grid;
  grid-template-columns: 40px 1fr;
  grid-template-rows: auto auto;
  grid-template-areas:
    "icon title"
    "icon meta";
  gap: 4px 14px;
  align-items: center;
  text-align: left;
  padding: 16px 18px;
  background: var(--bg, #fff);
  border: 1.5px solid var(--border, #e6e6ea);
  border-radius: 14px;
  cursor: pointer;
  transition: border-color 0.15s ease, transform 0.12s ease, box-shadow 0.15s ease;
  font-family: inherit;
}
.vpc-method:hover {
  border-color: var(--blue-500, #4c6ef5);
  transform: translateY(-1px);
  box-shadow: 0 4px 14px rgba(76, 110, 245, 0.12);
}
.vpc-method:disabled {
  opacity: 0.55;
  cursor: not-allowed;
  transform: none;
  box-shadow: none;
}
.vpc-method-icon {
  grid-area: icon;
  font-size: 1.8rem;
  align-self: center;
  justify-self: center;
}
.vpc-method-title {
  grid-area: title;
  font-size: 1rem;
  font-weight: 700;
  color: var(--text, #111);
}
.vpc-method-meta {
  grid-area: meta;
  font-size: 0.82rem;
  line-height: 1.35;
  color: var(--text-secondary, #5a5a66);
}
/* Subtle accent on the recommended (CC) option — kid gets bonus credits */
.vpc-method-cc {
  background: linear-gradient(135deg, rgba(76, 110, 245, 0.04), rgba(168, 85, 247, 0.04));
}

/* --- Loading + spinner reused from device-pairing --- */
/* WebSDK mount (live ID check, 2026-07-08) — give the partner iframe a
   sensible floor; adaptIframeHeight grows it as verification steps
   expand. The iframe always spans the modal's inner width. */
.vpc-provider-slot:not([hidden]) { min-height: 440px; }
.vpc-provider-slot iframe { width: 100% !important; border: 0; display: block; }

.vpc-loading {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 14px;
  padding: 48px 16px;
  color: var(--text-secondary, #5a5a66);
}
.vpc-spinner {
  width: 28px;
  height: 28px;
  border: 3px solid var(--border, #e6e6ea);
  border-top-color: var(--blue-500, #4c6ef5);
  border-radius: 50%;
  animation: vpc-spin 0.85s linear infinite;
}
@keyframes vpc-spin {
  to { transform: rotate(360deg); }
}

/* --- Verification provider slot (legacy, kept hidden) --- */
.vpc-provider-slot {
  width: 100%;
  min-height: 320px;
  padding: 4px 0;
}

/* --- Error state --- */
.vpc-error {
  text-align: center;
  padding: 28px 14px;
}
.vpc-error-msg {
  font-size: 0.95rem;
  line-height: 1.45;
  color: var(--text, #111);
  margin: 0 0 16px;
}
.vpc-error-retry {
  background: var(--blue-500, #4c6ef5);
  color: #fff;
  border: 0;
  padding: 10px 22px;
  border-radius: 999px;
  font-weight: 600;
  font-size: 0.95rem;
  cursor: pointer;
  font-family: inherit;
}
.vpc-error-retry:hover {
  background: var(--blue-600, #3b5bdb);
}

/* --- Bottom action row (just a Cancel button) --- */
.vpc-actions {
  margin-top: 18px;
  display: flex;
  justify-content: flex-end;
}
.vpc-cancel {
  background: transparent;
  border: 0;
  padding: 8px 16px;
  font-size: 0.92rem;
  color: var(--text-secondary, #5a5a66);
  cursor: pointer;
  border-radius: 8px;
  font-family: inherit;
}
.vpc-cancel:hover {
  background: var(--bg-secondary, #f4f4f6);
  color: var(--text, #111);
}

/* ================================================================
   Kid Mode — hide parent-only UI when a child account is signed in
   ================================================================
   Phase K (2026-05-23). When body.kid-mode is set (by updateGreeting()
   reading Auth.isChildAccount()), every element tagged .kid-hide is
   suppressed. !important so it overrides inline display:flex from
   other state toggles (the family-children-section uses display:flex
   when family-config is shown, for example).

   The body class is the single switch — child sign-in via either QR
   scan or 6-digit code lands the kid in this mode automatically, and
   sign-out flips it back off. No per-element JS to maintain.
   ================================================================ */
body.kid-mode .kid-hide {
  display: none !important;
}

/* kid-show — the inverse of kid-hide. Element is hidden by default
   and revealed only in kid-mode. Used for kid-specific cards (e.g.
   the credits card at the top of Settings — Bundle 1, 2026-05-26)
   that would be redundant or confusing for adult users. */
.kid-show {
  display: none !important;
}
body.kid-mode .kid-show {
  display: block !important;
}

/* ================================================================
   Connection badges — Phase AC (2026-05-23)
   ================================================================
   Small pill badge next to a connection's name. Currently only used
   for family connections (auto-created by parent verification), but
   the pattern is generic — future badges (e.g. "Trusted", "Co-parent",
   "Verified business") can reuse .connection-badge with a different
   color modifier class.

   .connection-name-row exists so the name + badge sit on one line
   without collapsing — original .connection-name was a bare block,
   so we wrap both in a flex row.
   ================================================================ */
.connection-name-row {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}
.connection-badge {
  display: inline-block;
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  padding: 2px 8px;
  border-radius: 999px;
  line-height: 1.5;
  vertical-align: middle;
}
/* Family — warm coral (distinct from blue/purple brand primary so the
   "this is family, not a regular friend" signal lands at a glance). */
.connection-badge-family {
  background: rgba(244, 114, 182, 0.14);
  color: rgb(190, 24, 93);
}
[data-theme="dark"] .connection-badge-family {
  background: rgba(244, 114, 182, 0.2);
  color: rgb(249, 168, 212);
}
/* Friend — calm blue (matches the brand primary, sets "default
   connection" expectations). When a card has no specific type, we
   render this as the fallback. (Jimmy 2026-05-27 — house style
   moving forward: every connection gets a typed pill on the right.) */
.connection-badge-friend {
  background: rgba(59, 130, 246, 0.12);
  color: rgb(29, 78, 216);
}
[data-theme="dark"] .connection-badge-friend {
  background: rgba(59, 130, 246, 0.18);
  color: rgb(147, 197, 253);
}
/* Group — soft violet, reserved for group chat connections. The
   actual group name comes through as the pill text in render(). */
.connection-badge-group {
  background: rgba(139, 92, 246, 0.14);
  color: rgb(109, 40, 217);
}
[data-theme="dark"] .connection-badge-group {
  background: rgba(139, 92, 246, 0.2);
  color: rgb(196, 181, 253);
}
/* Parent / child — quiet gray; these connections are managed via
   the Parent-Child Accounts surface, so the card pill is purely
   informational and shouldn't compete with Family for attention. */
.connection-badge-parent,
.connection-badge-child {
  background: rgba(100, 116, 139, 0.14);
  color: rgb(51, 65, 85);
}
[data-theme="dark"] .connection-badge-parent,
[data-theme="dark"] .connection-badge-child {
  background: rgba(148, 163, 184, 0.18);
  color: rgb(203, 213, 225);
}

/* Connection avatar — Scott edit 2026-05-30. Every connection row
   (Your Connections list AND Child Settings → Connections) now leads
   with a 40px circle showing either the connection's profile picture
   OR their initial, matching the Family Insights child-card avatar.
   Sized + colored to be visually consistent with .family-child-avatar
   so the design language stays unified across both surfaces. */
.connection-avatar {
  width: 40px;
  height: 40px;
  flex-shrink: 0;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--blue-500);
  color: #fff;
  font-weight: 700;
  font-size: 1rem;
  overflow: hidden;
  object-fit: cover;
}
img.connection-avatar {
  /* When the avatar is an <img>, drop the background/text styling */
  background: transparent;
  color: inherit;
}

/* Whole-connection-card click affordance (Jimmy 2026-05-27).
   Cards are now keyboard-focusable buttons that navigate to the
   per-connection detail page. Pill + chevron right-align via
   margin-left:auto on the pill; the chevron sits to the right of
   it. Hover/focus state mirrors the family-child-card pattern. */
.connection-row[data-open-connection] {
  cursor: pointer;
  transition: background 0.12s ease, border-color 0.12s ease;
}
.connection-row[data-open-connection]:hover,
.connection-row[data-open-connection]:focus-visible {
  background: var(--bg-subtle, #f5f6f8);
  border-color: var(--border-strong, #d8d8df);
  outline: none;
}
/* Pill right-alignment applies to every .connection-row — interactive
   (Your Connections list) AND read-only (Child Settings → Connections).
   The clickable affordances above stay scoped to [data-open-connection]
   so non-clickable rows don't get hover-cursor cues. (Jimmy 2026-05-27) */
.connection-row .connection-badge {
  margin-left: auto;
}
/* Keep the pill + chevron on the SAME line as the name on narrow screens
   (Jimmy 2026-06-07). The shared 200px min-width + flex-wrap was bumping the
   chevron onto its own line; let .connection-info shrink and don't wrap. */
.connection-row {
  flex-wrap: nowrap;
}
.connection-row .connection-info {
  min-width: 0;
}
.connection-row .connection-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* Messages page — "Start a chat" connections strip (Jimmy 2026-06-07). Rows
   reuse .messaging-thread-row styling; this is just the section chrome. */
/* (Removed 2026-06-16: .messaging-connections / -title styled the old separate
   "Start a chat" section, now merged into the single #messaging-thread-list.) */
/* Header unread-messages pill (DM notifications, 2026-06-07). On-brand bright
   green via --qbit-notify (single-line tweak), dark ink for readable count
   (a11y). Home-screen app-icon badge color is OS-controlled separately. */
/* Header notification chips (Jimmy/Scott 2026-06-08). BOTH are plain ROUNDED
   PILLS — never chat bubbles — sized to the profile avatar (36px tall). Each is
   [icon + count]: the MESSAGE pill is brand lime green (--qbit-notify) carrying
   the Messages glyph; the CHILD-ALERT pill is amber (--qbit-alert) carrying the
   parent/child glyph. Identical shape + size; only color + icon differ. The
   icon sits INSIDE the pill (it is not the pill). */
.header-msg-pill,
.header-alert-pill {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 4px;
  height: 36px;            /* match .header-avatar height */
  min-width: 36px;
  padding: 0 11px;
  margin-right: 4px;
  border: 0;
  border-radius: 999px;    /* full rounded pill — NOT a bubble */
  font-weight: 800;
  font-size: 0.85rem;
  line-height: 1;
  cursor: pointer;
  font-variant-numeric: tabular-nums;
  transition: transform var(--transition), box-shadow var(--transition);
}
.header-msg-pill:active,
.header-alert-pill:active { transform: scale(0.96); }
.header-msg-pill {
  /* Qbit blue to match the standard .count-badge pill (Jimmy 2026-06-18) — was
     lime green. DM-unread now reads as one consistent blue across the header
     pill + bottom-tab badge; the orange child-safety chip below stays distinct
     (a different alert type, not a DM alert). */
  background: var(--blue-500);
  color: #fff;
  box-shadow: 0 1px 4px rgba(59, 130, 246, 0.5);
}
.header-alert-pill {
  background: var(--qbit-alert, #f59e0b);
  color: var(--qbit-alert-ink, #1f1300);
  box-shadow: 0 1px 4px rgba(245, 158, 11, 0.5);
}
/* Icons sit inside the pill. 16px reads cleanly at 36px tall. The message glyph
   is a filled path; the parent/child glyph is a stroked path that inherits the
   pill's ink via currentColor. */
/* Both glyphs are now OUTLINE (stroked via currentColor → the pill's ink),
   matching Scott's Messages icon screenshot — not filled. */
.header-msg-pill-bubble {
  width: 16px; height: 16px; display: block;
  fill: none;
}
.header-alert-pill svg { width: 16px; height: 16px; display: block; }
/* Counts: identical size + weight on BOTH chips (Scott: the alert # looked
   lighter than the message #). Explicit so neither can drift from inherit. */
.header-msg-pill-count,
.header-alert-pill-count {
  font-weight: 800;
  font-size: 0.85rem;
  font-variant-numeric: tabular-nums;
  line-height: 1;
}

/* "Drag an image here" cue on the chat/search composers (2026-06-08).
   Added when a file is dragged over the message box; cleared on drop/leave. */
.qbit-drop-hover {
  outline: 2px dashed var(--qbit-notify, #a3e635);
  outline-offset: 3px;
  border-radius: 12px;
}

/* ===== Parenting Alerts page (Scott 2026-06-08) ===== */
#page-parenting-alerts { padding-bottom: 90px; }
.pa-topbar {
  display: flex; align-items: center; gap: 10px;
  padding: 14px 16px; position: sticky; top: 0; z-index: 5;
  background: var(--bg); border-bottom: 1px solid var(--border);
}
.pa-back-btn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 36px; height: 36px; border: none; border-radius: 50%;
  background: var(--bg-soft, #f1f5f9); color: var(--text); cursor: pointer; flex: none;
}
.pa-back-btn:hover { background: var(--border); }
.pa-title { font-size: 1.15rem; font-weight: 800; margin: 0; color: var(--text); }
.pa-tabs { display: flex; gap: 6px; padding: 12px 16px 4px; }
.pa-tab {
  flex: 1 1 0; min-width: 88px; padding: 9px 12px;
  border: 1px solid var(--border); border-radius: 999px;
  background: transparent; color: var(--text-muted);
  font-weight: 700; font-size: 0.9rem; cursor: pointer;
  transition: color var(--transition), background var(--transition), border-color var(--transition);
}
.pa-tab.is-active {
  background: var(--qbit-alert, #f59e0b); color: var(--qbit-alert-ink, #1f1300); border-color: transparent;
}
.pa-list { display: flex; flex-direction: column; gap: 10px; padding: 12px 16px; }
.pa-loading, .pa-error, .pa-empty {
  text-align: center; color: var(--text-muted); padding: 32px 12px; font-size: 0.95rem;
}
.pa-row { display: flex; flex-direction: column; gap: 10px; }
.pa-row-open { cursor: pointer; }
.pa-row-open:hover { box-shadow: 0 2px 10px rgba(0, 0, 0, 0.08); }
.pa-row-main { display: flex; gap: 10px; align-items: flex-start; }
.pa-src { flex: none; color: var(--text-muted); margin-top: 2px; }
.pa-src-icon { width: 20px; height: 20px; display: block; }
.pa-open-hint { font-size: 0.8rem; color: var(--blue-500, #3b82f6); margin-top: 6px; font-weight: 600; }
.pa-actions { display: flex; gap: 8px; justify-content: flex-end; }
.pa-action {
  padding: 7px 14px; border-radius: 8px; border: 1px solid var(--border);
  background: var(--bg); color: var(--text); font-weight: 700; font-size: 0.85rem;
  cursor: pointer; transition: background var(--transition);
}
.pa-action:hover { background: var(--bg-soft, #f1f5f9); }
.pa-action-clear { border-color: var(--qbit-alert, #f59e0b); color: #b45309; }
.pa-action-save.is-saved {
  background: var(--qbit-notify, #a3e635); color: var(--qbit-notify-ink, #0c1400); border-color: transparent;
}
.child-settings-open-alerts-btn {
  display: inline-block; margin: 4px 0 14px; padding: 10px 16px;
  border: none; border-radius: 10px; cursor: pointer; font-weight: 800; font-size: 0.92rem;
  background: var(--qbit-alert, #f59e0b); color: var(--qbit-alert-ink, #1f1300);
}

/* Child switcher (shown when 2+ children have current alerts) */
.pa-switcher { display: flex; gap: 8px; padding: 10px 16px 0; overflow-x: auto; -webkit-overflow-scrolling: touch; }
.pa-child-pill {
  display: inline-flex; align-items: center; gap: 6px; flex: none;
  padding: 7px 12px; border: 1px solid var(--border); border-radius: 999px;
  background: transparent; color: var(--text); font-weight: 700; font-size: 0.85rem; cursor: pointer;
  white-space: nowrap;
}
.pa-child-pill.is-active { background: var(--qbit-alert, #f59e0b); color: var(--qbit-alert-ink, #1f1300); border-color: transparent; }
.pa-child-pill-count {
  display: inline-flex; align-items: center; justify-content: center; min-width: 18px; height: 18px;
  padding: 0 5px; border-radius: 9px; background: rgba(0, 0, 0, 0.12); font-size: 0.72rem; font-weight: 800;
}
.pa-child-pill.is-active .pa-child-pill-count { background: rgba(0, 0, 0, 0.18); }
.connection-row-chevron {
  font-size: 1.4rem;
  line-height: 1;
  color: var(--text-muted);
  flex-shrink: 0;
  padding-left: 4px;
}
.connection-notes-count {
  font-size: 0.78rem;
  color: var(--text-muted);
  font-style: italic;
}

/* Notes section on the per-connection detail page. Cards-of-notes
   list + a compose box at the bottom. Pattern matches the rest of
   the multi-add surfaces in Qbit (Documents, Memories, Dates). */
.connection-notes-list {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin-bottom: 14px;
}
.connection-notes-empty {
  margin: 0 0 12px;
  font-size: 0.86rem;
  color: var(--text-muted);
  font-style: italic;
}
.connection-note-card {
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--bg);
  padding: 10px 12px;
}
.connection-note-text {
  font-size: 0.92rem;
  line-height: 1.5;
  color: var(--text);
  white-space: pre-wrap; /* preserve user line breaks */
  word-break: break-word;
}
.connection-note-meta {
  margin-top: 6px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  font-size: 0.74rem;
  color: var(--text-muted);
}
.connection-note-time {
  flex: 1;
  min-width: 0;
}
.connection-note-actions {
  display: inline-flex;
  gap: 6px;
}
.connection-note-action {
  background: transparent;
  border: 0;
  padding: 2px 8px;
  font-size: 0.78rem;
  font-weight: 600;
  color: var(--text-secondary);
  cursor: pointer;
  border-radius: 6px;
}
.connection-note-action:hover {
  background: var(--bg-subtle, #f5f6f8);
  color: var(--text);
}
.connection-note-action-delete:hover {
  color: #b91c1c;
  background: rgba(220, 38, 38, 0.08);
}
.connection-note-edit-actions {
  margin-top: 8px;
  display: flex;
  gap: 8px;
  justify-content: flex-end;
}
.connection-notes-compose {
  border-top: 1px dashed var(--border);
  padding-top: 12px;
  margin-top: 4px;
}
.connection-notes-compose-actions {
  margin-top: 8px;
  display: flex;
  justify-content: flex-end;
}
.connection-notes-status {
  margin-top: 6px;
  min-height: 1em;
  font-size: 0.78rem;
  color: var(--text-muted);
  text-align: right;
}
.connection-notes-status.notes-status-saving { color: var(--text-muted); }
.connection-notes-status.notes-status-saved  { color: #16a34a; }
.connection-notes-status.notes-status-error  { color: #dc2626; }

/* ============================================================
   Per-child Settings page — Phase CS1 (2026-05-25)
   ============================================================
   The page is reachable from Family Insights → child card →
   Settings button. ChildSettings module in app.js populates
   the section IDs at render time. Most sections ship as
   "Coming soon" placeholders in CS1 and get fleshed out in
   subsequent CS sub-phases.
   ============================================================ */
.child-settings-wrap {
  max-width: 720px;
  margin: 0 auto;
  /* Horizontal padding intentionally 0 — the parent .page already
     applies 20px side padding, and stacking another 20px here
     made the Child Settings page look noticeably narrower than
     every other page (Jimmy 2026-05-27). Vertical padding kept
     for the top breathing-room + bottom safe-area. */
  padding: 16px 0 80px;
}

.child-settings-back {
  background: transparent;
  border: 0;
  padding: 6px 0;
  font-size: 0.88rem;
  color: var(--text-secondary);
  cursor: pointer;
  margin-bottom: 12px;
  display: inline-flex;
  align-items: center;
  gap: 4px;
}
.child-settings-back:hover { color: var(--text); }

.child-settings-header {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 16px 0 20px;
  border-bottom: 1px solid var(--border);
  margin-bottom: 16px;
}
.child-settings-avatar {
  width: 56px;
  height: 56px;
  border-radius: 50%;
  background: var(--blue-500);
  color: white;
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: 700;
  font-size: 1.4rem;
  flex-shrink: 0;
}
.child-settings-header-text { flex: 1; min-width: 0; }
.child-settings-title {
  font-size: 1.3rem;
  font-weight: 700;
  margin: 0;
  color: var(--text);
}
.child-settings-subtitle {
  font-size: 0.85rem;
  color: var(--text-secondary);
  margin: 2px 0 0 0;
}

.child-settings-loading {
  text-align: center;
  padding: 40px 20px;
  color: var(--text-secondary);
  font-size: 0.92rem;
}

/* Each top-level section in the page. Visually distinct from
   the parent Settings page's accordion — child Settings is a
   linear scroll with section headers. */
.child-settings-section {
  background: var(--bg-secondary);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 16px 18px;
  margin-bottom: 14px;
}
/* When a collapsible section is nested INSIDE a parent-page
   .settings-card (Activity & Alerts Reporting on Parent-Child,
   Jimmy 2026-07-09), render it FLUSH — the card already provides the
   chrome, and a bordered card-in-card reads as clutter. */
.settings-card > .child-settings-section {
  background: transparent;
  border: none;
  border-radius: 0;
  padding: 14px 18px;
  margin-bottom: 0;
}
.child-settings-section-title {
  font-size: 1rem;
  font-weight: 700;
  color: var(--text);
  margin: 0 0 8px 0;
}

/* ── Collapsible Child Settings sections (Jimmy 2026-07-09) ──────────
   Six long sections opt in via data-cs-collapse on the <section>. The
   title row becomes the toggle: chevron on the right, pointer cursor,
   and when .cs-collapsed is set everything except the title hides.
   All ids/DOM stay put, so the render paths write into hidden nodes
   harmlessly. State is persisted by initChildSettingsCollapse (app.js). */
.child-settings-section[data-cs-collapse] > .child-settings-section-title {
  cursor: pointer;
  -webkit-user-select: none;
  user-select: none;
  display: flex;
  align-items: center;
  gap: 8px;
}
.child-settings-section[data-cs-collapse] > .child-settings-section-title::after {
  content: '';
  width: 8px;
  height: 8px;
  flex: 0 0 auto;
  margin-left: auto;
  border-right: 2px solid var(--text-secondary);
  border-bottom: 2px solid var(--text-secondary);
  /* UI STANDARD (2026-07-09): expanded = chevron-UP, collapsed =
     chevron-DOWN. The first cut pointed RIGHT when collapsed, which
     collided with the app-wide "right chevron = navigates to another
     page" affordance (brand/UI-UX-STANDARDS.md). */
  transform: rotate(-135deg);          /* chevron-up = expanded */
  transition: transform 0.15s ease;
}
.child-settings-section.cs-collapsed > .child-settings-section-title {
  margin-bottom: 2px;                  /* tight title→subtitle pair */
}
.child-settings-section.cs-collapsed > .child-settings-section-title::after {
  transform: rotate(45deg);            /* chevron-down = collapsed */
}
/* Collapsed = title + its one-line description (Jimmy 2026-07-09 —
   matches the My Profile collapse-cards, which always show a subtitle).
   The desc is the FIRST .child-settings-section-desc direct child;
   everything else hides. !important: some section children carry inline
   display styles from their render paths — the collapse must win. */
.child-settings-section.cs-collapsed
  > *:not(.child-settings-section-title):not(.child-settings-section-desc) {
  display: none !important;
}
/* Only the first desc shows collapsed — a later desc-classed line
   (e.g. a footnote) stays hidden with the rest of the body. */
.child-settings-section.cs-collapsed
  > .child-settings-section-desc ~ .child-settings-section-desc {
  display: none !important;
}
.child-settings-section.cs-collapsed > .child-settings-section-desc {
  margin-bottom: 0;
}

/* Sub-block inside a section (Jimmy 2026-07-09): Pending approvals now
   lives INSIDE Connections as a demoted h3 subheading — smaller than the
   section title so the hierarchy reads at a glance. */
.child-settings-subblock {
  margin: 0 0 14px 0;
}
.child-settings-subblock-title {
  font-size: 0.9rem;
  font-weight: 700;
  color: var(--text);
  margin: 0 0 6px 0;
}

.child-settings-section-desc {
  font-size: 0.85rem;
  color: var(--text-secondary);
  line-height: 1.5;
  margin: 0 0 10px 0;
}
.child-settings-coming-soon {
  font-style: italic;
  opacity: 0.85;
}

/* Profile rows — label / value */
.child-settings-row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 8px 0;
  border-bottom: 1px dashed var(--border);
  font-size: 0.9rem;
}
.child-settings-row:last-child { border-bottom: 0; }
/* When .child-settings-row (or its edit-form sibling) lives ANYWHERE
   inside a .settings-card — direct child OR grandchild via wrappers
   like .family-config (Parent-Child Accounts "Send reports to:" row,
   2026-05-30 Jimmy) — the row needs its own horizontal padding.
   .settings-card has none, relying on each child to supply it (the
   default .setting-item uses 16/18). Without this scoped override,
   the row sits flush against the card edge.

   v1 used `.settings-card >` (direct child only) and missed the
   .family-config wrapper case — Jimmy caught it in v518. Switched
   to descendant selector. Safe scope: .child-settings-row is only
   used on Child Settings (under .child-settings-section, not
   .settings-card) and these two Parent-Child rows. */
.settings-card .child-settings-row,
.settings-card .child-settings-row-edit-form {
  padding-left: 18px;
  padding-right: 18px;
}
.child-settings-row-label {
  flex: 0 0 130px;
  color: var(--text-secondary);
  font-weight: 500;
}
.child-settings-row-value {
  flex: 1;
  color: var(--text);
  font-weight: 600;
  word-break: break-word;
}

/* Toggle row — single-toggle layout used for auto-replenish and
   other CS sections that ship a per-child boolean later. */
.child-settings-toggle {
  display: flex;
  align-items: center;
  justify-content: space-between; /* text left, .toggle switch right —
                                     checkbox→switch pass (Jimmy 2026-07-09) */
  gap: 10px;
  padding: 6px 0;
  font-size: 0.92rem;
  color: var(--text);
  cursor: pointer;
}
.child-settings-toggle input[type="checkbox"] {
  width: 18px;
  height: 18px;
  cursor: pointer;
  accent-color: var(--blue-500);
}
.child-settings-toggle-label { font-weight: 500; }
.child-settings-toggle-status {
  font-size: 0.78rem;
  color: var(--text-secondary);
  min-height: 1.2em;
  margin-top: 4px;
}
.child-settings-toggle-status.error { color: #b91c1c; }

/* Security section banner — green "all clear" badge. CS7 will
   add a red variant for when alerts exist. */
.child-settings-security-banner {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 14px;
  background: rgba(34, 197, 94, 0.10);
  border-left: 3px solid #22c55e;
  border-radius: 8px;
  font-size: 0.88rem;
  color: var(--text);
  margin-bottom: 8px;
}
.child-settings-security-icon { font-size: 1.1rem; }

/* Danger zone — Pause + Delete buttons live here. */
.child-settings-section-danger {
  border-color: rgba(220, 38, 38, 0.25);
}
.child-settings-danger-actions {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
  margin-bottom: 10px;
}
.child-settings-action {
  font-size: 0.88rem;
  font-weight: 600;
  padding: 9px 18px;
  border-radius: 8px;
  cursor: pointer;
  border: 1.5px solid var(--border);
  background: var(--bg);
  color: var(--text);
}
.child-settings-action:hover { filter: brightness(0.97); }
.child-settings-action-pause {
  background: rgba(234, 179, 8, 0.12);
  border-color: rgba(234, 179, 8, 0.4);
  color: #92400e;
}
/* Verify CTA — prominent blue, since this is the single most
   important action a parent can take for a pending_vpc child.
   Added 2026-05-27 with the Sign-in section. */
.child-settings-action-verify {
  background: var(--blue-500);
  border-color: var(--blue-500);
  color: white;
}
.child-settings-action-verify:hover {
  background: var(--blue-600);
  border-color: var(--blue-600);
  filter: none;
}
/* Sign-in section button row — Generate code + (13-17) Set
   password sit side-by-side, wrap on narrow viewports. */
.child-settings-signin-actions {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
  margin-top: 8px;
}
/* Locked state for the password button when child is under 13.
   Visually distinct from a hover/active button but still clearly
   the same affordance — communicates "this is real, just not yet"
   rather than "broken / missing". Pairs with the COPPA note below.
   Added 2026-05-27 (Jimmy) so parents can see the option exists. */
.child-settings-action-locked,
.child-settings-action[disabled] {
  cursor: not-allowed;
  opacity: 0.55;
  background: var(--bg-subtle, #f5f6f8);
}
.child-settings-action-locked:hover,
.child-settings-action[disabled]:hover {
  filter: none;
}
.child-settings-signin-coppa-note {
  margin: 8px 0 0;
  font-size: 0.82rem;
  color: var(--text-muted);
  font-style: italic;
  line-height: 1.4;
}
.child-settings-action-resume {
  background: rgba(34, 197, 94, 0.12);
  border-color: rgba(34, 197, 94, 0.4);
  color: #166534;
}
.child-settings-action-delete {
  background: rgba(220, 38, 38, 0.10);
  border-color: rgba(220, 38, 38, 0.4);
  color: #991b1b;
}
[data-theme="dark"] .child-settings-action-pause { color: #fbbf24; }
[data-theme="dark"] .child-settings-action-resume { color: #86efac; }
[data-theme="dark"] .child-settings-action-delete { color: #fca5a5; }

.child-settings-danger-desc {
  font-size: 0.8rem;
  color: var(--text-secondary);
  margin: 0;
}

/* Settings button on the child card — matches the height + roundness
   of the other action buttons (.family-child-action-pair etc.) but
   uses a neutral fill so it doesn't fight the gradient pair button
   for attention. */
.family-child-action-settings {
  font-size: 0.78rem;
  font-weight: 600;
  padding: 6px 10px;
  border-radius: 8px;
  background: var(--bg, #fff);
  color: var(--text, #111);
  border: 1.5px solid var(--border, #e6e6ea);
  cursor: pointer;
  white-space: nowrap;
}
.family-child-action-settings:hover {
  filter: brightness(0.97);
}
[data-theme="dark"] .family-child-action-settings {
  background: var(--bg-secondary);
}

/* Inline edit affordances on Profile rows — Phase CS2 (2026-05-25).
   Pencil button sits at the right of the row; clicking it swaps
   the .child-settings-row for .child-settings-row-edit-form. */
.child-settings-row-edit {
  background: transparent;
  border: 0;
  padding: 4px 8px;
  margin-left: auto;
  font-size: 1rem;
  color: var(--text-secondary);
  cursor: pointer;
  border-radius: 6px;
}
.child-settings-row-edit:hover {
  background: var(--bg);
  color: var(--text);
}

.child-settings-row-edit-form {
  padding: 12px 0;
  border-bottom: 1px dashed var(--border);
}
.child-settings-row-edit-form .child-settings-row-label {
  flex: 0 0 auto;
  display: block;
  margin-bottom: 6px;
}
.child-settings-row-input {
  width: 100%;
  padding: 8px 10px;
  border: 1px solid var(--border);
  border-radius: 8px;
  font-size: 0.92rem;
  background: var(--bg);
  color: var(--text);
}
.child-settings-row-input:focus {
  outline: none;
  border-color: var(--blue-500);
  box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.15);
}
.child-settings-row-edit-warning {
  font-size: 0.78rem;
  color: var(--text-secondary);
  background: rgba(234, 179, 8, 0.10);
  border-left: 3px solid rgba(234, 179, 8, 0.6);
  padding: 8px 10px;
  border-radius: 6px;
  margin: 8px 0 0 0;
  line-height: 1.4;
}
.child-settings-row-edit-actions {
  display: flex;
  gap: 8px;
  margin-top: 10px;
}
.child-settings-row-edit-save,
.child-settings-row-edit-cancel {
  font-size: 0.85rem;
  font-weight: 600;
  padding: 7px 14px;
  border-radius: 8px;
  cursor: pointer;
  border: 1.5px solid var(--border);
}
.child-settings-row-edit-save {
  background: var(--blue-500);
  color: white;
  border-color: var(--blue-500);
}
.child-settings-row-edit-save:hover { filter: brightness(1.05); }
.child-settings-row-edit-save:disabled {
  opacity: 0.6;
  cursor: not-allowed;
}
.child-settings-row-edit-cancel {
  background: var(--bg);
  color: var(--text);
}
.child-settings-row-edit-cancel:hover { background: var(--bg-secondary); }
.child-settings-row-edit-status {
  font-size: 0.78rem;
  color: var(--text-secondary);
  min-height: 1.2em;
  margin-top: 6px;
}
.child-settings-row-edit-status.error { color: #b91c1c; }

/* Stacked row variant — label on top, control below. Used by the
   per-child Reporting controls (CS3) where the inputs are tall
   enough that side-by-side label/value layout doesn't fit. */
.child-settings-row-stacked {
  flex-direction: column;
  align-items: stretch;
}
.child-settings-row-stacked .child-settings-row-label {
  flex: 0 0 auto;
  margin-bottom: 6px;
}
.child-settings-row-input-narrow {
  width: 140px;
  max-width: 100%;
}
.child-settings-row-hint {
  font-size: 0.78rem;
  color: var(--text-secondary);
  margin: 6px 0 0 0;
  line-height: 1.4;
}
.child-settings-include-row {
  display: flex;
  align-items: flex-start;
  justify-content: space-between; /* text left, .toggle switch right —
                                     checkbox→switch pass (Jimmy 2026-07-09) */
  gap: 10px;
  padding: 6px 0;
  font-size: 0.85rem;
  color: var(--text);
  cursor: pointer;
  line-height: 1.5;
}
.child-settings-include-row input[type="checkbox"] {
  margin-top: 3px;
  flex-shrink: 0;
  accent-color: var(--blue-500);
}

/* Per-child Connections list — Phase CS4 (2026-05-25). One row per
   connection: avatar initial, name, type pill. Empty/loading/error
   states render inline in the same container. */
.child-settings-connections-list {
  margin-top: 4px;
}
.child-settings-connection-row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 0;
  border-bottom: 1px dashed var(--border);
  font-size: 0.88rem;
}
.child-settings-connection-row:last-child { border-bottom: 0; }
.child-settings-connection-avatar {
  width: 32px;
  height: 32px;
  border-radius: 50%;
  background: var(--blue-500);
  color: white;
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: 700;
  font-size: 0.85rem;
  flex-shrink: 0;
}
.child-settings-connection-name {
  flex: 1;
  font-weight: 600;
  color: var(--text);
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}
.child-settings-connection-empty,
.child-settings-connection-loading,
.child-settings-connection-error {
  padding: 10px 0;
  color: var(--text-secondary);
  font-size: 0.85rem;
  font-style: italic;
}
.child-settings-connection-error { color: #b91c1c; font-style: normal; }

/* Per-child Devices list — Phase CS6 (2026-05-25). Rows grouped
   server-side by deviceFingerprint. Last-seen + Revoke button per
   row; Revoke only shown for device-link rows (has linkId).
   SC-5 (2026-05-30): same visual rules also drive the adult self-
   pair Devices list — added .self-pair-device-* twins below each
   selector so both surfaces stay perfectly in sync. */
.child-settings-devices-list,
.self-pair-devices-list {
  margin-top: 8px;
}
.child-settings-device-row,
.self-pair-device-row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 0;
  border-bottom: 1px dashed var(--border);
  font-size: 0.88rem;
}
.child-settings-device-row:last-child,
.self-pair-device-row:last-child { border-bottom: 0; }
.child-settings-device-info,
.self-pair-device-info {
  flex: 1;
  min-width: 0;
}
.child-settings-device-label,
.self-pair-device-label {
  font-weight: 600;
  color: var(--text);
}
.child-settings-device-meta,
.self-pair-device-meta {
  font-size: 0.76rem;
  color: var(--text-secondary);
  margin-top: 2px;
  line-height: 1.4;
}
.child-settings-device-revoke,
.self-pair-device-revoke {
  font-size: 0.78rem;
  font-weight: 600;
  padding: 6px 12px;
  border-radius: 8px;
  background: rgba(220, 38, 38, 0.10);
  border: 1.5px solid rgba(220, 38, 38, 0.4);
  color: #991b1b;
  cursor: pointer;
  white-space: nowrap;
}
.child-settings-device-revoke:hover,
.self-pair-device-revoke:hover { filter: brightness(0.97); }
.child-settings-device-revoke:disabled,
.self-pair-device-revoke:disabled { opacity: 0.6; cursor: wait; }
[data-theme="dark"] .child-settings-device-revoke,
[data-theme="dark"] .self-pair-device-revoke { color: #fca5a5; }

/* Device-sign-in approval prompt (Red Sentry F0001 approval-binding,
   2026-07-24). Shown inside the "Sign in on another device" modal when a
   device enters the code and is waiting for the owner to approve it. */
.self-pair-approve {
  text-align: center;
  padding: 8px 4px 4px;
}
.self-pair-approve-icon { font-size: 2rem; line-height: 1; margin-bottom: 8px; }
.self-pair-approve-title { margin: 0 0 8px; font-size: 1.1rem; font-weight: 700; color: var(--text); }
.self-pair-approve-body { margin: 0 0 10px; font-size: 0.92rem; color: var(--text-secondary); line-height: 1.45; }
.self-pair-approve-device {
  display: inline-block;
  font-weight: 700;
  color: var(--text);
  background: var(--bg-elevated, rgba(59,130,246,0.08));
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 8px 14px;
  margin-bottom: 10px;
}
.self-pair-approve-warn { margin: 0 0 14px; font-size: 0.82rem; color: var(--text-secondary); }
.self-pair-approve-actions { display: flex; gap: 10px; justify-content: center; }
.self-pair-approve-yes,
.self-pair-approve-deny {
  flex: 1;
  max-width: 160px;
  padding: 11px 16px;
  border-radius: 12px;
  font-size: 0.95rem;
  font-weight: 700;
  cursor: pointer;
}
.self-pair-approve-yes { background: var(--blue-500, #3b82f6); color: #fff; border: none; }
.self-pair-approve-yes:hover { filter: brightness(0.96); }
.self-pair-approve-deny {
  background: transparent;
  color: #b91c1c;
  border: 1.5px solid rgba(220,38,38,0.4);
}
[data-theme="dark"] .self-pair-approve-deny { color: #fca5a5; }
.self-pair-approve-yes:disabled,
.self-pair-approve-deny:disabled { opacity: 0.6; cursor: wait; }
.self-pair-approve-status { margin-top: 10px; font-size: 0.82rem; color: var(--text-secondary); min-height: 1em; }
/* Info variant of the code-entry error slot (used for "waiting for approval"). */
.child-signin-error.is-info { color: var(--text-secondary); }

/* Shared-content list — "Sharing with others" Phase 3 (2026-07-23).
   Rows mirror the device list (info + red action button, reusing
   .child-settings-device-info/-meta/-revoke); only the question line
   and the inert revoked/expired state need their own rules. */
.child-settings-shared-list { margin-top: 4px; }
.child-settings-shared-row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 0;
  border-bottom: 1px dashed var(--border);
  font-size: 0.88rem;
}
.child-settings-shared-row:last-child { border-bottom: 0; }
.child-settings-shared-q {
  font-weight: 600;
  color: var(--text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.child-settings-shared-row-off .child-settings-shared-q {
  color: var(--text-secondary);
  font-weight: 500;
}

/* Adult "Shared links" modal rows (Phase 4, 2026-07-23) — ride the
   seclog row layout; the Turn off button must not shrink, and a
   revoked/expired row mutes its title like the parent-side list. */
.qbit-seclog-row .child-settings-device-revoke { flex-shrink: 0; align-self: center; }
.qbit-shared-row-off .qbit-seclog-device {
  color: var(--text-secondary);
  font-weight: 500;
}

/* The devices list and the "+ Add another device" button live as
   direct children of the .settings-card (alongside the .setting-
   item that hosts the section label). The .setting-item itself
   carries `padding: 16px 18px`, so to keep these siblings visually
   aligned with the label above and the card edges, give the list
   wrapper and the button matching horizontal padding (the 18px
   matters; the vertical pads are lighter so they don't fight the
   row's own 10px). v525 fix — Jimmy spotted that pre-v525 the
   error/empty/button text sat flush against the card edge. */
.self-pair-devices-list {
  padding: 0 18px;
}
/* Empty / loading / error states for the self-pair devices list.
   Mirror the child-settings equivalents but scoped to self-pair
   so we can tune one without nudging the other. */
.self-pair-devices-loading,
.self-pair-devices-empty {
  color: var(--text-secondary);
  font-size: 0.85rem;
  font-style: italic;
  margin-top: 8px;
}
.self-pair-devices-error { color: #b91c1c; font-style: normal; }
.self-pair-add-device-btn {
  margin: 4px 0 0 0;
  padding: 12px 18px 16px 18px;
  background: transparent;
  border: none;
  color: var(--blue-500);
  font-weight: 600;
  font-size: 0.92rem;
  cursor: pointer;
  text-align: left;
  width: 100%;
}
.self-pair-add-device-btn:hover { text-decoration: underline; }

/* "Sign out all other devices" — a quieter, destructive-leaning action under
   the device list (session revocation, 2026-06-17). */
.self-pair-signout-others {
  display: block; width: 100%; margin: 2px 0 6px;
  padding: 10px 18px; background: transparent; border: none;
  color: var(--red-600, #dc2626); font-weight: 600; font-size: 0.88rem;
  text-align: left; cursor: pointer;
}
.self-pair-signout-others:hover { text-decoration: underline; }
.self-pair-signout-others:disabled { opacity: 0.6; cursor: wait; }
[data-theme="dark"] .self-pair-signout-others { color: #fca5a5; }

/* Per-child Sign-in history list — Phase CS5 (2026-05-25). Each
   row is one login-events row, newest first. Success rows are
   neutral; failure rows are tinted red for at-a-glance scan. */
.child-settings-login-list {
  margin-top: 8px;
}
.child-settings-login-row {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  padding: 10px 0;
  border-bottom: 1px dashed var(--border);
  font-size: 0.86rem;
}
.child-settings-login-row:last-child { border-bottom: 0; }
.child-settings-login-row-failure {
  background: rgba(220, 38, 38, 0.05);
  margin: 0 -8px;
  padding-left: 8px;
  padding-right: 8px;
  border-radius: 6px;
}
.child-settings-login-icon {
  flex-shrink: 0;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: rgba(34, 197, 94, 0.18);
  color: #166534;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.8rem;
  font-weight: 700;
}
.child-settings-login-icon-failure {
  background: rgba(220, 38, 38, 0.18);
  color: #991b1b;
}
[data-theme="dark"] .child-settings-login-icon { color: #86efac; }
[data-theme="dark"] .child-settings-login-icon-failure { color: #fca5a5; }
.child-settings-login-info {
  flex: 1;
  min-width: 0;
}
.child-settings-login-primary {
  font-weight: 600;
  color: var(--text);
}
.child-settings-login-secondary {
  font-size: 0.76rem;
  color: var(--text-secondary);
  margin-top: 2px;
  line-height: 1.4;
}

/* Shared empty/loading/error states for devices + login history */
.child-settings-devices-empty,
.child-settings-devices-loading,
.child-settings-devices-error,
.child-settings-login-empty,
.child-settings-login-loading,
.child-settings-login-error {
  padding: 10px 0;
  color: var(--text-secondary);
  font-size: 0.85rem;
  font-style: italic;
}
.child-settings-devices-error,
.child-settings-login-error { color: #b91c1c; font-style: normal; }

/* Per-child Security alerts — Phase CS7 (2026-05-25). Banner shows
   "All clear" green state when no alerts; otherwise the list
   populates with one row per alert. Severity drives the border
   color (red = critical, amber = warning, blue = info). */
.child-settings-security-list { margin: 8px 0; }
.child-settings-security-alert {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  padding: 12px 14px;
  border-left: 4px solid #eab308;
  background: rgba(234, 179, 8, 0.06);
  border-radius: 8px;
  margin-bottom: 10px;
  font-size: 0.88rem;
}
.child-settings-security-alert-critical {
  border-left-color: #dc2626;
  background: rgba(220, 38, 38, 0.08);
}
.child-settings-security-alert-info {
  border-left-color: #3b82f6;
  background: rgba(59, 130, 246, 0.06);
}
.child-settings-security-alert-icon {
  flex-shrink: 0;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: rgba(234, 179, 8, 0.20);
  color: #92400e;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1rem;
  font-weight: 700;
}
.child-settings-security-alert-critical .child-settings-security-alert-icon {
  background: rgba(220, 38, 38, 0.20);
  color: #991b1b;
}
[data-theme="dark"] .child-settings-security-alert-icon { color: #fbbf24; }
[data-theme="dark"] .child-settings-security-alert-critical .child-settings-security-alert-icon { color: #fca5a5; }
.child-settings-security-alert-body {
  flex: 1;
  min-width: 0;
}
.child-settings-security-alert-title {
  font-weight: 700;
  color: var(--text);
  margin-bottom: 4px;
}
.child-settings-security-alert-detail {
  font-size: 0.8rem;
  color: var(--text-secondary);
  line-height: 1.5;
  margin: 0 0 6px 0;
}
.child-settings-security-alert-ack {
  font-size: 0.78rem;
  font-weight: 600;
  padding: 5px 12px;
  border-radius: 6px;
  background: var(--bg);
  border: 1.5px solid var(--border);
  color: var(--text);
  cursor: pointer;
}
.child-settings-security-alert-ack:hover { background: var(--bg-secondary); }
.child-settings-security-footnote {
  margin-top: 8px;
  font-style: normal;
  opacity: 1;
}
.child-settings-security-loading,
.child-settings-security-error {
  padding: 8px 0;
  color: var(--text-secondary);
  font-size: 0.85rem;
  font-style: italic;
}
.child-settings-security-error { color: #b91c1c; font-style: normal; }

/* ============================================================
   Safety Alerts (LA-UI, 2026-06-01) — content classifier surface
   ============================================================
   Visually distinct from Security (sign-in anomalies) by using a
   warmer amber accent. Security defaults green ("all clear");
   Safety follows the same pattern. When alerts exist, the per-row
   severity drives a left-border color so a parent can scan the
   list and spot anything high-severity instantly.
   ============================================================ */
.child-settings-safety-banner {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 14px;
  background: rgba(34, 197, 94, 0.10);
  border-left: 3px solid #22c55e;
  border-radius: 8px;
  font-size: 0.88rem;
  color: var(--text);
  margin-bottom: 8px;
}
.child-settings-safety-icon { font-size: 1.1rem; }

.child-settings-safety-list { margin: 8px 0; }
.child-settings-safety-alert {
  display: flex;
  gap: 12px;
  padding: 10px 14px;
  border: 1px solid var(--border);
  border-left: 3px solid #f59e0b;   /* amber default — "review me" */
  border-radius: 8px;
  background: var(--bg);
  margin-bottom: 6px;
  transition: background-color 220ms ease, box-shadow 220ms ease;
}
.child-settings-safety-alert-critical {
  border-left-color: #dc2626;
  background: rgba(220, 38, 38, 0.05);
}
.child-settings-safety-alert-low {
  border-left-color: #94a3b8;
}
.child-settings-safety-alert-body {
  flex: 1;
  min-width: 0;
}
.child-settings-safety-alert-header {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 12px;
  margin-bottom: 4px;
}
.child-settings-safety-alert-category {
  font-size: 0.82rem;
  font-weight: 600;
  text-transform: capitalize;
  color: var(--text);
}
.child-settings-safety-alert-time {
  font-size: 0.78rem;
  color: var(--text-secondary);
  white-space: nowrap;
}
.child-settings-safety-alert-snippet {
  font-size: 0.86rem;
  color: var(--text);
  margin: 4px 0 0;
  word-break: break-word;
}
.child-settings-safety-alert-meta {
  font-size: 0.76rem;
  color: var(--text-secondary);
  margin-top: 6px;
}
/* Flash animation when a deep-link from email/push targets a
   specific alertId — fades in/out over ~2s so the parent's eye
   tracks straight to the row that pinged them. */
.child-settings-safety-alert-highlight {
  animation: child-settings-safety-flash 2.2s ease-out 1;
  box-shadow: 0 0 0 2px rgba(245, 158, 11, 0.55);
}
@keyframes child-settings-safety-flash {
  0%   { background-color: rgba(245, 158, 11, 0.25); }
  60%  { background-color: rgba(245, 158, 11, 0.12); }
  100% { background-color: transparent; }
}

.child-settings-safety-loading,
.child-settings-safety-error {
  padding: 8px 0;
  color: var(--text-secondary);
  font-size: 0.85rem;
  font-style: italic;
}
.child-settings-safety-error { color: #b91c1c; font-style: normal; }

/* Configure panel — collapsible <details>. Closed by default so
   the section's primary surface is the alerts list. */
.child-settings-safety-config {
  margin-top: 12px;
  padding: 8px 12px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--bg-secondary);
}
.child-settings-safety-config-summary {
  cursor: pointer;
  font-size: 0.88rem;
  font-weight: 600;
  color: var(--text);
  padding: 4px 0;
  list-style: none;
}
.child-settings-safety-config-summary::-webkit-details-marker { display: none; }
.child-settings-safety-config-summary::before {
  content: "▶";
  display: inline-block;
  width: 1em;
  margin-right: 4px;
  font-size: 0.72em;
  color: var(--text-secondary);
  transition: transform 180ms ease;
}
.child-settings-safety-config[open] .child-settings-safety-config-summary::before {
  transform: rotate(90deg);
}
.child-settings-safety-config-body {
  padding: 10px 0 4px;
}
.child-settings-safety-config-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 6px 0;
  border-top: 1px solid var(--border);
}
.child-settings-safety-config-row:first-child { border-top: none; }
.child-settings-safety-config-label {
  font-size: 0.86rem;
  color: var(--text);
}
.child-settings-safety-config-row select,
.child-settings-safety-config-row input[type="checkbox"] {
  font-size: 0.86rem;
}
.child-settings-safety-config-status {
  margin-top: 8px;
  font-size: 0.82rem;
  color: var(--text-secondary);
  min-height: 1.2em;
}
.child-settings-safety-config-status.ok    { color: #15803d; }
.child-settings-safety-config-status.error { color: #b91c1c; }
.child-settings-safety-sms-note {
  font-size: 0.78rem;
  color: var(--text-secondary);
  margin-top: 4px;
  font-style: italic;
}

/* ============================================================
   QbitModal — branded reusable info dialog (2026-05-25)
   ============================================================
   Replaces native browser alert() in branded surfaces so the
   dialog gets a real Qbit title instead of "qbit.app says".
   See index.html → #qbit-info-modal and app.js → QbitModal.
   ============================================================ */
.qbit-modal-overlay {
  position: fixed;
  inset: 0;
  background: rgba(15, 23, 42, 0.45);
  z-index: 10000;
  display: none;
  align-items: center;
  justify-content: center;
  padding: 16px;
}
.qbit-modal-overlay.qbit-modal-open {
  display: flex;
}
.qbit-modal {
  background: var(--bg, #fff);
  border-radius: 14px;
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.25);
  width: 100%;
  max-width: 440px;
  overflow: hidden;
  display: flex;
  flex-direction: column;
}
.qbit-modal-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 14px 18px;
  background: linear-gradient(135deg, #3b82f6, #8b5cf6);
  color: white;
}
.qbit-modal-title {
  margin: 0;
  font-size: 1rem;
  font-weight: 700;
  letter-spacing: 0.01em;
}
.qbit-modal-close {
  background: transparent;
  border: 0;
  color: white;
  font-size: 1.4rem;
  line-height: 1;
  cursor: pointer;
  padding: 4px 8px;
  border-radius: 6px;
}
.qbit-modal-close:hover {
  background: rgba(255, 255, 255, 0.15);
}
.qbit-modal-body {
  padding: 18px 20px;
  font-size: 0.95rem;
  line-height: 1.55;
  color: var(--text, #1f2937);
}
.qbit-modal-footer {
  padding: 12px 18px 18px;
  display: flex;
  justify-content: flex-end;
}
.qbit-modal-ok {
  background: linear-gradient(135deg, #3b82f6, #8b5cf6);
  color: white;
  border: 0;
  border-radius: 999px;
  padding: 10px 24px;
  font-size: 0.92rem;
  font-weight: 600;
  cursor: pointer;
}
.qbit-modal-ok:hover { filter: brightness(1.05); }
.qbit-modal-ok:disabled {
  opacity: 0.55;
  cursor: not-allowed;
  filter: grayscale(40%);
}
.qbit-modal-cancel {
  background: transparent;
  color: var(--text-secondary);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 10px 22px;
  font-size: 0.92rem;
  font-weight: 500;
  cursor: pointer;
  margin-right: 8px;
}
.qbit-modal-cancel:hover { background: var(--bg-secondary); }

/* SMS Verify modal — LA-5 (2026-05-31). Two-step flow inside the
   shared qbit-modal shell: phone input → code input. Buttons live in
   the footer; the body holds the step-conditional inputs. */
.sms-verify-step { margin-top: 10px; }
.sms-verify-intro {
  margin: 0 0 6px;
  color: var(--text-secondary);
  font-size: 0.88rem;
  line-height: 1.5;
}
.sms-verify-label {
  display: block;
  font-size: 0.82rem;
  font-weight: 600;
  color: var(--text);
  margin-bottom: 6px;
}
/* Shared structured phone field — country-code <select> + national
   number <input>, built by renderPhoneInput(). Scott 2026-06-11:
   makes every phone field on the site consistent, mirroring the
   Month/Day/Year date dropdowns. Styled to match .sms-verify-input. */
.phone-input-row {
  display: flex;
  gap: 8px;
  align-items: stretch;
}
.phone-cc {
  flex: 0 0 auto;
  min-width: 92px;
  max-width: 46%;
  padding: 10px 8px;
  border: 1px solid var(--border);
  border-radius: 8px;
  font-size: 0.95rem;
  background: var(--bg);
  color: var(--text);
  box-sizing: border-box;
}
.phone-national {
  flex: 1 1 auto;
  min-width: 0;
  padding: 10px 12px;
  border: 1px solid var(--border);
  border-radius: 8px;
  font-size: 1rem;
  background: var(--bg);
  color: var(--text);
  box-sizing: border-box;
}
.phone-cc:focus,
.phone-national:focus {
  outline: none;
  border-color: #3b82f6;
  box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.18);
}

.sms-verify-input {
  width: 100%;
  padding: 10px 12px;
  border: 1px solid var(--border);
  border-radius: 8px;
  font-size: 1rem;
  background: var(--bg);
  color: var(--text);
  box-sizing: border-box;
}
.sms-verify-input:focus {
  outline: none;
  border-color: #3b82f6;
  box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.18);
}
.sms-verify-input-code {
  letter-spacing: 0.4em;
  text-align: center;
  font-variant-numeric: tabular-nums;
  font-size: 1.25rem;
}
.sms-verify-resend {
  background: none;
  border: 0;
  color: #3b82f6;
  font-size: 0.84rem;
  padding: 6px 0;
  margin-top: 6px;
  cursor: pointer;
  text-decoration: underline;
}
.sms-verify-resend:hover { color: #1d4ed8; }
.sms-verify-status {
  margin-top: 8px;
  min-height: 1.2em;
  font-size: 0.84rem;
  color: var(--text-secondary);
}
.sms-verify-status.ok    { color: #15803d; }
.sms-verify-status.error { color: #b91c1c; }
.sms-verify-footer {
  display: flex;
  justify-content: flex-end;
  align-items: center;
}

/* Safety Alerts — "Verify phone to enable SMS" CTA (LA-5). Replaces
   the pre-LA-5 "coming soon" footnote when the parent hasn't
   verified yet. Shows the verified phone (masked) once they have. */
.child-settings-safety-verify-cta {
  display: inline-block;
  background: linear-gradient(135deg, #3b82f6, #8b5cf6);
  color: white;
  font-size: 0.84rem;
  font-weight: 600;
  border: 0;
  border-radius: 999px;
  padding: 8px 18px;
  /* Sits between the SMS row above and the Push row below — needs
     breathing room on both sides or it visually crashes into them
     (Jimmy 2026-05-31 screenshot). */
  margin: 10px 0 14px;
  cursor: pointer;
}
.child-settings-safety-verify-cta:hover { filter: brightness(1.05); }
.child-settings-safety-verified-phone {
  font-size: 0.82rem;
  color: var(--text-secondary);
  margin-top: 4px;
}
.child-settings-safety-verified-phone strong {
  color: var(--text);
  font-variant-numeric: tabular-nums;
}

/* SCOTT-REDLINE #5 (2026-06-02) — per-category sensitivity preset.
   Sits below the channel rows on the per-child Alerts panel + on the
   parent-level defaults panel. 5 rows, each with a 3-position
   segmented preset (More sensitive / Default / Less sensitive). */
.child-settings-safety-sensitivity-block {
  margin: 18px 0 4px;
  padding-top: 14px;
  border-top: 1px solid var(--border, rgba(0,0,0,0.08));
}
.child-settings-safety-sensitivity-heading {
  font-size: 0.92rem;
  font-weight: 600;
  color: var(--text);
  margin-bottom: 4px;
}
.child-settings-safety-sensitivity-hint {
  font-size: 0.78rem;
  color: var(--text-secondary);
  margin-bottom: 12px;
}
.child-settings-safety-sensitivity-row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 8px 0;
  flex-wrap: wrap;
}
.child-settings-safety-sensitivity-label {
  flex: 0 0 auto;
  min-width: 92px;
  font-size: 0.86rem;
  color: var(--text);
  font-weight: 500;
}
.child-settings-safety-sensitivity-seg {
  display: inline-flex;
  flex: 1 1 auto;
  background: var(--surface-2, rgba(0,0,0,0.04));
  border-radius: 999px;
  padding: 3px;
  gap: 2px;
  min-width: 240px;
}
.child-settings-safety-sensitivity-btn {
  flex: 1 1 0;
  appearance: none;
  background: transparent;
  border: 0;
  color: var(--text-secondary);
  font-size: 0.78rem;
  font-weight: 600;
  padding: 6px 10px;
  border-radius: 999px;
  cursor: pointer;
  transition: background 120ms ease, color 120ms ease;
  white-space: nowrap;
}
.child-settings-safety-sensitivity-btn:hover {
  color: var(--text);
}
.child-settings-safety-sensitivity-btn.is-active {
  background: linear-gradient(135deg, #3b82f6, #8b5cf6);
  color: white;
  box-shadow: 0 1px 3px rgba(59, 130, 246, 0.3);
}
.child-settings-safety-sensitivity-btn.is-active:hover {
  color: white;
  filter: brightness(1.05);
}
/* SCOTT (2026-06-07): the SELECTED "off" stop is a muted/shaded red (not a
   bright alarm red) so a disabled category reads clearly as "off" — distinct
   from the blue "on" stops. */
.child-settings-safety-sensitivity-btn.child-settings-safety-sensitivity-off.is-active {
  background: linear-gradient(135deg, #c0504d, #9b3a37);
  box-shadow: 0 1px 3px rgba(155, 58, 55, 0.3);
}
.child-settings-safety-sensitivity-btn.child-settings-safety-sensitivity-off.is-active:hover {
  filter: brightness(1.04);
}
@media (max-width: 480px) {
  .child-settings-safety-sensitivity-row {
    flex-direction: column;
    align-items: stretch;
    gap: 6px;
  }
  .child-settings-safety-sensitivity-label {
    min-width: 0;
  }
  .child-settings-safety-sensitivity-seg {
    min-width: 0;
  }
  .child-settings-safety-sensitivity-btn {
    font-size: 0.74rem;
    padding: 6px 4px;
  }
}

/* SCOTT-REDLINE-R2 (2026-06-02) — full-page overlay shown on
   /family/mature-claim?token=… for newly-matured (just-18) users
   who need to set an email + password on their now-adult account.
   No auth required — the URL token IS the auth. */
.mature-claim-overlay {
  position: fixed;
  inset: 0;
  z-index: 99999;
  background: linear-gradient(180deg, #f3f4f6, #ffffff);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
}
.mature-claim-card {
  background: #ffffff;
  border-radius: 18px;
  box-shadow: 0 18px 48px rgba(15, 23, 42, 0.18);
  padding: 30px;
  max-width: 480px;
  width: 100%;
}
.mature-claim-title {
  font-size: 1.5rem;
  font-weight: 800;
  color: #1f2937;
  margin: 0 0 10px 0;
  line-height: 1.25;
}
.mature-claim-sub {
  font-size: 0.95rem;
  color: #4b5563;
  margin: 0 0 22px 0;
  line-height: 1.55;
}
.mature-claim-label {
  display: block;
  font-size: 0.82rem;
  font-weight: 600;
  color: #374151;
  margin-bottom: 4px;
  margin-top: 10px;
}
.mature-claim-input {
  width: 100%;
  padding: 11px 13px;
  border: 1px solid #d1d5db;
  border-radius: 10px;
  font-size: 0.95rem;
  background: #ffffff;
  box-sizing: border-box;
}
.mature-claim-input:focus {
  outline: 0;
  border-color: #3b82f6;
  box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.15);
}
.mature-claim-error {
  min-height: 18px;
  font-size: 0.82rem;
  color: #b91c1c;
  margin: 8px 0 4px 0;
}
.mature-claim-submit {
  width: 100%;
  margin-top: 14px;
  padding: 12px 22px;
  border: 0;
  border-radius: 999px;
  background: linear-gradient(135deg, #3b82f6, #8b5cf6);
  color: #ffffff;
  font-weight: 700;
  font-size: 0.98rem;
  cursor: pointer;
}
.mature-claim-submit:hover { filter: brightness(1.05); }

/* SCOTT-D (2026-06-01) — Email me row with inline display + edit
   affordance. The row collapses to label/email/pencil on the left
   + checkbox on the right; click pencil → input + Save/Cancel
   replaces the static value. */
.child-settings-safety-config-row-email {
  align-items: flex-start;
}
.child-settings-safety-email-block {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding-right: 12px;
}
.child-settings-safety-email-value {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 0.82rem;
  color: var(--text-secondary);
  flex-wrap: wrap;
}
.child-settings-safety-email-addr {
  font-variant-numeric: tabular-nums;
  word-break: break-all;
}
.child-settings-safety-email-edit {
  background: none;
  border: 1px solid var(--border);
  border-radius: 50%;
  width: 22px;
  height: 22px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  color: var(--text-secondary);
  flex-shrink: 0;
}
.child-settings-safety-email-edit:hover {
  color: #3b82f6;
  border-color: #3b82f6;
}
.child-settings-safety-email-input {
  flex: 1 1 auto;
  min-width: 160px;
  padding: 5px 8px;
  border: 1px solid var(--border);
  border-radius: 6px;
  font-size: 0.86rem;
  background: var(--bg);
  color: var(--text);
}
.child-settings-safety-email-input:focus {
  outline: none;
  border-color: #3b82f6;
  box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.18);
}
.child-settings-safety-email-save,
.child-settings-safety-email-cancel {
  background: none;
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 4px 10px;
  font-size: 0.82rem;
  cursor: pointer;
  color: var(--text);
}
.child-settings-safety-email-save {
  background: linear-gradient(135deg, #3b82f6, #8b5cf6);
  color: white;
  border-color: transparent;
}
.child-settings-safety-email-save:hover { filter: brightness(1.05); }
.child-settings-safety-email-cancel:hover { background: var(--bg-secondary); }
.child-settings-safety-email-status {
  flex-basis: 100%;
  margin-top: 4px;
  font-size: 0.78rem;
  color: var(--text-secondary);
  min-height: 1em;
}
.child-settings-safety-email-status.ok    { color: #15803d; }
.child-settings-safety-email-status.error { color: #b91c1c; }

/* Per-child Pending Approvals — Phase PA (2026-05-25). One row
   per connection request waiting on the parent's decision.
   Inline Approve + Deny buttons. Empty state when none pending. */
.child-settings-pending-approvals-list { margin-top: 4px; }
.child-settings-pending-approval-row {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: 10px 14px;
  padding: 12px 0;
  border-bottom: 1px dashed var(--border);
  font-size: 0.88rem;
}
.child-settings-pending-approval-row:last-child { border-bottom: 0; }
.child-settings-pending-approval-row.highlight {
  /* Used by the email deep-link handler to flash the targeted row. */
  background: rgba(59, 130, 246, 0.08);
  border-radius: 8px;
  padding: 12px 12px;
  margin: 0 -12px;
  animation: childSettingsApprovalHighlight 2.4s ease-out;
}
@keyframes childSettingsApprovalHighlight {
  0%   { background: rgba(59, 130, 246, 0.22); }
  100% { background: rgba(59, 130, 246, 0.08); }
}
.child-settings-pending-approval-info {
  flex: 1 1 60%;
  min-width: 0;
}
.child-settings-pending-approval-primary {
  font-weight: 600;
  color: var(--text);
  margin-bottom: 2px;
  line-height: 1.45;
}
.child-settings-pending-approval-secondary {
  font-size: 0.78rem;
  color: var(--text-secondary);
  line-height: 1.4;
}
.child-settings-pending-approval-message {
  font-style: italic;
  color: var(--text-secondary);
  font-size: 0.82rem;
  margin-top: 6px;
  padding: 6px 10px;
  background: var(--bg);
  border-left: 3px solid var(--border);
  border-radius: 4px;
  word-break: break-word;
}
.child-settings-pending-approval-actions {
  display: flex;
  gap: 8px;
  flex: 0 0 auto;
}
.child-settings-pending-approval-approve,
.child-settings-pending-approval-deny {
  font-size: 0.82rem;
  font-weight: 600;
  padding: 7px 14px;
  border-radius: 8px;
  cursor: pointer;
  border: 1.5px solid var(--border);
}
.child-settings-pending-approval-approve {
  background: linear-gradient(135deg, #22c55e, #16a34a);
  color: white;
  border-color: #16a34a;
}
.child-settings-pending-approval-approve:hover { filter: brightness(1.05); }
.child-settings-pending-approval-deny {
  background: var(--bg);
  color: #991b1b;
  border-color: rgba(220, 38, 38, 0.4);
}
.child-settings-pending-approval-deny:hover {
  background: rgba(220, 38, 38, 0.06);
}
[data-theme="dark"] .child-settings-pending-approval-deny { color: #fca5a5; }
.child-settings-pending-approval-approve:disabled,
.child-settings-pending-approval-deny:disabled { opacity: 0.6; cursor: wait; }

.child-settings-pending-approvals-empty,
.child-settings-pending-approvals-loading,
.child-settings-pending-approvals-error {
  padding: 10px 0;
  color: var(--text-secondary);
  font-size: 0.85rem;
  font-style: italic;
}
.child-settings-pending-approvals-error { color: #b91c1c; font-style: normal; }

/* "+ Add another device" link in the per-child Devices section
   (Phase CS-S3, 2026-05-25). Treated like a quiet link rather
   than a primary CTA — adding a device is a low-frequency action,
   we don't want it competing with the Revoke buttons. */
.child-settings-add-device {
  background: transparent;
  border: 0;
  padding: 8px 0;
  margin-top: 6px;
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--blue-500);
  cursor: pointer;
}
.child-settings-add-device:hover {
  text-decoration: underline;
}

/* Per-child credit balance display — Phase CS-S1/S2 (2026-05-25).
   Sits above the auto-replenish toggle. "Add more now" launches
   the pack-picker modal (CS-S4/5/6). */
.child-settings-credit-balance {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  padding: 12px 14px;
  background: linear-gradient(135deg, rgba(59,130,246,0.06), rgba(139,92,246,0.06));
  border-radius: 10px;
  margin-bottom: 12px;
  font-size: 0.92rem;
}
.child-settings-credit-balance-label {
  color: var(--text-secondary);
  font-weight: 500;
}
.child-settings-credit-balance-value {
  font-weight: 700;
  color: var(--text);
  font-size: 1.05rem;
  flex: 1;
}
.child-settings-credit-add-btn {
  background: linear-gradient(135deg, #3b82f6, #8b5cf6);
  color: white;
  border: 0;
  border-radius: 999px;
  padding: 8px 18px;
  font-size: 0.85rem;
  font-weight: 600;
  cursor: pointer;
  white-space: nowrap;
}
.child-settings-credit-add-btn:hover { filter: brightness(1.05); }
.child-settings-credit-add-btn:disabled { opacity: 0.5; cursor: not-allowed; }

/* Add credits inline-expand region — Phase CS-S6 (2026-05-25).
   Lives inside the auto-replenish section. Hidden until the
   parent clicks "Add more now". Pack tiles → checkout iframe
   swap-in within the same wrapper. */
.child-settings-add-credits-region {
  margin-top: 16px;
  padding: 16px;
  border: 1px dashed var(--border);
  border-radius: 10px;
  background: var(--bg);
}
.child-settings-add-credits-heading {
  margin: 0 0 12px 0;
  font-size: 0.95rem;
  font-weight: 700;
  color: var(--text);
}
/* Per-child Add Credits pack-tiles container (CS-S6, Scott
   2026-05-25 edit): match the adult-side Settings → Ads layout
   exactly. Reuses .credit-pack-card / -popular / -best classes
   defined for the adult tiles. The container is a flex row with
   flex:1 siblings (same as .credit-pack-options) so the look is
   identical without duplicated CSS. */
.child-settings-pack-tiles {
  display: flex;
  gap: 10px;
  margin-bottom: 12px;
  /* Top padding ensures the absolute-positioned .credit-pack-badge
     (which sits at top: -9px on .credit-pack-popular / -best)
     has visual room. The adult-side .credit-pack-options sits
     inside a panel with enough natural padding above; ours is
     tucked under a header so we add a touch ourselves. */
  padding-top: 14px;
}
.child-settings-pack-tiles > .credit-pack-card {
  flex: 1;
  cursor: pointer;
  /* The adult-side cards highlight via the radio :checked sibling
     selector. Ours are buttons (no radio), so add a real hover
     state for tap feedback. */
}
.child-settings-pack-tiles > .credit-pack-card:hover {
  border-color: var(--blue-500);
}
.child-settings-pack-tiles > .credit-pack-card:disabled {
  opacity: 0.55;
  cursor: wait;
}
@media (max-width: 480px) {
  .child-settings-pack-tiles { flex-direction: column; }
}

.child-settings-add-credits-iframe-mount {
  min-height: 480px;
  border-radius: 8px;
  overflow: hidden;
  margin-bottom: 10px;
}
.child-settings-add-credits-close {
  background: transparent;
  border: 1px solid var(--border);
  color: var(--text-secondary);
  border-radius: 8px;
  padding: 7px 16px;
  font-size: 0.85rem;
  cursor: pointer;
}
.child-settings-add-credits-close:hover {
  background: var(--bg-secondary);
  color: var(--text);
}
.child-settings-add-credits-status {
  margin-top: 8px;
  font-size: 0.82rem;
  color: var(--text-secondary);
  min-height: 1.2em;
}
.child-settings-add-credits-status.error { color: #b91c1c; }

/* ============================================================
   Notifications settings — Phase PN (2026-05-25)
   ============================================================
   Sits inside #notifications-section in Settings. Master toggle
   + per-category toggles + a status banner that surfaces
   browser permission state, iOS install requirement, etc.
   ============================================================ */
.push-notifications-status {
  /* Plain informational note — no border/radius/background by default
     so it doesn't masquerade as a clickable button (Jimmy 2026-05-27).
     The themed variants below (.status-on / .status-blocked /
     .status-needs-install) apply a tinted background that visually
     self-separates from the master toggle below; on those variants
     we drop the explicit border-bottom to avoid the "double line"
     look (tint-edge + 1px divider stacked). The plain off-state
     keeps a solid divider because there's no tint to do the work. */
  padding: 12px 18px;
  margin: 0;
  font-size: 0.86rem;
  line-height: 1.45;
  color: var(--text-secondary);
  border-bottom: 1px solid var(--border);
}
.push-notifications-status.status-on,
.push-notifications-status.status-blocked,
.push-notifications-status.status-unsupported,
.push-notifications-status.status-needs-install {
  border-bottom: 0;
}
.push-notifications-status.status-on {
  background: rgba(34, 197, 94, 0.10);
  border-color: rgba(34, 197, 94, 0.35);
  color: var(--text);
}
.push-notifications-status.status-blocked,
.push-notifications-status.status-unsupported {
  background: rgba(220, 38, 38, 0.08);
  border-color: rgba(220, 38, 38, 0.30);
  color: #991b1b;
}
[data-theme="dark"] .push-notifications-status.status-blocked,
[data-theme="dark"] .push-notifications-status.status-unsupported { color: #fca5a5; }
.push-notifications-status.status-needs-install {
  background: rgba(59, 130, 246, 0.08);
  border-color: rgba(59, 130, 246, 0.30);
  color: var(--text);
}

.push-categories {
  /* Solid top border = the hard visual break between the master
     "Enable push notifications" toggle and the per-category list.
     Stronger than the dashed inter-row dividers below so the
     "master controls everything below" hierarchy reads at a glance
     (Jimmy 2026-05-27). */
  border-top: 1px solid var(--border);
}
.push-category-row {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  padding: 12px 18px; /* horizontal padding matches .setting-item base */
  border-bottom: 1px dashed var(--border);
}
.push-category-row:last-child { border-bottom: 0; }
.push-category-row .setting-info { flex: 1; min-width: 0; }
.push-category-row.disabled .setting-info { opacity: 0.55; }
.push-category-row .toggle input:disabled + .toggle-slider {
  opacity: 0.45;
  cursor: not-allowed;
}

/* Birthday & important date reminders — sits as a plain .setting-item
   sibling of .push-categories inside #notifications-section. By
   default .setting-item uses 16/18 padding + solid border, which
   visually doesn't match the tighter row rhythm of the push
   categories above it. Style it as a row of the same shape so the
   card reads as one coherent list (Jimmy 2026-05-27).
   The top border is SOLID (not dashed) — Birthday is a different
   kind of notification (email-driven, works without push), so the
   separator marks the boundary out of the push-category group. */
#notifications-section .settings-card > .setting-item {
  padding: 12px 18px;
  border-top: 1px solid var(--border);
  border-bottom: 0;
}
/* The MASTER toggle is also a .setting-item child of .settings-card
   (it's the first one). It already separates itself from the status
   banner via the banner's own border-bottom — no top divider needed.
   Keep the comfortable 14px vertical padding so the master toggle
   reads as the prominent control it is. */
#notifications-section .settings-card > .setting-item:first-of-type {
  border-top: 0;
  padding: 14px 18px;
}

.push-ios-install {
  margin-top: 12px;
  padding: 10px 14px;
  background: rgba(59, 130, 246, 0.06);
  border-left: 3px solid rgba(59, 130, 246, 0.4);
  border-radius: 6px;
  font-size: 0.85rem;
  line-height: 1.5;
  color: var(--text);
}

/* ============================================================
   Messages (DM-P3, 2026-06-02) — CometChat-backed DMs + DeepL
   auto-translation. Ships dark (page-messages only reachable when
   window.QBIT_MESSAGING_ENABLED). Brand tokens: --text, --text-secondary,
   --border, --surface-2, accent gradient #3b82f6→#8b5cf6.
   ============================================================ */
.messaging-wrap { max-width: 980px; margin: 0 auto; padding: 16px; }
.messaging-header { margin-bottom: 14px; }
.messaging-title { font-size: 1.4rem; font-weight: 800; color: var(--text); margin: 0 0 4px; }
.messaging-subtitle { font-size: 0.86rem; color: var(--text-secondary); margin: 0; }

.messaging-layout {
  display: flex;
  gap: 16px;
  align-items: stretch;
  min-height: 60vh;
}
.messaging-thread-list-panel {
  flex: 0 0 300px;
  display: flex;
  flex-direction: column;
  /* Section container/border removed 2026-06-16 (Jimmy): the contacts list is the
     only thing on the DMs page now (the old "Start a chat" sibling section is gone),
     so the card wrapper isn't needed — the contact rows run full-width instead. */
}
.messaging-thread-list { overflow-y: auto; flex: 1; }
.messaging-thread-row {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  text-align: left;
  border: 0;
  background: transparent;
  padding: 12px 14px;
  cursor: pointer;
}
.messaging-thread-row:hover { background: var(--surface-2, rgba(0,0,0,0.03)); }
/* No dividers between conversation rows — match WhatsApp/Telegram (Scott 2026-06-15);
   hover + spacing separate them. */
.messaging-thread-avatar {
  flex: 0 0 36px; width: 36px; height: 36px; border-radius: 50%; overflow: hidden;
  display: inline-flex; align-items: center; justify-content: center;
  background: linear-gradient(135deg, #3b82f6, #8b5cf6); color: #fff;
  font-weight: 700; font-size: 0.9rem;
}
/* Real profile photo variant: covers the circle as a perfect round. Scott
   2026-06-14 — was rendering oval; needed explicit height + radius + no shrink
   (matches the round .header-avatar-img top-right). */
.messaging-thread-avatar-img { width: 36px; height: 36px; border-radius: 50%; object-fit: cover; background: none; flex-shrink: 0; }
/* Group avatar upload (Scott 2026-06-11): editable avatar in the group thread
   header — the avatar + a small camera badge, in a reset button. */
.messaging-group-avatar-edit {
  position: relative;
  display: inline-flex;
  padding: 0;
  border: none;
  background: none;
  cursor: pointer;
  border-radius: 50%;
  -webkit-tap-highlight-color: transparent;
}
.messaging-group-avatar-cam {
  position: absolute;
  right: -2px;
  bottom: -2px;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: var(--blue-500, #2563eb);
  color: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 2px solid var(--bg, #fff);
  box-sizing: border-box;
}
.messaging-thread-main { display: flex; flex-direction: column; min-width: 0; flex: 1; }
.messaging-thread-name { font-weight: 600; font-size: 0.9rem; color: var(--text); }
.messaging-thread-preview {
  font-size: 0.8rem; color: var(--text-secondary);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 220px;
}
/* Right-side date/time stamp + unread badge stacked (WhatsApp/Telegram, Scott 2026-06-15). */
.messaging-thread-meta { display: flex; flex-direction: column; align-items: flex-end; gap: 4px; flex-shrink: 0; margin-left: 8px; }
.messaging-thread-time { font-size: 0.72rem; color: var(--text-secondary); white-space: nowrap; }
.messaging-thread-unread {
  flex: 0 0 auto; min-width: 20px; height: 20px; padding: 0 6px;
  border-radius: 999px; background: #ef4444; color: #fff;
  font-size: 0.72rem; font-weight: 700;
  display: inline-flex; align-items: center; justify-content: center;
}

.messaging-conversation {
  flex: 1;
  border: 1px solid var(--border, rgba(0,0,0,0.08));
  border-radius: 14px;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  background: var(--surface, #fff);
}
.messaging-conversation-head {
  display: flex; align-items: center; gap: 10px;
  padding: 12px 14px; border-bottom: 1px solid var(--border, rgba(0,0,0,0.08));
  font-weight: 700; color: var(--text);
}
.messaging-conversation-back {
  border: 0; background: transparent; font-size: 1.2rem; cursor: pointer;
  color: var(--text-secondary); display: none;
}
.messaging-conversation-body {
  flex: 1; overflow-y: auto; padding: 16px;
  display: flex; flex-direction: column; gap: 8px;
}
.messaging-bubble-row { display: flex; }
.messaging-bubble-row.messaging-mine { justify-content: flex-end; }
.messaging-bubble-row.messaging-theirs { justify-content: flex-start; }
.messaging-bubble {
  max-width: 72%; padding: 9px 13px; border-radius: 16px;
  font-size: 0.92rem; line-height: 1.4; word-wrap: break-word;
}
.messaging-mine .messaging-bubble {
  background: linear-gradient(135deg, #3b82f6, #8b5cf6); color: #fff;
  border-bottom-right-radius: 4px;
}
.messaging-theirs .messaging-bubble {
  background: var(--surface-2, rgba(0,0,0,0.05)); color: var(--text);
  border-bottom-left-radius: 4px;
}
.messaging-bubble-text { display: block; white-space: pre-wrap; }
/* Group sender identity (Bucket 2b, Scott 2026-06-14): avatar + colored name
   once per run; the sender's bubbles indent to line up under the name. */
.messaging-sender-head { display: flex; align-items: center; gap: 7px; margin: 8px 0 1px; }
.messaging-sender-head .messaging-thread-avatar { flex: 0 0 26px; width: 26px; height: 26px; font-size: 0.78rem; }
.messaging-sender-name { font-size: 0.78rem; font-weight: 700; color: var(--blue-500, #3b82f6); }
.messaging-bubble-row.messaging-group-theirs { padding-left: 33px; }
/* Reactions (Bucket 3, Scott 2026-06-14) */
.messaging-bubble { position: relative; }
/* Touch: long-press a bubble = react. Suppress the native iOS text-selection
   callout (the Copy / Writing Tools / Look Up bar) so it can't hijack the
   gesture. Desktop keeps text selectable — reactions there use the hover
   button, not long-press. Scott 2026-06-14. */
@media (hover: none) {
  .messaging-bubble {
    -webkit-touch-callout: none;
    -webkit-user-select: none;
    user-select: none;
  }
}
.messaging-react-btn {
  position: absolute; top: -11px; right: -8px;
  width: 24px; height: 24px; border-radius: 50%; padding: 0;
  display: flex; align-items: center; justify-content: center;
  border: 1px solid var(--border); background: var(--bg-card); color: var(--text-secondary);
  line-height: 0; cursor: pointer;
  opacity: 0; transition: opacity .12s; box-shadow: var(--shadow);
}
.messaging-bubble-row:hover .messaging-react-btn, .messaging-react-btn:focus { opacity: 1; }
.messaging-react-btn:hover { color: var(--text); }
/* Touch: no persistent button — long-press the bubble opens the picker. */
@media (hover: none) { .messaging-react-btn { display: none; } }
.messaging-mine .messaging-react-btn { left: -8px; right: auto; }
/* Reactions as iMessage-style "tapback" badges on the bubble's top corner
   (Scott 2026-06-14), not a pill strip below. Anchored to the bubble so the
   group left-padding doesn't shift them. */
.messaging-reactions { position: absolute; top: -13px; display: flex; gap: 2px; z-index: 3; }
.messaging-theirs .messaging-reactions { right: 2px; }
.messaging-mine .messaging-reactions { left: 2px; }
.messaging-reaction {
  display: inline-flex; align-items: center; gap: 2px;
  padding: 2px 7px; border-radius: 13px; cursor: pointer;
  border: 1px solid var(--border); background: var(--bg-card); color: var(--text);
  box-shadow: 0 1px 3px rgba(0,0,0,0.2);
  font-size: 0.82rem; line-height: 1.15; font-family: inherit; font-variant-numeric: tabular-nums;
}
.messaging-reaction.mine { border-color: var(--blue-400, #60a5fa); background: rgba(59,130,246,0.2); }
/* Breathing room above a bubble carrying a tapback so it can't collide with the
   message above. */
.messaging-bubble-row.has-reactions { margin-top: 15px; }
/* Long-press / hover action menu: emoji row on top, actions (Copy) below. */
.messaging-reaction-picker {
  position: absolute; z-index: 9999;
  display: flex; flex-direction: column; gap: 4px;
  padding: 6px; border-radius: 16px;
  background: var(--bg-card); border: 1px solid var(--border); box-shadow: var(--shadow-lg);
}
.messaging-reaction-emojis { display: flex; gap: 2px; }
.messaging-reaction-opt { border: none; background: transparent; font-size: 1.35rem; line-height: 1; cursor: pointer; padding: 4px; border-radius: 8px; }
.messaging-reaction-opt:hover { background: var(--bg-soft); transform: scale(1.15); }
.messaging-reaction-actions { display: flex; flex-direction: column; }
.messaging-reaction-emojis + .messaging-reaction-actions { border-top: 1px solid var(--border); margin-top: 2px; padding-top: 4px; }
.messaging-reaction-action {
  display: flex; align-items: center; gap: 8px; width: 100%;
  padding: 8px 10px; border: none; border-radius: 8px; cursor: pointer;
  background: transparent; color: var(--text); font-family: inherit; font-size: 0.9rem; font-weight: 500;
}
.messaging-reaction-action:hover { background: var(--bg-soft); }
.messaging-translate-chip {
  display: inline-block; margin-top: 5px;
  font-size: 0.7rem; font-weight: 600;
  color: var(--text-secondary); background: rgba(0,0,0,0.04);
  border: 0; border-radius: 999px; padding: 2px 8px; cursor: pointer;
}
.messaging-mine .messaging-translate-chip { color: rgba(255,255,255,0.85); background: rgba(255,255,255,0.18); }
.messaging-translate-chip.is-original { opacity: 0.7; }

/* Phase 1 — conversation-header avatar, per-message time, day dividers */
.messaging-conversation-avatar { flex: 0 0 36px; width: 36px; height: 36px; border-radius: 50%; overflow: hidden; display: inline-flex; }
/* Header name: take remaining width + truncate, so it never collides with the
   avatar circle or the Report/Block actions (Jimmy 2026-06-10 "AAndrea" overlap). */
.messaging-conversation-title { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* "Manage connections & requests" link on the Messages page (menu consolidation). */
.messaging-manage-link { display: inline-block; margin-top: 6px; font-size: 0.85rem; font-weight: 600; color: #3b82f6; cursor: pointer; text-decoration: none; }
.messaging-manage-link:hover { text-decoration: underline; }
.messaging-bubble-time {
  display: block; margin-top: 3px; font-size: 0.66rem; line-height: 1;
  color: var(--text-secondary); text-align: right;
}
.messaging-mine .messaging-bubble-time { color: rgba(255,255,255,0.8); }
/* Telegram-style delivery/read ticks on outgoing bubbles: ✓ sent · ✓✓ delivered
   (faded) · ✓✓ read (bright white). Only ever render inside .messaging-mine. */
.messaging-tick { margin-left: 5px; font-size: 0.7rem; letter-spacing: -1.5px; }
.messaging-mine .messaging-tick.is-sent,
.messaging-mine .messaging-tick.is-delivered { color: rgba(255,255,255,0.6); }
.messaging-mine .messaging-tick.is-read { color: #ffffff; font-weight: 700; }
.messaging-date-divider { align-self: center; margin: 6px 0; }
.messaging-date-divider span {
  font-size: 0.72rem; font-weight: 600; color: var(--text-secondary);
  background: var(--surface-2, rgba(0,0,0,0.05)); padding: 3px 12px; border-radius: 999px;
}

.messaging-compose {
  display: flex; gap: 8px; align-items: center; padding: 12px;
  border-top: 1px solid var(--border, rgba(0,0,0,0.08));
}
.messaging-compose-input {
  flex: 1; resize: none; border: 1px solid var(--border, rgba(0,0,0,0.15));
  border-radius: 999px; padding: 9px 14px; font-size: 0.92rem; font-family: inherit;
  max-height: 120px; background: var(--surface, #fff); color: var(--text);
}
.messaging-compose-input:focus { outline: 0; border-color: #3b82f6; box-shadow: 0 0 0 3px rgba(59,130,246,0.15); }
.messaging-compose-send {
  flex: 0 0 auto; border: 0; border-radius: 999px; padding: 9px 18px;
  background: linear-gradient(135deg, #3b82f6, #8b5cf6); color: #fff;
  font-weight: 700; font-size: 0.9rem; cursor: pointer;
}
.messaging-compose-send:hover { filter: brightness(1.05); }
.messaging-compose-status { font-size: 0.78rem; color: #b91c1c; padding: 0 14px; min-height: 0; }

/* ---- Chat attachments (Phase 1: images everyone · docs adults-only) ---- */
.messaging-attach-input { display: none; }
.messaging-attach-btn {
  flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center;
  width: 38px; height: 38px; border-radius: 999px; border: 1px solid var(--border, rgba(0,0,0,0.15));
  background: var(--surface, #fff); color: var(--text-secondary, #6b7280); cursor: pointer;
  transition: background 0.15s, color 0.15s, border-color 0.15s;
}
.messaging-attach-btn svg { width: 19px; height: 19px; display: block; }
.messaging-attach-btn:hover { color: #3b82f6; border-color: #3b82f6; background: rgba(59,130,246,0.06); }
/* Approved media inside a bubble. */
.messaging-media { margin: 2px 0 4px; }
.messaging-media-img {
  max-width: 240px; max-height: 280px; width: auto; height: auto;
  border-radius: 10px; display: block; cursor: pointer;
  /* White backdrop so transparent PNGs read cleanly instead of showing the
     bubble gradient through them; invisible behind full-bleed photos (Jimmy
     2026-06-18). */
  background: #fff;
}
.messaging-media-loading, .messaging-media-err {
  display: inline-block; font-size: 0.8rem; color: var(--text-secondary, #6b7280);
  padding: 6px 0;
}
.messaging-media-err { color: #b91c1c; }
.messaging-media-doc {
  display: inline-flex; align-items: center; gap: 7px; text-decoration: none;
  padding: 8px 12px; border-radius: 10px; background: rgba(59,130,246,0.08);
  color: #3b82f6; font-size: 0.86rem; font-weight: 600; max-width: 240px;
  border: none; cursor: pointer; font-family: inherit; text-align: left;
}
.messaging-media-doc svg { width: 18px; height: 18px; flex: 0 0 auto; }
.messaging-media-doc span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.messaging-media-doc:hover { background: rgba(59,130,246,0.14); }

/* In-app attachment viewer — tap a chat photo to preview it here, then Save
   (download the file) or Share (hand the FILE to the OS share sheet). This
   replaces opening the raw, expiring, ungated presigned S3 URL in a new tab.
   (Scott 2026-06-22) */
.qbit-media-viewer {
  position: fixed; inset: 0; z-index: 99999;
  background: rgba(0,0,0,0.88);
  display: flex; flex-direction: column;
  align-items: center; justify-content: center; padding: 20px;
}
.qbit-media-viewer-stage {
  display: flex; align-items: center; justify-content: center;
  max-width: 94vw; max-height: 76vh;
}
.qbit-media-viewer-stage img {
  max-width: 94vw; max-height: 76vh; width: auto; height: auto;
  object-fit: contain; border-radius: 8px; background: #fff;
}
.qbit-media-viewer-loading { color: #e5e7eb; font-size: 0.9rem; }
.qbit-media-viewer-close {
  position: absolute; top: 14px; right: 16px;
  background: none; border: none; color: #fff;
  font-size: 32px; line-height: 1; cursor: pointer; padding: 4px 10px;
}
.qbit-media-viewer-bar { display: flex; gap: 12px; margin-top: 18px; }
.qbit-media-viewer-btn {
  border: 1px solid rgba(255,255,255,0.4); background: rgba(255,255,255,0.1);
  color: #fff; font: inherit; font-weight: 600; font-size: 0.95rem;
  padding: 10px 22px; border-radius: 999px; cursor: pointer;
}
.qbit-media-viewer-btn:hover { background: rgba(255,255,255,0.2); }
.qbit-media-viewer-btn-primary { background: var(--blue-500, #3b82f6); border-color: var(--blue-500, #3b82f6); }
.qbit-media-viewer-btn-primary:hover { background: var(--blue-600, #2563eb); }
/* "Reviewing…" placeholder bubble while moderation runs (sender side). */
.messaging-media-pending { opacity: 0.7; }
.messaging-media-reviewing {
  display: block; font-size: 0.72rem; color: var(--text-secondary, #6b7280);
  margin-top: 3px; font-style: italic;
}
.messaging-empty, .messaging-loading, .messaging-error { padding: 24px 16px; text-align: center; color: var(--text-secondary); font-size: 0.88rem; }
.messaging-error { color: #b91c1c; }
/* Diagnostic line under the messaging error — shows the failing stage + a short
   hint so a screenshot is actionable (kid/tester devices have no console). Kept
   small + muted so it never reads as the primary message. (2026-06-26) */
.messaging-error-detail { margin-top: 10px; font-size: 0.66rem; line-height: 1.35; color: var(--text-secondary); opacity: 0.72; word-break: break-word; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
/* "Try again" affordance on the messaging error state — lets the user re-run
   CometChat login after a transient SDK/network blip without a full reload. */
.messaging-retry-btn {
  display: inline-block;
  margin-top: 12px;
  padding: 8px 18px;
  border: none;
  border-radius: 999px;
  background: var(--blue-500, #2563eb);
  color: #fff;
  font-family: inherit;
  font-size: 0.88rem;
  font-weight: 600;
  cursor: pointer;
}
.messaging-retry-btn:active { transform: translateY(1px); }

@media (max-width: 720px) {
  /* Phones: edge-to-edge like every other page — drop the desktop wrap inset so the
     list aligns with the standard .page gutter (Jimmy 2026-06-16). Tablet/desktop
     keep the inset + the two-column layout. */
  .messaging-wrap { padding: 0; }
  .messaging-layout { flex-direction: column; min-height: 0; }
  /* Fill the screen below the page header. Was capped at max-height:40vh, which
     left a short contact list with the global footer peeking through underneath
     (Scott/Jimmy 2026-06-16). */
  .messaging-thread-list-panel { flex: 1 1 auto; flex-basis: auto; min-height: 60vh; }
  .messaging-thread-list-panel.messaging-panel-collapsed { display: none; }
  /* Open conversation = full-screen chat on phones: the composer pins to the
     bottom and the page footer/gaps can't intrude. height/top are overridden
     inline by syncChatViewport() so the composer stays above the on-screen
     keyboard (iOS doesn't resize the layout viewport for it). (Jimmy 2026-06-09) */
  .messaging-conversation:not([hidden]) {
    position: fixed; left: 0; right: 0; top: 0;
    height: 100vh; height: 100dvh; min-height: 0;
    z-index: 1200; border: 0; border-radius: 0;
    padding-bottom: env(safe-area-inset-bottom);
  }
  .messaging-conversation-back { display: inline-block; }
  .messaging-thread-preview { max-width: 60vw; }
}

/* Messages — DM-P4 block/report actions + parental read-only viewer */
.messaging-conversation-actions { margin-left: auto; display: flex; gap: 6px; }
.messaging-action-btn {
  border: 1px solid var(--border, rgba(0,0,0,0.15)); background: transparent;
  color: var(--text-secondary); font-size: 0.74rem; font-weight: 600;
  border-radius: 999px; padding: 4px 10px; cursor: pointer;
}
.messaging-action-btn:hover { color: #b91c1c; border-color: #b91c1c; }

.messaging-parent-overlay {
  position: fixed; inset: 0; z-index: 99998;
  background: rgba(15,23,42,0.45); display: flex; align-items: center; justify-content: center; padding: 20px;
}
.messaging-parent-card {
  background: var(--surface, #fff); border-radius: 16px; width: 100%; max-width: 560px;
  max-height: 80vh; display: flex; flex-direction: column; overflow: hidden;
  box-shadow: 0 18px 48px rgba(15,23,42,0.25);
}
.messaging-parent-head {
  display: flex; align-items: center; justify-content: space-between;
  padding: 14px 16px; border-bottom: 1px solid var(--border, rgba(0,0,0,0.08));
  font-weight: 700; color: var(--text);
}
.messaging-parent-head button { border: 0; background: transparent; font-size: 1.1rem; cursor: pointer; color: var(--text-secondary); }
.messaging-parent-body { overflow-y: auto; padding: 8px; }
.messaging-parent-thread {
  display: flex; flex-direction: column; gap: 2px; width: 100%; text-align: left;
  border: 0; background: transparent; padding: 11px 12px; cursor: pointer;
  border-bottom: 1px solid var(--border, rgba(0,0,0,0.06));
}
.messaging-parent-thread:hover { background: var(--surface-2, rgba(0,0,0,0.03)); }
/* "Group" tag in the parent message view (KG-2 2026-06-14) */
.messaging-parent-grouptag {
  display: inline-block; margin-left: 6px; padding: 1px 7px; border-radius: 8px;
  background: rgba(59,130,246,0.14); color: var(--blue-600, #2563eb);
  font-size: 0.66rem; font-weight: 700; vertical-align: middle;
}
.messaging-parent-backlink { border: 0; background: transparent; color: #3b82f6; font-weight: 600; font-size: 0.84rem; cursor: pointer; padding: 8px 4px; }
.messaging-parent-transcript { display: flex; flex-direction: column; gap: 8px; padding: 8px; }

/* SCOTT (2026-06-02) — "off" stop on the sensitivity sliders. Distinct
   muted/red active state so "off" reads as a disable, not just another
   sensitivity level. (Self-harm never renders this button.) */
.child-settings-safety-sensitivity-off.is-active {
  background: linear-gradient(135deg, #6b7280, #9ca3af);
  color: #fff;
  box-shadow: inset 0 0 0 1px rgba(0,0,0,0.06);
}

/* =============================================================
 * Photo-edit consent modal ("copyright checkbox") — PhotoEditConsent
 * in app.js. One-time affirmation before the first AI photo upload.
 * Mirrors the app's overlay+card pattern; auto-themes via --bg-card.
 * (SPEC-PHOTO-EDIT-LIKENESS, 2026-06-25)
 * ============================================================= */
.pe-consent-overlay {
  position: fixed; inset: 0; z-index: 100001;
  display: flex; align-items: center; justify-content: center;
  background: rgba(0, 0, 0, 0.5); padding: 16px;
  -webkit-backdrop-filter: blur(2px); backdrop-filter: blur(2px);
}
.pe-consent-modal {
  background: var(--bg-card, #fff);
  color: var(--text, #1f2937);
  border-radius: 18px;
  box-shadow: 0 12px 40px rgba(0, 0, 0, 0.28);
  max-width: 440px; width: 100%;
  padding: 28px 24px 22px;
  text-align: center;
  animation: pe-consent-pop .18s ease-out;
}
@keyframes pe-consent-pop { from { opacity: 0; transform: translateY(8px) scale(.98); } to { opacity: 1; transform: none; } }
.pe-consent-icon { font-size: 40px; line-height: 1; margin-bottom: 10px; }
.pe-consent-title { margin: 0 0 8px; font-size: 20px; font-weight: 800; }
.pe-consent-body { margin: 0 0 18px; font-size: 14.5px; line-height: 1.55; color: var(--text-secondary, #6b7280); }
.pe-consent-check {
  display: flex; gap: 10px; text-align: left;
  background: var(--bg-secondary, rgba(0,0,0,.03));
  border: 1px solid var(--border, #e5e7eb);
  border-radius: 12px; padding: 12px 14px; margin-bottom: 14px;
  font-size: 13.5px; line-height: 1.5; cursor: pointer;
}
.pe-consent-check input { margin-top: 2px; width: 18px; height: 18px; flex: 0 0 auto; accent-color: #3b82f6; cursor: pointer; }
.pe-consent-fineprint { margin: 0 0 18px; font-size: 12.5px; color: var(--text-muted, #9ca3af); }
.pe-consent-link { color: #3b82f6; font-weight: 600; text-decoration: none; }
.pe-consent-link:hover { text-decoration: underline; }
.pe-consent-actions { display: flex; gap: 10px; }
.pe-consent-cancel, .pe-consent-agree {
  flex: 1; padding: 12px 16px; border-radius: 999px; font-size: 15px; font-weight: 700; cursor: pointer; border: none;
}
.pe-consent-cancel { background: var(--bg-secondary, #f3f4f6); color: var(--text-secondary, #6b7280); }
.pe-consent-cancel:hover { background: var(--border, #e5e7eb); }
.pe-consent-agree { background: linear-gradient(135deg, #3b82f6, #8b5cf6); color: #fff; box-shadow: 0 4px 14px rgba(59,130,246,0.35); }
.pe-consent-agree:disabled { opacity: .5; cursor: not-allowed; box-shadow: none; }
.pe-consent-agree:not(:disabled):hover { filter: brightness(1.05); }

/* ── Desktop drag & drop (2026-07-10) ─────────────────────────────
 * Full-window dashed outline while a file is dragged over the app;
 * the drop router in app.js feeds the existing picker inputs. */
body.file-drag-active::after {
  content: '';
  position: fixed;
  inset: 8px;
  border: 2px dashed var(--blue-500, #3b82f6);
  border-radius: 16px;
  background: rgba(59, 130, 246, 0.06);
  pointer-events: none;
  z-index: 4000;
}


/* Send-by-email block inside the share modal (Jimmy 2026-07-11) —
   Qbit emails the styled template server-side. Divider separates it
   from the handoff options above. */
.share-email-block {
  margin-top: 14px;
  padding-top: 12px;
  border-top: 1px solid var(--border);
}
.share-email-toggle {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  width: 100%;
  padding: 11px 14px;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--bg-card);
  color: var(--text);
  font-size: 0.9rem;
  font-weight: 600;
  cursor: pointer;
  transition: background 0.15s ease;
}
.share-email-toggle:hover { background: rgba(59, 130, 246, 0.06); }
.share-email-form {
  display: flex;
  gap: 8px;
}
.share-email-input {
  flex: 1;
  min-width: 0;
  padding: 10px 12px;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--bg-card);
  color: var(--text);
  font-size: 0.9rem;
}
.share-email-input:focus { outline: none; border-color: var(--blue-500, #3b82f6); }
.share-email-send {
  padding: 10px 18px;
  border: none;
  border-radius: 10px;
  background: linear-gradient(135deg, #3b82f6, #8b5cf6);
  color: #fff;
  font-size: 0.9rem;
  font-weight: 700;
  cursor: pointer;
}
.share-email-send:disabled { opacity: 0.55; cursor: default; }
.share-email-status {
  margin-top: 8px;
  font-size: 0.8rem;
  color: #b91c1c;
}


/* ═══════════ Voice Mode overlay (Jimmy 2026-07-11) ═══════════
   brand/VOICE-MODE-SPEC.md — BLUE orb = Qbit, PURPLE = user.
   Deliberately one immersive dark look in both themes (like the
   reference experience); colors carry the meaning. */
/* ═══ Voice Mode v5 (Jimmy 2026-07-15) — transcript-first ═══
   SOLID theme background (the v3 frost read as blurry leftovers next
   to the reference app's crisp in-overlay transcript). The session
   reads TOP-DOWN like a normal chat and auto-scrolls; the glow (the
   orb) hugs the bottom edge as the living status light + tap target.
   Color law unchanged: purple=user, blue=Qbit. */
.voice-overlay {
  position: fixed;
  inset: 0;
  z-index: 20000;
  height: 100dvh;
  display: flex;
  flex-direction: column;
  align-items: center;
  background: var(--bg);
  color: var(--text);
}
/* Faintest brand wash so the solid screen still feels like Qbit. */
.voice-overlay::before {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  background:
    radial-gradient(60% 40% at 15% 0%, rgba(59, 130, 246, 0.05), transparent 70%),
    radial-gradient(60% 40% at 85% 0%, rgba(139, 92, 246, 0.05), transparent 70%);
}
/* The glow canvas — bottom edge, BEHIND everything, never a tap
   target itself (the dedicated .voice-orb-tap button is). */
#voice-orb {
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
}
/* ── header (Scott 2026-07-15): brand mark + feature name + X ── */
.voice-header {
  position: relative;
  z-index: 2;
  width: 100%;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: max(10px, env(safe-area-inset-top)) 14px 10px;
  border-bottom: 1px solid var(--border);
  background: var(--bg);
}
/* Brand lockup = the app header's own .header-logo-btn/.header-logo
   (mascot + gradient wordmark), reused verbatim (Jimmy 2026-07-15) —
   tapping it exits to Home. The title sits DEAD CENTER of the window,
   independent of the lockup's width. */
.voice-header-title {
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  max-width: 46%;
  font-size: 1rem;
  font-weight: 700;
  color: #3b82f6;                    /* brand blue — Qbit's channel */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.voice-header .voice-close {
  margin-left: auto;
  width: 38px;
  height: 38px;
}
.voice-header .voice-close svg { width: 18px; height: 18px; }
/* ── the transcript: the star of the screen ── */
.voice-transcript {
  position: relative;
  z-index: 1;
  width: min(680px, 100vw);
  flex: 1 1 0;
  min-height: 0;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  justify-content: flex-start;      /* start at the TOP, read down */
  gap: 14px;
  padding: 16px 20px 24px;          /* header owns the safe-area now */
  text-align: left;
  scrollbar-width: thin;
  scrollbar-color: var(--border) transparent;
}
.vt-line {
  font-size: 1.02rem;
  line-height: 1.55;
  white-space: pre-wrap;
  overflow-wrap: break-word;
}
/* User turns: right-aligned bubble with the waveform glyph — the
   reference-app grammar, in our purple. */
.vt-user {
  align-self: flex-end;
  max-width: 88%;
  display: flex;
  align-items: flex-start;
  gap: 8px;
  padding: 10px 14px;
  border-radius: 16px;
  border-bottom-right-radius: 6px;
  background: rgba(139, 92, 246, 0.10);
  border: 1px solid rgba(139, 92, 246, 0.18);
  color: var(--text);
  font-weight: 500;
}
.vt-user .vt-glyph {
  flex-shrink: 0;
  width: 15px;
  height: 15px;
  margin-top: 3px;
  color: #8b5cf6;               /* purple — the user's channel */
}
.vt-user .vt-glyph svg { width: 100%; height: 100%; display: block; }
/* Qbit turns: plain flowing text on the left — no bubble, larger,
   words brightening BLUE as she speaks them. */
.vt-qbit {
  align-self: flex-start;
  max-width: 94%;
  color: var(--text-secondary);
  font-size: 1.08rem;
}
.vt-qbit .vt-w { opacity: 0.45; transition: opacity 0.12s ease, color 0.12s ease; }
.vt-qbit .vt-w.on { opacity: 1; color: var(--text); }
.vt-qbit .vt-w.on.vt-now { color: #3b82f6; }  /* blue — the word being spoken */
/* The streaming tail: text that has GENERATED but not yet been spoken
   paints immediately in a muted style — the answer visibly streams
   top-down the moment it exists (Jimmy 2026-07-15), then brightens
   word-by-word as her voice reaches it. */
.vt-qbit .vt-pending { opacity: 0.38; }
/* ── the foot: status + hint + control row, above the glow ── */
.voice-foot {
  position: relative;
  z-index: 1;
  width: min(680px, 100vw);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  padding: 6px 20px max(18px, env(safe-area-inset-bottom));
  text-align: center;
}
.voice-status {
  font-size: 1rem;
  font-weight: 600;
  min-height: 1.4em;
  color: var(--text);
}
.voice-hint {
  font-size: 0.78rem;
  color: var(--text-muted);
  min-height: 1.2em;
  line-height: 1.5;
}
.voice-controls {
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}
.voice-ctl {
  width: 46px;
  height: 46px;
  border-radius: 50%;
  border: 1px solid var(--border);
  background: var(--bg-card);
  color: var(--text);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  flex-shrink: 0;
}
.voice-ctl svg { width: 20px; height: 20px; }
.voice-ctl:hover { background: rgba(59, 130, 246, 0.08); }
/* Stop (Scott 2026-07-15): the visible "stop/pause the conversation"
   control — video-player square, slightly weighted so it reads as THE
   button to press. */
.voice-stop {
  border-color: rgba(59, 130, 246, 0.35);
  color: #3b82f6;
}
.voice-stop svg { width: 18px; height: 18px; }
/* v5.3 (Jimmy 2026-07-15): the square mirrors the conversation state —
   SOLID while live, OUTLINE while paused (press again to resume). */
.voice-stop.paused svg rect {
  fill: none;
  stroke: currentColor;
  stroke-width: 2.4;
}
/* The orb's tap target: an invisible wide button over the glow between
   the gear and the X — end your turn / interrupt lives HERE, so the
   transcript above scrolls freely without accidental interrupts. */
.voice-orb-tap {
  flex: 1 1 auto;
  height: 64px;
  border: none;
  background: transparent;
  cursor: pointer;
  border-radius: 20px;
}
.voice-retry {
  padding: 11px 30px;
  border: none;
  border-radius: 999px;
  background: linear-gradient(135deg, #3b82f6, #8b5cf6);
  color: #fff;
  font-size: 0.92rem;
  font-weight: 700;
  cursor: pointer;
}
/* ── voice settings sheet (gear) ── */
.voice-settings-sheet {
  position: absolute;
  left: 16px;
  bottom: calc(84px + env(safe-area-inset-bottom));
  z-index: 2;
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: 14px;
  padding: 14px 16px;
  box-shadow: 0 8px 30px rgba(0, 0, 0, 0.18);
  text-align: left;
}
.voice-settings-row {
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--text-secondary);
  margin-bottom: 8px;
}
.voice-speed-btns { display: flex; gap: 8px; }
.voice-speed-btns button {
  padding: 7px 14px;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--bg);
  color: var(--text);
  font-size: 0.85rem;
  font-weight: 600;
  cursor: pointer;
}
.voice-speed-btns button.active {
  background: linear-gradient(135deg, #3b82f6, #8b5cf6);
  border-color: transparent;
  color: #fff;
}


/* Settings → Microphone row (Jimmy 2026-07-11) — voice-mode mic status.
   Not a toggle on purpose: the OS owns this permission; the button can
   only ASK (trigger the OS prompt). */
.mic-settings-btn {
  flex-shrink: 0;
  padding: 9px 18px;
  border: none;
  border-radius: 999px;
  background: linear-gradient(135deg, #3b82f6, #8b5cf6);
  color: #fff;
  font-size: 0.85rem;
  font-weight: 700;
  cursor: pointer;
}
.mic-settings-btn:disabled { opacity: 0.55; cursor: default; }
.mic-settings-help {
  /* settings-card has no padding of its own — rows carry 16px 18px,
     so the help line matches that inset. */
  margin: 0;
  padding: 12px 18px 16px;
  border-top: 1px solid var(--border);
  line-height: 1.5;
}


/* Lean Docs/Photos headers (Jimmy 2026-07-11): title + compact upload
   button clustered left on one row; the pill switcher is retired. */
/* Compact upload pill under the page header (Jimmy 2026-07-11, final
   order: title → sub-text → button). Docs: it sits between the hint and
   the folder rail; Photos: between the header and the gallery. */
.docs-upload-compact {
  width: auto;
  flex: 0 0 auto;
  padding: 9px 18px;
  font-size: 0.88rem;
  margin: 2px 0 10px;
}
/* Inside the header block (Photos) the pill hugs the sub-text — same
   tight rhythm as title→sub-text (Jimmy 2026-07-11); the header's own
   bottom padding provides the gap to the content below. */
.settings-header .docs-upload-compact { margin: 8px 0 0; }


/* Dictation states (Jimmy 2026-07-11): MIC = speech→text into the input.
   Recording pulses red; transcribing dims while the text lands. */
/* TWO buttons, never three (Jimmy 2026-07-13, Claude parity): MIC
   dictate (right:52) + ONE morphing slot at right:6 — the waveform
   (Voice Mode) when the box is empty, the send arrow once there's
   text. updateComposerMorph() in app.js drives the swap. */
.chat-dictate-btn { right: 52px; }

/* The waveform in the morph slot wears the same filled gradient as the
   send button so the swap reads as ONE button changing icon — exactly
   Claude's pattern (filled circle: waveform ↔ arrow). */
.chat-mic-btn.voice-morph {
  right: 6px;
  background: linear-gradient(135deg, var(--blue-500), var(--purple-500));
  color: #fff;
}
.chat-mic-btn.voice-morph:hover {
  color: #fff;
  background: linear-gradient(135deg, var(--blue-500), var(--purple-500));
  box-shadow: 0 4px 12px rgba(59, 130, 246, 0.4);
}
.chat-dictate-btn.dictating {
  color: #ef4444;
  animation: qbit-dictate-pulse 1.2s ease-in-out infinite;
}
@keyframes qbit-dictate-pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.45; }
}
.chat-dictate-btn.transcribing {
  opacity: 0.5;
  pointer-events: none;
}

/* Dictation parked (Scott 2026-07-13, QBIT_DICTATION_ENABLED=false in
   index.html): hide the mic button and reclaim its input padding. The
   composer is [+ attach] · input · [morph voice/send] — two controls.
   Revival = flip the flag; these rules become inert. */
.no-dictation .chat-dictate-btn { display: none !important; }
.no-dictation .search-input { padding-right: 56px; }
.no-dictation .chat-input { padding-right: 56px; }


/* Conversation-level action row (Jimmy 2026-07-13) — one cluster for
   the whole thread, rendered after the last answer. The host MUST be
   full-width: .chat-thread is a centering flex column (align-items:
   center), so without an explicit width the host shrinks to content
   and everything bunches in the middle. width+max-width mirror
   .chat-thread .qbit-turn so the row tracks the turn cards' edges;
   .turn-actions-share-group's margin-left:auto then pushes the five
   action icons right while the thumbs stay left — the exact
   .history-row-bottom layout. */
.conversation-actions-host {
  width: 100%;
  max-width: 760px;
  margin: 0 0 6px;
}
.conversation-actions-host .turn-actions {
  width: 100%;
}

/* ── EV check-your-email state (Scott 2026-07-16) ──
   .auth-checkmail-mode on the modal root swaps the whole body for the
   "we sent you a link" card: everything else hides, the card shows.
   The account does not exist until the emailed link is clicked. */
.auth-checkmail-mode .auth-bonus-banner,
.auth-checkmail-mode .auth-consent,
.auth-checkmail-mode .auth-oauth-row,
.auth-checkmail-mode .auth-divider,
.auth-checkmail-mode .auth-form,
.auth-checkmail-mode .auth-toggle,
.auth-checkmail-mode .auth-kid-row {
  display: none !important;
}
.auth-checkmail-mode .auth-check-email { display: block !important; }
.auth-check-email {
  text-align: center;
  padding: 6px 4px 2px;
}
.auth-check-email-icon { font-size: 44px; line-height: 1; margin-bottom: 10px; }
.auth-check-email-title {
  margin: 0 0 10px;
  font-size: 1.2rem;
  font-weight: 800;
  color: var(--text);
}
.auth-check-email-body {
  margin: 0 0 6px;
  font-size: 0.95rem;
  line-height: 1.5;
  color: var(--text);
  overflow-wrap: anywhere;      /* long emails wrap, never overflow */
}
.auth-check-email-sub {
  margin: 0 0 18px;
  font-size: 0.85rem;
  line-height: 1.5;
  color: var(--text-muted);
}
.auth-check-email .auth-submit-btn { width: 100%; }
.auth-check-email-back {
  display: inline-block;
  margin-top: 12px;
}

/* ================================================================
 * Cookie notice banner (legal, 2026-07-22)
 * z-index 950: above the bottom tab bar (850), below modals (1000)
 * and the Qbit toast (1100). Both buttons intentionally carry equal
 * visual weight — decline must be as easy as accept.
 * ================================================================ */
.cookie-banner {
  position: fixed;
  left: 12px;
  right: 12px;
  bottom: calc(12px + env(safe-area-inset-bottom, 0px));
  z-index: 950;
  background: var(--bg);
  border-radius: 16px;
  box-shadow: 0 8px 30px rgba(0,0,0,0.28);
  padding: 16px 18px;
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  align-items: center;
  max-width: 720px;
  margin: 0 auto;
}
.cookie-banner[hidden] { display: none; }

/* Cookie banner close (2026-07-23). Absolute inside the fixed banner;
   the text block gets right padding so long copy never runs under it. */
.cookie-banner-close {
  position: absolute;
  top: 8px;
  right: 10px;
  width: 32px;
  height: 32px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: none;
  background: none;
  color: var(--text-muted, #6b7280);
  font-size: 24px;
  line-height: 1;
  cursor: pointer;
  border-radius: 8px;
}
.cookie-banner-close:hover { background: rgba(0,0,0,0.06); color: var(--text); }
.cookie-banner-close:focus-visible { outline: 2px solid var(--blue-500, #3b82f6); outline-offset: 2px; }
.cookie-banner .cookie-banner-text { padding-right: 34px; }

.cookie-banner-text { flex: 1 1 340px; min-width: 0; }
.cookie-banner-title { display: block; margin-bottom: 4px; font-size: 0.95rem; }
.cookie-banner-body { margin: 0; font-size: 0.85rem; line-height: 1.45; opacity: 0.9; }
.cookie-banner-body a { font-weight: 600; margin-left: 4px; white-space: nowrap; }
.cookie-banner-actions { display: flex; gap: 10px; flex: 0 0 auto; margin-left: auto; }
.cookie-btn {
  padding: 10px 22px;
  border-radius: 999px;
  font-size: 0.9rem;
  font-weight: 700;
  cursor: pointer;
  border: none;
  transition: transform 0.15s ease;
}
.cookie-btn:active { transform: scale(0.97); }
.cookie-btn-accept {
  background: linear-gradient(135deg, var(--blue-500), var(--purple-500, #8b5cf6));
  color: #ffffff;
}
.cookie-btn-decline {
  background: transparent;
  color: inherit;
  border: 2px solid var(--blue-500);
  padding: 8px 20px; /* border compensates — same rendered height as accept */
}
@media (max-width: 520px) {
  .cookie-banner-actions { width: 100%; margin-left: 0; }
  .cookie-btn { flex: 1 1 0; text-align: center; }
}

/* "Trouble installing?" link on the PWA install banner (2026-07-22) —
 * quiet by design: visible to the kid who just hit Bark's block dialog,
 * ignorable for everyone else. */
.pwa-banner-trouble {
  display: block;
  margin-top: 2px;
  font-size: 0.78rem;
  font-weight: 600;
  text-decoration: underline;
  opacity: 0.9;
  width: fit-content;
  color: #ffffff; /* the banner is a purple gradient — UA link-blue sinks into it */
}

/* Legal-page ordered lists (ToS rewrite 2026-07-22): the lawyers' doc
 * uses a./b./c. and (I)/(II) lists; without this the markers hung in
 * the left margin (Jimmy's screenshot). Mirrors .legal-content ul. */
.legal-content ol {
  padding-left: 28px;
  margin-bottom: 12px;
}
.legal-content ol li {
  margin-bottom: 8px;
}
.legal-content ol ol {
  margin-top: 8px;
  margin-bottom: 0;
  padding-left: 32px;
}

/* TOS-recovery welcome note (2026-07-22): the "you're new here" message
 * shares #auth-consent-error with real errors — this class flips it from
 * red-alarm to friendly-blue so a designed flow stops reading as a
 * failed login (the demo-shoot lesson). */
.auth-error.auth-consent-note {
  color: #1d4ed8;
  background: rgba(59, 130, 246, 0.10);
  border: 1px solid rgba(59, 130, 246, 0.35);
  border-radius: 10px;
  padding: 10px 12px;
  font-weight: 600;
}
[data-theme="dark"] .auth-error.auth-consent-note {
  color: #93c5fd;
  background: rgba(59, 130, 246, 0.16);
  border-color: rgba(147, 197, 253, 0.35);
}
