:root {
  color-scheme: light;
  --bg: #eef3f8;
  --bg-accent: #dbeafe;
  --panel: #ffffff;
  --panel-raised: #f8fbfd;
  --ink: #142033;
  --muted: #607086;
  --line: #d8e1ea;
  --line-strong: #b9c7d6;
  --accent: #0a6ea8;
  --accent-strong: #075985;
  --accent-soft: #e6f3fb;
  --user: #e7f3fb;
  --assistant: #ffffff;
  --shadow: 0 18px 60px rgba(20, 32, 51, 0.12);
  --error: #b42318;
  --code-bg: #101820;
  --footer-bg: rgba(248, 251, 253, 0.96);
  /* Transcript grid surface + SideKick watermark. The grid was color-mix(panel 94%, bg);
     that resolves to this, spelled out so it can also tint the watermark veil. */
  --surface-grid: #f4f8fb;
  --wm-image: url("sidekick-logo.png?v=20260828");
  --wm-veil-idle: rgba(244, 248, 251, 0.62);   /* empty screen: logo reads through */
  --wm-veil-active: rgba(244, 248, 251, 0.9);   /* conversation started: logo recedes */
  /* Sign-in screen: sky blue + lavender glow from the SideKick mark over the theme base. */
  --gate-bg:
    radial-gradient(60rem 40rem at 10% 0%, rgba(125, 211, 252, 0.45), transparent 60%),
    radial-gradient(50rem 36rem at 95% 100%, rgba(165, 180, 252, 0.4), transparent 60%),
    linear-gradient(160deg, #f3f8fc, #e4eef7);
  /* Drawer widths; the user can drag them wider (app.js sets these inline, 86vw cap kept). */
  --drawer-left-w: min(360px, 86vw);
  --drawer-right-w: min(360px, 86vw);
}

:root[data-theme="dark"] {
  color-scheme: dark;
  --bg: #08111f;
  --bg-accent: #10324a;
  --panel: #0f1b2d;
  --panel-raised: #142238;
  --ink: #edf4fb;
  --muted: #9fb0c3;
  --line: #26374d;
  --line-strong: #3a506a;
  --accent: #38bdf8;
  --accent-strong: #7dd3fc;
  --accent-soft: #102f44;
  --user: #123451;
  --assistant: #111f33;
  --shadow: 0 18px 70px rgba(0, 0, 0, 0.32);
  --error: #f87171;
  --code-bg: #07111d;
  --footer-bg: rgba(15, 27, 45, 0.96);
  --surface-grid: #0f1a2c;
  --wm-image: url("sidekick-logo-dark.png?v=20260828");
  --wm-veil-idle: rgba(15, 26, 44, 0.5);
  --wm-veil-active: rgba(15, 26, 44, 0.88);
  --gate-bg:
    radial-gradient(60rem 40rem at 10% 0%, rgba(56, 189, 248, 0.22), transparent 60%),
    radial-gradient(50rem 36rem at 95% 100%, rgba(129, 140, 248, 0.2), transparent 60%),
    linear-gradient(160deg, #0f2238, #0b1a2e);
}

* {
  box-sizing: border-box;
}

body {
  margin: 0;
  min-height: 100vh;
  height: 100dvh;
  overflow: hidden;
  background:
    radial-gradient(circle at top left, var(--bg-accent), transparent 34rem),
    linear-gradient(135deg, var(--bg), var(--panel-raised));
  color: var(--ink);
  font: 15px/1.45 system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
}

.app-shell {
  height: 100%;
  min-height: 0;
  display: grid;
  place-items: stretch;
  padding: 18px;
  /* A drawer pushes the panel by adding padding on its side, rather than sliding
     over it. The panel below stays min(1600px,100%) centred, so it just shifts
     and squeezes into the room that is left. */
  transition: padding 0.18s ease;
}
body.shell-push-left .app-shell { padding-left: calc(18px + var(--drawer-left-w)); }
body.shell-push-right .app-shell { padding-right: calc(18px + var(--drawer-right-w)); }
@media (prefers-reduced-motion: reduce) { .app-shell { transition: none; } }

.chat-panel {
  display: grid;
  grid-template-rows: auto auto 1fr auto auto;
  width: min(1600px, 100%);
  height: 100%;
  min-height: 0;
  margin: 0 auto;
  overflow: hidden;
  background: color-mix(in srgb, var(--panel) 96%, transparent);
  border: 1px solid var(--line);
  border-radius: 18px;
  box-shadow: var(--shadow);
}

.topbar {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  gap: 18px;
  padding: 18px 22px;
  border-bottom: 1px solid var(--line);
  background: color-mix(in srgb, var(--panel) 92%, transparent);
}
/* Brand lockup + timer sit hard left; the controls are pushed to the far right. */
.topbar-actions { margin-left: auto; }

.brand-lockup,
.topbar-actions,
.app-footer {
  display: flex;
  align-items: center;
}

.brand-lockup {
  min-width: 0;
  gap: 13px;
}

.elapsed {
  /* Last in the lockup, not between the mark and the title: the min-width below reserves
     room so the timer does not shove the layout when it starts, and that reservation would
     otherwise sit as a permanent gap right where the wordmark should begin. */
  order: 1;
  min-width: 52px;
  flex: 0 0 auto;
  color: var(--muted);
  font-size: 13px;
  font-weight: 650;
  font-variant-numeric: tabular-nums;
}

h1 {
  margin: 0;
  font-size: 20px;
  letter-spacing: 0;
}

p {
  margin: 0;
}

.topbar p {
  margin-top: 3px;
  color: var(--muted);
}

.topbar-actions {
  flex: 0 0 auto;
  gap: 10px;
  flex-wrap: wrap;
  justify-content: flex-end;
}

/* ── Export (PDF/Word) menu ── */
.export-control {
  position: relative;
}

.export-control .secondary-button {
  height: 32px;
}

.export-menu {
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  z-index: 30;
  display: flex;
  flex-direction: column;
  min-width: 160px;
  padding: 6px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--panel);
  box-shadow: var(--shadow);
}

.export-menu[hidden] {
  display: none;
}

.export-menu button {
  width: 100%;
  height: 32px;
  padding: 0 10px;
  border: 0;
  border-radius: 6px;
  background: transparent;
  color: var(--ink);
  font-size: 13px;
  font-weight: 650;
  text-align: left;
  cursor: pointer;
}

.export-menu button:hover {
  background: var(--accent-soft);
  color: var(--accent-strong);
}

/* Label above the control, not beside it: in a row every label also costs its own width plus the
   gap, and this bar already wraps to a second line on a narrower window. Stacked, each column is
   only as wide as the wider of its two parts. All six controls are 32px tall, so the labels land
   on one line and the controls on another without any extra alignment. */
.sql-control,
.theme-control,
.extras-control,
.numeric-control {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 4px;
}

/* The hidden attribute is only display:none in the browser's own stylesheet, so the rule
   above outranks it and a hidden control would still be laid out. Needed by the semantic
   view picker, which is shown only on the Chat tab and only when there is a list to pick from. */
.numeric-control[hidden] {
  display: none;
}

/* The topbar's Data picker is the one control left in the topbar, so its label sits before the
   select on the same line. Drawer settings keep the stacked label. Outranks the phone-width
   .numeric-control rule, where the select then fills the half-row after its label. */
.topbar-actions .numeric-control {
  flex-direction: row;
  align-items: center;
  gap: 8px;
}
.topbar-actions .numeric-control select { flex: 1 1 auto; }

.sql-label,
.theme-label,
.extras-label,
.numeric-control span {
  color: var(--muted);
  font-size: 13px;
  font-weight: 650;
  white-space: nowrap;
}

/* "Classic" toggle: reveals the hidden Chat tab. Same 32px height and token palette as the
   other topbar controls; pressed state borrows the accent border from .tab-btn[aria-selected]. */
.extras-toggle {
  width: 34px;
  min-width: 34px;
  height: 32px;
  padding: 0;
  border: 1px solid var(--line-strong);
  border-radius: 8px;
  background: var(--panel-raised);
  color: var(--muted);
  font-size: 16px;
  line-height: 1;
  cursor: pointer;
}

.extras-toggle:hover {
  background: var(--accent-soft);
  color: var(--ink);
}

.extras-toggle[aria-pressed="true"] {
  border-color: var(--accent);
  color: var(--accent);
  background: var(--accent-soft);
}

.extras-toggle:focus-visible {
  outline: 3px solid color-mix(in srgb, var(--accent) 24%, transparent);
  border-color: var(--accent);
}

.numeric-control input,
.numeric-control select {
  width: 78px;
  height: 32px;
  padding: 0 8px;
  border: 1px solid var(--line-strong);
  border-radius: 8px;
  background: var(--panel-raised);
  color: var(--ink);
  font: inherit;
  font-size: 13px;
  font-weight: 650;
}

.numeric-control select {
  /* Sized to its longest option rather than a fixed width, which clipped both "Demo Data" and
     the semantic view names. Capped so a long view name cannot push the rest of the topbar
     actions onto a second row; the ellipsis covers anything longer still. */
  width: auto;
  min-width: 104px;
  max-width: 220px;
  text-overflow: ellipsis;
}

.numeric-control input:focus,
.numeric-control select:focus {
  outline: 3px solid color-mix(in srgb, var(--accent) 24%, transparent);
  border-color: var(--accent);
}

.sql-toggle {
  position: relative;
  width: 46px;
  min-width: 46px;
  height: 32px;
  border: 1px solid var(--line-strong);
  border-radius: 999px;
  background: var(--panel-raised);
  color: var(--ink);
  padding: 0;
}

.sql-toggle:hover {
  background: var(--accent-soft);
}

.sql-toggle-icon {
  position: absolute;
  top: 6px;
  left: 7px;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: var(--muted);
  transition: transform 160ms ease, background 160ms ease;
}

.sql-toggle[aria-pressed="true"] .sql-toggle-icon {
  transform: translateX(14px);
  background: var(--accent);
}

.status {
  display: grid;
  place-items: center;
  width: 40px;
  height: 40px;
  flex: 0 0 auto;
}

.status-mark {
  display: block;
  width: 100%;
  height: 100%;
}

.status.is-busy .status-mark {
  animation: hs-spin 3.2s linear infinite;
}

@keyframes hs-spin {
  from { transform: rotate(0deg); }
  to   { transform: rotate(360deg); }
}

@keyframes hs-pulse {
  50% { opacity: 0.45; }
}

@media (prefers-reduced-motion: reduce) {
  .status.is-busy .status-mark {
    animation: hs-pulse 1.6s ease-in-out infinite;
  }
}

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  border: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
}

