:root {
  /* "Digital Scientific Instrument": deep graphite/navy instead of flat black, an electric-cyan
     primary (oscilloscope trace / spectroscopy fluorescence) with amber as the secondary accent —
     the classic dual-trace-CRT pairing (cyan/amber phosphor) — hairline borders instead of heavy
     cards, and a near-rectangular radius throughout. */
  --bg: #0a0e14;
  --bg-grid-line: rgba(120, 200, 220, 0.05);
  --panel: #0e131b;
  --panel-2: #131a24;
  --border: #212a38;
  --border-strong: #2e3a4d;
  --text: #dbe4ea;
  --muted: #7c8ba0;
  --accent: #22e2c4;
  --accent-hover: #4ff0d6;
  --accent-contrast: #04211a;
  --accent-2: #ffb454;
  --user-bubble: #131a24;
  --assistant-bubble: #0c1119;
  --error: #ff5d7a;
  --radius: 2px;
  --font-mono: ui-monospace, "Cascadia Mono", "SF Mono", "JetBrains Mono", Consolas, monospace;
  /* Locked so the mobile sidebar-overlay's own `top` offset (below) can never silently drift out
     of sync with the topbar's real rendered height — see that rule's own comment. */
  --topbar-height: 54px;
}

* { box-sizing: border-box; }

html, body {
  height: 100%;
  margin: 0;
  background:
    linear-gradient(var(--bg-grid-line) 1px, transparent 1px) 0 0 / 32px 32px,
    linear-gradient(90deg, var(--bg-grid-line) 1px, transparent 1px) 0 0 / 32px 32px,
    var(--bg);
  color: var(--text);
  font-family: -apple-system, "Segoe UI", Arial, sans-serif;
}

/* Visualization-only rendering used by the public gallery cards. The URL and snapshot loader are
   identical to a normal shared lab; only the transcript chrome is removed inside the preview. */
body.shared-viz-embed { overflow: hidden; }
body.shared-viz-embed .topbar,
body.shared-viz-embed .chat,
body.shared-viz-embed .mobile-view-toggle { display: none !important; }
body.shared-viz-embed .app,
body.shared-viz-embed .workspace,
body.shared-viz-embed .viz-column,
body.shared-viz-embed .viz-workspace-host,
body.shared-viz-embed .viz-custom-interactive-host { width: 100%; height: 100%; min-height: 0; }
body.shared-viz-embed .workspace { display: block; }
body.shared-viz-embed .viz-column { display: block !important; padding: 0; border: 0; overflow: hidden; }
body.shared-viz-embed .viz-custom-interactive-frame { height: 100%; min-height: 100%; border: 0; }

.app-shell {
  display: flex;
  flex-direction: row-reverse; /* sidebar (first DOM child) renders on the left in this RTL layout */
  /* 100vh is the mobile browser's large/layout viewport on some tablets, so its bottom can sit
     behind browser chrome while overflow:hidden makes the composer unreachable. Keep vh as the
     legacy fallback, then prefer the dynamic viewport that tracks the actually visible height. */
  height: 100vh;
  height: 100dvh;
  overflow: hidden;
}
/* Matches the [hidden] override convention already used everywhere else in this file (.viz-column,
   .reply-menu, .modal-backdrop, ...) — without it, `display: flex` above has the same specificity
   as the browser's default `[hidden] { display: none }` and wins, so appShell.hidden = true
   visibly failed to hide it (masked until now only because .auth-gate had the identical bug and
   sat on top as a full-screen opaque overlay). */
.app-shell[hidden] { display: none; }

.sidebar {
  width: 260px;
  flex-shrink: 0;
  background: var(--panel);
  border-inline-end: 1px solid var(--border);
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 12px;
  overflow: hidden;
  transition: width 0.15s ease, padding 0.15s ease, opacity 0.1s ease;
}

/* SESSION LOG — a small monospace label above the session list, instrument-panel style. */
.sidebar-log-label {
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.14em;
  color: var(--muted);
  padding: 0 2px;
  text-transform: uppercase;
}

.sidebar.collapsed {
  width: 0;
  padding-inline: 0;
  border-inline-end: none;
  opacity: 0;
}

.sidebar-new-btn {
  width: 100%;
}

