@import url('/brand/opendrone-tokens.css');
/* ChatFPV web chat and widget. Dark by default; [data-theme=light] switches.
   Colors map the OpenDrone identity tokens (OpenDrone/brand/tokens):
   surface-dark/surface-light are the two backgrounds, paper/ink are the two
   text colors, gold is the identity accent. Per the token file, gold is safe
   for fills (badges, the send button, citation numbers) in both themes, but
   is never used as text, a focus ring or a control border on the light
   theme; the dark theme has no such restriction, so it keeps gold as its
   link/focus/border accent too. The light theme's own accent (links, the
   focus ring, the composer focus border, pressed feedback) is neutral.ink,
   the same near-black used for light-theme body text: it is not a second
   brand color, just the darkest neutral the identity defines, chosen because
   it clears WCAG contrast against every light surface where the previous
   off-brand blue did not reliably clear the 3:1 non-text floor next to gold.
   Because ink now equals --text in the light theme, plain links keep an
   explicit underline so they stay visible without relying on color alone.
   --gold/--on-gold are theme-independent. */
:root,
:root[data-theme='dark'] {
  color-scheme: dark;
  --bg: var(--od-color-neutral-surface-dark);
  --side: #121215;
  --panel: #17171b;
  --elev: #1f1f24;
  --hover: #26262c;
  --text: var(--od-color-neutral-paper);
  --muted: #a3a3a9;
  --border: #2a2a31;
  --ring: rgba(255, 183, 0, 0.35);
  --accent: var(--od-color-brand-gold);
  --accent-ink: var(--od-color-neutral-ink);
  --danger: #ff8a80;
  --flash: #3a2c0a;
  --gold: var(--od-color-brand-gold);
  --on-gold: var(--od-color-neutral-ink);
  --shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
  --shadow-lg: 0 8px 28px rgba(0, 0, 0, 0.45), 0 1px 2px rgba(0, 0, 0, 0.5);
  --tok-kw: #ff9e64;
  --tok-str: #9ece6a;
  --tok-num: #7dcfff;
  --tok-key: #73daca;
  --tok-op: #bb9af7;
}
:root[data-theme='light'] {
  color-scheme: light;
  --bg: var(--od-color-neutral-surface-light);
  --side: #efeee9;
  --panel: #ffffff;
  --elev: #ebe9e3;
  --hover: #e3e1da;
  --text: var(--od-color-neutral-ink);
  --muted: #55534f;
  --border: #d9d7ce;
  --ring: rgba(26, 26, 30, 0.1);
  --accent: var(--od-color-neutral-ink);
  --accent-ink: #ffffff;
  --danger: #b42318;
  --flash: #fdecc8;
  --gold: var(--od-color-brand-gold);
  --on-gold: var(--od-color-neutral-ink);
  --shadow: 0 1px 2px rgba(26, 26, 30, 0.12);
  --shadow-lg: 0 8px 28px rgba(26, 26, 30, 0.08), 0 1px 2px rgba(26, 26, 30, 0.1);
  --tok-kw: #8a5000;
  --tok-str: #2f6f1f;
  --tok-num: #1a5fb4;
  --tok-key: #0f6f72;
  --tok-op: #6b46c1;
}

/* Brand type: Inter, self-hosted latin subset (index.html preloads both
   faces). font-display: swap paints text at once in the fallback and swaps
   when the face lands; size-adjust on the fallback keeps that swap from
   shifting layout. JetBrains Mono (ASCII, 4.5 KB) is not preloaded: only
   code and source domains use it, so it is fetched only when one renders. */
@font-face { font-family: 'Inter'; font-style: normal; font-weight: 400; font-display: swap; src: url('/fonts/inter-latin-400.woff2') format('woff2'); }
@font-face { font-family: 'Inter'; font-style: normal; font-weight: 600 700; font-display: swap; src: url('/fonts/inter-latin-600.woff2') format('woff2'); }
@font-face { font-family: 'JetBrains Mono'; font-style: normal; font-weight: 400; font-display: swap; src: url('/fonts/jbmono-ascii-400.woff2') format('woff2'); }
@font-face { font-family: 'Inter Fallback'; src: local('Arial'), local('Helvetica'); size-adjust: 107%; ascent-override: 90%; descent-override: 22.4%; line-gap-override: 0%; }
:root {
  --font-body: 'Inter', 'Inter Fallback', system-ui, -apple-system, 'Segoe UI', Roboto, Arial, sans-serif;
  --font-mono-label: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  --r-sm: 8px; --r-md: 12px; --r-lg: 16px; --r-xl: 24px; --r-pill: 999px;
}
* { box-sizing: border-box; }
/* The [hidden] attribute must always win over a later `display:` rule.
   .empty used `display: flex` and silently overrode `hidden`, so the empty
   state kept rendering after the first answer arrived; this global rule
   makes that class of bug impossible instead of only patching .empty. */
