/* Element Copilot — chat-specific decoration styles.
 *
 * Structural sizing (widths, heights, flex/grid) lives in inline styles
 * on the Dash components so Bootstrap cannot override them.
 * This file handles only: colours, typography, shadows, borders, transitions,
 * hover states, scrollbars, and component-level chrome.
 *
 * All hex values must match copilot/theme.py ELEMENT_COLORS.
 */

/* ========================================================================
   BRAND — identical in light and dark. Logos, fills, and status colours
   must not shift when the theme flips.
   ======================================================================== */
:root {
  --darkgreen: #0b6623;
  --darkgreen-deep: #084a19;
  --lightgreen: #4CB748;
  --pale-green: #B7E2B6;
  --pale-green-bg: #d0f2d0;
  --leaf: #94D491;
  --orange: #F2A436;
  --cream: #FADBAF;
  --cream-soft: #FFE5C1;
  /* Fault — the third severity step. ELEMENT_COLORS had no error colour, so
     --orange carried both "warning" and "fault" and a diagnostics answer could
     not distinguish "check this" from "this failed". Warm enough to sit beside
     #F2A436 without clashing. Add to theme.py as FAULT / FAULT_DEEP so Plotly
     traces can reach the same colour. */
  --fault: #D14343;
  --fault-deep: #B3302F;
  /* The Element palette has no red; this is a dedicated token for the mic's
     transient "recording" state, where red is a near-universal convention.
     Not reused elsewhere. */
  --rec-red: #E5484D;

  /* ── Surfaces ───────────────────────────────────────────────────────── */
  --bg: #f5f7f5;              /* app canvas / chat column */
  --bg-soft: #eef2ee;         /* rails, sunken strips, table headers */
  --card-bg: #ffffff;         /* elevated: cards, modals, popovers */
  --card-bg-soft: #fbfdfb;    /* inset panel inside a card (plot controls) */
  --overlay-bg: #ffffff;      /* modal / drawer body */
  --scrim: rgba(15, 36, 23, 0.38);

  /* ── Text ──────────────────────────────────────────────────────────── */
  --ink: #1f2d3d;             /* body */
  --ink-soft: #4a5568;        /* secondary labels, table cells */
  --muted: #6c757d;           /* meta text — AA-safe smallest grey */
  --muted-2: #9aa3b2;         /* graphical marks + >=19px only, NOT body */

  /* ── Borders ───────────────────────────────────────────────────────── */
  --border: #e3e6f0;
  --border-strong: #d4dae3;
  --divider: rgba(11, 102, 35, 0.12);   /* dashed footer rules */

  /* ── Accent aliases — the green LAYER, flipped per theme ───────────── */
  --accent: var(--lightgreen);          /* dots, ticks, decorative fills */
  --accent-ink: var(--darkgreen);       /* accent-coloured TEXT + carets */
  --accent-ink-strong: var(--darkgreen-deep);
  --accent-bg: var(--pale-green-bg);    /* accent-tinted surface */
  --accent-bg-soft: rgba(76, 183, 72, 0.07);
  --accent-border: var(--pale-green);
  --focus-ring: rgba(76, 183, 72, 0.16);

  /* Text-bearing accent BUTTON. Split from --accent because #fff on
     --lightgreen is 2.9:1 — under the 3:1 floor even for a glyph control.
     --darkgreen carries white at 7.1:1. */
  --btn-accent-bg: var(--darkgreen);
  --btn-accent-bg-hover: var(--darkgreen-deep);
  --btn-accent-ink: #ffffff;

  /* ── Caution / warning (cream family) ──────────────────────────────── */
  --warn-ink: #7a4a00;
  --warn-bg: var(--cream-soft);
  --warn-border: var(--cream);
  --warn-accent: var(--orange);

  /* Fault — severity 3. */
  --fault-ink: var(--fault-deep);       /* 6.2:1 on white */
  --fault-bg: #FDECEA;
  --fault-border: #F5C9C4;
  --fault-accent: var(--fault);

  /* Chat surfaces. */
  --bubble-user-bg: #ffffff;
  --bubble-user-border: #d4dae3;
  --bubble-ella-bg: #f4f8f3;
  --bubble-ella-border: rgba(76, 183, 72, 0.18);
  --chip-bg: #ffffff;                   /* tool pills, suggest chips */
  --chip-border: #d4dae3;
  --chip-ink: var(--ink-soft);
  --composer-bg: #ffffff;
  --composer-border: #d4dae3;

  /* ── Code + terminal — intentionally dark in BOTH themes ───────────── */
  --code-bg: #0f2417;
  --code-bg-head: #0a1c11;
  --code-border: #143421;
  --code-ink: #d7eed2;
  --code-ink-soft: #8db48a;
  --terminal-bg: #0d1117;
  --terminal-fg: #c9d1d9;

  /* ── Elevation ─────────────────────────────────────────────────────── */
  --shadow-sm: 0 1px 2px rgba(11, 102, 35, 0.04), 0 1px 3px rgba(11, 102, 35, 0.06);
  --shadow-md: 0 4px 14px rgba(11, 102, 35, 0.10), 0 1px 3px rgba(11, 102, 35, 0.08);
}

/* ========================================================================
   DARK THEME
   Scoped to the Dash root container so it never touches custom.css (the
   verbatim TRACE copy, out of scope). Brand hexes are NOT redeclared —
   only the semantic aliases above.

   Surface ladder is deliberately 4 steps so elevation still reads without
   shadows (shadows are near-invisible on dark):
     --bg #15181a  <  --card-bg-soft #191d20  <  --bg-soft #1c2023  <  --card-bg #20252a
   ======================================================================== */
#app-shell[data-theme='dark'] {
  --bg: #15181a;
  --bg-soft: #1c2023;
  --card-bg: #20252a;
  --card-bg-soft: #191d20;
  --overlay-bg: #252b31;
  --scrim: rgba(0, 0, 0, 0.62);

  --ink: #e8eaed;             /* 13.2:1 on --card-bg */
  --ink-soft: #bcc3cc;        /*  8.6:1 */
  --muted: #98a1ab;           /*  5.9:1 — AA for body meta */
  --muted-2: #868f9a;         /*  4.7:1 — lifted from #7a828f, which was 4.1 */

  --border: #33393f;
  --border-strong: #434b53;
  --divider: rgba(148, 212, 145, 0.16);

  /* --darkgreen on a dark surface is ~1.3:1. Accent TEXT moves up the green
     ramp to --leaf (8.7:1) while accent FILLS stay --lightgreen, so buttons
     and logos keep their brand colour. */
  --accent: var(--lightgreen);
  --accent-ink: var(--leaf);
  --accent-ink-strong: var(--pale-green);
  --accent-bg: rgba(76, 183, 72, 0.14);
  --accent-bg-soft: rgba(76, 183, 72, 0.08);
  --accent-border: rgba(76, 183, 72, 0.32);
  --focus-ring: rgba(76, 183, 72, 0.28);

  --btn-accent-bg: var(--lightgreen);
  --btn-accent-bg-hover: var(--leaf);
  --btn-accent-ink: #08210d;

  /* Cream inverts: a light cream PANEL becomes a translucent amber wash with
     cream text (9.9:1) — the light-theme #7a4a00-on-cream pairing has no
     dark-theme equivalent. */
  --warn-ink: #F2C98A;
  --warn-bg: rgba(242, 164, 54, 0.14);
  --warn-border: rgba(242, 164, 54, 0.30);
  --warn-accent: var(--orange);

  --fault-ink: #F2938C;                 /* 6.9:1 on --card-bg */
  --fault-bg: rgba(209, 67, 67, 0.16);
  --fault-border: rgba(209, 67, 67, 0.34);
  --fault-accent: #E8615C;

  --bubble-user-bg: #262c32;
  --bubble-user-border: #3b444c;
  --bubble-ella-bg: #1e2529;
  --bubble-ella-border: rgba(76, 183, 72, 0.22);
  --chip-bg: #252b31;
  --chip-border: #3b444c;
  --chip-ink: var(--ink-soft);
  --composer-bg: #23282d;
  --composer-border: #3b444c;

  --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.30), 0 1px 3px rgba(0, 0, 0, 0.40);
  --shadow-md: 0 6px 18px rgba(0, 0, 0, 0.45), 0 2px 5px rgba(0, 0, 0, 0.35);

  color-scheme: dark;         /* native scrollbars, date pickers, inputs */
}

/* ===== Global body reset ===== */
html,
body,
#react-entry-point {
  height: 100%;
  margin: 0;
}

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

code,
pre,
.mono {
  font-family: "JetBrains Mono", SFMono-Regular, Menlo, Monaco, Consolas, monospace;
}

/* ===== Navbar decoration ===== */
/* Superseded by the NAVBAR block in ella-frame.css (.copilot-navbar,
   .nav-brand, .nav-center, .nav-right, .badge-version), which sizes and
   colours the bar through tokens.

   What used to live here was a blanket `.copilot-navbar * { margin: 0
   !important; padding: 0 !important }` to strip Bootstrap's navbar spacing.
   `!important` on a universal selector outranks every class in the component
   layer, so it silently flattened the version badge, the account chip and
   every row of the account popover to zero padding — with nothing in the
   inspector pointing at the cause. dbc.Navbar's own spacing is handled by
   .copilot-navbar / .copilot-navbar>.container-fluid there instead. */

/* ===== New conversation button hover ===== */
#btn-new-conversation:hover {
  /* --btn-accent-bg-hover, not --darkgreen: the button's resting surface IS
     --darkgreen in light mode (white on #4CB748 is 2.9:1, below the 3:1 floor),
     so hovering to the same colour was a no-op there — and in dark mode
     --darkgreen on a dark surface is ~1.3:1. */
  background: var(--btn-accent-bg-hover) !important;
  border-color: var(--btn-accent-bg-hover) !important;
}

/* ===== Search input focus ring ===== */
/* !important, so this beats .sb-search input:focus in ella-frame.css — kept
   deliberately (it also covers the Bootstrap default) but re-pointed at the
   same tokens so the two agree instead of fighting. */
#sidebar-search-input:focus {
  border-color: var(--accent-border) !important;
  box-shadow: 0 0 0 3px var(--focus-ring) !important;
  outline: none !important;
}

/* ===== Send button hover ===== */
/* Not while a turn runs: the greyed, disabled button must not light up. */
#btn-send:not(:disabled):hover {
  background: var(--btn-accent-bg-hover) !important;
}

#btn-send:not(:disabled):focus-visible,
#btn-clear-attachment:focus-visible {
  outline: 2px solid var(--lightgreen);
  outline-offset: 1px;
}

/* ===== Suggest starter cards hover ===== */
.ella-suggest-card:hover,
.ella-suggest-card:focus-visible {
  border-color: var(--accent) !important;
  background: var(--accent-bg-soft) !important;
  transform: translateY(-1px);
}

.ella-suggest-card:focus-visible {
  outline: 2px solid var(--lightgreen);
  outline-offset: 1px;
}

/* ===== Capabilities modal "Ask ELLA" mini-chat ===== */
/* #cap-chat-input sets outline:0 inline with no CSS replacement, so a
   keyboard user tabbing into it previously saw no focus indicator at all. */
.cap-chat-input-row:focus-within {
  border-color: var(--accent-border) !important;
  box-shadow: 0 0 0 3px var(--focus-ring) !important;
}

#btn-cap-chat-send:hover {
  background: var(--darkgreen-deep) !important;
}

#btn-cap-chat-send:focus-visible,
button[id*="cap-suggest"]:focus-visible {
  outline: 2px solid var(--lightgreen);
  outline-offset: 1px;
}

button[id*="cap-suggest"]:hover {
  border-color: var(--accent) !important;
  background: var(--accent-bg-soft) !important;
}

/* ===== Slide Studio action buttons — hover/focus (no Bootstrap-blue default outline) ===== */
button[id*="slides-build-btn"]:hover,
button[id*="slides-launch-image-studio-btn"]:hover {
  background: var(--darkgreen) !important;
  color: #fff !important;
}

button[id*="slides-launch-image-studio-btn"]:hover {
  border-color: var(--darkgreen) !important;
}

button[id*="slides-build-btn"]:focus-visible,
button[id*="slides-launch-image-studio-btn"]:focus-visible {
  outline: 2px solid var(--lightgreen);
  outline-offset: 1px;
}

/* ===== Slide Studio GUI editor — the controls added with the structured
   editor and the in-card assistant. Every one of them ships with a browser or
   react-select default that is blue; all of it is forced to the Element
   palette here (element-dash-ui: no Bootstrap blue, anywhere, ever). ===== */
button[id*="slides-ai-send-btn"]:hover,
button[id*="slides-add-btn"]:hover {
  background: var(--darkgreen) !important;
  color: #fff !important;
  border-color: var(--darkgreen) !important;
}

button[id*="slides-slide-action"]:hover {
  background: var(--pale-green-bg) !important;
  border-color: var(--lightgreen) !important;
  color: var(--darkgreen) !important;
}

button[id*="slides-ai-send-btn"]:focus-visible,
button[id*="slides-add-btn"]:focus-visible,
button[id*="slides-slide-action"]:focus-visible {
  outline: 2px solid var(--lightgreen);
  outline-offset: 1px;
}

/* ===== Streaming-turn controls — Stop & correct / Clear stuck turn ===== */
button[id*="stop-turn-btn"]:hover {
  background: var(--warn-bg) !important;
  border-color: var(--orange) !important;
}

button[id*="force-clear-turn-btn"]:hover {
  background: var(--pale-green-bg) !important;
  border-color: var(--border-strong) !important;
  color: var(--ink) !important;
}

button[id*="stop-turn-btn"]:focus-visible,
button[id*="force-clear-turn-btn"]:focus-visible {
  outline: 2px solid var(--lightgreen);
  outline-offset: 1px;
}

/* A disabled Send is the `running=` state while the assistant thinks — it must
   read as busy, not as a broken button. */
button[id*="slides-ai-send-btn"]:disabled {
  background: var(--lightgreen) !important;
  opacity: 0.55 !important;
  cursor: progress !important;
}

/* Single-slide workspace (EL-540): the thumbnail navigator, prev/next, the
   Insert Chart/Table/KPI shortcuts, suggestion chips, Undo and the import
   strip. Same rule — every hover/focus/disabled state is Element green. */
button[id*="slides-thumb"]:hover {
  border-color: var(--lightgreen) !important;
  box-shadow: 0 2px 8px rgba(11, 102, 35, 0.12);
}

button[id*="slides-nav"]:hover,
button[id*="slides-insert"]:hover,
button[id*="slides-ai-chip"]:hover,
button[id*="slides-undo-btn"]:hover:not(:disabled),
button[id*="slides-deck-open-btn"]:hover:not(:disabled),
div[id*="slides-upload-pptx"] button:hover {
  background: var(--pale-green-bg) !important;
  border-color: var(--lightgreen) !important;
  color: var(--darkgreen) !important;
}

button[id*="slides-thumb"]:focus-visible,
button[id*="slides-nav"]:focus-visible,
button[id*="slides-insert"]:focus-visible,
button[id*="slides-ai-chip"]:focus-visible,
button[id*="slides-undo-btn"]:focus-visible,
button[id*="slides-deck-open-btn"]:focus-visible,
div[id*="slides-upload-pptx"] button:focus-visible {
  outline: 2px solid var(--lightgreen);
  outline-offset: 1px;
}

/* Undo with nothing to undo, and Open/Build while their background job runs. */
button[id*="slides-undo-btn"]:disabled,
button[id*="slides-deck-open-btn"]:disabled,
button[id*="slides-build-btn"]:disabled {
  opacity: 0.5 !important;
  cursor: default !important;
}

button[id*="slides-build-btn"]:disabled {
  cursor: progress !important;
}

/* Inline Image Studio modal (EL-590): its buttons, prompt box and Bootstrap's
   close (×) — none may fall back to Bootstrap / user-agent blue. */
button[id*="slides-imgs-"]:hover:not(:disabled) {
  filter: brightness(0.95);
  border-color: var(--lightgreen) !important;
}
button[id*="slides-imgs-"]:focus-visible,
textarea[id*="slides-imgs-prompt"]:focus,
.ella-slides-imgs-modal .btn-close:focus {
  outline: 2px solid var(--lightgreen) !important;
  outline-offset: 1px;
  box-shadow: none !important;
  border-color: var(--lightgreen) !important;
}
button[id*="slides-imgs-"]:disabled {
  opacity: 0.5 !important;
  cursor: default !important;
}
.ella-slides-imgs-modal .modal-content {
  border: 0;
  border-top: 4px solid var(--darkgreen);
  border-radius: 10px;
}

/* Agent Board card v2 (EL-650): the channel rail, clickable notices/feed rows
   and the notice detail modal. Structure is inline; this is decoration only —
   and none of it may fall back to Bootstrap / user-agent blue.
   The card surface is white in BOTH themes (.ella-card), so the rail and hover
   colours are literal light-theme values, not tokens that flip under dark
   mode (a flipped --ink-soft measured ~1.8:1 on the white card). The modal is
   portaled outside #app-shell, so its tokens stay light. */