.conversation-list {
  flex: 1;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.conversation-item {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 8px 10px;
  border-radius: var(--radius);
  border-inline-start: 2px solid transparent;
  cursor: pointer;
  white-space: nowrap;
}

.conversation-item:hover { background: var(--panel-2); border-inline-start-color: var(--border-strong); }
.conversation-item.active { background: var(--user-bubble); border-inline-start-color: var(--accent); }

.conversation-item .conv-title {
  flex: 1;
  overflow: hidden;
  text-overflow: ellipsis;
  font-size: 13.5px;
}

.conversation-item .conv-delete {
  background: none;
  border: none;
  color: var(--muted);
  cursor: pointer;
  font-size: 13px;
  padding: 2px 4px;
  border-radius: 4px;
  flex-shrink: 0;
}
.conversation-item .conv-delete:hover { color: var(--error); background: rgba(255, 107, 107, 0.1); }

.conversation-empty-hint {
  color: var(--muted);
  font-size: 12.5px;
  padding: 8px 10px;
}

.app {
  display: flex;
  flex-direction: column;
  /* Fill the already viewport-bounded shell. Measuring 100vh again here can make this flex child
     taller than its parent on tablet browsers and push the non-shrinking composer below view. */
  height: 100%;
  min-height: 0;
  flex: 1;
  min-width: 0;
}

.topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 10px 16px;
  min-height: var(--topbar-height);
  background: var(--panel);
  border-bottom: 1px solid var(--border);
  /* A faint instrument-panel glow along the bottom edge instead of a flat line. */
  box-shadow: 0 1px 0 0 color-mix(in srgb, var(--accent) 20%, transparent);
  flex-shrink: 0;
}

/* Keep the app identity on the right and its action buttons on the left in both UI languages.
   Only the bar's two groups are mirrored; English text and the rest of the interface remain LTR. */
html[dir="ltr"] .topbar { flex-direction: row-reverse; }
html[dir="ltr"] .topbar-controls { flex-direction: row-reverse; }

.topbar-left {
  display: flex;
  align-items: center;
  gap: 10px;
}

.brand-block {
  display: flex;
  flex-direction: column;
  gap: 1px;
  line-height: 1.2;
}

.brand {
  font-family: var(--font-mono);
  font-weight: 700;
  font-size: 16px;
  letter-spacing: 0.12em;
  color: var(--text);
}

.brand-tagline {
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.04em;
  color: var(--accent);
}

.topbar-controls {
  display: flex;
  align-items: center;
  gap: 8px;
}

.topbar-control-separator {
  width: 1px;
  height: 20px;
  margin-inline: 3px;
  background: var(--border-strong);
  flex: 0 0 1px;
}

select, input, textarea, button {
  font-family: inherit;
  font-size: 14px;
  color: var(--text);
}

select {
  background: var(--panel-2);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 6px 8px;
}

.icon-btn {
  background: var(--panel-2);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 6px 10px;
  cursor: pointer;
  white-space: nowrap;
}
.icon-btn:hover { background: var(--border); }
.icon-btn-attach { display: inline-flex; align-items: center; justify-content: center; color: var(--text); }
.icon-btn.active {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-contrast);
}
.icon-btn.active:hover { background: var(--accent-hover); }

.primary-btn {
  background: var(--accent);
  border: none;
  border-radius: var(--radius);
  padding: 8px 16px;
  color: var(--accent-contrast);
  font-weight: 600;
  cursor: pointer;
}
.primary-btn:hover { background: var(--accent-hover); }
.primary-btn:disabled { opacity: 0.5; cursor: default; }

.primary-btn.small, .ghost-btn.small { padding: 6px 12px; font-size: 13px; }

.ghost-btn {
  background: transparent;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 8px 16px;
  cursor: pointer;
}
.ghost-btn:hover { background: var(--panel-2); }

/* The chat scrolls in the host. The visual side contains one full-height sandboxed iframe;
   that iframe owns the visual workspace's only scrollbar. */
.workspace {
  flex: 1;
  display: flex;
  position: relative;
  min-height: 0;
  overflow: hidden;
}

.mobile-view-toggle { display: none; }