.theme-toggle {
  position: relative;
  width: 46px;
  min-width: 46px;
  height: 32px;
  border: 1px solid var(--line-strong);
  border-radius: 999px;
  background: var(--panel-raised);
  color: var(--ink);
  padding: 0;
}

.theme-toggle:hover {
  background: var(--accent-soft);
}

.theme-toggle-icon {
  position: absolute;
  top: 6px;
  left: 7px;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: var(--accent);
  box-shadow: inset -5px -4px 0 color-mix(in srgb, var(--panel-raised) 70%, transparent);
  transition: transform 160ms ease, box-shadow 160ms ease;
}

:root[data-theme="dark"] .theme-toggle-icon {
  transform: translateX(14px);
  box-shadow: inset 0 0 0 4px var(--accent), 0 0 16px rgba(56, 189, 248, 0.22);
}

.messages {
  min-height: 0;
  overflow: auto;
  padding: 24px clamp(16px, 2vw, 32px) 48px;
  scrollbar-gutter: stable;
  scrollbar-width: thin;
  scrollbar-color: var(--line-strong) transparent;
  background:
    linear-gradient(var(--line) 1px, transparent 1px),
    linear-gradient(90deg, var(--line) 1px, transparent 1px);
  background-color: color-mix(in srgb, var(--panel) 94%, var(--bg));
  background-size: 32px 32px;
  background-position: -1px -1px;
}

/* SideKick watermark behind the transcript grid. Stacking order top->bottom:
   grid, grid, colour veil, logo. CSS has no per-layer opacity, so the veil (painted in the
   surface colour) is what fades the logo; [data-conversation="active"] just thickens the veil. */
#messages,
#assistant-messages {
  --wm-veil: var(--wm-veil-idle);
  background:
    linear-gradient(var(--line) 1px, transparent 1px),
    linear-gradient(90deg, var(--line) 1px, transparent 1px),
    linear-gradient(var(--wm-veil), var(--wm-veil)),
    var(--wm-image);
  background-repeat: repeat, repeat, no-repeat, no-repeat;
  background-size: 32px 32px, 32px 32px, cover, min(48%, 660px) auto;
  background-position: -1px -1px, -1px -1px, center, center;
  background-color: var(--surface-grid);
  transition: none;
}

#messages[data-conversation="active"],
#assistant-messages[data-conversation="active"] {
  --wm-veil: var(--wm-veil-active);
}

.message {
  display: flex;
  margin: 0 0 16px;
}

.message.user {
  justify-content: flex-end;
}

.bubble {
  position: relative;
  width: min(840px, 100%);
  padding: 14px 16px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--assistant);
  box-shadow: 0 8px 24px rgba(20, 32, 51, 0.06);
  overflow-wrap: anywhere;
}

/* Text stays at a readable measure; bubbles carrying a wide table or chart take the whole
   panel so the result uses the width instead of scrolling inside an 840px window. :has()
   also catches tables added later by the in-bubble Run SQL button. */
.bubble:has(.table-wrap),
.bubble:has(.chart-container) {
  width: 100%;
}

/* A pie/donut has no axis to spend width on: keep its bubble at the text measure and
   centre the chart, rather than stretching one wedge across 1600px. */
.bubble:has(.chart-container[data-shape="radial"]) {
  width: min(840px, 100%);
}
.chart-container[data-shape="radial"] {
  max-width: 560px;
  margin-inline: auto;
}
/* Nothing the renderer emits may be clipped by .chart-container's overflow: hidden. */
.chart-container :is(svg, canvas) {
  max-width: 100%;
  height: auto;
}

:root[data-theme="dark"] .bubble {
  box-shadow: none;
}

.message.user .bubble {
  background: var(--user);
  border-color: color-mix(in srgb, var(--accent) 40%, var(--line));
}

.bubble > * + * {
  margin-top: 12px;
}

.composer {
  position: relative;
  display: grid;
  grid-template-columns: 1fr auto auto;
  gap: 12px;
  padding: 16px 22px;
  border-top: 1px solid var(--line);
  background: color-mix(in srgb, var(--panel) 94%, transparent);
  backdrop-filter: blur(10px);
}

/* The Send/Ask button doubles as Stop while a request is in flight. */
.composer button.is-stop,
.ws-toolbar button.is-stop {
  background: var(--error);
  border-color: var(--error);
  color: #fff;
}

/* "/" shortcut autocomplete: a small list above the composer. */
.shortcut-hints {
  position: absolute;
  left: 22px;
  right: 22px;
  bottom: calc(100% - 8px);
  margin: 0;
  padding: 4px;
  list-style: none;
  max-height: 220px;
  overflow-y: auto;
  background: var(--panel);
  border: 1px solid var(--line-strong);
  border-radius: 10px;
  box-shadow: var(--shadow);
  z-index: 30;
}
.shortcut-hints[hidden] { display: none; }
.shortcut-hints li {
  display: flex;
  align-items: baseline;
  gap: 8px;
  padding: 7px 10px;
  border-radius: 7px;
  cursor: pointer;
  font-size: 13px;
}
.shortcut-hints li.is-active,
.shortcut-hints li:hover {
  background: var(--accent-soft);
}
.shortcut-hints .sh-token {
  font-weight: 700;
  color: var(--accent-strong);
  font-variant-numeric: tabular-nums;
}
.shortcut-hints .sh-title {
  color: var(--ink);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.shortcut-hints .sh-kind {
  margin-left: auto;
  flex: none;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--muted);
}

/* Mic button (attachDictation, voice.js): outlined like the textarea it sits beside, not filled
   like Send/Ask - dictation is optional, not the primary action. Pulses red while recording,
   accent while Whisper is turning the recording into a transcript. */
.mic-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 50px;
  min-width: 50px;
  height: 50px;
  padding: 0;
  border: 1px solid var(--line-strong);
  border-radius: 8px;
  background: var(--panel);
  color: var(--muted);
}
/* The class's display outranks the hidden attribute: without this a mic that is switched off
   (or unsupported) still draws as an empty box next to Ask. */