[hidden] { display: none !important; }
html, body { height: 100%; }
body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font: 16px/1.6 var(--font-body);
  letter-spacing: -0.006em;
  -webkit-font-smoothing: antialiased;
  overflow: hidden;
}
button, input, textarea { font: inherit; color: inherit; }
/* Unscoped so it also governs code/pre used outside .content (e.g. a probe's
   detached test node); .content code below repeats the family for specificity
   over its own font shorthand. */
code, pre, kbd { font-family: var(--font-mono-label); }
/* A plain inline link (the markdown renderer's [label](url), with no class)
   keeps the browser's own underline; nothing here removes it. That matters
   more in the light theme now that its accent is the same near-black as
   body text (see the file banner comment), so the underline is what still
   reads as "link" instead of color alone. The citation chip, source-card
   and skip-link anchors intentionally opt out with their own
   text-decoration: none and must keep winning that on specificity, so no
   rule here re-adds an underline to `a` in general. */
a { color: var(--accent); }
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}
.skip { position: absolute; left: -9999px; top: 8px; z-index: 50; background: var(--gold); color: var(--on-gold); padding: 8px 12px; border-radius: 6px; }
.skip:focus { left: 8px; }

/* Layout */
.app { display: grid; grid-template-columns: 260px minmax(0, 1fr); height: 100%; }
.sidebar {
  background: var(--side);
  border-right: 1px solid var(--border);
  display: flex; flex-direction: column; gap: 6px;
  padding: 14px 12px 12px; min-height: 0;
}
.sidebar-top { display: flex; align-items: center; justify-content: space-between; min-height: 40px; margin-bottom: 6px; }
.brand {
  display: flex; align-items: center; gap: 9px; margin: 0; padding-left: 6px;
  font-size: 18px; font-weight: 700; letter-spacing: -0.025em;
}
.brand-mark { display: block; flex: none; }
.brand .ai-badge { font-size: 10px; padding: 1px 5px; }
.new-chat {
  display: flex; align-items: center; gap: 8px;
  text-align: left; padding: 9px 12px; border-radius: var(--r-md);
  border: 1px solid var(--border); background: var(--bg); cursor: pointer;
  font-size: 14px; font-weight: 600; box-shadow: var(--shadow);
}
.new-chat svg { fill: none; stroke: currentColor; stroke-width: 2.2; stroke-linecap: round; }
.new-chat:hover { background: var(--hover); }
/* A small header link-button: share.html's "Continue this chat" and the main
   app's Share button share this look (gui/PLAN-5.md item 2). */
.topbar-link {
  flex: none; display: inline-flex; align-items: center; gap: 6px; padding: 0 12px; height: 32px;
  border-radius: var(--r-pill); border: 1px solid var(--border); background: none; color: var(--text);
  font: 600 13px/1 var(--font-body); text-decoration: none; cursor: pointer; white-space: nowrap;
}
.topbar-link:hover { background: var(--hover); }
.sidebar-heading {
  padding: 12px 10px 4px; font: 600 12px/1.4 var(--font-body); color: var(--muted);
  letter-spacing: 0;
}
.sidebar-search { padding: 0; }
.sidebar-search input::placeholder { color: var(--muted); opacity: 1; }
.sidebar-search input {
  width: 100%; padding: 8px 12px; border-radius: var(--r-md); border: 1px solid transparent;
  background: var(--hover); color: var(--text); font-size: 13.5px;
}
.sidebar-search input::-webkit-search-cancel-button { -webkit-appearance: none; }
.sidebar nav { flex: 1; overflow-y: auto; min-height: 0; margin: 0 -4px; }
.conv-list { list-style: none; margin: 0; padding: 0 4px; }
.conv-item { display: flex; align-items: center; gap: 2px; }
.conv {
  display: block; width: 100%; text-align: left; padding: 8px 10px; margin: 1px 0;
  border: 0; border-radius: var(--r-sm); background: none; cursor: pointer;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-size: 14px;
}
.conv-item .conv { flex: 1 1 auto; min-width: 0; }
.conv:hover { background: var(--hover); }
.conv[aria-current='true'] { background: var(--hover); font-weight: 600; }
.conv-actions .conv-action-btn { opacity: 0.7; }
.conv-actions { display: flex; flex: none; gap: 2px; }
.conv-action-btn {
  width: 26px; height: 26px; flex: none; padding: 0; display: inline-flex; align-items: center; justify-content: center;
  border-radius: 6px; border: 0; background: none; color: var(--muted); cursor: pointer; font-size: 13px; line-height: 1;
}
.conv-action-btn:hover { background: var(--hover); color: var(--text); }
.conv-rename-input {
  flex: 1 1 auto; min-width: 0; margin: 1px 0; padding: 7px 9px; border-radius: 6px;
  border: 1px solid var(--accent); background: var(--bg); color: var(--text); font-size: 14px;
}
.sidebar-foot { border-top: 1px solid var(--border); padding: 10px 4px 0; display: flex; flex-direction: column; gap: 2px; }
.sidebar-link {
  color: var(--text); text-decoration: none; font-size: 13.5px; padding: 6px 6px; border-radius: var(--r-sm);
}
.sidebar-link:hover { background: var(--hover); }
.sidebar-note { margin: 4px 0 0; color: var(--muted); font-size: 12px; padding: 0 6px; }
.scrim { display: none; }