.chat {
  flex: 1;
  min-width: 0;
  overflow-y: auto;
  padding: 20px;
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.viz-column {
  /* Give rich/custom figures enough room for mobile-width layouts (for example, two linked
     charts side by side) while preserving a usable text column on narrower desktop windows.
     The 780px reserved here is 520px for that chat column PLUS the sidebar's own 260px (see
     .sidebar) — both come out of the same viewport budget as this column. Confirmed live: an
     earlier version reserved only 520px total, silently assuming the sidebar was free — on any
     desktop width where 100vw-520px landed inside this clamp's own 520-760px bounds (roughly
     1040-1280px, an ordinary browser window), this column's width tracked the viewport 1:1, so
     every extra pixel of a wider window went entirely to THIS column and the actual chat column
     (viewport minus sidebar minus this) stayed pinned at a constant ~260px no matter how wide the
     window got — a cramped, oddly narrow text column that never grew. See the sidebar-collapsed
     override below, which gives the 260px back once the sidebar isn't actually there to reserve
     it for. */
  width: clamp(520px, calc(100vw - 780px), 760px);
  position: relative;
  flex-shrink: 0;
  overflow: hidden;
  padding: 0;
  border-inline-start: 1px solid var(--border);
  background: var(--panel);
}
.viz-column:empty { border-inline-start: none; }
.viz-column[hidden] { display: none; }

/* The sidebar is the ONE piece of chrome pinned to a fixed visual side regardless of UI
   language — its default (Hebrew/RTL) placement already lands it on the left for free, because a
   plain flex-direction:row visually mirrors under dir="rtl" (first DOM child renders rightmost).
   Under English/LTR that mirroring reverses, which would flip it to the right — so it gets the
   OPPOSITE flex-direction here to land back on the left. Everything else in the workspace
   (.workspace itself, .composer) deliberately does NOT get this treatment: both are ordinary
   content flow, so they mirror naturally with the language like any other text — in Hebrew the
   viz column sits left of the chat and the composer's controls trail to the textbox's left; in
   English the viz column sits to the chat's right (this request) and the composer's controls
   trail to the textbox's right — the plain default row direction already produces both sides of
   that with no override needed. Scoped to the two-column desktop layout only (matches the
   max-width:900px breakpoint below) — on mobile the workspace is already forced to stack and the
   composer already uses explicit `order` values for its own reasons, neither of which this
   should fight with. */
@media (min-width: 901px) {
  html[dir="ltr"] .app-shell { flex-direction: row; }
  /* The sidebar's 260px is only actually reserved while it's open (see .viz-column's own 780px
     comment) — collapsed, it takes 0px of layout width, so give that budget back to the viz
     column here instead of leaving the chat with 260px of dead space the (now-gone) sidebar was
     the one reserving, not the chat. Scoped to this same min-width:901px block so it never
     competes with the mobile override below, which always wins there on its own terms. */
  .app-shell:has(#sidebar.collapsed) .viz-column {
    width: clamp(520px, calc(100vw - 520px), 760px);
  }
}

@media (max-width: 900px) {
  /* The Chat/Visual action button appears at this breakpoint; reclaim top-bar width for it. */
  .brand-tagline { display: none; }
  .topbar-controls { gap: 6px; }
  .topbar-control-separator { height: 18px; margin-inline: 1px; }

  .workspace {
    --mobile-view-toggle-size: 52px;
    --mobile-view-toggle-edge-gap: max(16px, env(safe-area-inset-bottom));
    --mobile-view-toggle-clearance: calc(var(--mobile-view-toggle-size) + var(--mobile-view-toggle-edge-gap));
    flex-direction: column;
  }
  /* The floating Chat/Visual switcher overlaps the workspace. Reserve its complete vertical
     footprint at the end of the mobile chat scroll surface. The visual workspace gets the
     equivalent clearance INSIDE its iframe scroll document, so the iframe itself stays full
     height. On desktop these overrides do not exist. */
  .chat {
    padding-bottom: calc(20px + var(--mobile-view-toggle-clearance));
  }
  .workspace.mobile-viz-view .chat { display: none; }
  /* Keep the sandbox laid out while Chat is selected: display:none collapses its viewport and
     makes both responsive charts and the runtime readiness checks see a zero-sized panel. Move
     the full-size column just outside the clipped host instead, so it keeps running without any
     visual or pointer presence in the Chat view. */
  .workspace:not(.mobile-viz-view) .viz-column:not([hidden]) {
    position: absolute;
    inset-block: 0;
    inset-inline-start: calc(100% + 1px);
  }
  .viz-column {
    width: 100%;
    height: 100%;
    flex: 1 1 auto;
    border-inline-start: none;
    border-top: none;
    max-height: none;
  }

  .app.mobile-viz-view .composer,
  .app.mobile-viz-view .attachment-tray { display: none; }

  .mobile-view-toggle:not([hidden]) {
    position: absolute;
    right: max(16px, env(safe-area-inset-right));
    bottom: var(--mobile-view-toggle-edge-gap);
    z-index: 40;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: var(--mobile-view-toggle-size);
    height: var(--mobile-view-toggle-size);
    padding: 0 15px;
    border: 1px solid color-mix(in srgb, var(--accent) 70%, white 12%);
    border-radius: 999px;
    background: var(--accent);
    color: var(--accent-contrast);
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.42);
    cursor: pointer;
    font-size: 22px;
    line-height: 1;
  }
  .mobile-view-toggle:hover,
  .mobile-view-toggle:focus-visible { background: var(--accent-hover); }
  .mobile-view-toggle:focus-visible { outline: 2px solid white; outline-offset: 2px; }

  /* A narrow composer becomes one connected two-row card: the growing textarea owns the
     first row and the actions sit directly beneath it, like ChatGPT's mobile composer. */
  .app .composer {
    flex-wrap: wrap;
    margin: 8px 10px 10px;
    padding: 8px 10px;
    row-gap: 2px;
    border-radius: 0;
  }
  .composer #textInput {
    flex-basis: 100%;
    order: 1;
    min-height: 38px;
    padding: 5px 6px 8px;
  }
  .composer #attachBtn { order: 2; }
  .composer #thinkingEffortSelect {
    order: 3;
    flex: 0 0 auto;
    margin-inline-start: auto;
    font-size: 13px;
  }
  .composer #sendBtn { order: 4; }
}