.mic-button[hidden] {
  display: none;
}
.mic-button:hover:not(:disabled) {
  border-color: var(--accent);
  background: var(--accent-soft);
  color: var(--accent-strong);
}
.mic-button.is-recording {
  border-color: var(--error);
  color: var(--error);
  animation: mic-pulse 1.4s ease-in-out infinite;
}
.mic-button.is-transcribing {
  border-color: var(--accent);
  color: var(--accent-strong);
  animation: mic-pulse 1.1s ease-in-out infinite;
}
@keyframes mic-pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.45; }
}
@media (prefers-reduced-motion: reduce) {
  .mic-button.is-recording,
  .mic-button.is-transcribing {
    animation: none;
  }
}

textarea {
  resize: none;
  min-height: 50px;
  max-height: 180px;
  padding: 13px 14px;
  border: 1px solid var(--line-strong);
  border-radius: 8px;
  background: var(--panel);
  color: var(--ink);
  font: inherit;
}

textarea::placeholder {
  color: color-mix(in srgb, var(--muted) 82%, transparent);
}

textarea:focus {
  outline: 3px solid color-mix(in srgb, var(--accent) 24%, transparent);
  border-color: var(--accent);
}

button {
  min-width: 98px;
  height: 50px;
  border: 0;
  border-radius: 8px;
  background: var(--accent);
  color: #ffffff;
  font: inherit;
  font-weight: 750;
  cursor: pointer;
  transition: background 140ms ease, transform 140ms ease, opacity 140ms ease;
}

button:hover {
  background: var(--accent-strong);
}

button:active {
  transform: translateY(1px);
}

button:disabled {
  cursor: not-allowed;
  opacity: 0.6;
}

pre {
  margin: 0;
  padding: 12px;
  overflow: auto;
  border-radius: 8px;
  background: var(--code-bg);
  color: #eef4f6;
  font: 13px/1.45 "Consolas", "SFMono-Regular", monospace;
}

details {
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 10px 12px;
  background: var(--panel-raised);
}

summary {
  cursor: pointer;
  color: var(--accent-strong);
  font-weight: 700;
}

.table-wrap {
  overflow: auto;
  border: 1px solid var(--line);
  border-radius: 8px;
  /* Cap the height so a tall wide result keeps its horizontal scrollbar on screen instead of
     pushing it below the fold (issues/horizonal_scrollbar_issue.png). */
  max-height: min(62vh, 620px);
  scrollbar-width: thin;
  scrollbar-color: var(--line-strong) transparent;
}

.table-wrap.is-preview {
  max-height: min(42vh, 420px);
}

.result-table {
  border: 1px solid var(--line);
  border-radius: 8px;
  overflow: hidden;
  background: var(--panel);
}

.result-toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 8px 10px;
  border-bottom: 1px solid var(--line);
  background: var(--panel-raised);
}

.result-meta {
  min-width: 160px;
  color: var(--muted);
  font-size: 12px;
  font-weight: 700;
}

.result-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: 7px;
}

.secondary-button {
  min-width: 0;
  width: auto;
  height: 30px;
  padding: 0 10px;
  border: 1px solid var(--line-strong);
  background: var(--panel);
  color: var(--ink);
  font-size: 12px;
  font-weight: 750;
}

.secondary-button:hover {
  border-color: var(--accent);
  background: var(--accent-soft);
  color: var(--accent-strong);
}

.copy-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  align-self: center;
  width: 28px;
  min-width: 28px;
  height: 28px;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: 6px;
  background: transparent;
  color: var(--muted);
}

.copy-button:hover:not(:disabled) {
  border-color: var(--accent);
  background: var(--accent-soft);
  color: var(--accent-strong);
}

.copy-button.is-copied {
  border-color: var(--accent);
  color: var(--accent-strong);
}

.copy-button.is-failed {
  border-color: var(--error);
  color: var(--error);
}

/* The confirmation state disables the button for its 1.5s; the global dimming would hide it. */
.copy-button:disabled {
  opacity: 1;
}

.bubble > .copy-button {
  position: absolute;
  top: 8px;
  right: 8px;
  margin: 0;
}

.bubble.has-copy {
  padding-right: 46px;
}

/* ☆ save-as-prompt, sits just left of the copy icon on a question bubble. */
.star-button { color: var(--muted); }
.star-button.is-saved { border-color: var(--accent); color: var(--accent-strong); }
.bubble > .star-button {
  position: absolute;
  top: 8px;
  right: 42px;
  margin: 0;
}
.bubble.has-tools {
  padding-right: 80px;
}

/* 🔊 read-aloud, sits where ☆ does but on an assistant bubble instead (never both at once - see
   appendMessage). is-speaking recolours it the same way copy-button's is-copied does. */
.bubble > .speak-button {
  position: absolute;
  top: 8px;
  right: 42px;
  margin: 0;
}
.speak-button.is-speaking {
  border-color: var(--accent);
  color: var(--accent-strong);
}

details > .copy-button {
  margin-top: 10px;
  vertical-align: middle;
}

.run-sql-button + .copy-button {
  margin-left: 8px;
}

.result-table .table-wrap {
  border: 0;
  border-radius: 0;
}

table {
  /* Auto width so a wide result grows past .table-wrap and gives it something to scroll;
     min-width keeps a narrow result filling the panel. */
  width: auto;
  border-collapse: collapse;
  min-width: 100%;
  background: var(--panel);
}

th,
td {
  padding: 9px 10px;
  border-bottom: 1px solid var(--line);
  text-align: left;
  vertical-align: top;
}

th {
  background: var(--panel-raised);
  color: var(--muted);
  font-weight: 750;
  /* Column names are long SCREAMING_SNAKE_CASE; wrapping them mid-word was what crushed the
     columns. Let the header claim its width and scroll instead. */
  white-space: nowrap;
}

td {
  white-space: nowrap;
}

thead th {
  position: sticky;
  top: 0;
  z-index: 1;
}

.run-sql-button {
  margin-top: 10px;
  min-width: 0;
  width: auto;
  height: auto;
  padding: 6px 16px;
  font-size: 13px;
}

.suggestions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.suggestions button {
  width: auto;
  min-width: 0;
  height: auto;
  padding: 8px 10px;
  background: var(--accent-soft);
  color: var(--accent-strong);
  border: 1px solid color-mix(in srgb, var(--accent) 25%, var(--line));
  font-weight: 650;
}

/* "Would you like a chart or recommendations?" under a data-backed answer. */
.follow-ups {
  margin-top: 0.75rem;
}

/* One of several tables in a single answer: its caption, statement and table stay together. */
.result-block {
  margin-top: 0.85rem;
}

.result-title {
  margin: 0 0 0.35rem;
  color: var(--muted);
  font-size: 0.92rem;
  font-weight: 650;
}

/* Streaming answer: the latest agent status above the text being written. */
.live-status {
  margin: 0 0 0.4rem;
  color: var(--muted);
  font-size: 0.92rem;
}

.follow-ups-question {
  margin: 0 0 0.4rem;
  color: var(--muted);
  font-size: 0.92rem;
}

.follow-ups-empty {
  margin: 0.75rem 0 0;
  color: var(--muted);
  font-size: 0.92rem;
}

.error {
  color: var(--error);
  font-weight: 700;
}

.app-footer {
  min-height: 42px;
  gap: 12px;
  padding: 8px 22px;
  border-top: 1px solid var(--line);
  background: var(--footer-bg);
  color: var(--muted);
  font-size: 12px;
  font-weight: 650;
}

.partner-logo {
  display: block;
  width: auto;
  object-fit: contain;
}

.highspring-logo {
  height: 22px;
}

.snowflake-logo {
  height: 20px;
}

:root[data-theme="dark"] .highspring-logo,
:root[data-theme="dark"] .snowflake-logo {
  filter: brightness(0) invert(1);
}

.footer-divider {
  width: 1px;
  height: 20px;
  background: var(--line-strong);
}

/* ── Explicit grid-row placements (tab-bar added as row 2) ── */
.topbar         { grid-row: 1; }
.tab-bar        { grid-row: 2; }
.messages       { grid-row: 3; }
.worksheet-body { grid-row: 3; }
.composer       { grid-row: 4; }
.app-footer     { grid-row: 5; }

/* ── Tab bar ── */
.tab-bar {
  display: flex;
  padding: 0 14px;
  border-bottom: 1px solid var(--line);
  background: color-mix(in srgb, var(--panel) 92%, transparent);
}