.main { display: flex; flex-direction: column; min-width: 0; min-height: 0; height: 100%; position: relative; }
.topbar {
  display: flex; align-items: center; gap: 8px;
  padding: 10px 16px; min-height: 56px; position: relative; z-index: 3; background: var(--bg);
}
/* A soft fade instead of a hard edge where the thread scrolls under the
   bar; purely decorative and out of flow, so it cannot shift layout. */
.topbar::after {
  content: ''; position: absolute; left: 0; right: 12px; top: 100%; height: 20px; pointer-events: none;
  background: linear-gradient(var(--bg), transparent);
}
.topbar-spacer { flex: 1; }
.title { font-size: 17px; font-weight: 700; letter-spacing: -0.02em; margin: 0; display: flex; align-items: center; gap: 8px; white-space: nowrap; flex: 1; }
/* The OpenDrone mark, not the wordmark: header space is icon-sized, and the
   brand rules (OpenDrone/brand/BRAND.md) say to fall back to the mark below
   120px. Gold on both themes: logos are exempt from WCAG text-contrast (this
   is not text), and the brand rules bar gold FROM text/focus/borders, not
   from the mark itself. */
.od-mark { flex: none; display: inline-flex; align-items: center; justify-content: center; width: 28px; height: 28px; border-radius: 6px; }
.od-mark img { display: block; width: 20px; height: 20px; }
.od-mark:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.icon-btn {
  width: 36px; height: 36px; flex: none; border-radius: var(--r-pill); border: 1px solid transparent;
  background: none; cursor: pointer; font-size: 18px; line-height: 1;
  display: inline-flex; align-items: center; justify-content: center; color: var(--text);
}
.icon-btn svg { fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; }
.icon-btn:hover { background: var(--hover); }
.only-narrow { display: none; }

.ai-badge {
  display: inline-block; font-size: 11px; font-weight: 700; letter-spacing: 0.04em;
  padding: 1px 6px; border-radius: var(--r-pill); background: var(--gold); color: var(--on-gold); line-height: 1.3;
}


/* Thread */
/* Bottom padding matches the idle composer's own rendered height (measured
   97px desktop / 111px phone) plus a gap, so the last message -- in
   particular a follow-up chips row -- clears the composer instead of
   butting up against it once scrolled to the bottom. A static value, not
   tracked live off the composer (e.g. ResizeObserver): a post-paint change
   here measured a small amount of layout-shift even though it moves no
   on-screen content, so a very long draft can get a few px tight instead. */