/* Wider than the chat/viz mobile-stacking breakpoint above (900px) on purpose: the desktop split
   layout's .viz-column has a hard 520px floor (see its own comment above), so on any viewport from
   901px up to ~1300px that floor is what's actually in effect regardless of viewport width — a
   permanently-reserved 260px sidebar on TOP of that floor squeezes .chat (flex:1, no floor of its
   own) into an unreadably narrow sliver (confirmed live at ~954px: a ~174px chat column). The
   split view itself still starts at 901px unchanged; only the sidebar becomes an overlay earlier
   (up to 1200px) so it stops permanently reserving that 260px in the range where it hurts most —
   giving .chat that width back without touching the two-column layout itself. Closed by default in
   this range too (see the matching SIDEBAR_OVERLAY_BREAKPOINT startup logic in app.js). */
@media (max-width: 1200px) {
  .sidebar {
    position: fixed;
    /* Starts BELOW the topbar rather than under it: the topbar keeps a higher z-index than this
       overlay (see the ☰-toggle comment below) so its own hamburger button stays reachable while
       the sidebar is open, but that same z-index also painted straight over anything the sidebar
       rendered inside that same top strip — confirmed live: the sidebar's first row of content
       (originally the "+ New Experiment" button, later the "SESSION LOG" label + first
       conversation row once that button moved to the bottom) was rendered but fully hidden behind
       the header the whole time. Clipping the overlay's own box to start past the header removes
       the overlap entirely instead of relying on z-index order to keep content visible. */
    top: var(--topbar-height);
    bottom: 0;
    left: 0;
    z-index: 50;
    width: 70vw;
    max-width: 70vw;
    box-shadow: 4px 0 20px rgba(0, 0, 0, 0.5);
    transition: transform 0.2s ease;
  }
  .sidebar.collapsed {
    width: 70vw;
    max-width: 70vw;
    padding: 12px;
    opacity: 1;
    border-inline-end: 1px solid var(--border);
    transform: translateX(-100%);
  }
  /* keeps the ☰ toggle clickable above the open sidebar's overlay */
  .topbar { position: relative; z-index: 60; }
  /* dims the rest of the page while the sidebar is open, via a pseudo-element on its
     flex sibling — no extra DOM node needed */
  .sidebar:not(.collapsed) ~ .app::before {
    content: '';
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, 0.5);
    z-index: 45;
  }
}

/* RESEARCH LOG entries — not chat bubbles: a hairline-bordered record with a monospace
   timestamp/role header, a thin left "channel" accent (cyan for LAB27, violet for the user),
   and a bottom hairline separating consecutive entries instead of floating rounded cards. */
.msg {
  max-width: 860px;
  width: 100%;
  align-self: center;
  padding: 10px 14px 12px;
  border-radius: var(--radius);
  word-wrap: break-word;
  line-height: 1.6;
  border-inline-start: 2px solid transparent;
  border-bottom: 1px solid var(--border);
}

.msg.user {
  background: var(--user-bubble);
  border-inline-start-color: var(--accent-2);
}

.msg.assistant {
  background: var(--assistant-bubble);
  border: 1px solid var(--border);
  border-inline-start: 2px solid var(--accent);
}