.tab-btn {
  min-width: 0;
  width: auto;
  height: 42px;
  padding: 0 14px;
  background: transparent;
  border: none;
  border-bottom: 2px solid transparent;
  border-radius: 0;
  color: var(--muted);
  font-size: 14px;
  font-weight: 650;
  cursor: pointer;
  transition: color 140ms, border-color 140ms;
  margin-bottom: -1px;
}

.tab-btn:hover {
  background: transparent;
  color: var(--ink);
  transform: none;
}

.tab-btn:active {
  transform: none;
}

.tab-btn[aria-selected="true"] {
  color: var(--accent);
  border-bottom-color: var(--accent);
}

/* The UA hidden rule is weak and .tab-btn above outranks it (same reason as
   .numeric-control[hidden]). Chat starts hidden until the "Classic" toggle reveals it. */
.tab-btn[hidden] {
  display: none;
}

/* ── Worksheet visibility ── */
.worksheet-body {
  display: none;
  flex-direction: column;
  min-height: 0;
  overflow: hidden;
  background:
    linear-gradient(var(--line) 1px, transparent 1px),
    linear-gradient(90deg, var(--line) 1px, transparent 1px);
  background-color: color-mix(in srgb, var(--panel) 94%, var(--bg));
  background-size: 32px 32px;
  background-position: -1px -1px;
}

.chat-panel[data-tab="worksheet"] > .messages  { display: none; }
.chat-panel[data-tab="worksheet"] > .composer  { display: none; }
.chat-panel[data-tab="worksheet"] > .assistant-body { display: none; }
.chat-panel[data-tab="worksheet"] .worksheet-body { display: flex; }

/* ── Assistant panel (Cortex Agent) ── */
.assistant-body {
  grid-row: 3 / span 2;
  display: none;
  flex-direction: column;
  overflow: hidden;
  min-height: 0;
}

.assistant-body .assistant-messages {
  flex: 1 1 auto;
  overflow-y: auto;
}

.assistant-body .assistant-composer {
  flex: 0 0 auto;
}

.chat-panel[data-tab="assistant"] > .messages       { display: none; }
.chat-panel[data-tab="assistant"] > .composer       { display: none; }
.chat-panel[data-tab="assistant"] > .worksheet-body { display: none; }
.chat-panel[data-tab="assistant"] > .assistant-body { display: flex; }

/* Source citations rendered under an agent answer */
.sources {
  margin-top: 10px;
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.source-item {
  display: flex;
  align-items: baseline;
  gap: 8px;
  font-size: 13px;
}

.source-badge {
  flex: 0 0 auto;
  padding: 1px 7px;
  border-radius: 999px;
  font-size: 11px;
  font-weight: 650;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  background: color-mix(in srgb, var(--accent) 16%, transparent);
  color: var(--accent);
}

.source-name {
  flex: 0 0 auto;
  white-space: nowrap;
  font-weight: 600;
  color: var(--ink);
}

.source-snippet {
  flex: 1 1 auto;
  min-width: 0;
  color: var(--muted);
}

/* ── Recommended actions ── */
/* Priority colour comes from the theme tokens, so high/medium/low flip with light and dark
   instead of being pinned to one palette. */
.actions-block {
  margin-top: 12px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--panel-raised);
}

.actions-toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 8px 10px;
  border-bottom: 1px solid var(--line);
}

.actions-heading {
  color: var(--muted);
  font-size: 12px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.03em;
}

.actions-buttons {
  display: flex;
  align-items: center;
  gap: 7px;
}

.action-list {
  margin: 0;
  padding: 8px 10px;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.action-card {
  padding: 8px 10px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--panel);
}

.action-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
}

.action-check {
  display: flex;
  align-items: baseline;
  gap: 8px;
  cursor: pointer;
}

.action-check input {
  flex: 0 0 auto;
  margin: 0;
}

.action-title {
  font-size: 13px;
  font-weight: 700;
  color: var(--ink);
}

.action-badge {
  flex: 0 0 auto;
  padding: 1px 7px;
  border-radius: 999px;
  font-size: 11px;
  font-weight: 650;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  background: color-mix(in srgb, var(--muted) 16%, transparent);
  color: var(--muted);
}

.action-badge.is-high {
  background: color-mix(in srgb, var(--error) 16%, transparent);
  color: var(--error);
}

.action-badge.is-medium {
  background: color-mix(in srgb, var(--accent) 16%, transparent);
  color: var(--accent);
}

.action-line {
  margin: 4px 0 0;
  font-size: 13px;
  color: var(--ink);
}

.action-key {
  color: var(--muted);
  font-weight: 650;
}

.actions-note {
  margin: 0;
  padding: 0 10px 8px;
  color: var(--muted);
  font-size: 12px;
}

/* ── Worksheet layout ── */
.ws-editor-wrap {
  flex: 0 0 auto;
  padding: 16px 22px 0;
  background: color-mix(in srgb, var(--panel) 96%, transparent);
  border-bottom: 1px solid var(--line);
}

.ws-textarea {
  display: block;
  width: 100%;
  min-height: 160px;
  max-height: 380px;
  resize: vertical;
  padding: 12px 14px;
  border: 1px solid var(--line-strong);
  border-radius: 8px;
  background: var(--code-bg);
  color: #eef4f6;
  font: 13px/1.5 "Consolas", "SFMono-Regular", monospace;
  tab-size: 4;
}

.ws-textarea::placeholder {
  color: color-mix(in srgb, var(--muted) 50%, transparent);
}

.ws-textarea:focus {
  outline: 3px solid color-mix(in srgb, var(--accent) 24%, transparent);
  border-color: var(--accent);
}

.ws-toolbar {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 12px 0 16px;
}

.ws-toolbar button {
  min-width: 0;
  width: auto;
  height: 38px;
  padding: 0 18px;
  font-size: 14px;
}

.ws-save-btn {
  background: var(--panel-raised);
  color: var(--ink);
  border: 1px solid var(--line-strong);
}

.ws-save-btn:hover {
  background: var(--accent-soft);
  color: var(--accent-strong);
  border-color: var(--accent);
}

.ws-error {
  flex: 1 1 auto;
  color: var(--error);
  font-size: 13px;
  font-weight: 700;
}

.ws-results {
  flex: 1 1 auto;
  overflow: auto;
  padding: 20px 22px;
  scrollbar-width: thin;
  scrollbar-color: var(--line-strong) transparent;
}

.ws-no-results {
  color: var(--muted);
  font-size: 14px;
}

@media (max-width: 720px) {
  .app-shell {
    padding: 0;
  }

  .chat-panel {
    height: 100dvh;
    border-width: 0;
    border-radius: 0;
  }

  .topbar {
    align-items: flex-start;
    flex-direction: column;
  }

  .topbar-actions {
    width: 100%;
    justify-content: flex-start;
  }

  .messages,
  .composer,
  .topbar,
  .app-footer {
    padding-left: 14px;
    padding-right: 14px;
  }

  .composer {
    grid-template-columns: 1fr;
  }

  button {
    width: 100%;
  }

  .theme-toggle {
    width: 46px;
  }

  /* Icon-only, like theme-toggle above - full-width would look like a second Send button. */
  .mic-button {
    width: 46px;
    min-width: 46px;
  }

  /* Stretch, because the control now stacks under its label and would otherwise sit at its
     own width inside the half-row this gives it. */
  .numeric-control {
    width: calc(50% - 5px);
    align-items: stretch;
  }

  /* The control is half the row here, so the select shrinks to it instead of holding the
     desktop minimum and overflowing. */
  .numeric-control select {
    min-width: 0;
    max-width: 100%;
  }

  .sql-control,
  .theme-control {
    width: 100%;
  }

  .result-toolbar {
    align-items: flex-start;
    flex-direction: column;
  }

  .result-actions {
    width: 100%;
    justify-content: flex-start;
  }

  .app-footer {
    flex-wrap: wrap;
  }

  .ws-editor-wrap,
  .ws-results {
    padding-left: 14px;
    padding-right: 14px;
  }
}

/* ── Markdown answer body (Chat + Assistant bubbles) ── */
.md {
  line-height: 1.55;
}

.md > * + * {
  margin-top: 10px;
}

.md p {
  margin: 0;
}

.md-heading {
  margin: 0;
  line-height: 1.3;
  color: var(--ink);
  font-weight: 750;
}

.md h3.md-heading { font-size: 1.15rem; }
.md h4.md-heading { font-size: 1.05rem; }
.md h5.md-heading,
.md h6.md-heading { font-size: 1rem; }

.md > .md-heading + * {
  margin-top: 6px;
}