.agb-clickable {
  cursor: pointer;
  transition: border-color 0.12s ease, background-color 0.12s ease;
}
.agb-clickable:hover {
  border-color: #4CB748 !important;
  background-color: rgba(76, 183, 72, 0.07);
}
.agb-clickable:focus-visible,
.agb-rail-item:focus-visible,
.agb-modal-btn:focus-visible,
.agb-link:focus-visible {
  outline: 2px solid var(--lightgreen);
  outline-offset: 1px;
}
.agb-rail-item {
  cursor: pointer;
  color: #4a5568;
}
.agb-rail-item:hover {
  background: #eef2ee;
  color: #0b6623;
}
.agb-rail-item.is-active {
  background: #0b6623;
  color: #fff;
  font-weight: 700;
}
.agb-rail-count {
  font-family: "JetBrains Mono", monospace;
  font-size: 11px;
  color: #6c757d;
  flex-shrink: 0;
}
.agb-rail-item.is-active .agb-rail-count {
  color: #B7E2B6;
}
.agb-rail-item.is-warn .agb-rail-count {
  color: #7a4a00;
  background: #FFE5C1;
  border: 1px solid #F2A436;
  border-radius: 999px;
  padding: 0 6px;
  font-weight: 700;
}
.ella-agb-modal .modal-content {
  border: 0;
  border-top: 4px solid var(--darkgreen);
  border-radius: 10px;
}
.ella-agb-modal .btn-close:focus {
  outline: 2px solid var(--lightgreen) !important;
  outline-offset: 1px;
  box-shadow: none !important;
  border-color: var(--lightgreen) !important;
}
/* `.agb-modal-input` is dcc.Input's WRAPPER div under Dash 4 (the <input> is
   inside), so focus is :focus-within — styling the inner <input> alone left the
   browser's own (purple) ring on the wrapper. */
.agb-modal-input:focus,
.agb-modal-input:focus-within {
  outline: none !important;
  border-color: var(--lightgreen) !important;
  box-shadow: 0 0 0 2px rgba(183, 226, 182, 0.55) !important;
}
.agb-modal-input .dash-input-element,
.agb-modal-input .dash-input-element:focus {
  outline: none;
  box-shadow: none !important;
}
.agb-link,
.agb-link:visited {
  color: var(--darkgreen);
  text-decoration: none;
}
.agb-link:hover {
  color: var(--darkgreen-deep);
  text-decoration: underline;
}
.agb-modal-btn {
  cursor: pointer;
}
.agb-modal-btn:hover:not(:disabled) {
  filter: brightness(0.95);
}
.agb-modal-btn:disabled {
  opacity: 0.5;
  cursor: default;
}

/* The assistant's scope toggle: the native radio dot is accent-colored inline;
   this keeps its keyboard focus ring green too. */
.ella-slides-scope input[type="radio"]:focus-visible {
  outline: 2px solid var(--lightgreen);
  outline-offset: 1px;
}

/* Field widgets + the assistant's prompt bar: green focus ring, never the
   user-agent blue one. */
input[id*="slides-field"]:focus,
input[id*="slides-ai-prompt"]:focus,
input[id*="slides-deck-path"]:focus,
input[id*="slides-title"]:focus,
textarea[id*="slides-area"]:focus,
textarea[id*="slides-spec"]:focus {
  outline: none !important;
  border-color: var(--lightgreen) !important;
  box-shadow: 0 0 0 2px rgba(183, 226, 182, 0.55) !important;
}

/* ===== Slide Studio — modes, inserts, diagrams, drag-reorder, presenting (EL-578) ===== */

/* Edit slides | Ask a question: a segmented control, not two bare radio dots. */
.ella-slides-mode {
  display: inline-flex;
  background: var(--card-bg);
  border: 1px solid var(--pale-green);
  border-radius: 999px;
  padding: 2px;
}
/* Dash 4 markup: label.dash-options-list-option[.selected][aria-selected]
   > span.…-wrapper > input, + span.…-text. Its own option styling is
   pale-on-selected, so the selected pill is keyed on aria-selected. */
.ella-slides-mode .dash-options-list-option {
  display: inline-flex !important;
  align-items: center;
  position: relative;
  margin: 0 !important;
  padding: 3px 11px !important;
  border-radius: 999px;
  background: transparent !important;
  font-size: 11px;
  font-weight: 700;
  color: var(--darkgreen) !important;
}
.ella-slides-mode .dash-options-list-option-wrapper {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  opacity: 0;
}
.ella-slides-mode .dash-options-list-option[aria-selected="true"] {
  background: var(--darkgreen) !important;
  color: #ffffff !important;
}
.ella-slides-mode .dash-options-list-option:has(input:focus-visible) {
  outline: 2px solid var(--lightgreen);
  outline-offset: 1px;
}

/* Q&A answers render Markdown inside the transcript bubble. */
.ella-slides-answer p:last-child,
.ella-slides-answer ul:last-child,
.ella-slides-answer ol:last-child {
  margin-bottom: 0;
}
.ella-slides-answer ul,
.ella-slides-answer ol {
  padding-left: 18px;
  margin: 4px 0;
}

/* Insert-slide buttons + the image uploads: Element hover, never UA blue. */
button[id*="slides-insert"]:hover,
button[id*="slides-present-btn"]:hover,
[id*="slides-insert-image"] button:hover,
[id*="slides-upload-image"] button:hover {
  background: var(--pale-green-bg) !important;
}

/* A browser-rendered diagram (assets/slide_studio.js) inside a slide preview. */
.ella-slide-mermaid .ella-mmd-out {
  width: 100%;
}
/* Fill the slide body: mermaid's useMaxWidth caps the svg at its natural
   (small) width; the viewBox keeps the aspect as max-height bounds it. */
.ella-slide-mermaid svg {
  width: 100%;
  max-width: 100% !important;
  max-height: 250px;
  height: auto;
}
.ella-slide-mermaid.is-rendering:not(:has(.ella-mmd-out *))::before {
  content: "Rendering diagram…";
  color: var(--muted-2);
  font-size: 11px;
}
.ella-slide-mermaid-error {
  color: var(--warn-ink);
  font-size: 11px;
  font-weight: 700;
  margin-bottom: 4px;
}
/* KaTeX-rendered math (assets/slide_studio.js's scanMath) inside slide text —
   KaTeX's own stylesheet hardcodes black glyphs; inherit the slide's text
   colour instead so equations match a dark title/body/takeaway, not just a
   light background. */
.ella-slide .katex {
  color: inherit;
}
.ella-slide-mermaid pre {
  margin: 0;
  text-align: left;
  white-space: pre-wrap;
  font-size: 10.5px;
  color: var(--ink-soft);
}

/* Filmstrip drag-and-drop and the empty-slide cue: see the EL-597 block. */

/* The presentation overlay. Closed = invisible + inert; the asset JS owns the
   fullscreen request, the keys, and --ella-present-scale. */
.ella-present-overlay {
  position: fixed;
  inset: 0;
  z-index: 4000;
  display: flex;
  flex-direction: column;
  background: #0b120d;
  visibility: hidden;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.16s ease;
  outline: none;
}
.ella-present-overlay.is-open {
  visibility: visible;
  opacity: 1;
  pointer-events: auto;
}
/* Every slide is a stage stacked in ONE grid cell; only `is-current` shows.
   visibility (not display:none) keeps hidden charts laid out at full size. */
.ella-present-viewport {
  flex: 1;
  min-height: 0;
  display: grid;
  place-items: center;
  /* Centre the 960px track even when it overflows a small viewport. */
  place-content: center;
  overflow: hidden;
  cursor: pointer;
}
.ella-present-stage {
  grid-area: 1 / 1;
  visibility: hidden;
  transform: scale(var(--ella-present-scale, 1));
  transform-origin: center center;
}
.ella-present-stage.is-current {
  visibility: visible;
}
.ella-present-controls {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  padding: 8px 16px 14px;
  color: #c9d1d9;
  font-size: 12.5px;
  opacity: 0.4;
  transition: opacity 0.2s ease;
}
.ella-present-overlay:hover .ella-present-controls,
.ella-present-controls:focus-within {
  opacity: 1;
}
.ella-present-btn {
  background: rgba(255, 255, 255, 0.08);
  color: #ffffff;
  border: 1px solid rgba(255, 255, 255, 0.2);
  border-radius: 8px;
  padding: 5px 14px;
  font-size: 14px;
  font-weight: 700;
  cursor: pointer;
}
.ella-present-btn:hover {
  background: var(--lightgreen);
  border-color: var(--lightgreen);
}
.ella-present-btn:focus-visible {
  outline: 2px solid var(--lightgreen);
  outline-offset: 2px;
}
.ella-present-counter {
  min-width: 64px;
  text-align: center;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}
.ella-present-hint {
  opacity: 0.7;
  margin: 0 6px;
}

/* ===== Slide Studio — three zones, fixed-geometry stage, presenter (EL-597) =====
   Design: docs/designs/Slide Studio Review.html. The slide itself is a 960 x 540
   stage of inline-positioned nodes (scene.py) — never styled here beyond the
   scale and the diagram fit; this block is the card around it. Grid placement
   is here (not inline) because a container query must move the inspector
   below the canvas on a narrow card, and inline styles can't be queried. */