.msg.error {
  background: rgba(255, 93, 122, 0.08);
  border: 1px solid var(--error);
  color: var(--error);
}

@keyframes lab-pulse {
  0%, 100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent) 45%, transparent); }
  50% { box-shadow: 0 0 8px 1px color-mix(in srgb, var(--accent) 25%, transparent); }
}
/* The "stage" briefly pulses cyan while a round is running/using a tool — an oscilloscope-style
   heartbeat instead of a generic spinner. */
.msg.pending {
  color: var(--muted);
  border-inline-start-color: var(--accent);
  animation: lab-pulse 1.6s ease-in-out infinite;
}
.turn-activity {
  display: flex;
  align-items: center;
  gap: 7px;
  margin-top: 8px;
  color: var(--accent);
  font-family: var(--font-mono);
  font-size: 12px;
}
.turn-activity::before {
  content: '';
  width: 7px;
  height: 7px;
  flex: 0 0 auto;
  border-radius: 50%;
  background: currentColor;
  animation: turn-activity-pulse 1.15s ease-in-out infinite;
}
@keyframes turn-activity-pulse {
  50% { opacity: 0.32; transform: scale(0.7); }
}

.msg .msg-meta {
  display: flex;
  align-items: baseline;
  gap: 10px;
  font-family: var(--font-mono);
  font-size: 10.5px;
  letter-spacing: 0.06em;
  margin-bottom: 6px;
}
.msg-meta-role {
  font-weight: 700;
  text-transform: uppercase;
  color: var(--muted);
}
.msg.assistant .msg-meta-role { color: var(--accent); }
.msg.user .msg-meta-role { color: var(--accent-2); }
.msg-meta-time { color: var(--muted); opacity: 0.75; }

.msg-attachments {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-bottom: 8px;
}

.msg-text-plain {
  white-space: pre-wrap;
  font-size: 16px;
}