.md-list {
  margin: 0;
  padding-left: 22px;
}

.md-list li + li {
  margin-top: 4px;
}

.md-list .md-list {
  margin-top: 4px;
}

.md li > p + p {
  margin-top: 6px;
}

.md code {
  padding: 1px 5px;
  border: 1px solid var(--line);
  border-radius: 5px;
  background: var(--panel-raised);
  color: var(--accent-strong);
  font: 0.9em/1.4 "Consolas", "SFMono-Regular", monospace;
  overflow-wrap: anywhere;
}

.md pre {
  padding: 12px 14px;
}

.md pre code {
  padding: 0;
  border: 0;
  border-radius: 0;
  background: transparent;
  color: inherit;
  font: 13px/1.45 "Consolas", "SFMono-Regular", monospace;
}

.md blockquote {
  margin: 0;
  padding: 4px 0 4px 12px;
  border-left: 3px solid var(--accent);
  color: var(--muted);
}

.md hr {
  height: 1px;
  margin: 4px 0;
  border: 0;
  background: var(--line);
}

.md a {
  color: var(--accent-strong);
}

.md strong {
  font-weight: 750;
}

.md .md-table table {
  min-width: 100%;
  background: var(--panel);
}

.md .md-table th {
  white-space: normal;
}

.md .md-table td {
  white-space: nowrap;
}

/* ── Chart container (Vega-Lite from data_to_chart) ── */

.chart-container {
  margin: 0.75rem 0;
  padding: 1rem;
  border-radius: 10px;
  background: var(--code-bg);
  border: 1px solid var(--line);
  overflow: hidden;
}

/* vegaEmbed() puts class "vega-embed" on the container ITSELF, not on a child, so every rule
   below is a compound selector (.chart-container.vega-embed). Written as a descendant selector
   they never matched anything - which is why the bar under the chart and "Hide chart" both
   survived earlier fixes. Its injected CSS also makes the container inline-block. */
.chart-container.vega-embed {
  display: block;
}

.chart-container[hidden] {
  display: none !important;
}

.chart-container.vega-embed summary {
  top: 8px;
  right: 8px;
  color: inherit;
  font-weight: normal;
  opacity: 0.6;
  transition: opacity 0.2s;
}

/* vega-embed opens the menu at right:-9px, past the container's padding edge, where
   .chart-container's overflow:hidden cut it off. Keep it under the "…" and inside the frame. */
.chart-container.vega-embed .vega-actions {
  top: 44px;
  right: 8px;
}

.chart-container.vega-embed summary:hover,
.chart-container.vega-embed details[open] summary {
  opacity: 1;
}

/* The export menu is a <details> whose <summary> ("…") is absolutely positioned top-right.
   The global details rule (meant for the SQL block) boxed the rest into an empty bar under the
   chart that showed "Click to view actions" and did nothing. */
.chart-container.vega-embed details {
  border: 0;
  border-radius: 0;
  padding: 0;
  background: none;
}

.chart-container.vega-embed .vega-actions a {
  color: var(--accent);
  font-family: inherit;
  font-size: 0.8rem;
  padding: 4px 8px;
}

.chart-container.vega-embed .vega-actions a:hover {
  background: var(--line);
  border-radius: 4px;
}

:root[data-theme="dark"] .chart-container.vega-embed .vega-actions {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 6px;
}

/* Fix Vega tooltip which uses <table> internally and gets broken by global table styles */
#vg-tooltip-element {
  font-family: inherit;
  font-size: 13px;
  color: var(--ink);
  background: var(--panel-raised);
  border: 1px solid var(--line-strong);
  border-radius: 6px;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}

#vg-tooltip-element table {
  min-width: 0;
  width: auto;
  background: transparent;
  border: 0;
  margin: 0;
}

#vg-tooltip-element td,
#vg-tooltip-element th {
  padding: 3px 6px;
  border: 0;
  white-space: nowrap;
  vertical-align: middle;
}

/* ══ Left drawer, account, history, saved prompts ═════════════════════════ */

.drawer-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  min-width: 0;
  height: 34px;
  margin-right: 10px;
  border: 1px solid var(--line);
  border-radius: 9px;
  background: var(--panel-raised);
  color: var(--ink);
  font-size: 16px;
  cursor: pointer;
  flex: none;
}
.drawer-toggle:hover { border-color: var(--accent); color: var(--accent); }
.drawer-toggle:focus-visible {
  outline: 3px solid color-mix(in srgb, var(--accent) 24%, transparent);
  border-color: var(--accent);
}
/* Shown only for a signed-in user; author display rule outranks the UA [hidden]. */
.drawer-toggle[hidden] { display: none; }

.user-chip {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 4px 10px 4px 4px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--panel-raised);
  color: var(--ink);
  cursor: pointer;
  font-size: 13px;
  max-width: 190px;
}
.user-chip:hover { border-color: var(--accent); }
/* An author display rule outranks the UA [hidden]; restore it (same trap as .tab-btn). */
.user-chip[hidden] { display: none; }
.user-chip-avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  border-radius: 999px;
  background: var(--accent);
  color: #fff;
  font-weight: 700;
  font-size: 12px;
}
.user-chip-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.side-drawer {
  position: fixed;
  top: 0;
  left: 0;
  bottom: 0;
  width: var(--drawer-left-w);
  background: var(--panel);
  border-right: 1px solid var(--line);
  box-shadow: var(--shadow);
  transform: translateX(-100%);
  transition: transform 0.18s ease;
  z-index: 50;
  display: flex;
  flex-direction: column;
  overflow-y: auto;
  overscroll-behavior: contain;
}
.side-drawer.is-open { transform: translateX(0); }

/* Right-hand twin: anchored to the other edge, slides in from the right. */
.side-drawer--right {
  left: auto;
  right: 0;
  width: var(--drawer-right-w);
  border-right: 0;
  border-left: 1px solid var(--line);
  transform: translateX(100%);
}
.side-drawer--right.is-open { transform: translateX(0); }

@media (prefers-reduced-motion: reduce) { .side-drawer { transition: none; } }

/* Drag handle on the inner edge of an open drawer. A sibling of the drawer, not a child:
   the drawer scrolls (overflow-y:auto) and a child handle would scroll away with it. */
.drawer-resizer {
  display: none;
  position: fixed;
  top: 0;
  bottom: 0;
  width: 9px;
  z-index: 51;
  cursor: col-resize;
  touch-action: none;
  background: transparent;
}
.drawer-resizer::after {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  left: 4px;
  width: 1px;
  background: transparent;
  transition: background 0.12s ease;
}
.drawer-resizer:hover::after,
.drawer-resizer:focus-visible::after,
body.is-resizing-drawer .drawer-resizer::after { width: 2px; background: var(--accent); }
.drawer-resizer:focus-visible { outline: none; }
.drawer-resizer--left { left: calc(var(--drawer-left-w) - 5px); }
.drawer-resizer--right { right: calc(var(--drawer-right-w) - 5px); }
body.shell-push-left .drawer-resizer--left,
body.shell-push-right .drawer-resizer--right { display: block; }

/* While dragging: follow the pointer at once and do not select text on the way. */
body.is-resizing-drawer { cursor: col-resize; user-select: none; }
body.is-resizing-drawer .app-shell,
body.is-resizing-drawer .side-drawer { transition: none; }

/* Narrow window: stop pushing, let the drawer overlay instead. */
@media (max-width: 900px) {
  body.shell-push-left .app-shell,
  body.shell-push-right .app-shell { padding-left: 18px; padding-right: 18px; }
}

.drawer-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 14px 16px;
  border-bottom: 1px solid var(--line);
  position: sticky;
  top: 0;
  background: var(--panel);
  z-index: 1;
}
.drawer-title { font-weight: 700; letter-spacing: 0.02em; }
.drawer-close {
  border: 0;
  background: transparent;
  color: var(--muted);
  font-size: 22px;
  line-height: 1;
  cursor: pointer;
  padding: 0 4px;
}
.drawer-close:hover { color: var(--ink); }