.ella-ss { position: relative; container-type: inline-size; font-family: "Nunito", sans-serif; }
.ella-ss-tbr { display: flex; align-items: center; gap: 8px; padding: 0 0 10px; border-bottom: 1px solid var(--border); }
.ella-ss-tbr > *:not(.ella-ss-dt) { flex-shrink: 0; }
.ella-ss-dt {
  font: 800 15px "Nunito", sans-serif; color: var(--ink); padding: 4px 6px; border-radius: 6px;
  border: 1px solid transparent; background: transparent; min-width: 120px; flex: 0 1 420px;
  text-overflow: ellipsis;
}
.ella-ss-dt:hover { border-color: var(--border); }
input.ella-ss-dt:focus { outline: none; border-color: var(--lightgreen) !important; box-shadow: 0 0 0 2px rgba(183, 226, 182, 0.55); }
.ella-ss-chip:not(:empty) { font: 700 11px "Nunito", sans-serif; padding: 4px 9px; border-radius: 99px; background: #FFE5C1; color: #ae5c14; }
/* Autosave state (EL-620): quiet until a save lands, loud only when one fails. */
.ella-ss-save { font: 700 11px "Nunito", sans-serif; color: var(--muted); white-space: nowrap; }
.ella-ss-save.is-saved { color: var(--darkgreen); }
.ella-ss-save.is-error { color: var(--fault-ink); }
.ella-ss-tbr button:hover:not(:disabled), .ella-ss-cvh button:hover:not(:disabled),
.ella-ss-ask button:hover:not(:disabled) { filter: brightness(0.96); border-color: var(--lightgreen) !important; }
.ella-ss button:focus-visible, .ella-ss-dt:focus-visible { outline: 2px solid var(--lightgreen); outline-offset: 1px; }
.ella-ss button:disabled { opacity: 0.4; cursor: default !important; }
/* ↶ Undo / ↷ Redo — labelled (EL-608); disabled reads as "nothing to undo", not faded green. */
.ella-ss-tbr .ella-ss-hist:disabled { opacity: 1; color: #9aa3b2 !important; background: #f3f5f3 !important;
  border-color: var(--border) !important; }
.ella-ss-split { display: inline-flex; border: 1px solid var(--border); border-radius: 8px; overflow: hidden; background: #fff; }
.ella-ss-split button { border: 0; background: #fff; color: #0b6623; font: 700 12.5px "Nunito", sans-serif; padding: 7px 10px 7px 12px; cursor: pointer; }
.ella-ss-split .ella-ss-split-more { border-left: 1px solid var(--border); padding: 7px 8px; }
.ella-ss-split button:hover { background: #d0f2d0; }
.ella-ss-lines { display: flex; flex-direction: column; gap: 2px; padding: 4px 0; }
.ella-ss-toastline { font-size: 12px; }
.ella-ss-toastline:empty { display: none; }
.ella-ss-warn { font-size: 11.5px; color: #ae5c14; background: #FFE5C1; border-radius: 6px; padding: 4px 10px; }
.ella-ss-warn summary { cursor: pointer; font-weight: 700; }
.ella-ss-warn-more { font-weight: 400; opacity: 0.8; }
.ella-ss-warn ul { margin: 4px 0 2px; padding-left: 18px; color: #4a5568; }

/* The workspace: filmstrip | canvas | inspector; inspector wraps < 980px. The
   outer two widths are drag handles' CSS variables (assets/slide_studio.js —
   remembered per browser); the handles sit over the column borders, out of
   the grid flow, and hide wherever the container queries re-flow the grid. */
.ella-ss-ws { position: relative; display: grid; min-height: 560px;
  grid-template-columns: var(--ss-rail-w, 150px) minmax(0, 1fr) var(--ss-insp-w, 290px);
  border: 1px solid var(--border); border-radius: 10px; margin-top: 8px; background: #fff; }
.ella-ss-handle { position: absolute; top: 0; bottom: 0; width: 10px; z-index: 6; cursor: col-resize; touch-action: none; }
.ella-ss-handle.is-rail { left: calc(var(--ss-rail-w, 150px) - 5px); }
.ella-ss-handle.is-inspector { right: calc(var(--ss-insp-w, 290px) - 5px); }
.ella-ss-handle::after { content: ""; position: absolute; left: 4px; top: 0; bottom: 0; width: 2px; background: transparent;
  transition: background 0.12s; }
.ella-ss-handle:hover::after, .ella-ss-handle.is-dragging::after, .ella-ss-handle:focus-visible::after { background: var(--lightgreen); }
.ella-ss-handle:focus-visible { outline: none; }
body.ella-ss-resizing { cursor: col-resize !important; user-select: none; }
@container (max-width: 980px) {
  .ella-ss-ws { grid-template-columns: 120px minmax(0, 1fr); }
  .ella-ss-insp { grid-column: 1 / -1; border-left: 0 !important; border-top: 1px solid var(--border); }
  .ella-ss-handle { display: none; }
}
@container (max-width: 560px) {
  .ella-ss-ws { grid-template-columns: minmax(0, 1fr); }
  .ella-ss-filmpane { border-right: 0 !important; border-bottom: 1px solid var(--border); }
  .ella-ss-film { position: static !important; flex-direction: row !important; overflow-x: auto; overflow-y: hidden !important; }
  .ella-ss-th { width: 120px; flex-shrink: 0; }
  .ella-ss-plus, .ella-ss-grp { display: none; }
}

/* Filmstrip — the ONE internal scroll in the workspace, and it is a leaf (CORE
   RULE 1). Absolutely filled, so it runs the full height of the row (the canvas
   and inspector set that height) instead of stopping at a fixed cap. */
.ella-ss-filmpane { position: relative; border-right: 1px solid var(--border); background: #fafbfa; border-radius: 10px 0 0 10px; }
.ella-ss-film { position: absolute; inset: 0; display: flex; flex-direction: column; gap: 4px; padding: 12px 10px; overflow-y: auto; }
.ella-ss-grp { font: 800 9px "Nunito", sans-serif; letter-spacing: 0.12em; text-transform: uppercase; color: #378434;
  margin: 8px 0 0 18px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ella-ss-th { display: grid; grid-template-columns: 14px minmax(0, 1fr); gap: 4px; align-items: start; }
.ella-ss-no { font: 800 10px "Nunito", sans-serif; color: #9aa3b2; padding-top: 2px; text-align: right; }
.ella-ss-th.is-active .ella-ss-no { color: #0b6623; }
.ella-slides-thumb { display: block; width: 100%; padding: 0; border: 0; background: none; cursor: pointer; text-align: left; position: relative; }
.ella-ss-thumbface { position: relative; aspect-ratio: 16 / 9; border-radius: 3px; overflow: hidden; background: #fff;
  box-shadow: 0 0 0 1px var(--border); padding: 5px 7px; box-sizing: border-box; display: flex; flex-direction: column; justify-content: center; }
.ella-slides-thumb.is-active .ella-ss-thumbface { box-shadow: 0 0 0 2px var(--lightgreen); }
.ella-slides-thumb:hover .ella-ss-thumbface { box-shadow: 0 0 0 2px var(--pale-green); }
.ella-slides-thumb.is-active:hover .ella-ss-thumbface { box-shadow: 0 0 0 2px var(--lightgreen); }
.ella-slides-thumb.is-empty .ella-ss-thumbface { box-shadow: 0 0 0 1px #F2A436; }
.ella-ss-thumbface.has-clone { padding: 0; }
.ella-ss-thumbface.has-clone .ella-ss-sketch { display: none; }
.ella-ss-thumbclone { position: absolute; inset: 0; overflow: hidden; pointer-events: none; }
.ella-ss-thumbclone > .ella-slide { transform-origin: 0 0; }
.ella-ss-badge { position: absolute; right: 3px; top: 3px; z-index: 3; width: 14px; height: 14px; border-radius: 50%;
  font: 800 9px/14px "Nunito", sans-serif; text-align: center; color: #fff; }
.ella-ss-badge.is-pending { background: #9aa3b2; }
.ella-ss-badge.is-warn { background: #F2A436; }
.ella-ss-badge.is-ok { background: #4CB748; }
.ella-ss-thumbface.is-changed { animation: ella-ss-pulse 0.6s ease-out; }
@keyframes ella-ss-pulse { 0% { box-shadow: 0 0 0 0 rgba(76, 183, 72, 0.9); } 100% { box-shadow: 0 0 0 7px rgba(76, 183, 72, 0); } }
.ella-ss-plus { margin-left: 18px; height: 10px; border: 0; background: transparent; color: transparent; cursor: pointer;
  border-radius: 4px; font: 800 11px/10px "Nunito", sans-serif; padding: 0; }
.ella-ss-plus:hover, .ella-ss-plus:focus-visible { background: #d0f2d0; color: #0b6623; height: 16px; }
.ella-ss-addt { margin: 8px 0 0 18px; border: 1px dashed var(--pale-green); border-radius: 4px; color: #0b6623;
  font: 700 11px "Nunito", sans-serif; text-align: center; padding: 8px 0; background: #fff; cursor: pointer; }
.ella-ss-addt:hover { background: #d0f2d0; }
/* Drag: the dragged thumb at 35 %, a 2 px insertion line with an 8 px dot. */
.ella-slides-thumb.is-dragging { opacity: 0.35; }
.ella-slides-thumb.drop-before::before, .ella-slides-thumb.drop-after::after {
  content: ""; position: absolute; left: 0; right: 0; height: 2px; background: var(--lightgreen); border-radius: 2px; z-index: 4; }
.ella-slides-thumb.drop-before::before { top: -4px; }
.ella-slides-thumb.drop-after::after { bottom: -4px; }
.ella-slides-thumb.drop-before .ella-ss-thumbface::before, .ella-slides-thumb.drop-after .ella-ss-thumbface::after {
  content: ""; position: absolute; left: -3px; width: 8px; height: 8px; border-radius: 50%; background: var(--lightgreen); z-index: 5; }
.ella-slides-thumb.drop-before .ella-ss-thumbface::before { top: -7px; }
.ella-slides-thumb.drop-after .ella-ss-thumbface::after { bottom: -7px; }

/* Canvas */
.ella-ss-cv { padding: 14px 18px; background: #f3f5f3; }
.ella-ss-cvh { display: flex; align-items: center; gap: 6px; font: 700 12px "Nunito", sans-serif; color: #4a5568; }
.ella-ss-where b { color: #0b6623; }
.ella-ss-canvas { min-height: 120px; }
.ella-ss-canvas-hint { font-size: 11px; color: #4a5568; text-align: center; margin-top: -4px; }
/* Inline editing on the slide (assets/slide_studio.js, EL-608): a hover hint on
   the text it can edit, the edit box laid over the text, the original hidden. */
.ella-ss-canvas .ella-slide-title:hover, .ella-ss-canvas .ella-slide-kicker:hover,
.ella-ss-canvas .ella-slide-ribbon:hover, .ella-ss-canvas .ella-slide-bullets > div > div:hover {
  outline: 1.5px dashed rgba(76, 183, 72, 0.75); outline-offset: 3px; }
.ella-ss-inline { position: absolute; z-index: 40; box-sizing: border-box; white-space: pre-wrap; overflow-wrap: anywhere;
  outline-style: solid; outline-color: #4CB748; background: transparent; cursor: text; caret-color: #4CB748; }
.ella-ss-inline.is-pending { opacity: 0.7; }
.ella-ss-inline-src { visibility: hidden !important; }
.ella-slide-slot { border-radius: 6px; box-shadow: 0 1px 2px rgba(0, 0, 0, 0.06), 0 6px 24px rgba(16, 40, 20, 0.08); background: #fff; }
.ella-slide-slot > .ella-slide { position: absolute; left: 0; top: 0; transform-origin: 0 0;
  transform: scale(var(--ella-slide-scale, 0.6)); }
.ella-slide { user-select: text; }
.ella-slide .ella-slide-mermaid .ella-mmd-out { width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; }
.ella-slide .ella-slide-mermaid svg { max-width: 100% !important; max-height: 100%; width: auto; height: 100%; }
.ella-slide .ella-slide-chart .main-svg { overflow: visible; }

/* Command bar */
.ella-ss-cmd { position: relative; display: flex; flex-direction: column; gap: 8px; }
.ella-ss-ask { background: #fff; border: 1px solid var(--pale-green); border-radius: 12px; padding: 6px 6px 6px 10px;
  display: flex; align-items: center; gap: 8px; }
.ella-ss-ask:focus-within { border-color: var(--lightgreen); box-shadow: 0 0 0 3px rgba(183, 226, 182, 0.45); }
.ella-ss-askmode.is-on { background: #0b6623 !important; color: #fff !important; border-color: #0b6623 !important; }
.ella-ss-scope { width: 118px; font-size: 11.5px; }
.ella-ss-scope .dash-dropdown-trigger { border-radius: 99px !important; background: #eef2ee !important; border: 0 !important;
  color: #0b6623 !important; font-weight: 700; min-height: 28px !important; }
.ella-ss-sugg { display: flex; gap: 6px; flex-wrap: wrap; }
.ella-ss-sugg button:hover { background: #d0f2d0 !important; }

/* Popovers: Open…, Present ▾, the + picker (assets/slide_studio.js toggles is-open). */
.ella-ss-popwrap { position: relative; }
.ella-ss-pop { display: none; position: absolute; z-index: 30; background: #fff; border: 1px solid var(--border);
  border-radius: 10px; box-shadow: 0 10px 34px rgba(16, 40, 20, 0.16); padding: 10px 12px; }
.ella-ss-pop.is-open { display: block; }
.ella-ss-pop-head { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; }
.ella-ss-pop-title { font: 800 12px "Nunito", sans-serif; color: #0b6623; flex: 1; }
.ella-ss-pop-close { border: 0; background: none; color: #4a5568; cursor: pointer; font-size: 13px; }
.ella-ss-openmenu { right: 0; top: 40px; width: 320px; display: none; }
.ella-ss-openmenu.is-open { display: flex; flex-direction: column; gap: 8px; }
.ella-ss-attached { font-size: 11.5px; color: #0b6623; background: #d0f2d0; border-radius: 6px; padding: 5px 8px; }
.ella-ss-recent { display: flex; flex-direction: column; gap: 4px; }
.ella-ss-recent-item { display: flex; justify-content: space-between; gap: 8px; border: 1px solid var(--border);
  border-radius: 6px; background: #fff; padding: 6px 8px; cursor: pointer; text-align: left; font-size: 12px; color: #1f2d3d; }
.ella-ss-recent-item b { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ella-ss-recent-item small { color: #4a5568; flex-shrink: 0; }
.ella-ss-recent-item:hover { background: #d0f2d0; }
.ella-ss-presentmenu { right: 0; top: 40px; width: 230px; padding: 6px; }
.ella-ss-menuitem { display: block; width: 100%; text-align: left; border: 0; background: none; border-radius: 6px;
  padding: 8px 10px; font: 700 12.5px "Nunito", sans-serif; color: #0b6623; cursor: pointer; }
.ella-ss-menuitem:hover { background: #d0f2d0; }
.ella-ss-picker { left: 140px; top: 96px; width: min(560px, calc(100% - 160px)); }
.ella-ss-picker-where { font-size: 11px; color: #4a5568; }

/* Inspector */
.ella-ss-insp { border-left: 1px solid var(--border); display: flex; flex-direction: column; min-width: 0; }
.ella-ss-tabs { display: flex; border-bottom: 1px solid var(--border); }
.ella-ss-tab { flex: 1; text-align: center; font: 700 12px "Nunito", sans-serif; color: #4a5568; padding: 10px 0;
  border: 0; border-bottom: 2px solid transparent; background: none; cursor: pointer; }
.ella-ss-tab small { font-weight: 600; color: #9aa3b2; }
.ella-ss-insp.tab-content .ella-ss-tab[data-tab="content"],
.ella-ss-insp.tab-layout .ella-ss-tab[data-tab="layout"],
.ella-ss-insp.tab-notes .ella-ss-tab[data-tab="notes"] { color: #0b6623; border-bottom-color: #0b6623; }
.ella-ss-ib { padding: 14px; }
.ella-insp-pane { display: none; }
.ella-ss-insp.tab-content .ella-insp-pane[data-tab="content"],
.ella-ss-insp.tab-layout .ella-insp-pane[data-tab="layout"],
.ella-ss-insp.tab-notes .ella-insp-pane[data-tab="notes"] { display: block; }
/* `.ella-ss-input` is dcc.Input's WRAPPER div under Dash 4 (the <input> is inside),
   so focus is :focus-within; a dcc.Textarea is the element itself (:focus). */
.ella-ss-input:focus, .ella-ss-input:focus-within { outline: none !important; border-color: var(--lightgreen) !important;
  box-shadow: 0 0 0 2px rgba(183, 226, 182, 0.55) !important; }
.ella-ss-input .dash-input-element:focus { outline: none; box-shadow: none !important; }
/* An edit not committed yet (it commits on Enter / leaving the field). */
.ella-ss-input.is-dirty { box-shadow: inset 0 -1px 0 0 var(--lightgreen) !important; }
.ella-ss-input.is-dirty:focus, .ella-ss-input.is-dirty:focus-within { box-shadow: inset 0 -1px 0 0 var(--lightgreen), 0 0 0 2px rgba(183, 226, 182, 0.55) !important; }
.ella-ss-canvas .ella-slide { cursor: text; }
.ella-ss-rows { display: flex; flex-direction: column; gap: 6px; }
.ella-ss-row { display: grid; grid-template-columns: 12px minmax(0, 1fr) auto auto auto; gap: 5px; align-items: center; }
.ella-ss-row.is-sub { padding-left: 16px; }
.ella-ss-group .ella-ss-row { grid-template-columns: minmax(0, 1fr) auto; }
.ella-ss-hdl { color: #9aa3b2; font-size: 11px; letter-spacing: -2px; user-select: none; }
.ella-ss-card { border: 1px solid var(--border); border-radius: 8px; padding: 8px; display: flex; flex-direction: column;
  gap: 6px; background: #fff; }
.ella-ss-cardtools { display: flex; gap: 4px; justify-content: flex-end; }
.ella-ss-mini { font: 700 10px "Nunito", sans-serif; color: #4a5568; text-transform: uppercase; letter-spacing: 0.06em; }
.ella-ss-stepno { width: 22px; height: 22px; flex-shrink: 0; border-radius: 50%; background: #0b6623; color: #fff;
  font: 800 11px/22px "Nunito", sans-serif; text-align: center; }
.ella-ss-span { font: 700 10.5px "JetBrains Mono", monospace; color: #0b6623; background: #eef2ee; padding: 2px 6px; border-radius: 4px; }
.ella-ss-block-sum { list-style: none; cursor: pointer; }
.ella-ss-block-sum::-webkit-details-marker { display: none; }
.ella-ss-addrow:hover { background: #d0f2d0 !important; }
.ella-ss-rowbtn:hover:not(:disabled) { background: #d0f2d0 !important; }
.ella-ss-stash { font-size: 11px; color: #4a5568; background: #eef2ee; border-radius: 6px; padding: 6px 8px; }
.ella-ss-notes-stats { font-size: 11px; color: #4a5568; text-align: right; margin-top: -6px; }
.ella-ss-notes textarea { min-height: 220px !important; font-family: "Nunito", sans-serif !important; font-size: 13px !important; }
.ella-ss-lays { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 6px; }
.ella-ss-lays-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.ella-ss-lay { border: 1px solid var(--border); border-radius: 6px; padding: 6px; font: 700 10px "Nunito", sans-serif;
  color: #4a5568; display: flex; flex-direction: column; gap: 4px; background: #fff; cursor: pointer; text-align: left; width: 100%; }
.ella-ss-lay small { font-weight: 600; color: #378434; font-size: 9px; }
.ella-ss-lay:hover:not(:disabled) { border-color: var(--lightgreen); background: #f6fbf5; }
.ella-ss-lay.is-on { border-color: var(--lightgreen); color: #0b6623; background: #f6fbf5; opacity: 1 !important; }
.ella-ss-ico { aspect-ratio: 16 / 9; background: #fff; box-shadow: 0 0 0 1px var(--border); border-radius: 2px; position: relative;
  overflow: hidden; padding: 4px 5px; box-sizing: border-box; display: flex; flex-direction: column; justify-content: center; }
.ella-ss-ico-glyph { align-items: center; color: #0b6623; font-size: 18px; }
/* "Apply changes" — always visible: pinned to the inspector's foot, and sticky
   to the bottom of the thread while the inspector is on screen (#chat-thread
   is the scroller; nothing between here and it may scroll — CORE RULE 1). */
.ella-ss-applybar { position: sticky; bottom: 0; z-index: 4; margin-top: auto; display: flex; align-items: center; gap: 10px;
  padding: 10px 14px; background: #fff; border-top: 1px solid var(--border); border-radius: 0 0 10px 0; }
.ella-ss-apply-note { flex: 1; min-width: 0; font-size: 11px; line-height: 1.3; color: #4a5568; }
.ella-ss-applybar.is-armed .ella-ss-apply-note { color: #ae5c14; font-weight: 700; }
.ella-ss-apply { flex-shrink: 0; white-space: nowrap; cursor: pointer; border-radius: 8px; padding: 7px 14px;
  font: 800 12.5px "Nunito", sans-serif; color: #0b6623; background: #fff; border: 1px solid #4CB748; }
.ella-ss-apply:hover { background: #d0f2d0; }
.ella-ss-apply.is-armed { color: #fff; background: #4CB748; box-shadow: 0 0 0 3px rgba(183, 226, 182, 0.6); }
.ella-ss-apply.is-armed:hover { background: #0b6623; border-color: #0b6623; }

/* Drawers below the workspace (EL-608): the Assistant transcript and the Raw
   JSON editor — native <details>, full width. The log scrolls as a leaf. */
.ella-ss-drawer { border: 1px solid var(--border); border-radius: 10px; margin-top: 10px; background: #fff; }
.ella-ss-drawer-sum { list-style: none; cursor: pointer; display: flex; align-items: baseline; gap: 10px; padding: 10px 14px;
  font: 700 12.5px "Nunito", sans-serif; color: #0b6623; }
.ella-ss-drawer-sum::-webkit-details-marker { display: none; }
.ella-ss-drawer-sum:hover { background: #f6fbf5; border-radius: 10px; }
.ella-ss-drawer-sum:focus-visible { outline: 2px solid var(--lightgreen); outline-offset: -2px; border-radius: 10px; }
.ella-ss-drawer-caret { display: inline-block; color: #4CB748; transition: transform 0.15s; }
.ella-ss-drawer[open] .ella-ss-drawer-caret { transform: rotate(90deg); }
.ella-ss-drawer[open] > .ella-ss-drawer-sum { border-bottom: 1px solid var(--border); border-radius: 10px 10px 0 0; }
.ella-ss-drawer-title { font-weight: 800; }
.ella-ss-drawer-count:not(:empty) { font-size: 11px; font-weight: 700; color: #0b6623; background: #d0f2d0; border-radius: 99px; padding: 1px 8px; }
.ella-ss-jsondrawer .ella-ss-drawer-count:not(:empty) { color: #ae5c14; background: #FFE5C1; }
.ella-ss-drawer-hint { font-size: 11.5px; font-weight: 600; color: #4a5568; }
.ella-ss-log { display: flex; flex-direction: column; gap: 6px; padding: 12px 14px; max-height: 420px; overflow-y: auto; }
.ella-ss-log-empty { font-size: 12px; font-style: italic; color: #4a5568; }
.ella-ss-drawer-body { display: flex; flex-direction: column; gap: 8px; padding: 12px 14px 14px; }
.ella-ss-json.is-dirty { border-color: #F2A436 !important; }
.ella-ss-json:focus { outline: none; border-color: var(--lightgreen) !important; box-shadow: 0 0 0 2px rgba(183, 226, 182, 0.55); }
.ella-ss-json-status { font-size: 12px; }
.ella-ss-json-status:empty { display: none; }
.ella-ss-json-bar { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.ella-ss-json-bar button:hover { filter: brightness(0.96); border-color: var(--lightgreen) !important; }
.ella-ss-json-hint { font-size: 11px; color: #4a5568; }

/* Presenter console (Presenter view): current + timer left, next + notes right. */
.ella-present-overlay.is-presenter { background: #111612; }
.ella-pc-main { flex: 1; min-height: 0; display: flex; flex-direction: column; }
.ella-pc { display: none; }
.ella-pc-timer, .ella-pc-current-label { display: none; }
.ella-present-overlay { flex-direction: row; flex-wrap: wrap; }
.ella-present-overlay > .ella-pc-main { flex: 1 1 100%; }
.ella-present-overlay > .ella-present-controls { flex: 1 1 100%; }
.ella-present-overlay.is-presenter > .ella-pc-main { flex: 1.7 1 0; padding: 18px 9px 0 18px; }
.ella-present-overlay.is-presenter > .ella-pc { display: block; flex: 1 1 0; padding: 18px 18px 0 9px; color: #d7dfd8; overflow-y: auto; }
.ella-present-overlay.is-presenter .ella-pc-timer,
.ella-present-overlay.is-presenter .ella-pc-current-label { display: flex; }
.ella-pc-label { font: 800 10px "Nunito", sans-serif; letter-spacing: 0.12em; text-transform: uppercase; color: #7f8c82; margin-bottom: 8px; }
.ella-pc-timer { gap: 18px; align-items: baseline; padding: 12px 0; font: 700 13px "Nunito", sans-serif; color: #9fb0a2; }
.ella-pc-timer b { font: 800 30px "Nunito", sans-serif; color: #fff; font-variant-numeric: tabular-nums; }
.ella-pc-timer.is-behind b { color: #F2A436; }
.ella-pc-next { position: relative; aspect-ratio: 16 / 9; border-radius: 4px; overflow: hidden; background: #1a211b; }
.ella-pc-next > .ella-slide { position: absolute; left: 0; top: 0; transform-origin: 0 0; }
.ella-pc-warn { font: 700 12.5px/1.45 "Nunito", sans-serif; color: #FFE5C1; background: rgba(242, 164, 54, 0.16);
  border: 1px solid rgba(242, 164, 54, 0.5); border-radius: 8px; padding: 10px 12px; margin-bottom: 14px; }
.ella-pc-notes { font: 400 15px/1.55 "Nunito", sans-serif; color: #e7ece8; background: #1a211b; border-radius: 8px;
  padding: 14px 16px; white-space: pre-wrap; min-height: 80px; }
.ella-present-blank { display: none; position: absolute; inset: 0; background: #fff; z-index: 5; }
.ella-present-overlay.is-blank:not(.is-presenter) .ella-present-blank { display: block; }
.ella-present-overlay.is-blank.is-presenter .ella-pc-current-label::after { content: " · audience blanked (B)"; color: #F2A436; }
.ella-present-grid { display: none; position: absolute; inset: 0; z-index: 6; background: rgba(11, 18, 13, 0.96);
  overflow-y: auto; padding: 24px; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 16px; align-content: start; }
.ella-present-overlay.is-grid .ella-present-grid { display: grid; }
.ella-present-grid > div { position: relative; aspect-ratio: 16 / 9; border-radius: 4px; overflow: hidden; cursor: pointer;
  box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.15); }
.ella-present-grid > div.is-current { box-shadow: 0 0 0 3px var(--lightgreen); }
.ella-present-grid > div > .ella-slide { position: absolute; left: 0; top: 0; transform-origin: 0 0; }
.ella-present-grid > div > span { position: absolute; left: 6px; bottom: 4px; z-index: 2; font: 800 11px "Nunito", sans-serif;
  color: #fff; background: rgba(0, 0, 0, 0.55); border-radius: 4px; padding: 1px 6px; }
.ella-present-stage { width: 960px; height: 540px; }

/* ===== Inertia Fleet Analytics + Weekly Update action buttons — hover/focus
   (these previously had inline style= only, so a keyboard tab fell back to
   the browser's default blue outline). ===== */
button[id*="ifa-update-plot-btn"]:hover,
button[id*="weekly-update-generate-btn"]:hover {
  filter: brightness(1.08);
}

button[id*="weekly-update-publish-btn"]:hover {
  background: var(--pale-green-bg) !important;
}

button[id*="ifa-update-plot-btn"]:focus-visible,
button[id*="weekly-update-generate-btn"]:focus-visible,
button[id*="weekly-update-publish-btn"]:focus-visible {
  outline: 2px solid var(--lightgreen);
  outline-offset: 2px;
}

input[id*="weekly-update-days"]:focus {
  outline: none !important;
  border-color: var(--lightgreen) !important;
  box-shadow: 0 0 0 2px rgba(183, 226, 182, 0.55) !important;
}

/* script_framework's file-path override input sets outline:none inline
   (ui.py::file_inputs_section) with no replacement — keyboard users tabbing
   to it saw no focus cue at all. Same green-ring treatment as the slides
   fields above. */
input[id*="script-file-path"]:focus {
  outline: none !important;
  border-color: var(--lightgreen) !important;
  box-shadow: 0 0 0 2px rgba(183, 226, 182, 0.55) !important;
}

.ella-slides-layout-select .Select-control,
.ella-slides-layout-select .is-focused .Select-control,
.ella-slides-layout-select .is-open .Select-control {
  border-color: #B7E2B6 !important;
  box-shadow: none !important;
}

.ella-slides-layout-select .Select-option.is-selected,
.ella-slides-layout-select .Select-option.is-focused {
  background: #d0f2d0 !important;
  color: #0b6623 !important;
}

/* ===== Input-wrap focus ring ===== */
#input-wrap:focus-within {
  border-color: var(--accent) !important;
  box-shadow: 0 0 0 4px var(--focus-ring) !important;
}

/* ===== Scrollbar chrome ===== */
#chat-thread::-webkit-scrollbar,
#conversation-list::-webkit-scrollbar {
  width: 8px;
}

#chat-thread::-webkit-scrollbar-thumb,
#conversation-list::-webkit-scrollbar-thumb {
  background: rgba(11, 102, 35, 0.15);
  border-radius: 6px;
  border: 2px solid transparent;
  background-clip: content-box;
}

/* ===== Code block syntax tokens ===== */
.tk-kw {
  color: #f2a436;
}

.tk-fn {
  color: #94d491;
}

.tk-str {
  color: #ffe5c1;
}

.tk-com {
  color: #6f9670;
  font-style: italic;
}

.tk-num {
  color: #ffb46b;
}

.tk-sym {
  color: #d7eed2;
}

/* ===== DataTable header override ===== */
/* Ensures the Element palette applies even when Bootstrap styles are present */
.dash-header {
  font-family: "Nunito", sans-serif !important;
}

/* ===== table-card chrome ===== */
.table-card {
  margin: 4px 0 0;
  background: #fff;
  border: 1px solid var(--border-strong);
  border-radius: 10px;
  overflow: hidden;
}

.table-card__header {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 9px 12px;
  border-bottom: 1px solid var(--border);
  background: linear-gradient(0deg, #fbfdfb, #fff);
}

.table-card__icon {
  width: 22px;
  height: 22px;
  border-radius: 6px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--pale-green-bg);
  color: var(--darkgreen);
  flex-shrink: 0;
}

.table-card__title-meta {
  flex: 1;
  min-width: 0;
}

.table-card__title-row {
  display: flex;
  align-items: center;
}

.table-card__title {
  font-weight: 700;
  font-size: 13px;
  color: var(--ink);
}

.table-card__row-badge {
  display: inline-flex;
  align-items: center;
  background: var(--pale-green-bg);
  color: var(--darkgreen);
  padding: 1px 8px;
  border-radius: 999px;
  font-size: 10.5px;
  font-weight: 700;
  margin-left: 8px;
}

.table-card__subtitle {
  font-size: 11.5px;
  color: var(--muted);
}

.table-card__viewer-actions {
  margin-left: auto;
  display: flex;
}

.table-card__viewer-btn {
  display: inline-flex;
  align-items: center;
  padding: 3px 9px;
  border-radius: 999px;
  font-size: 11.5px;
  font-weight: 600;
  color: #fff;
  background: var(--lightgreen);
  border: 1px solid var(--lightgreen);
  text-decoration: none;
}

.table-card__viewer-btn:hover,
.table-card__viewer-btn:focus-visible {
  background: var(--darkgreen);
  border-color: var(--darkgreen);
  outline: none;
}

.table-card__footer {
  padding: 8px 12px;
  border-top: 1px solid var(--border);
  background: #fbfdfb;
  font-size: 11.5px;
  color: var(--muted);
}

.table-card__empty-state {
  padding: 22px 12px;
  text-align: center;
  font-size: 12.5px;
  color: var(--muted);
}

/* ===== Bubble prose defaults ===== */
/* Typeset math (dcc.Markdown mathjax=...) is unbounded by default, so a long
   inline formula or a $$…$$ display block would stretch the bubble and the
   whole thread column. Scroll it inside itself, exactly as .bubble-prose table
   does below. Scoped to the mjx-container leaf on purpose: CORE RULE 1 forbids
   overflow on .ella-body or any ancestor between .ella-sticky and #chat-thread,
   and this touches neither. */
.bubble-prose mjx-container {
  overflow-x: auto;
  max-width: 100%;
}

.bubble-prose p {
  margin: 0 0 8px;
}

.bubble-prose p:last-child {
  margin-bottom: 0;
}

.bubble-prose ul,
.bubble-prose ol {
  margin: 4px 0 8px 20px;
  padding: 0;
}

.bubble-prose li {
  margin-bottom: 2px;
}

.bubble-prose strong {
  /* --accent-ink, not --darkgreen: the brand green is ~1.3:1 on the dark
     bubble; the alias IS --darkgreen in light and moves to --leaf in dark. */
  color: var(--accent-ink);
}

/* GFM tables from dcc.Markdown had zero rules here: no borders/collapse, and
   a wide table simply squeezed into the fixed-width bubble with no scroll
   affordance — the wrapped/squeezed layout also made a plain text-selection
   copy come out misaligned. `display: block` + `overflow-x: auto` on the
   table itself (no wrapper element needed, since dcc.Markdown emits a bare
   <table>) lets it scroll horizontally instead of destroying its own layout.
   `width: max-content` (same as GitHub's markdown CSS for this exact pattern)
   is required too: a block-level table still defaults to filling its
   container's width, so without it wrappable cell text just re-wraps to fit
   instead of the table overflowing and triggering the scrollbar. */
.bubble-prose table {
  display: block;
  overflow-x: auto;
  width: max-content;
  max-width: 100%;
  border-collapse: collapse;
  margin: 4px 0 8px;
  font-size: 13px;
}

.bubble-prose th,
.bubble-prose td {
  border: 1px solid var(--border);
  padding: 4px 8px;
  text-align: left;
}

.bubble-prose th {
  background: var(--bg-soft);
  color: var(--darkgreen);
  font-weight: 600;
}

/* Prevent markdown headings inside chat bubbles from rendering at browser-default h1/h2 size */
.bubble-prose h1,
.bubble-prose h2,
.bubble-prose h3,
.bubble-prose h4,
.bubble-prose h5,
.bubble-prose h6 {
  font-size: 14px;
  font-weight: 600;
  line-height: 1.5;
  margin: 0.4em 0 0.25em;
  color: inherit;
}

/* ===== Inline visualization cards (Hybrid UI, 2026-08-13) ===== */
/* dyn_plot_card / dyn_layout_card render INLINE, inside the bubble's markdown
 * flow (clients/dash/messages.py), instead of full-width below it. A subtle
 * inset shadow + off-white fill (decoration only — structural sizing stays in
 * the inline style={} per the no-CSS-for-layout rule) distinguishes the card
 * from the surrounding prose without the heavier full-width script-card chrome. */
.ella-inline-viz {
  background-color: var(--card-bg-soft);
  box-shadow: inset 0 1px 3px rgba(15, 36, 23, 0.05);
}

/* ===== Greeting animation ===== */
#greeting-block {
  animation: fadeInUp 0.3s ease;
}

@keyframes fadeInUp {
  from {
    opacity: 0;
    transform: translateY(10px);
  }

  to {
    opacity: 1;
    transform: translateY(0);
  }
}

/* ===== Draggable resize handles ===== */
/* Vertical drag (changes height) — drag up/down. */
.resizable-handle-vertical {
  cursor: row-resize;
  transition: background 0.12s;
}

.resizable-handle-vertical:hover {
  background: var(--leaf) !important;
}

/* Horizontal drag (changes width) — drag left/right. */
.resizable-handle-horizontal {
  cursor: col-resize;
  background: transparent;
  transition: background 0.12s;
}

.resizable-handle-horizontal:hover {
  background: var(--leaf);
}

/* "Help & setup" toggle hover affordance. */
#sidebar-help-setup-toggle:hover #sidebar-help-setup-label,
#sidebar-help-setup-toggle:hover #sidebar-help-setup-caret {
  color: var(--darkgreen);
}

/* ── Capabilities modal tab row — modern flat tabs ─────────────────────────
 * Unselected: subtle gray fill, rounded top corners, no bottom border so
 * they read as inactive surface. Selected: white background with a solid
 * 2px ELEMENT DARKGREEN underline. Slight transition for hover affordance.
 */
.cap-tab-group {
  display: inline-flex;
  align-items: flex-end;
  gap: 2px;
}

.cap-tab-btn {
  background: #f5f5f5;
  color: var(--muted);
  border: 1px solid transparent;
  border-bottom: 0;
  border-top-left-radius: 8px;
  border-top-right-radius: 8px;
  font-family: inherit;
  font-size: 13px;
  font-weight: 600;
  padding: 0 14px;
  height: 34px;
  display: inline-flex;
  align-items: center;
  gap: 7px;
  cursor: pointer;
  white-space: nowrap;
  transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
}

.cap-tab-btn:hover {
  background: #ecefec;
  color: var(--darkgreen);
}

.cap-tab-btn:focus-visible {
  outline: 2px solid var(--lightgreen);
  outline-offset: 1px;
}

.cap-tab-btn--active {
  background: #ffffff;
  color: var(--darkgreen);
  border-color: #e3e6f0;
  border-bottom: 2px solid var(--darkgreen);
  margin-bottom: -1px;
  font-weight: 700;
}

.cap-tab-btn--active:hover {
  background: #ffffff;
  color: var(--darkgreen);
}

.cap-tab-pill {
  background: rgba(11, 102, 35, 0.10);
  color: var(--darkgreen);
  font-size: 10.5px;
  font-weight: 800;
  padding: 1px 7px;
  border-radius: 999px;
  font-family: "JetBrains Mono", monospace;
}

.cap-tab-btn--active .cap-tab-pill {
  background: rgba(11, 102, 35, 0.18);
  color: var(--darkgreen);
}

/* ── Script card full-width breakout ─────────────────────────────────────────
 * Script cards sit inside the assistant body div (flex: 1, minus the 28px
 * avatar + 12px gap).  This class pulls the card left by that same offset so
 * it spans the full chat panel width, leaving a small margin on both sides.
 * Applied to the outside_block wrapper in messages.py::render_message().
 */
/* Script/data cards cancel the chat-thread's 36px side padding symmetrically
 * so the card's .ella-frame fills the full chat panel width.  The 12px
 * side-margin on .ella-frame then reserves space for the SVG border overhang
 * and a small visual breathing room, keeping everything edge-to-edge. */
.script-card-full-width {
  margin-left: -36px;
  margin-right: -36px;
  width: calc(100% + 72px);
}
/* The standalone page (/?standalone_card=, the /v2 Dash-fallback iframe) has no
 * 36px chat padding to cancel: the bleed pushed every card 22px past both edges
 * of its 14px-padded host (EL-677). */
#standalone-card-host > .script-card-full-width {
  margin-left: 0;
  margin-right: 0;
  width: auto;
}

/* Segmented radio groups (dbc.RadioItems className="btn-group radio-group") wrap
 * onto a second row instead of overflowing a narrow container (EL-677). A no-op
 * wherever the group fits, so desktop is unchanged; a wrapped row loses its pill
 * end radii, which is the accepted cost on a phone. Wrapping also lowers the
 * group's min-content to its widest option, so it can shrink in a flex toolbar.
 * `div.` outranks custom.css's `.radio-group.btn-group { gap: 0 }`, which loads
 * after this file (custom.css is the verbatim Element sheet — never edited). */
div.radio-group.btn-group {
  flex-wrap: wrap;
  max-width: 100%;
  row-gap: 4px;
}

/* Inertia Node Summary diagnostics row: five proportional tiles on desktop,
 * two columns on a phone so each tile's label row fits (EL-677). The inline
 * gridTemplateColumns is the desktop value, hence !important. */
@media (max-width: 640px) {
  .ins-diag-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr)) !important;
    /* SOD spans a row: pack SOH+SOS, SOD, Impedance+Leakage with no holes. */
    grid-auto-flow: dense;
  }
  /* Summary Status row (SoXa · Alerts · Recommended action): one per row. */
  .ins-stat-grid {
    grid-template-columns: minmax(0, 1fr) !important;
  }
  /* dbc options are .form-check-inline (16px right margin), not .btn: tighten
   * so short options pair up on a row instead of stacking one per line. */
  div.radio-group.btn-group > .form-check-inline {
    margin-right: 6px;
  }
  /* SOD carries three sub-metrics (LLI / LAM-PE / LAM-NE): a full row. */
  .ins-diag-grid > :nth-child(2) {
    grid-column: 1 / -1;
  }
  /* Micro-shorts fit-window slider: its end marks are date labels centred on
   * the rail ends, so half of each hangs past a narrow card's edge. */
  .ms-range-slider {
    padding-left: 32px;
    padding-right: 32px;
  }
}

/* ════════════════════════════════════════════════════════════════════
   ELLA card chrome — shared "Cards by ELLA" frame + sticky dark header
   Applied to every script card (see ella/scripts/ui.py + the inertia card).
   The card is centered in the chat panel and spans nearly its full width,
   leaving a small symmetric margin on either side for the SVG logo border.
   ════════════════════════════════════════════════════════════════════ */

/* ── Frame wrapper ─────────────────────────────────────────────────────────
 * z-index: 1 creates a stacking context for the whole card unit.  This is the
 * key that makes .ella-sticky paint on top of preceding message rows (bubbles,
 * Insights collapsibles) when the sticky header is pinned at the top.  Without
 * it, `position: relative` alone doesn't create a stacking context, so the
 * sticky header's z-index: 6 is scoped to the root stacking context and
 * still loses to elements painted later in DOM order.
 * 12px symmetric side margin gives the SVG border (9px overhang) clearance. */
.ella-frame {
  position: relative;
  z-index: 1;
  /* creates stacking context; card paints above message rows */
  margin: 10px 12px 0;
}

.ella-frame-svg {
  position: absolute;
  inset: -9px;
  pointer-events: none;
  z-index: 4;
  overflow: visible;
}

/* ── Card surface ──────────────────────────────────────────────────────────
 * NO overflow:hidden — that would break position:sticky on .ella-sticky.
 * Shadow echoes the Element logo colour. */
.ella-card {
  position: relative;
  background: #fff;
  border-radius: 12px;
  box-shadow: 0 4px 16px rgba(11, 102, 35, 0.09), 0 2px 6px rgba(11, 102, 35, 0.05);
  width: 100%;
}

/* ── Sticky chrome ─────────────────────────────────────────────────────────
 * Header + tab strip stay pinned at the top of #chat-thread while the card
 * body scrolls beneath it.
 *
 * border-top: the SVG logo border is positioned relative to the full .ella-frame,
 * so its top dark-green arc scrolls off the viewport when the header becomes sticky.
 * Adding border-top directly on .ella-sticky gives the sticky header a persistent
 * dark-green top edge that moves WITH it regardless of scroll position.
 * The SVG still provides the segmented side/bottom decorative border. */
.ella-sticky {
  position: sticky;
  top: 0;
  z-index: 6;
  border-radius: 12px 12px 0 0;
  overflow: hidden;
  border-top: 3px solid var(--darkgreen);
}

/* Non-sticky header chrome — same visual appearance as .ella-sticky but scrolls
   with the card content instead of pinning to the top of the viewport. */
.ella-chrome {
  border-radius: 12px 12px 0 0;
  overflow: hidden;
  border-top: 3px solid var(--darkgreen);
}

/* ── Body — flows with the chat thread (NOT an internal scroll container) ───
 * CORE CARD RULE: showing the ENTIRE card is the priority over every other
 * layout component. The body therefore must NOT scroll internally — it grows
 * to its natural height and participates in the #chat-thread scroll. Because
 * no ancestor between .ella-sticky and #chat-thread sets `overflow`, the
 * sticky header's containing block is #chat-thread itself: the header scrolls
 * up seamlessly with the card and then pins to the top of the app (just under
 * the navbar) once it reaches it. Do NOT re-introduce overflow/max-height here
 * — that would scope the sticky header to the card and cap the card's height,
 * which is exactly the behaviour we are removing. */
.ella-body {
  border-radius: 0 0 12px 12px;
}

/* Dark-green header bar */
.ella-head {
  display: flex;
  align-items: center;
  gap: 13px;
  padding: 11px 15px 11px 12px;
  background: linear-gradient(180deg, var(--darkgreen) 0%, var(--darkgreen-deep) 100%);
  position: relative;
}

/* Rainbow keyline under the header */
.ella-head::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 2px;
  background: linear-gradient(90deg, var(--lightgreen), var(--orange));
  opacity: 0.85;
}

/* Dash Dropdown inside the dark header — keep a light, legible control */
.ella-head .Select-control,
.ella-head .Select-menu-outer {
  border-color: rgba(255, 255, 255, 0.45) !important;
}

.ella-head .Select.is-focused>.Select-control,
.ella-head .Select.is-open>.Select-control {
  box-shadow: 0 0 0 3px rgba(255, 255, 255, 0.35) !important;
  border-color: rgba(255, 255, 255, 0.6) !important;
}

/* Tab strip sits in the sticky header region, directly below the dark header.
   The nav-only dbc.Tabs renders empty panes — hide that empty content area so
   only the tab labels show; the real panes live in the scrolling body. */
.ella-chrome .tab-content {
  display: none;
}

/* Tab labels on a card-bg strip, pinned with the header while the body scrolls */
.ella-chrome .nav-tabs {
  background: var(--card-bg);
  padding: 4px 12px 0;
  border-bottom: 1px solid var(--border);
  gap: 2px;
}

/* Element-themed tabs — override Bootstrap's blue active/hover entirely */
.ella-chrome .nav-tabs .nav-link {
  color: var(--muted);
  font-weight: 700;
  font-size: 13px;
  border: 0;
  border-bottom: 2.5px solid transparent;
  border-radius: 0;
  padding: 9px 14px 8px;
  margin-bottom: -1px;
  background: transparent;
  transition: color .12s, border-color .12s;
}

.ella-chrome .nav-tabs .nav-link:hover,
.ella-chrome .nav-tabs .nav-link:focus {
  color: var(--ink-soft);
  border-bottom-color: var(--leaf);
  isolation: isolate;
}

.ella-chrome .nav-tabs .nav-link.active {
  color: var(--darkgreen);
  background: transparent;
  border-bottom-color: var(--darkgreen);
}

/* Embedded node-summary viewers injected into a card BODY (e.g. the SoXa
   discrepancy analyzer's row-click detail) sit outside .ella-chrome, so they
   need the same Element tab theming re-scoped — otherwise dbc.Tabs falls back
   to stock Bootstrap blue. Mirrors the .ella-chrome rules above. */
.ella-embed .tab-content {
  display: none;
}

.ella-embed .nav-tabs {
  background: var(--card-bg);
  padding: 4px 12px 0;
  border-bottom: 1px solid var(--border);
  gap: 2px;
}

.ella-embed .nav-tabs .nav-link {
  color: var(--muted);
  font-weight: 700;
  font-size: 13px;
  border: 0;
  border-bottom: 2.5px solid transparent;
  border-radius: 0;
  padding: 9px 14px 8px;
  margin-bottom: -1px;
  background: transparent;
  transition: color .12s, border-color .12s;
}

.ella-embed .nav-tabs .nav-link:hover,
.ella-embed .nav-tabs .nav-link:focus {
  color: var(--ink-soft);
  border-bottom-color: var(--leaf);
  isolation: isolate;
}

.ella-embed .nav-tabs .nav-link.active {
  color: var(--darkgreen);
  background: transparent;
  border-bottom-color: var(--darkgreen);
}

/* The Logs tab's per-repo sub-tabs (rail.py `_render_agent_logs_tab`) render
   their panes inline inside each dbc.Tab rather than via a separate
   .tab-content area, and sit inside the Capabilities modal rather than under
   .ella-chrome/.ella-embed — so they need their own scope, not the shared
   .tab-content-hiding rule above (that would hide real content here). */
.agent-logs-repo-tabs.nav-tabs {
  border-bottom: 1px solid var(--border);
  gap: 2px;
}

.agent-logs-repo-tabs.nav-tabs .nav-link {
  border: 0;
  border-bottom: 2.5px solid transparent;
  border-radius: 0;
  background: transparent;
  transition: color .12s, border-color .12s;
}

.agent-logs-repo-tabs.nav-tabs .nav-link:hover,
.agent-logs-repo-tabs.nav-tabs .nav-link:focus-visible {
  border-bottom-color: var(--leaf);
  isolation: isolate;
}

.agent-logs-repo-tabs.nav-tabs .nav-link.active {
  background: transparent;
  border-bottom-color: var(--darkgreen);
}

/* ── dbc.Alert → Element palette (.claude/rules/dash-ui.md/§6: no stock Bootstrap greens/
   reds; every surface reads as a custom Element component). Used only by the
   SoXa Discrepancy Analyzer Accept/Reject feedback alert
   (`sda-decision-alert`), so these class overrides are effectively scoped to
   that card. success → green, warning → cream/orange, danger → filled orange
   (the palette has no red). */
.alert-success {
  background-color: var(--pale-green-bg);
  border-color: var(--pale-green);
  color: var(--darkgreen);
}

.alert-warning {
  background-color: var(--cream-soft);
  border-color: var(--orange);
  color: #8a5410;
}

.alert-danger {
  background-color: var(--orange);
  border-color: #d98a1f;
  color: #4a2c05;
}

/* ── ELLA-standard data grid (script_framework/grid.py) ────────────────────
   The shared table treatment for EVERY card's AgGrid: 2-level group headers
   with a distinct background per L1 category, wrapped header text, and
   light-gray/white alternating body rows. headerClass values are assigned by
   grid.build_grouped_column_defs (first group dark, then the ella-hdr-<n>
   cycle). New cards get all of this by using the grid module — no new CSS. */
.ella-grid .ag-header-group-cell.ella-hdr-dark,
.ella-grid .ag-header-cell.ella-hdr-dark {
  background: var(--darkgreen);
  color: #fff;
}

/* Sort/menu icons on the dark header — the theme forces .ag-icon dark green,
   which would be invisible on the dark-green header background. */
.ella-grid .ella-hdr-dark .ag-icon {
  color: #fff;
}

.ella-grid .ag-header-group-cell.ella-hdr-0,
.ella-grid .ag-header-cell.ella-hdr-0 {
  background: #e8f5e9;
  /* very light green */
  color: var(--darkgreen);
}

.ella-grid .ag-header-group-cell.ella-hdr-1,
.ella-grid .ag-header-cell.ella-hdr-1 {
  background: #fff3e0;
  /* very light orange */
  color: #7a4a10;
}

.ella-grid .ag-header-group-cell.ella-hdr-2,
.ella-grid .ag-header-cell.ella-hdr-2 {
  background: #e3f0f7;
  /* very light blue-gray */
  color: #2b5b7a;
}

.ella-grid .ag-header-group-cell.ella-hdr-3,
.ella-grid .ag-header-cell.ella-hdr-3 {
  background: #fdf3e3;
  /* cream */
  color: #8a6d3b;
}

.ella-grid .ag-header-group-cell.ella-hdr-4,
.ella-grid .ag-header-cell.ella-hdr-4 {
  background: #efeaf5;
  /* very light purple-gray */
  color: #5a4a8c;
}

.ella-grid .ag-header-group-cell.ella-hdr-5,
.ella-grid .ag-header-cell.ella-hdr-5 {
  background: #f1f3f4;
  /* very light gray */
  color: #4a5361;
}

.ella-grid .ag-header-cell-label,
.ella-grid .ag-header-group-cell-label {
  white-space: normal;
  line-height: 1.15;
  justify-content: center;
  text-align: center;
  font-weight: 700;
}

/* Alternating light-gray/white body rows (overrides the theme default) */
.ella-grid .ag-row-even {
  background: #ffffff;
}

.ella-grid .ag-row-odd {
  background: #f2f3f5;
}

.ella-grid .ag-row-selected {
  background: #d0f2d0 !important;
  /* PALEGREEN_BG */
}

/* Diagnostics — always five small bubbles in a single row, side by side */
.diag-grid-5 {
  display: grid;
  gap: 10px;
  grid-template-columns: repeat(5, minmax(0, 1fr));
}

/* ── Sidebar Tools checklist — ELEMENT-themed dbc.Checklist ──────────────── */
.custom-checklist .form-check {
  margin-bottom: 0;
  padding: 0;
}

.custom-checklist .form-check-input {
  accent-color: var(--darkgreen);
  background-color: #fff;
  border: 1.5px solid var(--border-strong);
  margin-top: 4px;
  flex-shrink: 0;
}

.custom-checklist .form-check-input:checked {
  background-color: var(--darkgreen);
  border-color: var(--darkgreen);
}

.custom-checklist .form-check-input:focus {
  border-color: var(--lightgreen);
  box-shadow: 0 0 0 3px rgba(76, 183, 72, 0.18);
}

.custom-checklist .form-check-label {
  cursor: pointer;
}

/* ===== Chat composer — collapses entirely while a card is open ===========
 * CORE CARD RULE: a script card is treated as the END of a conversation. When
 * any `.ella-frame` card is present the composer is fully collapsed (it does
 * NOT pop back up on scroll). The user re-opens it deliberately via the
 * floating "Continue chat" button (see #continue-chat-btn + scroll.js).
 * max-height transition actually reclaims the vertical space (translateY
 * alone keeps the layout footprint).  Padding is zeroed so no gap remains. */
#chat-input-container {
  overflow: hidden;
  max-height: 220px;
  /* generous upper bound — actual height ~180px */
  transition: max-height 0.35s ease-in-out,
    opacity 0.25s ease-in-out,
    padding-top 0.25s ease-in-out,
    padding-bottom 0.25s ease-in-out;
}

#chat-input-container.input-hidden {
  max-height: 0;
  padding-top: 0 !important;
  padding-bottom: 0 !important;
  opacity: 0;
  pointer-events: none;
}

/* ===== "Continue chat" button — reveals the composer while a card is open ==
 * Hidden by default. scroll.js adds `.visible` only when a card is present and
 * the composer is collapsed. Clicking it reveals + focuses the composer and
 * hides this button. Sits centered just above the composer baseline. */
#continue-chat-btn {
  position: absolute;
  bottom: 10px;
  left: 50%;
  transform: translateX(-50%) translateY(8px);
  z-index: 20;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 7px 14px;
  border: 0;
  border-radius: 999px;
  background: linear-gradient(180deg, var(--lightgreen) 0%, var(--darkgreen) 100%);
  color: #fff;
  font-family: "Nunito", sans-serif;
  font-size: 12px;
  font-weight: 800;
  letter-spacing: 0.01em;
  cursor: pointer;
  box-shadow: 0 4px 14px rgba(11, 102, 35, 0.30), 0 2px 5px rgba(11, 102, 35, 0.20);
  opacity: 0;
  pointer-events: none;
  visibility: hidden;
  transition: opacity 0.25s ease-in-out, transform 0.25s ease-in-out;
}

#continue-chat-btn.visible {
  opacity: 1;
  pointer-events: auto;
  visibility: visible;
  transform: translateX(-50%) translateY(0);
}

#continue-chat-btn:hover {
  box-shadow: 0 6px 18px rgba(11, 102, 35, 0.38), 0 3px 7px rgba(11, 102, 35, 0.24);
}

#continue-chat-btn:focus-visible {
  outline: 2px solid var(--lightgreen);
  outline-offset: 2px;
}

#continue-chat-btn .cc-arrow {
  font-size: 10px;
  line-height: 1;
}

/* "N new lines" pill.
 *
 * Shown by scroll.js only after the user scrolls up and output arrives below
 * the fold — the visible half of "streaming must never yank you back down".
 * Same stacking context and centring as #continue-chat-btn above; the two are
 * never visible at the same time (a card ends the conversation, so nothing is
 * still streaming under it). */
#chat-new-lines-pill {
  position: absolute;
  bottom: 10px;
  left: 50%;
  transform: translateX(-50%) translateY(8px);
  z-index: 20;
  display: inline-flex;
  align-items: center;
  gap: 7px;
  white-space: nowrap;
  padding: 6px 14px;
  border: 1px solid var(--accent-border);
  border-radius: 999px;
  background: var(--card-bg);
  color: var(--accent-ink);
  font-family: "Nunito", sans-serif;
  font-size: 12px;
  font-weight: 700;
  cursor: pointer;
  box-shadow: var(--shadow-md);
  opacity: 0;
  pointer-events: none;
  visibility: hidden;
  transition: opacity 0.25s ease-in-out, transform 0.25s ease-in-out;
}

#chat-new-lines-pill.visible {
  opacity: 1;
  pointer-events: auto;
  visibility: visible;
  transform: translateX(-50%) translateY(0);
}

#chat-new-lines-pill:hover {
  border-color: var(--accent);
  box-shadow: var(--shadow-md), 0 0 0 1px var(--accent-border);
}

#chat-new-lines-pill:focus-visible {
  outline: 2px solid var(--lightgreen);
  outline-offset: 2px;
}

#chat-new-lines-pill .ella-new-pill-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--accent);
  animation: newPillPulse 1.4s ease-in-out infinite;
}

#chat-new-lines-pill .ella-new-pill-arrow {
  font-size: 10px;
  line-height: 1;
}

@keyframes newPillPulse {
  0%,
  100% {
    transform: scale(1);
    opacity: 1;
  }

  50% {
    transform: scale(1.35);
    opacity: 0.55;
  }
}

/* Minimize composer button */
#btn-minimize-composer {
  position: absolute;
  right: 0;
  top: 50%;
  transform: translateY(-50%);
}

#btn-minimize-composer:hover {
  color: var(--ink-soft);
}

#btn-minimize-composer:focus-visible {
  outline: 2px solid var(--lightgreen);
  outline-offset: 1px;
  color: var(--ink-soft);
}

/* ===== "Cards by ELLA" status pill — design-reference style ==================
 * Use className="ella-status" on the status <span> inside .ella-head.
 * Variant modifiers: pill-run (green pulse dot), pill-warn (orange), pill-error.
 * The default is the neutral "ready" style (white-alpha bg, leaf dot).
 * NOTE: the existing per-card inline-styled pills work independently; this class
 * is provided for new cards and future migration. */
.ella-status {
  padding: 3px 11px;
  border-radius: 999px;
  font-size: 11px;
  font-weight: 800;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  letter-spacing: 0.02em;
  background: rgba(255, 255, 255, 0.14);
  color: #eafbe7;
  border: 1px solid rgba(255, 255, 255, 0.22);
  transition: background 0.15s, color 0.15s, border-color 0.15s;
}

.ella-status::before {
  content: "";
  width: 6px;
  height: 6px;
  border-radius: 50%;
  flex-shrink: 0;
  background: var(--leaf);
}

.ella-status.pill-run::before {
  background: var(--lightgreen);
  box-shadow: 0 0 0 3px rgba(76, 183, 72, 0.3);
}

.ella-status.pill-warn {
  background: rgba(242, 164, 54, 0.18);
  color: var(--cream-soft);
  border-color: rgba(242, 164, 54, 0.45);
}

.ella-status.pill-warn::before {
  background: var(--orange);
}

.ella-status.pill-error {
  background: rgba(179, 42, 42, 0.18);
  color: #ffd9d6;
  border-color: rgba(179, 42, 42, 0.45);
}

.ella-status.pill-error::before {
  background: var(--danger-fg);
}

/* ── Code-block action buttons (Copy / Download) ───────────────────────────
 * Injected by copy_code.js into every fenced markdown block and dark
 * code_block(). Floated top-right as a small green button group so the
 * "copy or download the whole instruction file in one click" workflow stays
 * on-brand. Element green — never Bootstrap blue. */
.bubble-prose pre,
.custom-code-block {
  position: relative;
}

.copy-btn,
.download-btn,
.save-btn {
  position: absolute;
  top: 6px;
  font-family: Nunito, sans-serif;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.02em;
  padding: 3px 9px;
  border-radius: 6px;
  border: 1px solid rgba(76, 183, 72, 0.55);
  background: rgba(76, 183, 72, 0.16);
  /* These sit on the CODE BLOCK, which is dark in BOTH themes — so the label
   * must be a LIGHT green, not --darkgreen (2.29:1 on --code-bg, measured;
   * the earlier "legible at rest" fix assumed a light ground). --leaf clears
   * 9:1 there. Not --accent-ink: that flips per theme, and this surface does
   * not. */
  color: var(--leaf);
  cursor: pointer;
  opacity: 0.95;
  transition: opacity 0.15s ease, background 0.15s ease;
  /* copy_code.js appends these as literal DOM children of the <pre> that
   * also holds the code text (position:absolute only changes where they're
   * PAINTED, not their place in DOM/selection order). Without this, a user
   * who drags to manually select+copy raw code/markdown — instead of
   * clicking the Copy button — picks up "CopyDownloadSave" appended to
   * their clipboard text, corrupting the paste. */
  user-select: none;
}

.copy-btn {
  right: 8px;
}

.download-btn {
  right: 62px;
}

.copy-btn:hover,
.download-btn:hover,
.save-btn:hover {
  opacity: 1;
  background: var(--lightgreen);
  /* Dark ink on the light-green fill — #fff on --lightgreen is 2.9:1, under
     the 3:1 floor even for a control. --code-bg-head is theme-invariant, like
     the surface these buttons live on. */
  color: var(--code-bg-head);
}

/* Tab-focusing any of these three (no prior :focus-visible rule) showed the
   browser's native BLUE ring (.claude/rules/dash-ui.md) — plain inline `style={}` on the
   Python side cannot express :focus-visible. */
.copy-btn:focus-visible,
.download-btn:focus-visible,
.save-btn:focus-visible {
  outline: 2px solid var(--lightgreen, #4cb748);
  outline-offset: 2px;
}

.copy-btn.copied,
.download-btn.copied,
.save-btn.copied {
  opacity: 1;
  background: var(--lightgreen);
  color: var(--code-bg-head);
  border-color: var(--lightgreen);
}

/* Save button — sits left of the Download button. Copy ends ~46px from the
 * right edge, Download (right:62px) ends ~138px from it; right:146px leaves an
 * ~8px gap so the three never overlap, even when Save reads "Saving…". */
.save-btn {
  right: 146px;
}

.save-btn:disabled {
  opacity: 0.6;
  cursor: default;
}

/* The SoXa Discrepancy Analyzer's five writer buttons (Limit/Unlimit, the fill
   prompt's Confirm/Cancel, Apply) cross-disable while ANY staging/apply job
   runs (running= on their background callbacks). Their inline styles hardcode
   cursor: pointer, hence the !important — without this rule a disabled button
   keeps its full colour and pointer and silently swallows clicks for the whole
   background job. */
.sda-writer-btn:disabled {
  opacity: 0.55;
  cursor: default !important;
}

/* Degradation Matrix Explorer: everything else (buttons, Inspect, summaries,
   focus rings) is deg-matrix.css (EL-668). */
/* EL-660: AG Grid's autoHeight layout reserves 150px for its "No Rows"
   overlay, so the one-row Thermal domain grid trailed a blank band. These
   grids always have rows, so let the body hug them. */
.deg-matrix-grid-wrap .ag-layout-auto-height .ag-center-cols-viewport,
.deg-matrix-grid-wrap .ag-layout-auto-height .ag-center-cols-container,
.deg-matrix-grid-wrap .ag-layout-auto-height .ag-center-cols-clipper {
  min-height: 0 !important;
}

/* Inline filename editor opened by the Save button. */
.save-prompt-row {
  position: absolute;
  top: 32px;
  right: 8px;
  display: flex;
  gap: 4px;
  align-items: center;
  background: #fff;
  border: 1px solid var(--palegreen);
  border-radius: 6px;
  padding: 4px 6px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.12);
  z-index: 10;
}

.save-name-input {
  width: 220px;
  font-family: 'JetBrains Mono', SFMono-Regular, Menlo, Monaco, Consolas, monospace;
  font-size: 11.5px;
  border: 1px solid var(--palegreen);
  border-radius: 4px;
  padding: 2px 6px;
  color: var(--darkgreen);
}

.save-name-input:focus {
  outline: none;
  border-color: var(--lightgreen);
}

.save-confirm-btn {
  background: var(--lightgreen);
  color: #fff;
  border: none;
  border-radius: 4px;
  padding: 2px 8px;
  cursor: pointer;
  font-size: 12px;
  font-weight: 700;
}

.save-confirm-btn:hover {
  background: var(--darkgreen);
}

.save-cancel-btn {
  background: var(--border);
  color: var(--ink);
  border: none;
  border-radius: 4px;
  padding: 2px 6px;
  cursor: pointer;
  font-size: 12px;
}

.save-cancel-btn:hover {
  background: var(--border-strong);
}

/* State Tracker AI date-window picker — keep react-dates calendar green
 * (never the library's default teal). The input border is already themed in
 * custom.css; this covers the selected/hovered day cells and the in-range span. */
.custom-daterange .CalendarDay__selected,
.custom-daterange .CalendarDay__selected:active,
.custom-daterange .CalendarDay__selected:hover,
.custom-daterange .CalendarDay__selected_start,
.custom-daterange .CalendarDay__selected_end {
  background: #0b6623 !important;
  border-color: #0b6623 !important;
  color: #fff !important;
}

.custom-daterange .CalendarDay__selected_span,
.custom-daterange .CalendarDay__hovered_span,
.custom-daterange .CalendarDay__hovered_span:hover {
  background: #B7E2B6 !important;
  border-color: #94D491 !important;
  color: #0b6623 !important;
}

/* Single-date pickers (DatePickerSingle) — same rule set as the range picker so
 * the selected/hovered day cell reads Element green, never react-dates' default
 * teal/blue (.claude/rules/dash-ui.md: no Bootstrap blue). Applied via className=
 * "custom-datepicker" on every DatePickerSingle. No __selected_span here — a
 * single picker has no in-range span. */
.custom-datepicker .CalendarDay__selected,
.custom-datepicker .CalendarDay__selected:active,
.custom-datepicker .CalendarDay__selected:hover {
  background: #0b6623 !important;
  border-color: #0b6623 !important;
  color: #fff !important;
}

.custom-datepicker .CalendarDay__hovered,
.custom-datepicker .CalendarDay:hover:not(.CalendarDay__selected) {
  background: #B7E2B6 !important;
  border-color: #94D491 !important;
  color: #0b6623 !important;
}

/* ===== Micro-shorts fit-window range slider ===== */
/* Hide the raw Unix-timestamp tooltip that appears during drag; the formatted
   date range is shown in the ins-ms-range-label div below the slider. */
.ms-range-slider .rc-slider-tooltip {
  display: none !important;
}

/* SOH cell rail chips — clickable html.Div (role="button") drives the trend
   highlight + OCV fit; needs its own visible keyboard-focus ring since a div
   gets none by default. Activation (Enter/Space) is delegated in
   keyboard.js. */
.ins-soh-cell-chip:focus-visible {
  outline: 2px solid var(--lightgreen);
  outline-offset: 1px;
}

/* ===== Agent Logs accordion (Capabilities modal "Logs" tab) ===== */
/* Re-theme dbc.Accordion from Bootstrap blue to the Element palette. */
.agent-logs-accordion .accordion-item {
  border: 1px solid rgba(11, 102, 35, 0.14);
  border-radius: 10px;
  margin-bottom: 8px;
  overflow: hidden;
}

.agent-logs-accordion .accordion-button {
  background: #fff;
  color: #1f2d3d;
  font-family: inherit;
  font-size: 12.5px;
  padding: 10px 14px;
  box-shadow: none;
}

.agent-logs-accordion .accordion-button:not(.collapsed) {
  background: #d0f2d0;
  /* PALEGREEN_BG */
  color: #0b6623;
  /* DARKGREEN */
  box-shadow: inset 0 -1px 0 rgba(11, 102, 35, 0.14);
}

.agent-logs-accordion .accordion-button:focus {
  border-color: #4CB748;
  /* LIGHTGREEN */
  box-shadow: 0 0 0 3px rgba(76, 183, 72, 0.25);
}

/* Default caret is a blue SVG chevron — recolor to dark green. */
.agent-logs-accordion .accordion-button::after {
  background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%230b6623'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");
}

.agent-logs-accordion .accordion-button:not(.collapsed)::after {
  background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%230b6623'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");
}

.agent-logs-accordion .accordion-body {
  padding: 14px 16px;
  background: #fff;
}

/* ── Capabilities modal — "Cards" tab ─────────────────────────────────────
 * Netflix-style script-card catalog (rail.py::_cards_tab_content, EL-642):
 * per-category horizontal carousels (.cards-tab-row) of 2:3 poster tiles
 * (.cards-tab-tile), and an inspect panel (.cards-tab-detail) a tile click
 * opens under its row. Decoration only — the carousel, tile and panel sizing
 * are inline styles in rail.py. Semantic tokens only — but in Dash the
 * modal portals to <body>, outside #app-shell[data-theme='dark'], so it
 * renders light in dark mode like the rest of the Capabilities modal. /v2
 * renders it inside the themed shell and picks up the dark tokens.
 * .cards-tab-summary / -details / -cmd-label are the pre-EL-642 <details>
 * card's classes, kept as aliases for anything still emitting them. */
.cards-tab-category {
  font-size: 13px;
  font-weight: 800;
  color: var(--accent-ink);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  margin: 14px 0 10px;
}

.cards-tab-row {
  scrollbar-width: thin;
  scrollbar-color: var(--accent-border) transparent;
}

.cards-tab-card {
  background: var(--card-bg);
  border: 1px solid var(--border);
  border-radius: 10px;
  overflow: hidden;
  color: var(--ink);
  font-family: inherit;
  cursor: pointer;
  transition: box-shadow 0.15s ease, border-color 0.15s ease, transform 0.15s ease;
}

.cards-tab-card:hover {
  border-color: var(--accent);
  box-shadow: var(--shadow-md);
  transform: translateY(-2px);
}

.cards-tab-card:focus-visible {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--focus-ring);
}

/* A double-click launches the tile (EL-691) — don't select its text first. */
.cards-tab-tile {
  user-select: none;
}

.cards-tab-card[open],
.cards-tab-tile--active {
  border-color: var(--accent-ink);
  box-shadow: 0 0 0 2px var(--focus-ring);
}

.cards-tab-summary {
  cursor: pointer;
  display: block;
  list-style: none;
  /* hide the default disclosure triangle */
}

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

.cards-tab-preview {
  border-bottom: 1px solid var(--divider);
  background: var(--bg-soft);
}

.cards-tab-title {
  font-weight: 800;
  font-size: 12.5px;
  line-height: 1.3;
  color: var(--ink);
}

.cards-tab-blurb {
  font-size: 11px;
  line-height: 1.4;
  color: var(--muted);
}

.cards-tab-badge {
  display: inline-block;
  font-size: 10.5px;
  font-weight: 700;
  line-height: 1.4;
  color: var(--accent-ink);
  background: var(--accent-bg);
  border: 1px solid var(--accent-border);
  border-radius: 999px;
  padding: 1px 7px;
}

.cards-tab-detail {
  background: var(--card-bg-soft);
  border: 1px solid var(--accent-border);
  border-radius: 12px;
  padding: 14px;
  margin-top: 6px;
}

.cards-tab-detail-preview {
  border-radius: 8px;
  border: 1px solid var(--border);
}

.cards-tab-detail-title {
  font-size: 15px;
  font-weight: 800;
  color: var(--ink);
  margin: 0 0 6px;
}

.cards-tab-details {
  padding: 0 12px 12px;
}

.cards-tab-desc {
  font-size: 12px;
  color: var(--ink-soft);
  line-height: 1.5;
  margin: 0 0 8px;
}

.cards-tab-fact {
  font-size: 12px;
  color: var(--ink-soft);
  line-height: 1.6;
}

.cards-tab-fact-label {
  font-weight: 700;
  color: var(--muted);
}

.cards-tab-cmd-label {
  font-size: 11.5px;
  font-weight: 700;
  color: var(--accent-ink);
}

.cards-tab-cmd {
  font-family: "JetBrains Mono", SFMono-Regular, Menlo, Monaco, Consolas, monospace;
  font-size: 11.5px;
  color: var(--accent-ink);
  background: var(--accent-bg);
  border-radius: 6px;
  padding: 2px 6px;
  user-select: all;
  /* one click selects the whole command for copying */
}

.cards-tab-copy {
  color: var(--muted);
}

.cards-tab-copy:hover {
  color: var(--accent-ink);
}

.cards-tab-launch {
  font-family: inherit;
  font-size: 12.5px;
  font-weight: 700;
  color: var(--btn-accent-ink);
  background: var(--btn-accent-bg);
  border: 0;
  border-radius: 8px;
  padding: 6px 16px;
  cursor: pointer;
}

.cards-tab-launch:hover:not(:disabled) {
  background: var(--btn-accent-bg-hover);
}

.cards-tab-launch:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px var(--focus-ring);
}

.cards-tab-launch:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}

/* ── Capabilities modal — "Skills" tab ────────────────────────────────────
 * Read-only skill rows (rail.py::_skill_row / _skill_category_section), each
 * a native <details> mirroring the Cards tab pattern above. Without these
 * rules the rows had NO styling at all: <summary>'s default `list-item`
 * display left the icon/title/desc stacking onto separate lines behind the
 * browser's raw (unthemed, often blue) disclosure triangle. */
.skill-details {
  border-bottom: 1px solid #e3e6f0;
}

.skill-details--last {
  border-bottom: 0;
}

.skill-summary {
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: 8px;
  list-style: none;
  padding: 8px 10px;
  transition: background 0.15s ease;
}

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

.skill-summary:hover {
  background: #f5f9f5;
}

.skill-summary:focus-visible {
  outline: 2px solid #4cb748;
  outline-offset: -2px;
}

/* Green chevron replaces the default marker, same SVG as the Logs accordion. */
.skill-summary::after {
  content: "";
  width: 16px;
  height: 16px;
  margin-left: auto;
  flex-shrink: 0;
  background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%230b6623'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");
  background-repeat: no-repeat;
  background-position: center;
  transition: transform 0.15s ease;
}

.skill-details[open] .skill-summary::after {
  transform: rotate(180deg);
}

.skill-detail-body {
  padding: 0 10px 10px 38px;
}

/* ───────────────────────────────────────────────────────────────────────
   Left toolbar — narrow icon strip controlling the main left sidebar.
   Structural sizing (width, layout) is set inline in left_toolbar.py;
   these rules are decoration only (colour, hover, active state).
   ─────────────────────────────────────────────────────────────────────── */
/* .toolbar-icon-btn / --active / .toolbar-divider are the LEFT TOOLBAR block
   in ella-frame.css now. Only the focus ring stays here: the component layer
   does not define one, and a keyboard user needs it in both themes. */
.toolbar-icon-btn:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

/* ── File-system viewer ─────────────────────────────────────────────────── */
.fsv-details>.fsv-summary {
  list-style: none;
}

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

.fsv-summary {
  border-radius: 5px;
}

/* Rotate the caret glyph when its folder is open. */
.fsv-details[open]>.fsv-summary .fsv-caret {
  transform: rotate(90deg);
  display: inline-block;
}

.fsv-caret {
  transition: transform 0.1s ease;
}

/* Collapsible Claude Code Execution Report bubble — reuses the fsv custom-caret
   pattern so it matches every other disclosure widget in the app (no native OS
   triangle). */
.claude-ella-log-summary {
  list-style: none;
  border-radius: 6px;
  padding: 2px 4px;
}

.claude-ella-log-summary::-webkit-details-marker {
  display: none;
}

.claude-ella-log-summary:hover {
  background: #f0f7f0;
}

.claude-ella-log-caret {
  transition: transform 0.1s ease;
  display: inline-block;
}

details[open]>.claude-ella-log-summary .claude-ella-log-caret {
  transform: rotate(90deg);
}

/* Slim, themed scrollbar for the tree. */
.fsv-tree::-webkit-scrollbar {
  width: 8px;
  height: 8px;
}

.fsv-tree::-webkit-scrollbar-thumb {
  background: #d0e6cf;
  border-radius: 4px;
}

/* ── File viewer modal — read-only source with a line-number gutter ──────── */
.fv-modal-content {
  border: 0;
  border-radius: 14px;
  overflow: hidden;
}

/* Clickable file rows in the tree — see the FILES VIEW block in
   ella-frame.css. The rules that were here painted #f0f7f0/#0b6623 on hover
   via a `span` descendant selector, which outranks .fsv-file-name and so
   survived the theme flip as dark green on a dark row. */

/* Code surface: gutter + source share a horizontal scroll container. */
.fv-code-wrap {
  display: flex;
  align-items: stretch;
  background: #fbfdfb;
  max-height: 70vh;
  overflow: auto;
  margin: 0;
}

.fv-gutter {
  margin: 0;
  padding: 14px 10px 14px 16px;
  text-align: right;
  color: #b3bccb;
  background: #f3f7f3;
  border-right: 1px solid #e3e6f0;
  user-select: none;
  font-family: "JetBrains Mono", SFMono-Regular, Menlo, Monaco, Consolas, monospace;
  font-size: 12.5px;
  line-height: 1.6;
  white-space: pre;
  flex-shrink: 0;
}

.fv-code {
  margin: 0;
  padding: 14px 18px;
  color: #1f2d3d;
  font-family: "JetBrains Mono", SFMono-Regular, Menlo, Monaco, Consolas, monospace;
  font-size: 12.5px;
  line-height: 1.6;
  white-space: pre;
  flex: 1;
  min-width: 0;
}

.fv-markdown {
  padding: 18px 22px;
  max-height: 70vh;
  overflow: auto;
}

/* Rendered/Raw segmented toggle — now owned entirely by ella-frame.css's
   token-based .fv-seg/.fv-seg-btn rules (dark-mode migration); this hardcoded
   light-only block was left behind duplicating (and, via a since-removed
   .fv-seg--on variant, briefly breaking) that migration. */

/* Themed dropdown for the root selector — keep it on-brand. */
.fsv-root-dropdown .Select-control,
.fsv-root-dropdown .is-focused .Select-control {
  border-color: #B7E2B6 !important;
  box-shadow: none !important;
}

/* Themed dropdown for <questions> card ui_type: "dropdown" pickers. */
.ella-q-dropdown .Select-control,
.ella-q-dropdown .is-focused .Select-control {
  border-color: #B7E2B6 !important;
  box-shadow: none !important;
}

.ella-q-dropdown .Select-option.is-selected,
.ella-q-dropdown .Select-option.is-focused {
  background: #d0f2d0 !important;
  color: #0b6623 !important;
}

/* ── dcc.Dropdown, Dash 4.x ──────────────────────────────────────────────
   Dash 4 replaced the bundled react-select with its own component and renamed
   the entire DOM: `.Select-control` / `.Select-menu-outer` / `.Select-option`
   are now `.dash-dropdown-trigger` / `.dash-options-list` /
   `.dash-options-list-option`. Every legacy `.Select-*` rule in this file (and
   the ones in the dark block below) matches NOTHING on Dash 4.1 — which is why
   the model picker opened as a white panel with near-black text and a
   Bootstrap-blue selected row in dark mode, with no rule to point at.

   Rules below are token-driven so they hold in BOTH themes; the legacy
   `.Select-*` blocks are kept only for a Dash-3 rollback and are inert here.
   If a dropdown ever goes white again, check the class names FIRST. */
.dash-dropdown {
  background: transparent;
  color: var(--ink);
}

.dash-dropdown-trigger,
.dash-dropdown-wrapper {
  background: var(--card-bg);
  color: var(--ink);
  border-color: var(--border-strong);
}

.dash-dropdown-value-item,
.dash-dropdown-value {
  color: var(--ink);
}

.dash-dropdown-trigger-icon {
  color: var(--muted);
}

/* The open menu is a floating surface — it must use the overlay token, not
   inherit whatever is behind it. */
.dash-options-list,
.dash-dropdown-options,
.dash-dropdown-content {
  background: var(--overlay-bg) !important;
  border: 1px solid var(--border) !important;
  color: var(--ink) !important;
  box-shadow: var(--shadow-md);
}

.dash-options-list-option,
.dash-dropdown-option {
  color: var(--ink) !important;
  background: transparent !important;
}

.dash-options-list-option:hover,
.dash-dropdown-option:hover,
.dash-options-list-option.selected,
.dash-dropdown-option.selected {
  background: var(--accent-bg) !important;
  color: var(--accent-ink-strong) !important;
}

/* A disabled option (the "… N more — refine your search" hint in the node
   pickers) must not light up like a choice under the hover rule above. */
.dash-options-list-option:has(input:disabled) {
  background: transparent !important;
  color: var(--muted-2) !important;
  font-style: italic;
  cursor: default !important;
}

.dash-dropdown:focus-within .dash-dropdown-trigger,
.dash-dropdown:focus-within .dash-dropdown-wrapper {
  border-color: var(--accent-border) !important;
  box-shadow: 0 0 0 3px var(--focus-ring) !important;
  outline: none !important;
}

/* Dash 4.1's own `.dash-dropdown:focus` border/outline reads
   --Dash-Fill-Interactive-Strong, which custom.css re-points only for the
   slider/datepicker — so a focused dropdown drew Dash's default PURPLE ring
   (#7F4BC4) around the trigger above (EL-665 UI review). Re-point it here. */
.dash-dropdown,
.dash-dropdown-content {
  --Dash-Fill-Interactive-Strong: var(--lightgreen);
  --Dash-Fill-Interactive-Weak: var(--focus-ring);
}
.dash-dropdown:focus {
  border-color: var(--accent-border) !important;
  outline: none !important;
  box-shadow: 0 0 0 3px var(--focus-ring) !important;
}

/* LEGACY (Dash 3 react-select). Inert on Dash 4.x — see the note above.
   Themed dropdown for the sidebar/account model selector (#model-select,
   #acct-sb-model) — grew from 4 to 9 options (core/agent/streaming.py
   MODEL_CATALOG), making the default Bootstrap-blue focus ring / selected-row
   highlight far more visible on the taller open menu. */
.ella-model-select .Select-control,
.ella-model-select .is-focused .Select-control,
.ella-model-select .is-open .Select-control {
  border-color: #B7E2B6 !important;
  box-shadow: none !important;
}

.ella-model-select .Select-option.is-selected,
.ella-model-select .Select-option.is-focused {
  background: #d0f2d0 !important;
  color: #0b6623 !important;
}

/* Editable combo box for <questions> card ui_type: "combobox" — a text input
   backed by a native <datalist> (pick an existing value OR type a new one, e.g.
   a git branch that doesn't exist yet). Chrome shows no affordance for a
   datalist, so a dark-green caret hints that suggestions exist. !important
   because the component's inline `background` shorthand would otherwise clear
   the caret image. Known, accepted tradeoffs of the native control: the OPEN
   suggestion popup is browser chrome and cannot be themed by CSS at all, and
   Firefox draws its own arrow next to ours. Don't chase either with more CSS —
   replacing the datalist with a custom clientside-filtered list is the only
   fix, and it isn't worth it for this card. */
.ella-q-combobox {
  padding-right: 26px !important;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' fill='none' stroke='%230b6623' stroke-width='1.6' stroke-linecap='round'/%3E%3C/svg%3E") !important;
  background-repeat: no-repeat !important;
  background-position: right 9px center !important;
}

.ella-q-combobox:focus {
  border-color: #4CB748 !important;
  box-shadow: 0 0 0 2px rgba(183, 226, 182, 0.55) !important;
}

/* Live "Executing plan" checklist — the active (in-flight) step's icon pulses
   so the user can see which step ELLA is currently drafting/executing. */
@keyframes execStepPulse {

  0%,
  100% {
    opacity: 1;
  }

  50% {
    opacity: 0.35;
  }
}

.exec-step-icon-active {
  display: inline-block;
  animation: execStepPulse 1s ease-in-out infinite;
}

/* Composer icon buttons (mic, ...) — decoration only; structural sizing for
   each button stays inline per element-dash-ui. */
.composer-btn {
  color: var(--muted);
}

.composer-btn:hover,
.composer-btn:focus-visible {
  background: var(--bg-soft);
  color: var(--ink);
}

.composer-btn:focus-visible {
  outline: 2px solid var(--lightgreen);
  outline-offset: 1px;
}

/* Thinking toggle in the composer rail: the ON state is a filled accent chip
   with a dot, because the button is icon-only and colour alone must not be
   the only carrier of state. */
#btn-thinking-toggle {
  position: relative;
}

#btn-thinking-toggle[aria-pressed="true"]::after {
  content: "";
  position: absolute;
  right: 3px;
  top: 3px;
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--accent);
}

#btn-thinking-toggle:hover {
  background: var(--bg-soft);
}

#btn-thinking-toggle:focus-visible {
  outline: 2px solid var(--lightgreen);
  outline-offset: 1px;
  background: var(--bg-soft);
}

/* Composer keyboard hints — Enter/Shift-Enter and the "/" vocabulary were
   bound but never surfaced. */
.ella-composer-hint kbd {
  font-family: "JetBrains Mono", ui-monospace, monospace;
  font-size: 10px;
  border: 1px solid var(--border);
  border-radius: 4px;
  padding: 0 4px;
  color: var(--muted);
  /* Bootstrap's own `kbd { background: #212529; color: #fff }` was still
     winning here: this rule re-pointed the border and the text but never the
     surface, so the three hints rendered as solid black chips on the light
     composer. box-shadow clears Bootstrap's inset highlight with it. */
  background: var(--chip-bg);
  box-shadow: none;
}

/* Mic button while voice dictation is recording (speech-active-store, driven
   by speech.js + the clientside_callback in chat_core.py). */
@keyframes micPulse {

  0%,
  100% {
    opacity: 1;
  }

  50% {
    opacity: 0.4;
  }
}

.composer-btn.mic-active {
  color: var(--rec-red);
  animation: micPulse 1.4s ease-in-out infinite;
}

/* Navbar unread badge — small red-orange count dot on the 🔔 bell when ELLA has
   posted escalated decisions or learning acks the user hasn't seen. */
.nav-unread-badge {
  /* Geometry only — colour and placement come from ella-frame.css, where the
     badge is a dot on the bell rather than a counted pill. */
  font-size: 10px;
  font-weight: 800;
  line-height: 16px;
  text-align: center;
  align-items: center;
  justify-content: center;
  box-shadow: 0 0 0 2px #fff;
}

/* Notifications popover (navbar bell) — Element-green chrome, never Bootstrap
   blue/grey. Targets the dbc.Popover wrapper id="notifications-popover". */
#notifications-popover .popover,
#notifications-popover.popover {
  border: 1.5px solid #0b6623;
  border-radius: 10px;
}

.notif-item {
  border-radius: 6px;
  transition: background 0.12s ease;
}

.notif-item:hover {
  background: #d0f2d0;
  /* PALEGREEN_BG */
}

/* Collaborative-share reply box: green focus ring on the Send button (no
   default browser/Bootstrap blue outline). */
#share-collab-send-btn:focus {
  outline: none;
  box-shadow: 0 0 0 2px #B7E2B6;
}

/* Collapsible long code blocks — a 100+-line Claude Code instruction file is
   clamped to a preview height with a "Show more" toggle so it doesn't bury the
   rest of the thread. Applied client-side by copy_code.js (addCollapse). */
.bubble-prose pre.code-collapsed,
.custom-code-block.code-collapsed {
  max-height: 460px;
  overflow: hidden;
  position: relative;
}

/* Fade the cut edge so it reads as "more below". */
.bubble-prose pre.code-collapsed::after,
.custom-code-block.code-collapsed::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 64px;
  pointer-events: none;
  background: linear-gradient(to bottom, rgba(255, 255, 255, 0), var(--bg, #f5f7f5));
}

/* Dark custom-code blocks fade to their own dark surface, not the page bg. */
.custom-code-block.code-collapsed::after {
  background: linear-gradient(to bottom, rgba(8, 30, 16, 0), #0b1f12);
}

.expand-btn {
  position: absolute;
  bottom: 8px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 3;
  font-family: Nunito, sans-serif;
  font-size: 11.5px;
  font-weight: 700;
  letter-spacing: 0.02em;
  padding: 3px 14px;
  border-radius: 999px;
  border: 1px solid var(--lightgreen, #4cb748);
  background: var(--pale-green-bg, #d0f2d0);
  color: var(--darkgreen, #0b6623);
  cursor: pointer;
  box-shadow: 0 1px 4px rgba(11, 102, 35, 0.18);
  transition: background 0.12s, color 0.12s;
}

.expand-btn:hover {
  background: var(--lightgreen, #4cb748);
  color: #fff;
}

/* When expanded, the toggle sits in normal flow at the end (no overlap/fade). */
.code-expanded .expand-btn {
  position: static;
  transform: none;
  display: block;
  margin: 8px auto 0;
}

/* ── Conversation sharing ─────────────────────────────────────────────── */
/* THE share button in a sidebar conversation row — hover-revealed, Element
   green. Singular now: the row used to carry this plus a separate
   `.conv-copy-link-btn`, and two link glyphs on a 300px row could not explain
   which one you wanted. Both links live in the share modal instead.

   `.cv-row`, not `.conv-entry`: the sidebar row was rebuilt around the
   .cv-row / .cv-row-text markup. The old selector matched nothing, which left
   the share button stuck at opacity 0 — present, focusable, and invisible.

   `visibility` rides along with `opacity` so the hidden button is not a
   click target over the row's own load-conversation area. */
.conv-share-btn {
  background: none;
  border: none;
  color: var(--accent-ink);
  padding: 2px 4px;
  flex-shrink: 0;
  visibility: hidden;
  opacity: 0;
  transition: opacity 0.15s;
  cursor: pointer;
  font-size: 12px;
  line-height: 1;
  display: grid;
  place-items: center;
}

.cv-row:hover .conv-share-btn,
.conv-share-btn:focus {
  visibility: visible;
  opacity: 1;
}

.conv-share-btn:hover {
  color: var(--accent);
}

/* Keyboard focus: Element-green ring, never the browser/Bootstrap blue outline. */
.conv-share-btn:focus-visible {
  outline: none;
  border-radius: 4px;
  box-shadow: 0 0 0 2px var(--accent-border);
  visibility: visible;
  opacity: 1;
}

/* Session view's "Compress" button — a plain dbc.Button, so Bootstrap paints
   it blue unless overridden. .btn-ghost (ella-frame.css) gives it its resting
   look; these are the states a class alone did not cover, and they must stay
   token-driven so the button does not hover to pale-green-on-dark-green in
   dark mode (.claude/rules/dash-ui.md, no Bootstrap blue ever). */
#btn-compress-session:hover {
  background: var(--accent-bg) !important;
  color: var(--accent-ink) !important;
  border-color: var(--accent-border) !important;
}

#btn-compress-session:focus,
#btn-compress-session:focus-visible {
  outline: none;
  box-shadow: 0 0 0 2px var(--accent-border) !important;
}

/* Banner shown atop a read-only shared-conversation view */
.shared-view-banner {
  background: #d0f2d0;
  border-left: 3px solid #0b6623;
  padding: 8px 14px;
  font-size: 13px;
  color: #0b6623;
  margin-bottom: 8px;
  border-radius: 6px;
}

.shared-view-banner a {
  color: #0b6623;
  font-weight: 700;
  text-decoration: underline;
  cursor: pointer;
}

/* Centered muted status line for a collaborative-share join/system event */
.collab-system-notice {
  text-align: center;
  font-size: 12px;
  color: var(--muted);
  margin: 10px 0;
}

/* Notice shown in place of the thread when a ?conv_id= link resolves to no
   saved history (unknown id, or still syncing) instead of a blank greeting */
.url-restore-notice {
  background: var(--warn-bg);
  border-left: 3px solid var(--warn-border);
  padding: 8px 14px;
  font-size: 13px;
  color: var(--warn-ink);
  margin-bottom: 8px;
  border-radius: 6px;
}

/* ─── AG Grid — Element ("Cards by ELLA") theme ──────────────────────────────
 * Reusable green skin for dash-ag-grid. Applied via the `ella-ag-theme` class
 * on top of `ag-theme-quartz` (see script_framework/aggrid.py). All overrides go
 * through AG Grid's `--ag-*` CSS variables so we never fight its internals —
 * headers/selection/hover/focus read Element green, never AG Grid's stock blue.
 * Keep this the single source of AG Grid styling; new grids just add the class. */
.ella-ag-theme {
  --ag-font-family: "Nunito", sans-serif;
  --ag-font-size: 12px;
  --ag-foreground-color: #1f2d3d;
  --ag-border-color: #e3e6f0;
  --ag-row-border-color: #eef0f5;
  --ag-secondary-border-color: #eef0f5;
  --ag-borders: solid 1px;
  --ag-border-radius: 8px;
  /* Header: pale-green surface, dark-green text — matches table-card headers. */
  --ag-header-background-color: #d0f2d0;
  --ag-header-foreground-color: #0b6623;
  --ag-header-column-resize-handle-color: #4cb748;
  /* Interactive accents: light-green fills, pale-green selection. */
  --ag-active-color: #4cb748;
  --ag-selected-row-background-color: rgba(183, 226, 182, 0.45);
  --ag-row-hover-color: rgba(208, 242, 208, 0.5);
  --ag-range-selection-border-color: #4cb748;
  --ag-range-selection-background-color: rgba(76, 183, 72, 0.12);
  --ag-input-focus-border-color: #4cb748;
  --ag-checkbox-checked-color: #0b6623;
  --ag-alpine-active-color: #4cb748;
}

/* Filter/menu icons and sort indicators in dark green, not the default grey-blue. */
.ella-ag-theme .ag-header-cell-menu-button .ag-icon,
.ella-ag-theme .ag-icon-filter,
.ella-ag-theme .ag-sort-indicator-icon .ag-icon {
  color: #0b6623;
}

/* Pagination footer accents. */
.ella-ag-theme .ag-paging-panel {
  color: #4a5568;
}


/* ── Feedback thumbs — CSS-masked SVG icons (NO emoji) ───────────────────────
   Recurring Feedback 1 ("👍 looks selected by default") was caused by the
   browser painting a multicolour emoji glyph that CSS could not reliably
   flatten. The deterministic, browser-independent fix: draw the thumb as a
   solid block masked into a thumb silhouette and fill it with `currentColor`,
   so the icon's colour is governed ENTIRELY by the button's inline `color`
   (grey `#b2bac4` when off, DARKGREEN `#0b6623` when on — see
   `_THUMB_BTN_INLINE`/`_THUMB_BTN_SELECTED` in clients/dash/messages.py). There
   is no emoji font in play, so "off" can never render in a thumb's own colours. */
.ella-thumb-glyph {
  display: inline-block;
  width: 15px;
  height: 15px;
  vertical-align: middle;
  background-color: currentColor;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-position: center;
  mask-position: center;
  -webkit-mask-size: contain;
  mask-size: contain;
}

.ella-thumb-glyph-up {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M1 21h4V9H1v12zm22-11c0-1.1-.9-2-2-2h-6.31l.95-4.57.03-.32c0-.41-.17-.79-.44-1.06L14.17 1 7.59 7.59C7.22 7.95 7 8.45 7 9v10c0 1.1.9 2 2 2h9c.83 0 1.54-.5 1.84-1.22l3.02-7.05c.09-.23.14-.47.14-.73v-2z'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M1 21h4V9H1v12zm22-11c0-1.1-.9-2-2-2h-6.31l.95-4.57.03-.32c0-.41-.17-.79-.44-1.06L14.17 1 7.59 7.59C7.22 7.95 7 8.45 7 9v10c0 1.1.9 2 2 2h9c.83 0 1.54-.5 1.84-1.22l3.02-7.05c.09-.23.14-.47.14-.73v-2z'/%3E%3C/svg%3E");
}

.ella-thumb-glyph-down {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M15 3H6c-.83 0-1.54.5-1.84 1.22l-3.02 7.05c-.09.23-.14.47-.14.73v2c0 1.1.9 2 2 2h6.31l-.95 4.57-.03.32c0 .41.17.79.44 1.06L9.83 23l6.59-6.59c.36-.36.58-.86.58-1.41V5c0-1.1-.9-2-2-2zm4 0v12h4V3h-4z'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M15 3H6c-.83 0-1.54.5-1.84 1.22l-3.02 7.05c-.09.23-.14.47-.14.73v2c0 1.1.9 2 2 2h6.31l-.95 4.57-.03.32c0 .41.17.79.44 1.06L9.83 23l6.59-6.59c.36-.36.58-.86.58-1.41V5c0-1.1-.9-2-2-2zm4 0v12h4V3h-4z'/%3E%3C/svg%3E");
}

/* ── "Copy response" icon button in a message's who-row — hover/focus
   decoration only; structural sizing/colour stays inline in `render_message`
   (clients/dash/messages.py) per the sizing-inline convention. Mirrors
   `.copy-btn`'s hover treatment (same green pill family) so it hover-fills
   like the fenced-code-block Copy button it visually pairs with. */
.ella-copy-msg-btn:hover {
  opacity: 1;
  background: var(--lightgreen) !important;
  color: #fff !important;
}
.ella-copy-msg-btn:focus-visible {
  outline: 2px solid var(--lightgreen, #4cb748);
  outline-offset: 2px;
  border-radius: 6px;
}

/* ── Generated-image "Download"/"Copy image" action bar (messages.py::
   _image_action_bar) — structural sizing/colour stays inline (btn_style,
   shared by both); these carried no className at all, so hover gave zero
   feedback and Tab-focusing either button showed the browser's native BLUE
   ring (.claude/rules/dash-ui.md), same gap already fixed above for `.ella-copy-msg-btn`
   and `.ella-script-btn`. */
.ella-img-copy:hover,
.ella-img-download:hover {
  background: var(--pale-green-bg, #d0f2d0) !important;
  border-color: var(--lightgreen, #4cb748) !important;
}
.ella-img-copy:focus-visible,
.ella-img-download:focus-visible {
  outline: 2px solid var(--lightgreen, #4cb748);
  outline-offset: 2px;
}

/* ── "Continue generating" button (truncated MAX_TOKENS response) and "Try
   again" button (transient network-error response) — both share this class;
   hover/focus decoration only, structural sizing/colour stays inline in
   `_continue_button_row` / `_retry_button_row` (clients/dash/messages.py) per
   the sizing-inline convention. Element green throughout, never Bootstrap blue. */
.ella-continue-btn:hover {
  background: var(--darkgreen-deep, #084a19) !important;
}
.ella-continue-btn:focus-visible {
  outline: 2px solid var(--lightgreen, #4cb748);
  outline-offset: 2px;
}

/* ── User file-attachment chip glyph — same CSS-masked SVG approach as the
   thumbs above (no emoji font in play). Paperclip icon, filled via
   currentColor so `user_attachment_chip` (clients/dash/messages.py) controls
   colour purely via inline style. */
.ella-attachment-glyph {
  display: inline-block;
  width: 12px;
  height: 12px;
  vertical-align: middle;
  background-color: currentColor;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-position: center;
  mask-position: center;
  -webkit-mask-size: contain;
  mask-size: contain;
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M16.5 6v11.5c0 2.21-1.79 4-4 4s-4-1.79-4-4V5c0-1.38 1.12-2.5 2.5-2.5S13.5 3.62 13.5 5v10.5c0 .55-.45 1-1 1s-1-.45-1-1V6H10v9.5c0 1.38 1.12 2.5 2.5 2.5s2.5-1.12 2.5-2.5V5c0-2.21-1.79-4-4-4S7 2.79 7 5v12.5c0 3.04 2.46 5.5 5.5 5.5s5.5-2.46 5.5-5.5V6h-1.5z'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M16.5 6v11.5c0 2.21-1.79 4-4 4s-4-1.79-4-4V5c0-1.38 1.12-2.5 2.5-2.5S13.5 3.62 13.5 5v10.5c0 .55-.45 1-1 1s-1-.45-1-1V6H10v9.5c0 1.38 1.12 2.5 2.5 2.5s2.5-1.12 2.5-2.5V5c0-2.21-1.79-4-4-4S7 2.79 7 5v12.5c0 3.04 2.46 5.5 5.5 5.5s5.5-2.46 5.5-5.5V6h-1.5z'/%3E%3C/svg%3E");
}

/* ── script_framework/ui.py shared builders (run_btn, action_btn, param_field)
   ──────────────────────────────────────────────────────────────────────────
   Same gap as the Manual Changes tab below: these plain html.Button /
   dcc.Input builders are reused by every script card (bi_sox, bt2_soh_
   variability, ...) and carried no className, so Tab-focusing Run/Update
   Plot/any text field showed the browser's native BLUE focus ring
   (.claude/rules/dash-ui.md) instead of Element green. */
.ella-script-btn:hover {
  filter: brightness(0.92);
}
.ella-script-btn:focus-visible,
.ella-script-input:focus-visible {
  outline: 2px solid var(--lightgreen, #4cb748);
  outline-offset: 2px;
}
.ella-script-input:focus {
  outline: none;
  border-color: var(--lightgreen);
  box-shadow: 0 0 0 3px rgba(76, 183, 72, 0.18);
}

/* ── Manual Changes tab (SoXa Discrepancy Analyzer) ────────────────────────
   Plain dcc.Input / dcc.Textarea and html.Button carry no Element theming of
   their own: inline styles cannot express :focus or :hover, and there is no
   generic input:focus rule in this file. Without these, the reason-code inputs
   show the browser's native BLUE focus ring (.claude/rules/dash-ui.md) and none of the
   tab's buttons give any hover feedback. */
.mc-argon-input:focus {
  outline: none;
  border-color: var(--lightgreen);
  box-shadow: 0 0 0 3px rgba(76, 183, 72, 0.18);
}

.mc-btn-primary:hover {
  background: var(--darkgreen-deep) !important;
}

.mc-btn-ghost:hover {
  background: #f7f8f7 !important;
  border-color: #d4d8d4 !important;
}

.mc-btn-danger:hover {
  background: #e0952b !important;
}

/* Keyboard focus must be Element green, never the browser/Bootstrap blue ring —
   these classes had :hover but no :focus, so tabbing to one (e.g. the SoXa
   card's Confirm/Cancel on the missing-new-value prompt) leaked blue. */
.mc-btn-primary:focus-visible,
.mc-btn-ghost:focus-visible,
.mc-btn-danger:focus-visible {
  outline: 2px solid var(--lightgreen);
  outline-offset: 1px;
}

/* Override Bootstrap's default pink for inline code */
:root {
  --bs-code-color: #C97812 !important;
}

code {
  color: #C97812 !important;
}

/* Html blocks */
.hljs-section {
  color: #0b6623 !important;
  font-weight: bold;
}

.hljs-title,
.hljs-title.class_,
.hljs-title.class_.inherited__,
.hljs-title.function_ {
  color: #0b6623 !important;
}

.hljs-doctag,
.hljs-keyword,
.hljs-meta .hljs-keyword,
.hljs-template-tag,
.hljs-template-variable,
.hljs-type,
.hljs-variable.language_ {
  color: #4CB748 !important;
}

/* Lazy-tab / onboarding-modal loading spinners (rail.py, onboarding/modal.py):
   dbc.Spinner rejects both style= and className=, so `color="success"` was the
   only way to theme it and rendered Bootstrap's --bs-success (#198754), not
   Element green. spinner_class_name="ella-spinner" targets the actual
   spinner-border element instead (ELLA_INSIGHTS 2026-07-21 / 07-30). */
.ella-spinner {
  color: var(--darkgreen) !important;
}

/* Onboarding modal: while the lazy body placeholder (a lone Spinner) is
   #onboard-body-grid's only child, span both grid columns and centre it —
   otherwise it sits in column 1 of the "1fr 360px" grid, leaving a bare
   360px preview column until build_onboard_body fills both panes
   (ELLA_INSIGHTS 2026-07-21). :only-child means this never matches once the
   real two-pane body (form + preview) is built, so it can't disturb the
   finished layout. */
#onboard-body-grid > *:only-child {
  grid-column: 1 / -1;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 200px;
}

/* ── Standalone single-card view (/?standalone_card=…) ──────────────────────
   The /v2 Dash-fallback iframe's page background (clients/dash/standalone_card.py;
   structural padding stays inline per the styling rules). */
#standalone-card-host {
  background: var(--bg, #f5f7f5);
}

/* ========================================================================
   THEME-AWARE CHROME
   Third-party widgets (Bootstrap, react-select, dash_table) paint their own
   light backgrounds. These rules re-point them at the tokens. Add to the
   bottom of copilot.css.
   ======================================================================== */

/* dcc.Dropdown / react-select */
/* LEGACY (Dash 3 react-select) — inert on Dash 4.x, which renamed these to
   `.dash-dropdown-*`. The live rules are in the dropdown block above, and are
   token-driven for both themes rather than dark-only. */
#app-shell[data-theme='dark'] .Select-control,
#app-shell[data-theme='dark'] .Select-menu-outer {
  background: var(--card-bg) !important;
  border-color: var(--border-strong) !important;
  color: var(--ink) !important;
}

#app-shell[data-theme='dark'] .Select-value-label,
#app-shell[data-theme='dark'] .Select-placeholder,
#app-shell[data-theme='dark'] .Select-input>input {
  color: var(--ink) !important;
}

#app-shell[data-theme='dark'] .Select-option {
  background: var(--card-bg) !important;
  color: var(--ink-soft) !important;
}

#app-shell[data-theme='dark'] .Select-option.is-focused,
#app-shell[data-theme='dark'] .Select-option.is-selected {
  background: var(--accent-bg) !important;
  color: var(--accent-ink) !important;
}

/* dash_table. Cells only — the header keeps its inline DARKGREEN fill with
   white text, which is legible on both grounds and is the table's one piece of
   brand chrome. Including `th` here would flatten it to the card surface. */
#app-shell[data-theme='dark'] .dash-table-container .dash-spreadsheet-container .dash-spreadsheet-inner td {
  background-color: var(--card-bg) !important;
  color: var(--ink) !important;
  border-color: var(--border) !important;
}

#app-shell[data-theme='dark'] .dash-table-container .dash-spreadsheet-container .dash-spreadsheet-inner th {
  border-color: var(--border) !important;
}

/* Native form controls inherit the composer surface rather than UA white */
#app-shell[data-theme='dark'] textarea,
#app-shell[data-theme='dark'] input[type='text'],
#app-shell[data-theme='dark'] input[type='search'] {
  color: var(--ink);
  background-color: transparent;
}

/* …except a control that is a surface in its own right. The rule above is an
   id+attribute selector, so it outranks .sb-search input's `background:
   var(--card-bg)` and left the sidebar's hero search box with no fill at all
   in dark mode — an invisible input on the panel it sits in. Any future
   self-surfaced input belongs on this list, not in an inline style. */
#app-shell[data-theme='dark'] .sb-search input {
  background-color: var(--card-bg);
}

#app-shell[data-theme='dark'] ::placeholder {
  color: var(--muted-2);
  opacity: 1;
}

/* Scrollbars */
#app-shell[data-theme='dark'] #chat-thread::-webkit-scrollbar-thumb,
#app-shell[data-theme='dark'] #conversation-list::-webkit-scrollbar-thumb {
  background: var(--border-strong);
}

/* Anchors — defined for both themes so user-authored links in markdown never
   render UA blue. */
#app-shell a {
  color: var(--accent-ink);
  text-decoration: none;
}

#app-shell a:hover {
  color: var(--accent-ink-strong);
  text-decoration: underline;
}

/* Bootstrap alert variants already in copilot.css use raw hexes; re-point the
   two that appear on the chat surface. */
#app-shell[data-theme='dark'] .alert-success {
  background: var(--accent-bg) !important;
  border-color: var(--accent-border) !important;
  color: var(--accent-ink) !important;
}

#app-shell[data-theme='dark'] .alert-warning {
  background: var(--warn-bg) !important;
  border-color: var(--warn-border) !important;
  color: var(--warn-ink) !important;
}

/* The one place that must NOT invert: the greeting avatar and any brand
   imagery. Left untouched by design — noted here so nobody "fixes" it. */

/* Questions card Submit, once clicked (EL-596): muted, with a busy cursor, so
   the click visibly registered while the server starts the turn. */
button[id*="ella-q-submit"]:disabled {
  opacity: 0.6;
  /* !important: the button's inline style sets cursor: pointer. */
  cursor: progress !important;
}

/* ── Tool-call inspector (EL-708) ─────────────────────────────────────────
 * A finished tool row in the chat work trail (messages.py::_tool_inspector,
 * toolViz.tsx::ToolInspector) is a native <details>: the row itself is the
 * <summary>, so it reads exactly like a plain row until clicked open to show
 * the tool's logs and return value. Shared by both clients (clients/web links
 * this stylesheet). The raw disclosure triangle is replaced by a muted caret
 * after the mark. */
.ella-tool-inspect > .ella-tool-inspect__summary {
  cursor: pointer;
  list-style: none;
}

.ella-tool-inspect > .ella-tool-inspect__summary::-webkit-details-marker {
  display: none;
}

.ella-tool-inspect > .ella-tool-inspect__summary::after {
  content: "  ▸" / "";
  white-space: pre;
  color: var(--muted-2);
  font-size: 10px;
}

.ella-tool-inspect[open] > .ella-tool-inspect__summary::after {
  content: "  ▾" / "";
}

.ella-tool-inspect > .ella-tool-inspect__summary:hover {
  text-decoration: underline dotted var(--muted-2);
}

.ella-tool-inspect > .ella-tool-inspect__summary:focus-visible {
  outline: 2px solid var(--lightgreen);
  outline-offset: 1px;
  border-radius: 2px;
}