.msg-markdown {
  font-size: 16px;
}
.msg-markdown > *:first-child { margin-top: 0; }
.msg-markdown > *:last-child { margin-bottom: 0; }
.msg-markdown p { margin: 0.6em 0; }
.msg-markdown ul, .msg-markdown ol { margin: 0.5em 0; padding-inline-start: 1.4em; }
.msg-markdown li { margin: 0.2em 0; }
.msg-markdown h1, .msg-markdown h2, .msg-markdown h3 { margin: 0.8em 0 0.4em; line-height: 1.3; }
.msg-markdown code {
  background: var(--panel-2);
  border-radius: 4px;
  padding: 0.1em 0.35em;
  font-size: 0.9em;
  direction: ltr;
  unicode-bidi: embed;
}
.msg-markdown pre {
  background: var(--panel-2);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 10px 12px;
  overflow-x: auto;
  direction: ltr;
  text-align: left;
}
.msg-markdown pre code { background: none; padding: 0; }
.msg-markdown blockquote {
  border-inline-start: 3px solid var(--border);
  margin: 0.6em 0;
  padding-inline-start: 0.8em;
  /* full message text color, not muted — a model-written blockquote (e.g. a "bottom line"
     summary with **bold**) is often the most important line in the message, so dimming it to
     gray made it look washed-out/de-emphasized instead of highlighted */
}
.msg-markdown table { border-collapse: collapse; margin: 0.6em 0; }
.msg-markdown th, .msg-markdown td { border: 1px solid var(--border); padding: 4px 8px; }
.msg-markdown a { color: #7d9dff; }

.msg-markdown .katex {
  font-size: 1.05em;
  direction: ltr;
  unicode-bidi: isolate;
}
.msg-markdown .katex-display {
  overflow-x: auto;
  overflow-y: hidden;
  padding: 4px 0;
  direction: ltr;
  unicode-bidi: isolate;
  text-align: center;
}

.msg-reasoning {
  margin-bottom: 10px;
  background: var(--panel-2);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 2px 10px;
}

.msg-reasoning summary {
  cursor: pointer;
  padding: 6px 0;
  font-size: 12.5px;
  color: var(--muted);
  font-weight: 600;
  user-select: none;
}

.msg-reasoning-text {
  white-space: pre-wrap;
  font-size: 13px;
  color: var(--muted);
  line-height: 1.5;
  padding-bottom: 8px;
  max-height: 300px;
  overflow-y: auto;
}

.msg-cache-note {
  margin-top: 4px;
  font-family: var(--font-mono);
  font-size: 10.5px;
  color: var(--muted);
  opacity: 0.8;
}

.msg-footer {
  margin-top: 4px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}
.msg-footer .msg-cache-note { margin-top: 0; }

.msg-share-btn {
  flex-shrink: 0;
  background: transparent;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 2px 8px;
  font-family: var(--font-mono);
  font-size: 10.5px;
  color: var(--muted);
  cursor: pointer;
}
.msg-share-btn:hover { background: var(--panel-2); color: var(--text); }

/* Every round of a turn except its real last one — see collapseIntermediateRounds — folds into
   one of these, so a multi-round turn reads as one clean final answer with its intermediate
   tool-calling steps tucked away instead of several near-identical cards in a row. */
.turn-collapsed-rounds {
  max-width: 860px;
  width: 100%;
  align-self: center;
  margin-bottom: -1px; /* the last nested .msg already draws its own border-bottom separator */
}
.turn-collapsed-rounds > summary {
  cursor: pointer;
  padding: 8px 14px;
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.05em;
  color: var(--muted);
  border: 1px dashed var(--border);
  border-radius: var(--radius);
  user-select: none;
}
.turn-collapsed-rounds > summary:hover { background: var(--panel-2); }
.turn-collapsed-rounds[open] > summary {
  border-bottom-left-radius: 0;
  border-bottom-right-radius: 0;
  margin-bottom: 4px;
}
.turn-collapsed-rounds .msg { max-width: none; }

.toast {
  position: fixed;
  bottom: 24px;
  left: 50%;
  transform: translateX(-50%) translateY(8px);
  background: var(--panel-2);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 10px 18px;
  font-size: 13px;
  color: var(--text);
  opacity: 0;
  transition: opacity 0.2s ease, transform 0.2s ease;
  z-index: 1000;
  pointer-events: none;
}
.toast.visible {
  opacity: 1;
  transform: translateX(-50%) translateY(0);
}

.msg-attachments img, .msg-attachments video {
  max-width: 220px;
  max-height: 220px;
  border-radius: 8px;
  display: block;
}

.msg-attachments img {
  cursor: zoom-in;
}

.lightbox-backdrop {
  flex-direction: column;
  gap: 16px;
}

.lightbox-img {
  max-width: 92vw;
  max-height: 82vh;
  border-radius: 8px;
  object-fit: contain;
}

.lightbox-close {
  position: absolute;
  top: 16px;
  inset-inline-end: 16px;
}

.lightbox-download {
  position: absolute;
  top: 16px;
  inset-inline-start: 16px;
  text-decoration: none;
}

.file-chip {
  display: flex;
  align-items: center;
  gap: 6px;
  background: var(--panel-2);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 6px 10px;
  font-size: 13px;
  cursor: pointer;
}
.file-chip:hover { background: var(--border); }

.quote-chip {
  max-width: 320px;
}

.reply-menu {
  position: fixed;
  z-index: 30;
  background: var(--panel-2);
  border: 1px solid var(--border);
  border-radius: 8px;
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.25);
  padding: 4px;
}
.reply-menu[hidden] { display: none; }
.reply-menu button {
  display: block;
  width: 100%;
  white-space: nowrap;
  background: none;
  border: none;
  color: var(--text);
  font-size: 13px;
  padding: 8px 12px;
  border-radius: 6px;
  cursor: pointer;
  text-align: start;
}
.reply-menu button:hover { background: var(--border); }

.file-preview-content {
  white-space: pre-wrap;
  word-wrap: break-word;
  font-family: ui-monospace, "Cascadia Code", Consolas, monospace;
  font-size: 13px;
  line-height: 1.6;
  max-height: 60vh;
  overflow-y: auto;
  background: var(--panel-2);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 10px 12px;
  margin: 0;
}

.drop-overlay {
  position: fixed;
  inset: 0;
  background: rgba(108, 92, 231, 0.15);
  border: 3px dashed var(--accent);
  z-index: 20;
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: none;
}
.drop-overlay[hidden] { display: none; }

.drop-overlay-text {
  background: var(--panel);
  border: 1px solid var(--accent);
  border-radius: var(--radius);
  padding: 14px 24px;
  font-weight: 600;
}

.attachment-tray {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  padding: 10px 16px;
  background: var(--panel);
  border-top: 1px solid var(--border);
}
.attachment-tray[hidden] { display: none; }

.attachment-tray .chip {
  display: flex;
  align-items: center;
  gap: 6px;
  background: var(--panel-2);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 4px 8px;
  font-size: 13px;
}