/* Pin: keeps the drawer open while a prompt is picked and a question is asked. */
.drawer-head-actions { display: flex; align-items: center; gap: 2px; }
.drawer-pin {
  border: 0;
  background: transparent;
  color: var(--muted);
  font-size: 15px;
  line-height: 1;
  cursor: pointer;
  padding: 2px 4px;
  opacity: 0.55;
  transition: opacity 0.12s ease, transform 0.12s ease;
}
.drawer-pin:hover { opacity: 1; }
.drawer-pin[aria-pressed="true"] { opacity: 1; transform: rotate(-20deg); }
.drawer-pin:focus-visible {
  outline: 3px solid color-mix(in srgb, var(--accent) 24%, transparent);
  border-radius: 6px;
}
/* A pinned drawer says so on its inner edge, where the eye already is. */
.side-drawer.is-pinned { border-right-color: var(--accent); }
.side-drawer--right.is-pinned { border-right-color: var(--line); border-left-color: var(--accent); }
@media (prefers-reduced-motion: reduce) { .drawer-pin { transition: none; } }

.drawer-section {
  padding: 14px 16px;
  border-bottom: 1px solid var(--line);
}
.drawer-section-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  margin-bottom: 8px;
}
.drawer-section-head h2 {
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--muted);
  margin: 0;
}
.drawer-section-head > div { display: flex; gap: 6px; }

.drawer-mini,
.drawer-link {
  border: 1px solid var(--line);
  background: var(--panel-raised);
  color: var(--accent-strong);
  border-radius: 7px;
  padding: 4px 8px;
  font-size: 12px;
  cursor: pointer;
}
.drawer-mini:hover,
.drawer-link:hover { border-color: var(--accent); background: var(--panel-raised); }
/* The Users head's "Audit" is a link to /admin.html styled as the button next to it. */
a.drawer-mini { display: inline-flex; align-items: center; text-decoration: none; font-weight: 750; }
/* Fixed width so the head row does not shift as the label cycles A→Z / Z→A / 🕒 New. */
.drawer-section-head .drawer-sort { min-width: 4.6em; text-align: center; white-space: nowrap; }

/* The global `button` rule sizes every button for the composer (min-width 98px, height 50px,
   full width under 720px) and paints it accent on hover; drawer buttons are compact. */
.drawer-close,
.drawer-mini,
.drawer-link,
.drawer-item-main,
.drawer-item-del,
.drawer-item-edit,
.drawer-more,
.drawer-subquestions button {
  min-width: 0;
  width: auto;
  height: auto;
}
:is(.drawer-close, .drawer-item-main, .drawer-item-del, .drawer-item-edit):hover { background: transparent; }

.drawer-account-line { margin: 0 0 8px; font-size: 13px; color: var(--ink); }
.drawer-role-badge {
  display: inline-block;
  margin-left: 6px;
  padding: 1px 7px;
  border-radius: 999px;
  background: var(--accent-soft);
  color: var(--accent-strong);
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
}
.drawer-role-badge[hidden] { display: none; }
.drawer-account-actions { display: flex; gap: 8px; }