.thread { flex: 1; overflow-y: auto; overflow-x: hidden; padding: 20px 16px 120px; min-height: 0; display: flex; flex-direction: column; }
.thread > * { max-width: 720px; width: 100%; margin-left: auto; margin-right: auto; }
.empty { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center; padding: 8px 8px 40px; }
.empty-mark { display: block; margin: 0 auto 18px; }
:root:not([data-theme='light']) .empty-mark { filter: drop-shadow(0 0 22px rgba(255, 183, 0, 0.35)); }
.empty h2 { font-size: 32px; font-weight: 700; letter-spacing: -0.035em; line-height: 1.15; margin: 0 0 10px; }
.empty p { color: var(--muted); margin: 0 auto 28px; max-width: 480px; font-size: 15px; }
.starters { display: grid; gap: 10px; width: 100%; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); }
.starters button {
  text-align: left; padding: 14px 16px; border-radius: var(--r-lg); border: 1px solid var(--border);
  background: var(--panel); cursor: pointer; font-size: 14px; line-height: 1.45; box-shadow: var(--shadow);
  transition: border-color 0.15s, background 0.15s;
}
.starters button:hover { background: var(--hover); border-color: var(--muted); }

.msg { margin-bottom: 28px; overflow-wrap: anywhere; }
.msg.user { display: flex; justify-content: flex-end; align-items: flex-end; gap: 6px; }
.msg.user .bubble {
  background: var(--elev); padding: 10px 16px; border-radius: 20px 20px 6px 20px; max-width: 78%; white-space: pre-wrap;
}
.edit-btn {
  flex: none; width: 32px; height: 32px; padding: 0; justify-content: center;
  border-radius: 50%; align-self: flex-end;
}
/* On a mouse/trackpad, the pencil only appears on hover or keyboard focus of
   the row it belongs to, so it reads as a control instead of permanent
   clutter beside every user turn. Touch has no hover to reveal it with, so
   it stays visible there at its existing 32px target (above the 24px WCAG
   2.5.8 minimum). */
@media (hover: hover) and (pointer: fine) {
  .edit-btn { opacity: 0; transition: opacity 0.12s ease; }
  .msg.user:hover .edit-btn,
  .edit-btn:focus-visible {
    opacity: 1;
  }
}
.who { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; font-size: 14px; font-weight: 600; letter-spacing: -0.01em; }
.status { color: var(--muted); font-size: 14px; }
.status:not(:empty)::after { content: ''; display: inline-block; width: 1.2em; animation: dots 1.2s steps(4) infinite; }
@keyframes dots { 0% { content: ''; } 25% { content: '.'; } 50% { content: '..'; } 75% { content: '...'; } }
.content > :first-child { margin-top: 0; }
.content > :last-child { margin-bottom: 0; }
.content { line-height: 1.7; }
.content p, .content ul, .content ol { margin: 0 0 14px; }
.content ul, .content ol { padding-left: 22px; }
.content li { margin: 4px 0; }
.content li::marker { color: var(--muted); }
.content strong { font-weight: 600; }
.content h3, .content h4, .content h5, .content h6 { margin: 22px 0 8px; font-size: 17px; font-weight: 600; letter-spacing: -0.015em; }
.content code { background: var(--elev); padding: 2px 6px; border-radius: 6px; font: 13.5px/1.4 var(--font-mono-label); }
.code-block { border: 1px solid var(--border); border-radius: var(--r-md); overflow: hidden; background: var(--panel); margin: 4px 0 10px; }
.code-head {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  padding: 5px 6px 5px 12px; background: var(--elev); border-bottom: 1px solid var(--border);
}
.code-lang {
  font: 12px/1.4 var(--font-mono-label); color: var(--muted);
  text-transform: lowercase; letter-spacing: 0.02em;
}
.content pre { background: none; border: none; border-radius: 0; margin: 0; padding: 12px; overflow-x: auto; max-width: 100%; }
.content pre code { background: none; padding: 0; white-space: pre; }
/* Tiny highlighter token colors (app.js highlightCode): tuned so every one
   clears 4.5:1 against both the dark and light --panel background. */