.attachment-tray .chip img {
  width: 24px;
  height: 24px;
  object-fit: cover;
  border-radius: 4px;
}

.attachment-tray .chip button {
  background: none;
  border: none;
  color: var(--muted);
  cursor: pointer;
  font-size: 14px;
  padding: 0;
}
.attachment-tray .chip button:hover { color: var(--error); }

.composer {
  display: flex;
  align-items: flex-end;
  gap: 6px;
  margin: 10px 16px 12px;
  padding: 6px 8px 6px 14px;
  background: var(--panel-2);
  border: 1px solid var(--border-strong);
  border-radius: 0;
  flex-shrink: 0;
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
}
.composer:focus-within {
  border-color: color-mix(in srgb, var(--border-strong) 65%, var(--accent));
  box-shadow: 0 0 0 1px rgba(34, 226, 196, 0.08);
}

.composer textarea {
  flex: 1;
  min-width: 0;
  min-height: 38px;
  background: transparent;
  border: none;
  border-radius: 0;
  padding: 9px 4px 8px;
  line-height: 1.45;
  resize: none;
  max-height: 200px;
}
.composer textarea:focus { outline: none; }

.composer .icon-btn-attach {
  flex: 0 0 36px;
  width: 36px;
  height: 36px;
  padding: 0;
  background: transparent;
  border-color: transparent;
  border-radius: 0;
}
.composer .icon-btn-attach:hover { background: var(--border); }

.composer .thinking-effort-select {
  flex: 0 0 auto;
  align-self: flex-end;
  max-width: 190px;
  padding: 7px 5px;
  background: transparent;
  border: none;
  color: var(--muted);
  cursor: pointer;
}
.composer .thinking-effort-select:focus-visible {
  outline: 1px solid var(--accent);
  outline-offset: 1px;
  border-radius: 0;
}

.composer #sendBtn {
  flex: 0 0 auto;
  min-height: 36px;
  padding: 7px 15px;
  border-radius: 0;
}

.modal-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.6);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 10;
}

.modal-backdrop[hidden] {
  display: none;
}

.modal {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 20px;
  width: min(420px, 90vw);
  max-height: 85vh;
  overflow-y: auto;
}

.modal h2 { margin: 0 0 12px; font-size: 16px; }

.modal-wide { width: min(520px, 90vw); }

.lang-switch-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 14px;
}

.lang-switch {
  display: flex;
  gap: 4px;
  background: var(--panel-2);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 2px;
}

.lang-btn {
  background: none;
  border: none;
  color: var(--muted);
  padding: 5px 12px;
  font-size: 13px;
  border-radius: 6px;
  cursor: pointer;
}
.lang-btn:hover { color: var(--text); }
.lang-btn.active {
  background: var(--accent);
  color: var(--accent-contrast);
}

.tabs-bar {
  display: flex;
  gap: 4px;
  border-bottom: 1px solid var(--border);
  margin-bottom: 14px;
  overflow-x: auto;
}

.tab-btn {
  background: none;
  border: none;
  color: var(--muted);
  padding: 8px 10px;
  font-size: 13px;
  cursor: pointer;
  white-space: nowrap;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
}
.tab-btn:hover { color: var(--text); }
.tab-btn.active {
  color: var(--text);
  border-bottom-color: var(--accent);
  font-weight: 600;
}

.memory-entries-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-bottom: 12px;
  max-height: 260px;
  overflow-y: auto;
}

.memory-entry {
  background: var(--panel-2);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 8px;
}

.memory-entry textarea {
  margin-bottom: 6px;
  min-height: 44px;
}

.memory-entry-footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}

.memory-entry-date {
  font-size: 11px;
  color: var(--muted);
}

.memory-entry-actions {
  display: flex;
  gap: 6px;
}

.memory-entry-actions button {
  background: none;
  border: 1px solid var(--border);
  border-radius: 6px;
  color: var(--muted);
  font-size: 12px;
  padding: 3px 8px;
  cursor: pointer;
}
.memory-entry-actions button:hover { color: var(--text); background: var(--border); }
.memory-entry-actions .memory-entry-delete:hover { color: var(--error); background: rgba(255, 107, 107, 0.1); }

.memory-empty-hint {
  color: var(--muted);
  font-size: 12.5px;
  text-align: center;
  padding: 12px;
}

.memory-add-row {
  display: flex;
  gap: 8px;
}
.memory-add-row input { flex: 1; margin-bottom: 0; }