.drawer-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }
.drawer-item {
  display: flex;
  align-items: stretch;
  gap: 6px;
  border: 1px solid var(--line);
  border-radius: 9px;
  background: var(--panel-raised);
  padding: 6px 8px;
}
.drawer-item.is-current,
.drawer-item:has(> button.drawer-item-main:hover) { border-color: var(--accent); }
/* Rows past the "Show all" limit; the display:flex above outranks the UA [hidden]. */
.drawer-item[hidden] { display: none; }
.drawer-item-main {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
  border: 0;
  background: transparent;
  text-align: left;
  cursor: pointer;
  color: var(--ink);
  font-weight: 400;
  padding: 2px;
}
.drawer-item-main.is-static { cursor: default; }
.drawer-item-title {
  font-size: 13px;
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.drawer-item-meta {
  font-size: 11px;
  color: var(--muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.drawer-item-del,
.drawer-item-edit {
  border: 0;
  background: transparent;
  color: var(--muted);
  cursor: pointer;
  font-size: 13px;
  padding: 0 4px;
  align-self: center;
}
.drawer-item-del:hover { color: var(--error); }
.drawer-item-edit:hover { color: var(--accent-strong); }
/* Edit/delete icons of a saved prompt, top-right beside the title. */
.drawer-item-tools { display: flex; gap: 2px; align-self: flex-start; }

.drawer-subquestions {
  margin: 4px 0 2px;
  padding: 0 0 0 8px;
  display: flex;
  flex-direction: column;
  gap: 4px;
  border-left: 2px solid var(--line);
}
.drawer-subquestions button {
  border: 0;
  background: transparent;
  color: var(--accent-strong);
  text-align: left;
  cursor: pointer;
  font-size: 12px;
  padding: 2px 4px;
}
.drawer-subquestions button:hover { text-decoration: underline; }

.drawer-empty { font-size: 12px; color: var(--muted); margin: 6px 0 0; }
.drawer-empty code,
.drawer-item-meta code {
  background: var(--accent-soft);
  color: var(--accent-strong);
  padding: 0 4px;
  border-radius: 4px;
}
.drawer-item-meta code { margin-right: 6px; font-size: 11px; }

.drawer-more {
  display: block;
  margin: 6px auto 0;
  border: 0;
  background: transparent;
  color: var(--accent-strong);
  font-size: 12px;
  font-weight: 600;
  padding: 2px 6px;
  cursor: pointer;
}
.drawer-more:hover { background: transparent; text-decoration: underline; }
.drawer-more[hidden] { display: none; }

/* A temporary password: large enough to read back, selectable in one click, with a copy button. */
.temp-password { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.temp-password code {
  font-size: 16px;
  letter-spacing: 0.04em;
  padding: 6px 10px;
  border-radius: 6px;
  background: var(--accent-soft);
  color: var(--accent-strong);
  user-select: all;
}

.drawer-settings { display: flex; flex-direction: column; gap: 12px; align-items: stretch; }
.drawer-settings .numeric-control,
.drawer-settings .sql-control,
.drawer-settings .theme-control,
.drawer-settings .extras-control { width: 100%; }
.drawer-settings .numeric-control input,
.drawer-settings .numeric-control select { width: 100%; max-width: none; }

/* ── Auth gate + dialogs ─────────────────────────────────────────────── */

.auth-view {
  position: fixed;
  inset: 0;
  display: grid;
  place-items: center;
  padding: 20px;
  background: rgba(15, 26, 44, 0.55);
  z-index: 100;
}
/* An author display rule outranks the UA [hidden]; restore it (same trap as .tab-btn). */
.auth-view[hidden] { display: none; }
/* Until someone is signed in nothing of the app shows: not the datasets, not the last conversation. */
body.is-gated .app-shell,
body.is-gated .side-drawer,
body.is-gated .drawer-resizer { visibility: hidden; }
body.is-gated .auth-view { background: var(--gate-bg); }
/* SideKick wordmark; --wm-image already swaps to the light-on-dark file in dark theme. */
.auth-brand {
  width: min(230px, 80%);
  aspect-ratio: 900 / 274;
  margin: 0 auto 4px;
  background: var(--wm-image) center / contain no-repeat;
}
.auth-card-foot {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  margin-top: 4px;
  padding-top: 12px;
  border-top: 1px solid var(--line);
  color: var(--muted);
  font-size: 12px;
  font-weight: 650;
}
#login-form { width: min(400px, 100%); padding: 28px; }
#login-form h2 { text-align: center; font-size: 18px; }
.auth-card {
  width: min(380px, 100%);
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 14px;
  box-shadow: var(--shadow);
  padding: 22px;
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.auth-card h2, .auth-card h3 { margin: 0; }
.auth-card label {
  display: flex;
  flex-direction: column;
  gap: 4px;
  font-size: 13px;
  color: var(--muted);
}
.auth-card input,
.auth-card textarea,
.auth-card select {
  font: inherit;
  padding: 8px 10px;
  border: 1px solid var(--line-strong);
  border-radius: 8px;
  background: var(--panel-raised);
  color: var(--ink);
}
.auth-card input:focus,
.auth-card textarea:focus,
.auth-card select:focus {
  outline: 3px solid color-mix(in srgb, var(--accent) 24%, transparent);
  border-color: var(--accent);
}
.auth-card button[type="submit"],
.auth-card-actions button {
  font: inherit;
  padding: 8px 14px;
  border: 1px solid var(--accent);
  border-radius: 8px;
  background: var(--accent);
  color: #fff;
  cursor: pointer;
}
.auth-card-actions { display: flex; gap: 8px; justify-content: flex-end; }
.auth-card-actions button[type="button"] {
  background: var(--panel-raised);
  color: var(--accent-strong);
  border-color: var(--line);
}
.auth-error { margin: 0; color: var(--error); font-size: 13px; }
.auth-note { margin: 0; color: var(--muted); font-size: 12px; }

/* A request in flight (sign-in, save, create, delete, sign-out): a spinner in the button, kept at
   full strength while it is disabled. currentColor, so it reads on an accent button and on a pale
   drawer link alike. */
button.is-busy:disabled { opacity: 1; cursor: progress; }
button.is-busy::before {
  content: "";
  display: inline-block;
  width: 1em;
  height: 1em;
  margin-right: 0.5em;
  vertical-align: -0.15em;
  border: 2px solid color-mix(in srgb, currentColor 35%, transparent);
  border-top-color: currentColor;
  border-radius: 50%;
  animation: hs-spin 0.8s linear infinite;
}
@media (prefers-reduced-motion: reduce) {
  button.is-busy::before { animation: hs-pulse 1.6s ease-in-out infinite; }
}

#prompt-dialog, #user-dialog, #profile-dialog, #delete-user-dialog, #confirm-dialog, #email-dialog {
  border: 1px solid var(--line);
  border-radius: 14px;
  padding: 22px;
  max-width: min(460px, 92vw);
  background: var(--panel);
  color: var(--ink);
  box-shadow: var(--shadow);
}
#prompt-dialog, #email-dialog { max-width: min(560px, 92vw); }
#profile-dialog { width: min(640px, 92vw); max-width: min(640px, 92vw); max-height: 88vh; overflow-y: auto; }
#prompt-dialog::backdrop, #user-dialog::backdrop, #profile-dialog::backdrop,
#delete-user-dialog::backdrop, #confirm-dialog::backdrop, #email-dialog::backdrop { background: rgba(15, 26, 44, 0.5); }
#prompt-dialog form, #user-dialog form, #profile-dialog form,
#delete-user-dialog form, #confirm-dialog form, #email-dialog form { display: flex; flex-direction: column; gap: 14px; }
#prompt-dialog h3, #user-dialog h3, #profile-dialog h3, #delete-user-dialog h3, #confirm-dialog h3, #email-dialog h3 { margin: 0; }
/* The confirm text is built from plain strings; a "\n" in one is a line break. */
#confirm-dialog-message { white-space: pre-line; }
#prompt-dialog label, #user-dialog label, #profile-dialog label, #delete-user-dialog label, #email-dialog label {
  display: flex;
  flex-direction: column;
  gap: 4px;
  font-size: 13px;
  color: var(--muted);
}
/* A field group: label, control, then a hint below. The label is a plain block,
   so an inline <code> in a hint no longer lands on its own line. */
#prompt-dialog .dialog-field, #email-dialog .dialog-field { display: flex; flex-direction: column; gap: 4px; }
#prompt-dialog .dialog-field > label, #email-dialog .dialog-field > label {
  display: block;
  font-size: 13px;
  font-weight: 650;
  color: var(--ink);
}
.dialog-field-legend {
  font-size: 13px;
  font-weight: 650;
  color: var(--ink);
}
.dialog-intro {
  margin: 0;
  color: var(--muted);
  font-size: 13px;
  line-height: 1.5;
}
.field-hint {
  margin: 2px 0 0;
  color: var(--muted);
  font-size: 12px;
  line-height: 1.45;
}
.field-optional { color: var(--muted); font-weight: 400; }
#prompt-dialog input, #prompt-dialog textarea,
#user-dialog input, #user-dialog select,
#profile-dialog input[type="text"], #profile-dialog select, #delete-user-dialog input,
#email-dialog input, #email-dialog textarea {
  font: inherit;
  padding: 8px 10px;
  border: 1px solid var(--line-strong);
  border-radius: 8px;
  background: var(--panel-raised);
  color: var(--ink);
}
#prompt-dialog textarea { min-height: 140px; resize: vertical; }

/* Mic in a dialog field (prompt body, email note): overlaps the textarea's bottom-right corner,
   the same trick as .bubble > .copy-button but anchored to the field instead of a bubble. */
.field-with-mic { position: relative; }
.field-with-mic textarea { padding-right: 44px; }
.field-with-mic .mic-button {
  position: absolute;
  right: 8px;
  bottom: 8px;
  width: 28px;
  min-width: 28px;
  height: 28px;
  border-radius: 6px;
}

/* Admin profile: read-only controls look settled, not broken. */
#profile-dialog input:disabled, #profile-dialog select:disabled,
#user-dialog input:disabled, #user-dialog select:disabled { opacity: 0.75; cursor: default; }
.profile-head { display: flex; align-items: center; gap: 2px; }
#profile-dialog label.profile-check, #user-dialog label.profile-check, #email-dialog label.profile-check { flex-direction: row; align-items: center; gap: 8px; color: var(--ink); }
/* Per-user dataset grants (Add user + profile). One fieldset, two faces: chips when read-only,
   a scrollable checklist (+ filter once the catalog is long) when editing. Every rule with a
   `display` outranks the UA [hidden], so each hideable part restates it below. */
.dataset-grants { margin: 0; padding: 0; border: 0; display: flex; flex-direction: column; gap: 6px; min-width: 0; }
.dataset-grants[hidden] { display: none; }
.dataset-grants legend { padding: 0; margin-bottom: 6px; font-size: 13px; font-weight: 650; color: var(--ink); }
.grant-count { margin-left: 6px; font-weight: 500; color: var(--muted); }
.dataset-grants input:disabled { opacity: 0.75; cursor: default; }

.grant-chips { display: flex; flex-wrap: wrap; gap: 6px; margin: 0; padding: 0; list-style: none; }
.grant-chips[hidden] { display: none; }
.grant-chip {
  padding: 2px 10px;
  border-radius: 999px;
  background: var(--accent-soft);
  color: var(--accent-strong);
  font-size: 12px;
  font-weight: 600;
}
.grant-chip.is-empty { background: transparent; color: var(--muted); font-weight: 400; padding-left: 0; }

.grant-filter {
  font: inherit;
  font-size: 13px;
  padding: 6px 10px;
  border: 1px solid var(--line-strong);
  border-radius: 8px;
  background: var(--panel-raised);
  color: var(--ink);
}
.grant-filter[hidden] { display: none; }

/* About nine rows tall, then it scrolls: the dialog stays the same size at 10 datasets or 100. */
.grant-list {
  display: flex;
  flex-direction: column;
  gap: 2px;
  max-height: 236px;
  overflow-y: auto;
  padding: 4px 8px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--panel-raised);
}
.grant-list[hidden] { display: none; }
.grant-list label.profile-check { padding: 3px 0; }
.grant-list label.profile-check[hidden] { display: none; }
.grant-list .grant-none { padding: 6px 0; font-size: 12px; color: var(--muted); }

.grant-actions { display: flex; gap: 14px; }
.grant-actions[hidden] { display: none; }
.grant-actions button {
  min-width: 0;
  width: auto;
  padding: 0;
  border: 0;
  background: transparent;
  color: var(--accent-strong);
  font: inherit;
  font-size: 12px;
  cursor: pointer;
}
.grant-actions button:hover { text-decoration: underline; background: transparent; }
.grant-actions button:disabled { color: var(--muted); cursor: default; text-decoration: none; }