.tok-kw { color: var(--tok-kw); }
.tok-str { color: var(--tok-str); }
.tok-num { color: var(--tok-num); }
.tok-key { color: var(--tok-key); }
.tok-op { color: var(--tok-op); }
.tok-comment { color: var(--muted); font-style: italic; }
.code-copy-btn {
  flex: none; width: 24px; height: 24px; padding: 0;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: 6px; border: 1px solid transparent; background: none;
  color: var(--muted); cursor: pointer;
}
.code-copy-btn svg { fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.code-copy-btn:hover { background: var(--hover); color: var(--text); }
.code-copy-btn.copied { color: var(--text); border-color: var(--accent); }
/* GFM tables (markdown() in app.js): scrolls horizontally within its own
   border instead of overflowing the page on a 390px phone. */
.table-wrap { overflow-x: auto; margin: 4px 0 14px; border: 1px solid var(--border); border-radius: var(--r-md); }
.content table { width: 100%; min-width: 320px; border-collapse: collapse; font-size: 14px; }
.content table th, .content table td { padding: 6px 10px; border-bottom: 1px solid var(--border); text-align: left; vertical-align: top; }
.content table th { color: var(--muted); font-weight: 600; background: var(--elev); white-space: nowrap; }
.content table tr:last-child td { border-bottom: none; }
.cite-ref {
  display: inline-block; min-width: 1.5em; padding: 0 5px; margin: 0 1px; border-radius: var(--r-pill);
  background: var(--gold); color: var(--on-gold); font-size: 11px; font-weight: 700; line-height: 1.6;
  text-align: center; text-decoration: none; vertical-align: 1px;
}
.cite-ref:hover { filter: brightness(0.92); }
.error { color: var(--danger); }
.notice {
  display: block; padding: 10px 12px; border-radius: 8px;
  border: 1px solid var(--border); background: var(--elev); color: var(--text);
}
.muted { color: var(--muted); }
.stopped-note { color: var(--muted); font-size: 13px; margin-top: 6px; }

/* The Turnstile interactive checkbox, shown only when Cloudflare decides a
   click is needed. Hidden the rest of the time via the [hidden] attribute. */
.ts-card {
  margin: 0 16px 8px; padding: 10px 12px; border-radius: 8px;
  border: 1px solid var(--border); background: var(--elev); color: var(--text);
}
.ts-card p { margin: 0 0 8px; font-size: 13px; color: var(--muted); }

.cites { margin-top: 16px; }
.cites-label { font: 600 13px/1.4 var(--font-body); color: var(--muted); margin-bottom: 8px; }
.chips { list-style: none; margin: 0; padding: 0; display: grid; gap: 8px; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); }
.chip {
  display: flex; gap: 8px; align-items: flex-start; padding: 9px 12px; border-radius: var(--r-md);
  border: 1px solid var(--border); background: var(--panel); color: var(--text); text-decoration: none; min-width: 0;
  transition: background 0.2s;
}
a.chip:hover { background: var(--hover); }
.chip.flash { background: var(--flash); }
.chip-n {
  flex: none; font-size: 11px; font-weight: 700; color: var(--on-gold); background: var(--gold);
  border-radius: var(--r-pill); padding: 0 6px; line-height: 1.7;
}
/* A neutral inline-SVG link glyph (img-src stays 'self'), not a fetched
   favicon or a letter badge, which read as an unfilled avatar placeholder. */