.usage-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  margin-bottom: 12px;
}

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

.usage-row {
  background: var(--panel-2);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 10px 12px;
}

.usage-row-title {
  font-size: 13px;
  font-weight: 600;
  margin-bottom: 6px;
}

.usage-row-bar {
  height: 6px;
  border-radius: 3px;
  background: var(--border);
  overflow: hidden;
  margin-bottom: 6px;
}
.usage-row-bar-fill { height: 100%; background: var(--accent); }
.usage-row-bar-fill.warn { background: #e6b450; }
.usage-row-bar-fill.danger { background: var(--error); }

.usage-row-detail {
  font-size: 12px;
  color: var(--muted);
}

.field-label {
  display: block;
  font-size: 12px;
  color: var(--muted);
  margin-bottom: 6px;
}

.modal input, .modal textarea {
  width: 100%;
  background: var(--panel-2);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 8px 10px;
  margin-bottom: 10px;
  font-family: inherit;
  resize: vertical;
}

.modal-text {
  font-size: 12px;
  color: var(--muted);
  line-height: 1.5;
}

.bug-report-textarea {
  width: 100%;
  margin-top: 10px;
  padding: 10px 12px;
  background: var(--panel-2);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  color: var(--text);
  font-family: inherit;
  font-size: 13px;
  line-height: 1.5;
  resize: vertical;
}
.bug-report-textarea:focus { outline: 1px solid var(--accent); }

.modal-actions {
  display: flex;
  gap: 8px;
  margin-top: 14px;
  justify-content: flex-end;
}

.empty-hint {
  margin: auto;
  color: var(--muted);
  text-align: center;
  font-size: 14px;
}

.memory-note {
  align-self: center;
  max-width: 860px;
  width: 100%;
  font-size: 12.5px;
  color: var(--muted);
  background: var(--panel-2);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 6px 12px;
}

/* --- one persistent visual workspace --- */
.viz-workspace-host,
.viz-custom-interactive-host {
  position: relative;
  width: 100%;
  height: 100%;
  min-height: 0;
  overflow: hidden;
}
.viz-custom-interactive-frame {
  display: block;
  width: 100%;
  height: 100%;
  max-width: 100%;
  border: 0;
  background: var(--assistant-bubble);
}
.viz-custom-status {
  position: absolute;
  inset: 0;
  z-index: 2;
  display: grid;
  place-items: center;
  padding: 18px;
  background: var(--panel);
  color: var(--muted);
  text-align: center;
  font-size: 13px;
}
.viz-custom-errors {
  position: absolute;
  inset-inline: 12px;
  bottom: 12px;
  z-index: 3;
  max-height: 35%;
  overflow: auto;
  padding: 8px 10px;
  border: 1px solid #d05a67;
  border-radius: 6px;
  color: #ff9aa6;
  background: rgba(208, 90, 103, 0.1);
  font-size: 12px;
  white-space: pre-wrap;
}

/* --- auth gate + user badge (backend/auth) --- */
.auth-gate {
  position: fixed;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--bg);
  z-index: 50;
}
/* Matches the [hidden] override convention used everywhere else in this file (.viz-column,
   .reply-menu, .modal-backdrop, ...) — without it, `display: flex` above has the same specificity
   as the browser's default `[hidden] { display: none }` and wins the cascade regardless of the
   attribute. Confirmed live: authGate.hidden = true left getComputedStyle(authGate).display as
   "flex", not "none" — a signed-in user kept seeing the sign-in card covering the correctly
   rendered (but likewise-affected) app underneath. */
.auth-gate[hidden] { display: none; }
.auth-gate-card {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
  padding: 32px 40px;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--accent) 15%, transparent);
  text-align: center;
}
.auth-gate-title {
  font-family: var(--font-mono);
  font-size: 22px;
  font-weight: 700;
  letter-spacing: 0.12em;
  color: var(--text);
  margin-bottom: -8px;
}
.auth-gate-tagline {
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.04em;
  color: var(--accent);
}
.auth-gate-text { margin: 0; color: var(--muted); font-size: 14px; }
.auth-gate-btn { text-decoration: none; }
.user-badge { display: flex; align-items: center; gap: 6px; }
.user-badge[hidden] { display: none; }
.user-badge-credits {
  font-family: var(--font-mono);
  font-size: 11.5px;
  font-weight: 600;
  color: var(--accent);
  background: var(--panel-2);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  padding: 3px 8px;
  white-space: nowrap;
}