/* Users panel: search + access filter, shown once the list is longer than the row limit. */
.users-toolbar { display: flex; gap: 6px; margin: 0 0 8px; }
.users-toolbar[hidden] { display: none; }
.users-toolbar input, .users-toolbar select {
  min-width: 0;
  font: inherit;
  font-size: 12px;
  padding: 5px 8px;
  border: 1px solid var(--line-strong);
  border-radius: 8px;
  background: var(--panel-raised);
  color: var(--ink);
}
.users-toolbar input { flex: 1; }
.users-toolbar select { flex: 0 1 45%; }
/* History rows for a dataset the account no longer holds: visible, not openable. */
.drawer-item.is-locked .drawer-item-main { opacity: 0.55; cursor: not-allowed; }
.profile-section { border-top: 1px solid var(--line); padding-top: 12px; }
.profile-section h4 {
  margin: 0 0 8px;
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--muted);
}
.profile-count { text-transform: none; letter-spacing: 0; }
.profile-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }
.profile-list li {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 6px 8px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--panel-raised);
}
.profile-list li.is-empty { border: 0; background: none; padding: 0; color: var(--muted); font-size: 12px; }
.profile-row-title { font-size: 13px; color: var(--ink); overflow-wrap: anywhere; }
.profile-row-meta {
  font-size: 11px;
  color: var(--muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
#user-dialog .temp-password code, #profile-dialog .temp-password code { padding: 6px 10px; }
.auth-card-actions button.is-danger { background: var(--error); border-color: var(--error); color: #fff; }

#prompt-dialog code, #user-dialog code, #profile-dialog code, #delete-user-dialog code,
.field-hint code, .dialog-intro code {
  background: var(--accent-soft);
  color: var(--accent-strong);
  padding: 0 4px;
  border-radius: 4px;
}

/* ══ Motion + feedback ═════════════════════════════════════════════════════
   Every animation and transition in this section takes its duration from a --motion token, so the
   single reduced-motion block at the end stills all of them at once. tests/ui_feedback.test.js
   holds that line. Keep this section last in the file. */

:root {
  --ease-out: cubic-bezier(0.2, 0.8, 0.2, 1);
  --motion-fast: 140ms;
  --motion: 220ms;
  --motion-slow: 900ms;
  --success: #15803d;
}
:root[data-theme="dark"] { --success: #4ade80; }

@keyframes fx-rise {
  from { opacity: 0; transform: translateY(6px); }
  to { opacity: 1; transform: none; }
}
@keyframes fx-fade {
  from { opacity: 0; }
}
@keyframes fx-shake {
  20%, 60% { transform: translateX(-5px); }
  40%, 80% { transform: translateX(5px); }
}
@keyframes fx-flash {
  0%, 30% {
    border-color: var(--accent);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 38%, transparent);
  }
  100% { box-shadow: 0 0 0 0 transparent; }
}
@keyframes fx-dots {
  0%, 80%, 100% { opacity: 0.25; transform: none; }
  40% { opacity: 1; transform: translateY(-3px); }
}
@keyframes fx-shimmer {
  from { background-position: 100% 0; }
  to { background-position: -100% 0; }
}

/* Dialogs fade and settle in. Leaving the top layer can only be animated where overlay +
   allow-discrete are supported (Chromium); elsewhere a dialog simply closes. */
dialog {
  opacity: 0;
  transform: translateY(8px) scale(0.98);
  transition:
    opacity var(--motion) var(--ease-out),
    transform var(--motion) var(--ease-out),
    overlay var(--motion) allow-discrete,
    display var(--motion) allow-discrete;
}
dialog[open] { opacity: 1; transform: none; }
@starting-style {
  dialog[open] { opacity: 0; transform: translateY(8px) scale(0.98); }
}
dialog::backdrop {
  opacity: 0;
  transition:
    opacity var(--motion) var(--ease-out),
    overlay var(--motion) allow-discrete,
    display var(--motion) allow-discrete;
}
dialog[open]::backdrop { opacity: 1; }
@starting-style {
  dialog[open]::backdrop { opacity: 0; }
}

/* Sign-in and password cards rise in over a fading veil. */
.auth-view:not([hidden]) { animation: fx-fade var(--motion) var(--ease-out); }
.auth-view:not([hidden]) .auth-card { animation: fx-rise var(--motion) var(--ease-out); }

/* A form error shakes once, so the same message shown twice still registers. */
.auth-error.is-shaking { animation: fx-shake calc(var(--motion) * 1.8) ease-in-out; }
.auth-note.is-success { color: var(--success); font-weight: 650; }
.auth-success {
  margin: 0;
  padding: 8px 10px;
  border-radius: 8px;
  background: color-mix(in srgb, var(--success) 12%, transparent);
  color: var(--success);
  font-size: 13px;
  font-weight: 650;
  animation: fx-rise var(--motion) var(--ease-out);
}

/* Transcript: each new message rises in; the pending placeholder carries three bouncing dots. */
.message { animation: fx-rise var(--motion) var(--ease-out); }
.typing-dots {
  display: inline-flex;
  gap: 4px;
  margin-left: 8px;
  vertical-align: middle;
}
.typing-dots i {
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--muted);
  animation: fx-dots calc(var(--motion) * 6) ease-in-out infinite;
}
.typing-dots i:nth-child(2) { animation-delay: calc(var(--motion) * 0.9); }
.typing-dots i:nth-child(3) { animation-delay: calc(var(--motion) * 1.8); }

/* Tab switch: the panel that becomes visible fades in (an animation restarts whenever display does). */
#messages,
#chat-form,
.assistant-body,
.worksheet-body { animation: fx-fade var(--motion-fast) ease-out; }

/* Drawer lists: rows rise in when a drawer opens, dim while a delete runs, and a saved or created
   row flashes once. */
.drawer-item.is-entering { animation: fx-rise var(--motion) var(--ease-out); }
.drawer-item.is-flash { animation: fx-flash var(--motion-slow) var(--ease-out); }
.drawer-item.is-removing {
  opacity: 0.5;
  pointer-events: none;
  transition: opacity var(--motion-fast) ease-out;
}
.drawer-subquestions { animation: fx-rise var(--motion-fast) var(--ease-out); }
.shortcut-hints:not([hidden]) { animation: fx-rise var(--motion-fast) var(--ease-out); }

/* Loading placeholders: drawer lists on first load, and the admin profile. */
.drawer-skeleton,
.profile-list li.is-skeleton,
#profile-username.is-loading {
  background: linear-gradient(90deg, var(--panel-raised) 25%, var(--line) 50%, var(--panel-raised) 75%);
  background-size: 200% 100%;
  animation: fx-shimmer calc(var(--motion) * 6) linear infinite;
}
.drawer-skeleton { height: 46px; border: 1px solid var(--line); border-radius: 9px; }
.profile-list li.is-skeleton { height: 38px; }
#profile-username.is-loading { min-width: 140px; border-radius: 6px; color: transparent; }

/* A list that failed to load says so above the list, with a retry. */
.drawer-load-error {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0 0 8px;
  color: var(--error);
  font-size: 12px;
}
.drawer-load-error button {
  min-width: 0;
  width: auto;
  height: auto;
  padding: 2px 6px;
  border: 0;
  background: transparent;
  color: var(--accent-strong);
  font-size: 12px;
  font-weight: 600;
}
.drawer-load-error button:hover { background: transparent; text-decoration: underline; }

/* A temporary password arrives with a single highlight ring. */
.temp-password { animation: fx-rise var(--motion) var(--ease-out); }
.temp-password code { animation: fx-flash var(--motion-slow) var(--ease-out) var(--motion); }

/* Toasts: a bottom-centre stack. The region is a manual popover (top layer, above an open modal),
   so the browser's popover box is reset to a transparent, click-through column. */
.toast-region {
  position: fixed;
  inset: auto auto 24px 50%;
  transform: translateX(-50%);
  display: flex;
  flex-direction: column;
  gap: 8px;
  width: min(420px, calc(100vw - 32px));
  margin: 0;
  padding: 0;
  border: 0;
  background: transparent;
  overflow: visible;
  pointer-events: none;
  z-index: 200;
}
.toast {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 8px 10px 12px;
  border: 1px solid var(--line-strong);
  border-left: 4px solid var(--accent);
  border-radius: 10px;
  background: var(--panel);
  color: var(--ink);
  box-shadow: var(--shadow);
  font-size: 13px;
  pointer-events: auto;
  animation: fx-rise var(--motion) var(--ease-out);
}
.toast.is-settled { animation: none; }
.toast.is-success { border-left-color: var(--success); }
.toast.is-error { border-left-color: var(--error); }
.toast-icon { display: inline-flex; flex: none; color: var(--accent); }
.toast.is-success .toast-icon { color: var(--success); }
.toast.is-error .toast-icon { color: var(--error); }
.toast-text { flex: 1; min-width: 0; overflow-wrap: anywhere; }
.toast-close {
  flex: none;
  width: 26px;
  min-width: 0;
  height: 26px;
  padding: 0;
  border: 0;
  border-radius: 6px;
  background: transparent;
  color: var(--muted);
  font-size: 18px;
  font-weight: 400;
  line-height: 1;
}
.toast-close:hover { background: var(--accent-soft); color: var(--ink); }

@media (prefers-reduced-motion: reduce) {
  :root { --motion-fast: 0ms; --motion: 0ms; --motion-slow: 0ms; }
}

/* Safety net for Ctrl+P on the app itself: the real PDF export builds its own printable
   document (export-print.js) in a hidden iframe, so this should never actually be seen -
   it only guards against printing the live UI (drawers, dialogs, chrome) by mistake. */
@media print {
  .app-shell { display: none; }
}