.chip-favicon {
  flex: none; width: 20px; height: 20px; border-radius: 50%; display: inline-flex;
  align-items: center; justify-content: center; background: var(--elev); border: 1px solid var(--border);
  color: var(--muted);
}
.chip-favicon svg { width: 12px; height: 12px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.chip-text { display: flex; flex-direction: column; min-width: 0; }
.chip-title { font-size: 14px; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.chip-meta { font-size: 12px; color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.handoff {
  display: inline-block; margin-top: 12px; padding: 8px 16px; border-radius: var(--r-pill);
  background: var(--gold); color: var(--on-gold); font-weight: 600; text-decoration: none;
}
.followups { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 14px; }
.followup {
  padding: 7px 14px; border-radius: var(--r-pill); border: 1px solid var(--border); background: var(--panel);
  cursor: pointer; font-size: 14px; text-align: left;
}
.followup:hover { background: var(--hover); }

.actions { display: inline-flex; align-items: center; gap: 2px; margin-top: 10px; }
.action-btn {
  display: inline-flex; align-items: center; gap: 6px; height: 32px; padding: 0 10px; border-radius: var(--r-pill);
  border: 1px solid transparent; background: none; cursor: pointer; color: var(--muted); font-size: 13px;
}
.action-btn svg { fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.action-btn:hover { background: var(--hover); color: var(--text); }
.action-btn:disabled { opacity: 0.6; cursor: default; }
.action-btn:disabled:hover { background: none; }
.action-btn.copied { color: var(--text); border-color: var(--border); background: var(--elev); }
.full-note { font-size: 13px; margin-left: 8px; }

.feedback { display: inline-flex; align-items: center; gap: 4px; margin-top: 8px; }
.fb {
  display: inline-flex; align-items: center; justify-content: center; width: 32px; height: 32px;
  border: 1px solid transparent; background: none; border-radius: var(--r-pill); cursor: pointer; color: var(--muted);
}
.fb svg { fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.fb:hover { background: var(--hover); color: var(--text); }
.fb[aria-pressed='true'] { color: var(--accent); border-color: var(--border); background: var(--elev); }
.fb[aria-pressed='true'] svg { fill: currentColor; fill-opacity: 0.25; }
.fb-thanks { font-size: 13px; color: var(--muted); margin-left: 6px; }
.fb-note { display: flex; gap: 8px; margin-top: 8px; max-width: 520px; }
.fb-note[hidden] { display: none; }
.fb-note input {
  flex: 1; min-width: 0; height: 36px; padding: 0 14px; border-radius: var(--r-pill); border: 1px solid var(--border); background: var(--panel); font-size: 14px;
}
.fb-note input:focus-visible { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--ring); }
/* Same height and radius as the note field beside it (was a square 8px
   button next to a taller field). */
.fb-send {
  flex: none; height: 36px; padding: 0 16px; border-radius: var(--r-pill); border: 0;
  background: var(--gold); color: var(--on-gold); font-weight: 600; cursor: pointer; font-size: 14px;
}
.fb-send:hover { filter: brightness(0.94); }

/* Composer */
/* env(safe-area-inset-bottom) is 0 on devices without a home indicator, so
   this is a no-op there and only adds room on notched/gesture-nav phones. */
.composer { padding: 4px 16px calc(10px + env(safe-area-inset-bottom)); position: relative; z-index: 3; background: var(--bg); }
/* Mirror of the topbar fade: content softens into the composer. */
.composer::before {
  content: ''; position: absolute; left: 0; right: 12px; bottom: 100%; height: 24px; pointer-events: none;
  background: linear-gradient(transparent, var(--bg));
}
.composer-box {
  max-width: 720px; margin: 0 auto; display: flex; align-items: flex-end; gap: 8px;
  background: var(--panel); border: 1px solid var(--border); border-radius: var(--r-xl); padding: 10px 10px 10px 18px;
  box-shadow: var(--shadow-lg); transition: border-color 0.15s, box-shadow 0.15s;
}
.composer-box:focus-within { border-color: var(--accent); box-shadow: var(--shadow-lg), 0 0 0 3px var(--ring); }
.composer-box:focus-within :focus-visible { outline: none; }
.composer-box .send:focus-visible, .composer-box .mic:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.composer textarea {
  flex: 1; min-width: 0; border: 0; background: none; resize: none; outline: none;
  max-height: 200px; padding: 7px 0; line-height: 1.5; font-size: 16px;
}
.composer textarea::placeholder { color: var(--muted); opacity: 1; }
.send {
  flex: none; width: 36px; height: 36px; border-radius: 50%; border: 0; cursor: pointer;
  background: var(--gold); color: var(--on-gold); font-size: 18px; font-weight: 700;
  font-family: system-ui, -apple-system, sans-serif; transition: filter 0.15s, opacity 0.15s;
}
.send:hover:not(:disabled) { filter: brightness(0.94); }
.send:disabled { opacity: 0.4; cursor: default; }
.send.stop { background: var(--elev); color: var(--text); border: 1px solid var(--border); font-size: 15px; }
.send.stop:hover { background: var(--hover); }
/* Voice dictation (app.js): hidden until SpeechRecognition support is
   detected. 36px matches .send, above the 24px small-target floor. */
.mic {
  flex: none; width: 36px; height: 36px; border-radius: 50%; border: 1px solid var(--border);
  background: none; color: var(--muted); cursor: pointer; display: inline-flex; align-items: center; justify-content: center;
}
.mic:hover { background: var(--hover); }
.mic svg { width: 18px; height: 18px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.mic[aria-pressed='true'] { background: var(--gold); color: var(--on-gold); border-color: var(--gold); animation: mic-pulse 1.6s ease-in-out infinite; }
@keyframes mic-pulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(255, 183, 0, 0.4); }
  50% { box-shadow: 0 0 0 6px rgba(255, 183, 0, 0); }
}
@media (prefers-reduced-motion: reduce) { .mic[aria-pressed='true'] { animation: none; } }
.disclosure { max-width: 720px; margin: 8px auto 0; text-align: center; font-size: 12px; color: var(--muted); }

/* Inline [n] citation: hover/focus/tap shows a popover (title + domain) via
   plain CSS on the marker's own descendant, no JS visibility toggling needed
   for the mouse/keyboard cases. display:none (not [hidden], which this file
   marks !important) so :hover/:focus-visible can flip it back on. */
.cite-ref { position: relative; }
.cite-pop {
  display: none; position: absolute; z-index: 20; left: 50%; bottom: calc(100% + 7px);
  transform: translateX(-50%); width: max-content; max-width: 240px; text-align: left;
  padding: 8px 10px; border-radius: 8px; background: var(--elev); border: 1px solid var(--border);
  box-shadow: var(--shadow); font-weight: 400; line-height: 1.4;
}
.cite-ref:hover .cite-pop,
.cite-ref:focus-visible .cite-pop,
.cite-ref:active .cite-pop {
  display: block;
}
.cite-pop-title { display: block; font-size: 12.5px; font-weight: 600; color: var(--text); }
.cite-pop-domain { display: block; margin-top: 2px; font: 11px var(--font-mono-label); color: var(--muted); letter-spacing: 0.01em; }
@media (max-width: 420px) {
  .cite-pop { left: auto; right: -10px; transform: none; max-width: 200px; }
}

/* "Jump to latest" (app.js): shown only while the thread is scrolled away
   from the bottom, positioned over the thread rather than in flex flow so it
   does not push the composer or shift layout (CLS) when it appears. */
.jump-latest {
  position: absolute; left: 50%; bottom: 84px; transform: translateX(-50%); z-index: 5;
  display: flex; align-items: center; gap: 6px; padding: 8px 14px; border-radius: var(--r-pill);
  border: 1px solid var(--border); background: var(--panel); color: var(--text);
  box-shadow: var(--shadow); cursor: pointer; font-size: 13px;
}
.jump-latest:hover { background: var(--hover); }

/* Keyboard shortcuts dialog (app.js openShortcuts): a native <dialog>, so
   showModal() gives focus trapping and Esc-to-close for free. */
dialog {
  background: var(--panel); color: var(--text); border: 1px solid var(--border);
  border-radius: var(--r-lg); padding: 22px 24px; max-width: 420px; width: calc(100% - 32px); box-shadow: var(--shadow-lg);
}
dialog::backdrop { background: rgba(0, 0, 0, 0.5); }
dialog h2 { margin: 0 0 14px; font-size: 18px; }
.shortcuts-list { display: grid; gap: 10px; margin: 0 0 18px; }
.shortcuts-list > div { display: flex; justify-content: space-between; align-items: center; gap: 14px; font-size: 14px; }
.shortcuts-list dt { display: flex; gap: 4px; flex-wrap: wrap; align-items: center; }
.shortcuts-list dd { margin: 0; color: var(--muted); text-align: right; }
kbd { background: var(--elev); border: 1px solid var(--border); border-radius: 4px; padding: 1px 6px; font-size: 11px; }
.dialog-close { padding: 8px 16px; border-radius: var(--r-pill); border: 1px solid var(--border); background: var(--elev); cursor: pointer; font-size: 14px; }
.dialog-close:hover { background: var(--hover); }

/* Share dialog (app.js openShareDialog, gui/PLAN-5.md item 2). */
.share-dialog-note { margin: 0 0 14px; font-size: 13px; color: var(--muted); }
.share-link-row { display: flex; gap: 8px; margin: 0 0 14px; }
.share-link-row input {
  flex: 1; min-width: 0; padding: 8px 10px; border-radius: 8px; border: 1px solid var(--border);
  background: var(--bg); color: var(--text); font: 13px var(--font-mono-label);
}
.share-dialog-actions { display: flex; gap: 8px; flex-wrap: wrap; justify-content: flex-end; }
.share-status-note { margin: 0 0 14px; font-size: 13px; }

/* The read-only /c/:id page (share.html, share.js): no sidebar, centered
   like the thread column it renders. */
.share-page { max-width: 900px; margin: 0 auto; width: 100%; }

/* Narrow screens: the sidebar slides over the thread. */
@media (max-width: 800px) {
  .app { grid-template-columns: minmax(0, 1fr); }
  .only-narrow { display: inline-flex; }
  .title.only-narrow { display: flex; font-size: 16px; }
  .sidebar {
    position: fixed; inset: 0 auto 0 0; width: min(300px, 85vw); z-index: 20;
    transform: translateX(-100%); transition: transform 0.2s ease;
  }
  .sidebar.open { transform: none; }
  .scrim { display: block; position: fixed; inset: 0; background: rgba(0, 0, 0, 0.5); z-index: 10; }
  .scrim[hidden] { display: none; }
  .topbar { padding: 8px 10px; gap: 6px; min-height: 52px; }
  .od-mark { display: none; }
  .thread { padding: 16px 12px 135px; }
  .composer { padding: 6px 10px calc(8px + env(safe-area-inset-bottom)); }
  .empty h2 { font-size: 25px; }
  .empty-mark { width: 36px; height: 36px; margin-bottom: 14px; }
  .starters { grid-template-columns: minmax(0, 1fr); gap: 8px; }
  .starters button { padding: 12px 14px; }
  .msg.user .bubble { max-width: 88%; }
}

/* chatfpv polish-beta-ui: narrow phones (390px and under) already had the
   topbar's "Sign in with OpenDrone" pill push the theme toggle button
   off-screen before this change (measured ~13px overflow); the wider
   "AI · Beta" badge made it worse (~49px). Both shrink here instead of
   redesigning the topbar. */
@media (max-width: 420px) {
  .title.only-narrow { font-size: 14px; gap: 5px; }
  .title.only-narrow .ai-badge { font-size: 9.5px; padding: 1px 4px; }
  .topbar { gap: 4px; padding-left: 8px; padding-right: 8px; }
  .topbar-link { padding: 0 7px; font-size: 12px; }
}

/* Widget */
.embed .topbar { min-height: 48px; padding: 6px 48px 6px 12px; }
.embed .title { font-size: 15px; }
.embed #new-chat { margin-left: auto; }
.embed .thread { padding: 14px 12px 6px; }
.embed .empty { padding: 24px 8px; }
.embed .empty h2 { font-size: 19px; }
.embed .chips { grid-template-columns: minmax(0, 1fr); }
.embed .composer { padding: 6px 10px 8px; }
.embed .disclosure { font-size: 12px; }
/* chatfpv polish-beta-ui: the embed panel is one narrow column, so its
   starter chips (populated client-side by app.js) always stack. */
.embed .starters { grid-template-columns: minmax(0, 1fr); gap: 8px; }
.embed .starters button { padding: 12px 14px; font-size: 13.5px; }

/* chatfpv polish-beta-ui: ?chrome=compact (read client-side in app.js,
   which sets data-chrome="compact" on <body>) is for a host that draws its
   own panel chrome (OD emblem, "ChatFPV", a Beta tag, close) around the
   /embed iframe, so /embed's own title/badge row would double it up. Drop
   that row and pin the reset control as a small icon over the top-right of
   the message area instead; the composer's beta line is unaffected. Plain
   /embed (no param) is unchanged, for widget.js's own floating panel (its
   own external close button) and any other embedder. */
.embed[data-chrome='compact'] .topbar {
  position: absolute; top: 0; right: 0; left: auto; width: auto;
  min-height: 0; padding: 8px 8px 0 0; background: none; z-index: 4;
}
.embed[data-chrome='compact'] .topbar .title { display: none; }
.embed[data-chrome='compact'] .topbar #new-chat {
  background: var(--panel); box-shadow: var(--shadow); border-radius: var(--r-pill);
}
.embed[data-chrome='compact'] .thread { padding-top: 44px; }

/* chatfpv polish-beta-ui: /continue's redeem/empty state reuses .empty, so
   an expired or missing ticket lands on the same centered layout as the home
   page's empty thread instead of a bare status line. */
#continue-empty .muted { margin: 0 0 14px; font-size: 13px; }

@media (prefers-reduced-motion: reduce) {
  * { transition: none !important; animation: none !important; }
}

/* A plain scrolling document page (privacy.html), unlike the fixed-height
   single-screen app/embed layout the rest of this file assumes. */
.legal {
  height: auto;
  overflow: visible;
  max-width: 720px;
  margin: 0 auto;
  padding: 32px 20px 64px;
}
.legal h1 { font-size: 24px; margin: 0 0 8px; }
.legal h2 { font-size: 16px; margin: 28px 0 8px; color: var(--muted); }
.legal p, .legal li { font-size: 15px; }
.legal ul { padding-left: 20px; }
.legal table { display: block; overflow-x: auto; width: 100%; border-collapse: collapse; font-size: 14px; margin: 8px 0 14px; }
.legal th, .legal td { padding: 6px 10px; border-bottom: 1px solid var(--border); text-align: left; vertical-align: top; }
.legal th { color: var(--muted); font-weight: 600; }
html:has(.legal), body:has(.legal) { height: auto; overflow: visible; }
