/* ═══════════════════════════════════════════════════════════════════════════
   Dashboard Studio (panel-4)
   Layout: left chat column  |  right live preview column
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── panel root ─────────────────────────────────────────────────────────── */

/* NOTE: display is controlled by .step-panel / .step-panel.active in app.css.
   Setting display here with an ID selector would override those rules (ID
   specificity 100 > class specificity 10) and make the panel always visible. */
#panel-4 {
  flex-direction: column;
  overflow:   hidden;
  height:     100%;
}

/* ── split layout ────────────────────────────────────────────────────────── */

.builder-layout {
  display:         grid;
  /* dashboard canvas is the hero on the left; the AI agent chat is a focused
     rail on the right (Hex-style). Width is a variable, not a literal, so
     resize/collapse (below) only ever have to change ONE number — see
     _applyChatPrefs() in builder.js. */
  --chat-w-min: 300px;              /* keeps the composer + send button usable */
  --chat-w-max: min(560px, 45vw);   /* the vw term keeps the canvas usable on a small laptop */
  grid-template-columns: 1fr var(--chat-w, 372px);
  grid-template-rows: 100%;
  height:          100%;
  overflow:        hidden;
  position:        relative;   /* anchors the resizer */
}
/* pin both to row 1 — the chat is first in the DOM, so without an explicit row
   the preview would auto-flow into a second row instead of sitting left of it */
.builder-preview { grid-column: 1; grid-row: 1; }
.builder-chat    { grid-column: 2; grid-row: 1; }

/* RTL boards (theme.dir === 'rtl'): only the panel's PLACEMENT and its handle
   flip, driven by _dashDir() — the chat's own content stays app-direction, not
   mirrored (see _applyChatPrefs()). */
.builder-layout.chat-rtl {
  grid-template-columns: var(--chat-w, 372px) 1fr;
}
.builder-layout.chat-rtl .builder-chat    { grid-column: 1; }
.builder-layout.chat-rtl .builder-preview { grid-column: 2; }

/* ── collapse ────────────────────────────────────────────────────────────── */

/* The chat DOM is never removed on collapse — only visually clipped — so the
   transcript, its scroll position and any in-flight stream survive a re-open
   with no reload and no re-fetch (see docs/features/001, "Alternatives rejected"). */
.builder-layout.chat-collapsed {
  grid-template-columns: 1fr 0px;
}
.builder-layout.chat-collapsed.chat-rtl {
  grid-template-columns: 0px 1fr;
}
.builder-layout.chat-collapsed .builder-chat {
  overflow: hidden;
  border:   none;
}
.builder-layout.chat-collapsed .builder-chat-resizer { display: none; }

/* No chat column at all: the board is read-only for this person, so the
   preview takes the whole width rather than leaving a 372px gap. It has to
   undo the RTL placement above as well — that column holds a chat that is not
   rendered, and the preview would otherwise sit in an implicit second one.
   The collapse preference is written whether or not a chat exists, so every
   combination is listed and the block sits AFTER the collapse rules: a stored
   "collapsed" plus RTL is otherwise matched by the equally specific rule above
   and the board gets a 0px column. */
.builder-layout.no-chat,
.builder-layout.no-chat.chat-rtl,
.builder-layout.no-chat.chat-collapsed,
.builder-layout.no-chat.chat-collapsed.chat-rtl   { grid-template-columns: 1fr; }
.builder-layout.no-chat .builder-preview          { grid-column: 1; }

/* Small-screen-only backdrop for the expanded chat sheet (see the 960px query
   below); hidden by default so it isn't a stray clickable grid item above
   that breakpoint, where the chat is a normal column and no sheet exists. */
.chat-sheet-backdrop { display: none; }

.chat-collapse-btn {
  display:         flex;
  align-items:     center;
  justify-content: center;
  width:           26px;
  height:          26px;
  border-radius:   7px;
  border:          none;
  background:      none;
  color:           var(--muted);
  cursor:          pointer;
  transition:      color 0.12s, background 0.12s;
  flex-shrink:     0;
}
.chat-collapse-btn:hover { color: var(--accent); background: var(--accent-bg); }

/* ── resize ──────────────────────────────────────────────────────────────── */

/* Sits on the chat panel's inner edge. Pointer Events (not mouse-specific) so
   touch/stylus drag for free — see _initChatResizer() in builder.js. */
.builder-chat-resizer {
  position:    absolute;
  top:         0;
  bottom:      0;
  right:       calc(var(--chat-w, 372px) - 3px);
  width:       6px;
  cursor:      col-resize;
  z-index:     6;
  touch-action: none;
}
.builder-layout.chat-rtl .builder-chat-resizer {
  right: auto;
  left:  calc(var(--chat-w, 372px) - 3px);
}
.builder-chat-resizer:hover,
.builder-chat-resizer:active { background: color-mix(in srgb, var(--accent) 25%, transparent); }

/* During a drag: kill text selection and pin the cursor everywhere, not just
   over the 6px hit target, so a fast pointer that outruns the handle doesn't
   flicker back to the default cursor. */
body.resizing-chat {
  cursor:      col-resize !important;
  user-select: none !important;
}
body.resizing-chat .builder-chat-resizer { background: color-mix(in srgb, var(--accent) 35%, transparent); }

/* ── chat column ─────────────────────────────────────────────────────────── */

.builder-chat {
  display:         flex;
  flex-direction:  column;
  border-left:     1px solid var(--border);
  overflow:        hidden;
  background:      var(--surface);
}

.builder-chat-hdr {
  display:         flex;
  align-items:     center;
  justify-content: space-between;
  padding:         14px 18px 12px;
  border-bottom:   1px solid var(--border);
  flex-shrink:     0;
}

.builder-chat-hdr-left {
  display:     flex;
  align-items: center;
  gap:         8px;
  font-size:   13px;
  font-weight: 600;
  color:       var(--text);
}

.builder-chat-hdr-left svg {
  color: var(--accent);
  flex-shrink: 0;
}

.builder-chat-hdr-right {
  display:     flex;
  align-items: center;
  gap:         6px;
}

/* ── session picker ──────────────────────────────────────────────────────── */

.session-picker-wrap {
  position: relative;
}

.session-picker-trigger {
  display:     flex;
  align-items: center;
  gap:         5px;
  font-size:   12px;
}

/* Face, edge, corner, shadow and animation come from the dropdown system; this
   menu scrolls its own list and has a footer, so it keeps its padding at 0. */
.session-picker-dropdown {
  top:      calc(100% + 6px);
  right:    0;
  width:    270px;
  z-index:  200;
  padding:  0;
  overflow: hidden;
}

.session-dropdown-list {
  max-height: 300px;
  overflow-y: auto;
  padding:    6px;
}

/* Item padding, radius and hover come from the dropdown system; a session item
   stacks a title over its timestamp, so it overrides the row layout. */
.session-item {
  flex-direction: column;
  align-items:   flex-start;
  gap:           3px;
}
.session-item.active { background: color-mix(in srgb, var(--accent) 10%, transparent); }

.session-item-title {
  font-size:   13px;
  font-weight: 500;
  color:       var(--text);
  white-space: nowrap;
  overflow:    hidden;
  text-overflow: ellipsis;
}
.session-item.active .session-item-title { color: var(--accent); }

.session-item-meta {
  font-size: 11px;
  color:     var(--faint);
}

.session-loading,
.session-empty {
  padding:   16px 10px;
  font-size: 12px;
  color:     var(--faint);
  text-align: center;
}

.session-dropdown-footer {
  border-top: 1px solid var(--border);
  padding:    6px;
}

/* The one item in the family that is an action, not a choice — it keeps the
   accent ink and an accent-tinted hover. Everything else is shared. */
.session-new-btn {
  gap:         7px;
  font-size:   12.5px;
  font-weight: 500;
  color:       var(--accent);
}
.session-new-btn:hover { background: color-mix(in srgb, var(--accent) 10%, transparent); }

/* ── recent dashboards on welcome screen ─────────────────────────────────── */

.builder-recent {
  width:      100%;
  margin-top: 8px;
  text-align: left;
}

.builder-recent-title {
  font-size: max(11px, var(--label-min-size, 0px));
  font-weight:   600;
  text-transform: var(--label-case, uppercase);
  letter-spacing: var(--label-tracking, 1px);
  color:         var(--faint);
  margin-bottom: 8px;
  padding:       0 4px;
}

.builder-recent-list {
  display:        flex;
  flex-direction: column;
  gap:            4px;
}

.builder-recent-item {
  display:     flex;
  align-items: center;
  gap:         10px;
  padding:     9px 12px;
  border:      1px solid var(--border);
  border-radius: 10px;
  background:  var(--lift);
  cursor:      pointer;
  text-align:  left;
  transition:  border-color 0.12s, background 0.12s, transform 0.1s;
}
.builder-recent-item:hover {
  border-color: var(--accent);
  background:   color-mix(in srgb, var(--accent) 6%, transparent);
  transform:    translateX(2px);
}

.builder-recent-item-icon {
  width:         32px;
  height:        32px;
  flex-shrink:   0;
  border-radius: 8px;
  background:    color-mix(in srgb, var(--accent) 12%, transparent);
  color:         var(--accent);
  display:       flex;
  align-items:   center;
  justify-content: center;
}

.builder-recent-item-title {
  font-size:   13px;
  font-weight: 500;
  color:       var(--text);
  white-space: nowrap;
  overflow:    hidden;
  text-overflow: ellipsis;
}
.builder-recent-item-meta {
  font-size: 11px;
  color:     var(--faint);
  margin-top: 1px;
}

/* ── message list ────────────────────────────────────────────────────────── */

.builder-msgs {
  flex:           1;
  overflow-y:     auto;
  overflow-x:     hidden;   /* the panel itself never scrolls sideways — content
                                that can't shrink scrolls inside its own block
                                instead (see .builder-msg--ai pre/code/table below) */
  padding:        20px 18px;
  display:        flex;
  flex-direction: column;
  gap:            16px;
  scroll-behavior: smooth;
}

/* welcome / empty state */
.builder-welcome {
  display:         flex;
  flex-direction:  column;
  align-items:     center;
  text-align:      center;
  padding:         32px 16px;
  gap:             12px;
  color:           var(--muted);
}

.builder-welcome-icon {
  width:           52px;
  height:          52px;
  border-radius:   50%;
  background:      var(--accent-bg);
  display:         flex;
  align-items:     center;
  justify-content: center;
  color:           var(--accent);
  margin-bottom:   4px;
}

.builder-welcome-title {
  font-size:   15px;
  font-weight: 600;
  color:       var(--text);
  margin:      0;
}

.builder-welcome-sub {
  font-size:   13px;
  line-height: 1.55;
  margin:      0;
  max-width:   280px;
}

.builder-chips {
  display:   flex;
  flex-wrap: wrap;
  gap:       6px;
  justify-content: center;
  margin-top: 8px;
}

.builder-chip {
  padding:       5px 11px;
  background:    var(--surface);
  border:        1px solid var(--border);
  border-radius: 20px;
  font-size:     12px;
  color:         var(--muted);
  cursor:        pointer;
  transition:    border-color 0.15s, color 0.15s;
}
.builder-chip:hover {
  border-color: var(--accent);
  color:        var(--accent);
}

/* ── individual messages ─────────────────────────────────────────────────── */

.builder-msg {
  display:        flex;
  flex-direction: column;
  gap:            4px;
  max-width:      100%;
  min-width:      0;   /* a flex child defaults to min-width:auto, which lets wide
                           content (a long SQL string, a wide table) push this
                           column past its track — the classic flex-overflow bug
                           that would widen the panel past --chat-w */
}

.builder-msg--user {
  align-items: flex-end;
}

.builder-msg--ai {
  align-items: flex-start;
}

.builder-bubble {
  padding:       9px 14px;
  border-radius: 14px;
  font-size:     13px;
  line-height:   1.55;
  max-width:     88%;
  min-width:     0;   /* same flex-overflow guard as .builder-msg, one level down */
  white-space:   pre-wrap;
  word-break:    break-word;
}

/* Non-prose content an AI bubble can contain (code blocks, wide SQL, result
   tables): shrink to the bubble, and when the content genuinely can't shrink
   — a long statement, a wide table — scroll inside ITS OWN box rather than
   widening the panel. The panel-level overflow-x:hidden above is the backstop;
   this is what keeps that backstop from ever clipping content. */
.builder-msg--ai pre,
.builder-msg--ai code,
.builder-msg--ai table {
  max-width:  100%;
  overflow-x: auto;
}

.builder-msg--user .builder-bubble {
  background:    var(--accent);
  color:         var(--on-accent, #fff);
  border-bottom-right-radius: 4px;
}

.builder-msg--ai .builder-bubble {
  background:    var(--surface);
  border:        1px solid var(--border);
  color:         var(--text);
  border-bottom-left-radius: 4px;
}

/* blinking cursor while streaming */
.builder-bubble--streaming::after {
  content:    '▋';
  animation:  blink 0.7s steps(1) infinite;
  color:      var(--accent);
  font-size:  0.85em;
}
@keyframes blink {
  0%, 100% { opacity: 1; }
  50%       { opacity: 0; }
}

/* thinking pill */
.builder-thinking {
  display:       flex;
  align-items:   center;
  gap:           7px;
  padding:       6px 12px;
  background:    var(--accent-bg);
  border-radius: 20px;
  font-size:     12px;
  color:         var(--accent);
  width:         fit-content;
}

.builder-thinking-dot {
  width:            7px;
  height:           7px;
  border-radius:    50%;
  background:       var(--accent);
  animation:        pulse 1.2s ease-in-out infinite;
}

@keyframes pulse {
  0%, 100% { opacity: 1;   transform: scale(1);    }
  50%       { opacity: 0.4; transform: scale(0.75); }
}

.builder-sql-pill {
  display:       inline-block;
  padding:       2px 7px;
  background:    color-mix(in srgb, var(--accent) 12%, transparent);
  border-radius: 4px;
  font-family:   var(--mono);
  font-size:     10.5px;
  color:         var(--accent);
  max-width:     220px;
  overflow:      hidden;
  text-overflow: ellipsis;
  white-space:   nowrap;
  vertical-align: middle;
}

/* ── input row ───────────────────────────────────────────────────────────── */

.builder-input-row {
  display:      flex;
  align-items:  flex-end;
  gap:          8px;
  padding:      12px 14px 14px;
  border-top:   1px solid var(--border);
  flex-shrink:  0;
}

.builder-input {
  flex:          1;
  resize:        none;
  border:        1px solid var(--border);
  border-radius: 10px;
  background:    var(--lift, var(--surface));
  color:         var(--text);
  font-size:     13px;
  font-family:   inherit;
  line-height:   1.5;
  padding:       9px 12px;
  outline:       none;
  transition:    border-color 0.15s;
  min-height:    42px;
  max-height:    120px;
  overflow-y:    auto;
}

.builder-input:focus {
  border-color: var(--accent);
}

.builder-send-btn {
  width:           38px;
  height:          38px;
  flex-shrink:     0;
  border:          none;
  border-radius:   10px;
  background:      var(--accent);
  color:           var(--on-accent, #fff);
  cursor:          pointer;
  display:         flex;
  align-items:     center;
  justify-content: center;
  transition:      opacity 0.15s, transform 0.1s;
}

.builder-send-btn:hover:not(:disabled)  { opacity: 0.85; }
.builder-send-btn:active:not(:disabled) { transform: scale(0.93); }
.builder-send-btn:disabled              { opacity: 0.4; cursor: default; }

/* ── voice input (feature 002) ──────────────────────────────────────────── */

.builder-mic-btn {
  width:           38px;
  height:          38px;
  flex-shrink:     0;
  border:          1px solid var(--border);
  border-radius:   10px;
  background:      var(--lift, var(--surface));
  color:           var(--muted);
  cursor:          pointer;
  display:         flex;
  align-items:     center;
  justify-content: center;
  transition:      opacity 0.15s, transform 0.1s, background 0.15s, color 0.15s;
}

.builder-mic-btn[hidden] { display: none; }

.builder-mic-btn:hover:not(:disabled)  { color: var(--text); border-color: var(--bhi); }
.builder-mic-btn:active:not(:disabled) { transform: scale(0.93); }
.builder-mic-btn:disabled              { opacity: 0.5; cursor: default; }

/* Recording is a distinct, unmistakable state per the design — not a subtle
   colour shift — so it gets its own background/border plus a pulsing dot,
   never just a colour swap on the existing idle button. */
.builder-mic-btn.is-recording {
  background:   var(--rose);
  border-color: var(--rose);
  color:        var(--on-accent, #fff);
}

.builder-mic-btn.is-transcribing { color: var(--accent); border-color: var(--accent); }

.builder-mic-btn .mic-spinner {
  width:  15px;
  height: 15px;
  border: 2px solid currentColor;
  border-top-color: transparent;
  border-radius: 50%;
  animation: builder-mic-spin 0.7s linear infinite;
}

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

/* The status row sits BELOW the input row so the textarea/Send never move or
   resize while recording — the composer must stay exactly as usable. */
.builder-voice-status {
  display:      flex;
  align-items:  center;
  gap:          8px;
  padding:      0 14px 10px;
  font-size:    12px;
  color:        var(--muted);
  flex-shrink:  0;
}

.builder-voice-status[hidden] { display: none; }

.builder-voice-status.is-recording { color: var(--rose); }
.builder-voice-status.is-error     { color: var(--rose); }

.builder-voice-dot {
  width:         8px;
  height:        8px;
  border-radius: 50%;
  background:    var(--rose);
  animation:     builder-voice-pulse 1.1s ease-in-out infinite;
  flex-shrink:   0;
}

@keyframes builder-voice-pulse {
  0%, 100% { opacity: 1;   transform: scale(1); }
  50%      { opacity: 0.35; transform: scale(0.8); }
}

.builder-voice-cancel {
  border:        none;
  background:    none;
  color:         inherit;
  text-decoration: underline;
  cursor:        pointer;
  font-size:     12px;
  padding:       0;
  margin-left:   auto;
}

/* Playback control on a chat bubble that has an attached recording. */
.builder-msg-audio {
  display:      flex;
  align-items:  center;
  gap:          6px;
  margin-top:   6px;
}

.builder-audio-play {
  width:           26px;
  height:          26px;
  border:          1px solid var(--border);
  border-radius:   50%;
  background:      var(--lift, var(--surface));
  color:           var(--text);
  cursor:          pointer;
  display:         flex;
  align-items:     center;
  justify-content: center;
  flex-shrink:     0;
}

.builder-audio-play:hover { border-color: var(--accent); color: var(--accent); }

/* ── preview column ──────────────────────────────────────────────────────── */

.builder-preview {
  display:        flex;
  flex-direction: column;
  overflow:       hidden;
  background:     var(--void);
}

.builder-preview-hdr {
  display:         flex;
  align-items:     center;
  gap:             12px;
  padding:         13px 20px 11px;
  border-bottom:   1px solid var(--border);
  background:      var(--surface);
  flex-shrink:     0;
}

.builder-title-input {
  flex:          1;
  border:        none;
  background:    transparent;
  color:         var(--text);
  font-size:     15px;
  font-weight:   600;
  font-family:   inherit;
  outline:       none;
  padding:       0;
}

.builder-title-input:focus {
  border-bottom: 1px solid var(--accent);
}

/* The same line, for someone who cannot rename the board: a plain element, not
   a readOnly input, so there is no text box inviting an edit that never saves. */
.builder-title-static {
  flex:        1;
  color:       var(--text);
  font-size:   15px;
  font-weight: 600;
}

.builder-preview-actions {
  display: flex;
  gap:     8px;
}

/* Opens the chat from the same toolbar line as Export; shown only while the
   chat is collapsed. `position: relative` anchors the unread dot. */
.builder-open-chat { position: relative; }

/* unread dot — CSS only, no image asset, follows theme like everything else */
.builder-open-chat.has-unread::after {
  content:       '';
  position:      absolute;
  top:           1px;
  right:         1px;
  width:         8px;
  height:        8px;
  border-radius: 50%;
  background:    var(--rose);
  border:        2px solid var(--surface);
}

/* ── export dropdown ─────────────────────────────────────────────────────── */
.export-wrap { position: relative; display: inline-block; }
/* Face, edge, corner, shadow, items and hover come from the dropdown system in
   app.css. It used to paint itself from --card-bg / --fg, which no theme
   defines, so it always fell back to hardcoded dark hexes. */
.export-menu {
  right: 0;
  top: calc(100% + 6px);
  z-index: 40;
  min-width: 156px;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.export-menu[hidden] { display: none; }

/* ── appearance (board colours) dropdown ─────────────────────────────────── */
/* Same dropdown system as the export menu; only the swatch is new. Its own
   wrapper class rather than .export-wrap: that one names the menu a viewer
   exports from, and this menu is for someone who may change the board. */
.appearance-wrap { position: relative; display: inline-block; }
.appearance-menu { right: 0; top: calc(100% + 6px); z-index: 40; min-width: 214px; }
.appearance-menu[hidden] { display: none; }
.appearance-menu button.active { color: var(--accent); }

/* Fixed width whether or not the row has colours, so "Automatic" and "Match the
   current theme" line up with the palettes above them. */
.appearance-swatch {
  display:     inline-flex;
  gap:         2px;
  flex-shrink: 0;
  width:       38px;
}
.appearance-swatch i {
  width:         6px;
  height:        12px;
  border-radius: 2px;
}

/* ── dashboard canvas ────────────────────────────────────────────────────── */

.builder-canvas {
  flex:       1;
  overflow-y: auto;
  padding:    22px;
  position:   relative;
  font-family: var(--dash-font, inherit);
}

/* canvas empty state */
.builder-canvas-empty {
  position:  absolute;
  inset:     0;
  display:   flex;
  flex-direction: column;
  align-items:    center;
  justify-content: center;
  gap:       12px;
  color:     var(--muted);
  pointer-events: none;
}

.builder-canvas-empty-icon {
  width:    56px;
  height:   56px;
  border:   2px dashed var(--border);
  border-radius: 14px;
  display:  flex;
  align-items: center;
  justify-content: center;
  color:    var(--muted);
}

.builder-canvas-empty p {
  margin:    0;
  font-size: 13px;
}

/* hide empty state when charts exist */
.builder-canvas-empty.hidden { display: none; }

/* ── dashboard layout: stack of titled sections ──────────────────────────── */

.builder-grid {
  display:        flex;
  flex-direction: column;
  gap:            22px;
}

/* ── filter bar ──────────────────────────────────────────────────────────── */

.dash-filters {
  display:       flex;
  align-items:   center;
  flex-wrap:     wrap;
  gap:           14px;
  padding:       12px 16px;
  background:    var(--dash-card-bg, var(--surface));
  border:        1px solid var(--dash-card-border, var(--border));
  border-radius: var(--dash-radius, 14px);
}
.dash-filters-label {
  font-size:      11px;
  font-weight:    700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color:          var(--muted);
}
.dash-filter { display: flex; align-items: center; gap: 7px; }
.dash-filter-label { font-size: 12px; color: var(--muted); }
/* A dashboard filter is a compact boxed dropdown (app.css) that defers to the
   AI skin when one is applied: the board's own card face, edge, accent and font
   win inside #builder-canvas, and its corner radius is clamped so a 20px card
   radius can't turn a 26px-tall control into a pill. */
.dash-filter-select {
  background-color: var(--dash-card-bg, var(--lift));
  border-color:     var(--dash-card-border, var(--border));
  border-radius:    min(var(--dash-radius, var(--dd-r-sm)), 10px);
  font-family:      var(--dash-font, var(--sans));
}
.dash-filter-select:hover        { border-color: var(--dash-accent, var(--bhi)); }
.dash-filter-select:focus-visible { border-color: var(--dash-accent, var(--accent)); }

/* ── section ──────────────────────────────────────────────────────────────
   The heading IS the structure. In a published report nothing separates one
   band of charts from the next except a heading and some air — no rule, no
   accent tick, no uppercase micro-label. So this is set as a real heading and
   the subtitle moves to the far end of the same line, where it reads as a
   caption on the band rather than a second title. */

.dash-section { display: flex; flex-direction: column; gap: 12px; }

.dash-section-hdr {
  display:        flex;
  align-items:    baseline;
  gap:            16px;
  padding-bottom: 2px;
}
.dash-section-titles {
  display:         flex;
  align-items:     baseline;
  gap:             16px;
  flex-wrap:       wrap;
  flex:            1;
  min-width:       0;
  justify-content: space-between;
}
.dash-section-title {
  font-family:    var(--dash-heading-font, inherit);
  font-size:      16.5px;
  font-weight:    600;
  letter-spacing: -0.01em;
  color:          var(--text);
}
.dash-section-sub {
  font-size:   11.5px;
  color:       var(--muted);
  font-weight: 400;
  text-align:  end;
}

/* ── the free-form grid ───────────────────────────────────────────────────
   GridStack positions .grid-stack-item absolutely and owns their geometry, so
   the section body is just a positioning context. Every card inside stretches
   to fill the cell it was given, and the plot takes whatever height is left
   after the header — heights are a layout property now, not a per-type CSS
   constant. */
/* gridstack insets every item by `margin` on all four sides, which would indent
   the leftmost card away from the section heading above it. Pulling the grid out
   by the same half-gap puts the cards' edges back on the heading's baseline
   grid, which is the alignment the whole layout is read against. */
.dash-section-grid {
  position:      relative;
  margin-inline: calc(var(--dash-gap, 18px) / -2);
}

.grid-stack-item-content {
  inset:    0;
  display:  flex;
  overflow: visible;   /* tooltips and hover controls escape the cell */
}
.grid-stack-item-content > .chart-panel,
.grid-stack-item-content > .kpi-card {
  flex:           1;
  min-width:      0;
  display:        flex;
  flex-direction: column;
}

/* Grab affordances only while the board is being edited. */
.grid-stack-item > .ui-resizable-handle { opacity: 0; transition: opacity .15s; }
.grid-stack-item:hover > .ui-resizable-handle { opacity: 1; }
.grid-stack-item.ui-draggable-dragging .chart-panel,
.grid-stack-item.ui-resizable-resizing .chart-panel {
  border-color: var(--dash-accent, var(--accent));
}
/* The drop target gridstack shows while an item is in flight. */
.grid-stack > .grid-stack-placeholder > .placeholder-content {
  border:        1px dashed var(--dash-accent, var(--accent));
  border-radius: var(--dash-radius, 8px);
  background:    color-mix(in srgb, var(--dash-accent, var(--accent)) 7%, transparent);
}

/* ── KPI stat card ─────────────────────────────────────────────────────────
   The number is the hero and reads FIRST: value on top, label under it. The
   markup still emits label-then-value (it is the sensible reading order for a
   screen reader, and `aria-label` on the card states both anyway), so the
   visual order is flipped with `order` rather than by rearranging the DOM. */

.kpi-card {
  grid-column:   span 3;
  background:    var(--dash-card-bg, transparent);
  border:        1px solid var(--dash-card-border, var(--border));
  border-radius: var(--dash-radius, 8px);
  padding:       15px 16px 13px;
  display:       flex;
  flex-direction: column;
  gap:           3px;
  position:      relative;
  min-height:    82px;
  box-shadow:    var(--dash-shadow, none);
}
/* A KPI has no header to hang a grip in, so it floats top-right — out of the
   number's way, and in the same place the chart panels put their controls. */
.kpi-card .chart-drag-handle {
  position:         absolute;
  top:              6px;
  inset-inline-end: 6px;
  padding:          1px 3px;
}
.kpi-label {
  order:          2;
  font-size:      11.5px;
  font-weight:    400;
  letter-spacing: 0;
  color:          var(--muted);
}
.kpi-value-row { order: 1; display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; }
.kpi-value {
  font-family:          var(--dash-heading-font, inherit);
  font-size:            25px;
  font-weight:          600;
  color:                var(--text);
  line-height:          1.15;
  letter-spacing:       -0.02em;
  font-variant-numeric: tabular-nums;
}
/* Direction is carried by the ▴/▾ glyph in the text, not by the colour alone —
   the colour is reinforcement, so this still reads under any colour vision. */
.kpi-delta {
  font-size:   12px;
  font-weight: 500;
  padding:     0;
  background:  none;
}
.kpi-delta.is-up   { color: #10b981; }
.kpi-delta.is-down { color: #f43f5e; }
.kpi-spark { order: 3; width: 100%; height: 30px; margin-top: auto; opacity: .85; }

/* ── insight callouts ────────────────────────────────────────────────────── */

.dash-insights {
  display:               grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap:                   12px;
}
/* A callout should read as a margin note, not as an alert banner: one accent
   rule on the leading edge, no tinted fill, no box around it. */
.insight-card {
  display:       flex;
  align-items:   flex-start;
  gap:           10px;
  padding:       2px 0 2px 12px;
  background:    none;
  border:        none;
  border-inline-start: 2px solid var(--dash-accent, var(--accent));
  border-radius: 0;
}
.insight-icon {
  color:       var(--dash-accent, var(--accent));
  flex-shrink: 0;
  margin-top:  1px;
}
.insight-text { font-size: 12.5px; line-height: 1.45; color: var(--text); }

/* ── chart card ──────────────────────────────────────────────────────────── */

/* Two chrome tiers, assigned per item in _buildChartPanel:

     --boxed   a container for a chart that SHARES a row. The border is what
               says "these two things are separate", so it earns its keep.
     --bare    no container at all. A full-width chart already occupies the
               page; a box around it just draws a rectangle round the viewport
               and adds a second, competing edge inside the canvas.

   How loud "boxed" is comes from the skin's `chrome` token (bare | hairline |
   framed) via --dash-card-bg / --dash-card-border / --dash-shadow, so the
   Style agent can dial the whole board without either tier being rewritten. */
.chart-panel {
  position:      relative;
  grid-column:   span 6;
  border-radius: var(--dash-radius, 8px);
  transition:    border-color 0.15s;
}

.chart-panel--boxed {
  background:  var(--dash-card-bg, transparent);
  border:      1px solid var(--dash-card-border, var(--border));
  box-shadow:  var(--dash-shadow, none);
  padding:     var(--dash-pad, 16px);
}

.chart-panel--bare {
  background: none;
  border:     1px solid transparent;   /* keeps geometry identical to --boxed */
  padding:    0;
}

.chart-panel[data-width="full"]  { grid-column: span 12; }
.chart-panel[data-width="half"]  { grid-column: span 6;  }
.chart-panel[data-width="third"] { grid-column: span 4;  }

/* ── card header ──────────────────────────────────────────────────────────
   There is no header BAR any more — no fill, no divider, no toolbar. A boxed
   widget gets a small muted label; a bare chart's title is a heading, since it
   is doing the same job the section heading does one level up. */

.chart-panel-hdr {
  display:     flex;
  align-items: center;
  gap:         8px;
  padding:     0 0 10px;
  min-height:  22px;
}

/* drag handle (6-dot grip) */
.chart-drag-handle {
  cursor:        grab;
  color:         var(--faint);
  flex-shrink:   0;
  display:       flex;
  align-items:   center;
  padding:       2px 4px;
  border-radius: 4px;
  transition:    color 0.12s, background 0.12s, opacity 0.15s;
  opacity:       0;                      /* occupies space so nothing shifts */
}
.kpi-card:hover .chart-drag-handle,
.kpi-card:focus-within .chart-drag-handle,
.chart-panel:hover .chart-drag-handle,
.chart-panel:focus-within .chart-drag-handle { opacity: 1; }
.chart-drag-handle:hover {
  color:      var(--muted);
  background: color-mix(in srgb, var(--text) 6%, transparent);
}
.chart-drag-handle:active { cursor: grabbing; }

/* A bare chart's heading must align with the section heading above it, so the
   grip moves out into the gutter instead of indenting the title. Only ever
   full-width panels, so there is no neighbour to the left to collide with. */
.chart-panel--bare .chart-drag-handle {
  position: absolute;
  left:     -19px;
  top:      3px;
  padding:  2px 3px;
}

.chart-panel-title {
  flex:           1;
  min-width:      0;
  font-size:      12px;
  font-weight:    400;
  color:          var(--muted);
  letter-spacing: 0;
  overflow:       hidden;
  text-overflow:  ellipsis;
  white-space:    nowrap;
}
.chart-panel--bare .chart-panel-title {
  font-family:    var(--dash-heading-font, inherit);
  font-size:      15px;
  font-weight:    600;
  color:          var(--text);
  letter-spacing: -0.01em;
}
/* the section heading above already says it — see `soloBare` in _buildSection */
.chart-panel--untitled .chart-panel-title { display: none; }
.chart-panel--untitled .chart-panel-hdr   { padding-bottom: 0; min-height: 0; }

/* Controls lift out of the header flow entirely and float at the top-right on
   their own backdrop, so the resting board carries no buttons at all. */
.chart-panel-actions {
  position:        absolute;
  top:             4px;
  inset-inline-end: 5px;
  z-index:         3;
  display:         flex;
  align-items:     center;
  gap:             2px;
  padding:         2px;
  border-radius:   6px;
  background:      color-mix(in srgb, var(--surface) 90%, transparent);
  border:          1px solid var(--border);
  backdrop-filter: blur(5px);
  opacity:         0;
  transition:      opacity 0.15s;
}
.chart-panel:hover .chart-panel-actions,
.chart-panel:focus-within .chart-panel-actions { opacity: 1; }

/* width toggle buttons */
.chart-w-btn {
  border:        none;
  background:    none;
  color:         var(--faint);
  cursor:        pointer;
  font-size:     11px;
  font-weight:   600;
  padding:       3px 6px;
  border-radius: 5px;
  display:       flex;
  align-items:   center;
  justify-content: center;
  transition:    color 0.12s, background 0.12s;
  line-height:   1;
}
.chart-w-btn:hover {
  color:       var(--text);
  background:  color-mix(in srgb, var(--text) 8%, transparent);
}
.chart-w-btn.active {
  color:       var(--accent);
  background:  color-mix(in srgb, var(--accent) 12%, transparent);
}

/* remove button */
.chart-panel-remove {
  border:          none;
  background:      none;
  color:           var(--faint);
  cursor:          pointer;
  padding:         4px 5px;
  border-radius:   5px;
  display:         flex;
  align-items:     center;
  justify-content: center;
  flex-shrink:     0;
  transition:      color 0.12s, background 0.12s;
}
.chart-panel-remove:hover {
  color:       #f43f5e;
  background:  color-mix(in srgb, #f43f5e 12%, transparent);
}

/* ── chart container ─────────────────────────────────────────────────────── */

/* A Vega spec is compiled in the browser, so a chart can fail after the panel
   already exists. Say so where the chart would have been — an empty panel reads
   as "no data", which is a different and much more misleading statement. */
.chart-error {
  display:     flex;
  flex-direction: column;
  gap:         6px;
  align-items: center;
  justify-content: center;
  height:      100%;
  padding:     16px;
  text-align:  center;
  font-size:   12.5px;
  color:       var(--muted);
}
.chart-error span {
  font-family: var(--mono);
  font-size:   10.5px;
  color:       var(--faint);
  max-width:   90%;
  overflow-wrap: anywhere;
}

/* Fills whatever the grid cell leaves after the header. `min-height:0` is what
   lets a flex child actually shrink — without it the canvas pins the panel open
   at its natural size and the cell can never be dragged smaller. */
.chart-container {
  width:      100%;
  flex:       1;
  min-height: 0;
  padding:    0;
  /* A dc.js chart cannot be drawn narrower than its own margins, so on a very
     narrow panel it is drawn at that floor and is wider than its cell. Clip it
     here rather than let it paint over the chart beside it. */
  overflow:   hidden;
}

/* A chart sized by its content (a horizontal bar axis gives every category a
   readable row) can be taller than the panel it sits in. Scroll it there rather
   than compressing the rows into each other — which is the whole point of
   sizing by content. Horizontal stays hidden so the board never side-scrolls. */
.chart-container--scroll {
  overflow-y: auto;
  overflow-x: hidden;
}

/* ── resize handle ───────────────────────────────────────────────────────── */

.chart-resize-handle {
  position:      absolute;
  bottom:        0;
  left:          0;
  right:         0;
  height:        6px;
  cursor:        ns-resize;
  display:       flex;
  align-items:   center;
  justify-content: center;
  opacity:       0;
  transition:    opacity 0.15s;
}
.chart-panel:hover .chart-resize-handle { opacity: 1; }

.chart-resize-handle::after {
  content:       '';
  width:         36px;
  height:        3px;
  border-radius: 2px;
  background:    var(--border);
  transition:    background 0.12s, width 0.12s;
}
.chart-resize-handle:hover::after {
  width:       52px;
  background:  var(--accent);
}

/* ── drag states (SortableJS) ────────────────────────────────────────────── */

.chart-panel--ghost {
  opacity:    0.3;
  border:     2px dashed var(--accent) !important;
  background: color-mix(in srgb, var(--accent) 5%, transparent) !important;
}
.chart-panel--chosen {
  cursor: grabbing;
}
.chart-panel--dragging {
  opacity:    0.95;
  box-shadow: 0 16px 40px rgba(0,0,0,0.35) !important;
  transform:  scale(1.01) !important;
  z-index:    50;
}

/* ── loading skeletons (generate pipeline in flight) ─────────────────────── */

.skeleton-board { display: flex; flex-direction: column; gap: 14px; padding-top: 4px; }
.skeleton-stage {
  font-size:      12px;
  color:          var(--muted);
  letter-spacing: .03em;
}
.skeleton-row  { display: flex; gap: 14px; }
.skeleton-row > * { flex: 1; }
.skeleton-card {
  border:        1px solid var(--border);
  border-radius: var(--dash-radius, 14px);
  background: linear-gradient(100deg,
    rgba(255,255,255,.035) 40%, rgba(255,255,255,.085) 50%, rgba(255,255,255,.035) 60%);
  background-size: 200% 100%;
  animation: skeleton-shimmer 1.4s linear infinite;
}
.skeleton-kpi   { height: 110px; }
.skeleton-chart { height: 260px; }
.skeleton-wide  { height: 300px; }
@keyframes skeleton-shimmer { to { background-position: -200% 0; } }
@media (prefers-reduced-motion: reduce) { .skeleton-card { animation: none; } }

/* ── dc drill-down: time-grain pills (M / W / D) ─────────────────────────── */

.bin-pills {
  display: inline-flex;
  gap: 2px;
  margin-right: 6px;
  padding: 2px;
  border: 1px solid var(--border);
  border-radius: 7px;
}
.bin-pill {
  width: 20px; height: 18px;
  border: 0; border-radius: 5px;
  background: transparent;
  color: var(--muted);
  font: inherit; font-size: 10px; font-weight: 700;
  cursor: pointer;
  line-height: 1;
}
.bin-pill:hover  { color: var(--text); }
.bin-pill.active { background: var(--dash-accent, var(--accent)); color: #fff; }

/* ── keyboard focus (accessibility) ──────────────────────────────────────── */

.chart-w-btn:focus-visible, .chart-panel-remove:focus-visible,
.bin-pill:focus-visible, .fw-btn:focus-visible,
.export-menu button:focus-visible, .builder-chip:focus-visible {
  outline: 2px solid var(--dash-accent, var(--accent));
  outline-offset: 2px;
}

/* ── responsive ──────────────────────────────────────────────────────────── */

@media (max-width: 1180px) {
  /* two KPIs per row, charts full width on medium screens */
  .kpi-card    { grid-column: span 6 !important; }
  .chart-panel { grid-column: span 12 !important; }
}

@media (max-width: 960px) {
  /* Below this breakpoint the chat is never a side column — it is either away
     entirely (reopened from the toolbar's own button) or a full-width sheet
     OVER the canvas. This branches
     on .chat-collapsed rather than adding a third breakpoint: the collapse
     state already IS the "do I want the chat right now" signal, so reusing it
     here is one responsive story instead of two. Dragging a full-width sheet
     is meaningless, so the resizer is hidden outright (not just off-canvas). */
  .builder-layout {
    grid-template-columns: 1fr;
    grid-template-rows:    1fr;
  }
  .builder-preview { grid-column: 1; grid-row: 1; }
  .builder-chat-resizer { display: none; }

  /* Collapsed (default small-screen state): single column, canvas full
     height, chat reachable only from the board toolbar's open-chat button.
     This reclaims the 42% the old permanent bottom slab always spent on chat
     whether or not the user wanted it there. */
  .builder-layout.chat-collapsed .builder-chat { display: none; }

  /* Expanded: a full-width sheet over the canvas, not a stacked row — the
     collapse control (and a tap on the backdrop) dismiss it again. */
  .builder-layout:not(.chat-collapsed) .builder-chat {
    position:   absolute;
    inset:      0;
    z-index:    20;
    border-left: none;
  }
  .chat-sheet-backdrop {
    position: absolute;
    inset:    0;
    z-index:  19;   /* under the sheet, over the canvas */
    background: color-mix(in srgb, var(--void) 55%, transparent);
  }
  .builder-layout:not(.chat-collapsed) .chat-sheet-backdrop { display: block; }

  .kpi-card { grid-column: span 12 !important; }

  /* header + toolbars wrap instead of overflowing on narrow screens */
  .builder-preview-hdr { flex-wrap: wrap; row-gap: 8px; }
  .builder-title-input { min-width: 0; flex: 1 1 160px; }
  .skeleton-row        { flex-direction: column; }

  /* thumb-sized touch target for the open-chat button (spec: ≥44px) */
  .builder-open-chat {
    min-width:  44px;
    min-height: 44px;
  }

  /* composer + collapse control stay reachable with a thumb (spec names the
     collapse control alongside the open-chat button — same ≥44px target, not
     the 26px desktop size) */
  .builder-input-row  { padding: 12px 14px 16px; }
  .chat-collapse-btn  { width: 44px; height: 44px; }
}

/* ── btn-sm utility ──────────────────────────────────────────────────────── */

.btn-sm {
  padding:   5px 12px;
  font-size: 12px;
}

/* ── Studio: dashboard management view ─────────────────────────────────────── */
.studio-manage { flex-direction: column; height: 100%; overflow: hidden; }
.manage-hdr {
  display: flex; align-items: flex-start; justify-content: space-between; gap: 20px;
  padding: 22px 28px 18px; border-bottom: 1px solid var(--border);
  background: var(--depth); flex-shrink: 0;
}
.manage-grid { flex: 1; overflow-y: auto; padding: 20px 28px 40px; }
.manage-loading { color: var(--muted); font-size: 13px; padding: 40px; text-align: center; }
.manage-empty {
  display: flex; flex-direction: column; align-items: center; gap: 16px;
  padding: 72px 20px; color: var(--muted); font-size: 14px;
  border: 1px dashed var(--border); border-radius: var(--r-lg);
}

/* ── list view ─────────────────────────────────────────────────────────────── */
.manage-list {
  display: flex; flex-direction: column;
  border: 1px solid var(--border); border-radius: var(--r-lg); overflow: hidden;
  background: var(--surface); max-width: 1100px;
}
.dash-row {
  display: flex; align-items: center; gap: 14px; padding: 13px 16px; cursor: pointer;
  border-bottom: 1px solid var(--border); transition: background .12s;
}
.dash-row:last-child { border-bottom: none; }
.dash-row:hover { background: var(--lift); }
.dash-row:focus-visible { outline: none; box-shadow: inset 0 0 0 2px var(--accent); }
.dash-row-icon {
  flex-shrink: 0; width: 34px; height: 34px; border-radius: 9px;
  display: grid; place-items: center; color: var(--accent); background: var(--accent-bg);
}
.dash-row-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.dash-row-title {
  font-family: var(--display); font-size: 14px; font-weight: 600; color: var(--text);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.dash-row-meta { font-family: var(--mono); font-size: 11px; color: var(--muted); }
.dash-row-fw {
  flex-shrink: 0; font-family: var(--label-font, var(--mono)); font-size: max(9.5px, var(--label-min-size, 0px)); letter-spacing: var(--label-tracking, .1em);
  text-transform: var(--label-case, uppercase); padding: 3px 8px; border-radius: 999px;
  border: 1px solid var(--border); background: var(--void); color: var(--muted);
}
.dash-row-fw.fw-dcjs { color: var(--violet); border-color: color-mix(in srgb, var(--violet) 40%, transparent); }
.dash-row-fw.fw-echarts { color: var(--cyan); border-color: color-mix(in srgb, var(--cyan) 40%, transparent); }
.dash-row-fw.fw-vega { color: var(--emerald); border-color: color-mix(in srgb, var(--emerald) 40%, transparent); }
.dash-row-actions { flex-shrink: 0; display: flex; align-items: center; gap: 6px; }
.dash-row-btn {
  font-family: var(--display); font-size: 12px; font-weight: 600; color: var(--accent);
  background: none; border: 1px solid var(--border); border-radius: 7px;
  padding: 5px 12px; cursor: pointer; transition: border-color .12s, background .12s;
}
.dash-row-btn:hover { border-color: var(--accent); background: var(--accent-bg); }
.dash-row-del {
  display: grid; place-items: center; width: 30px; height: 30px; border-radius: 7px;
  color: var(--faint); background: none; border: none; cursor: pointer; transition: color .12s, background .12s;
}
.dash-row-del:hover { color: var(--rose); background: color-mix(in srgb, var(--rose) 12%, transparent); }

/* ── collections: the folders dashboards are filed into ────────────────────── */
.manage-hdr-actions { display: flex; align-items: center; gap: 8px; flex-shrink: 0; }
.coll-list {
  display: flex; flex-direction: column;
  border: 1px solid var(--border); border-radius: var(--r-lg); overflow: hidden;
  background: var(--surface); max-width: 1100px; margin-bottom: 18px;
}
.coll-row {
  display: flex; align-items: center; gap: 14px; padding: 13px 16px; cursor: pointer;
  border-bottom: 1px solid var(--border); transition: background .12s;
}
.coll-row:last-child { border-bottom: none; }
.coll-row:hover { background: var(--lift); }
.coll-row:focus-visible { outline: none; box-shadow: inset 0 0 0 2px var(--accent); }
.coll-row-icon {
  flex-shrink: 0; width: 34px; height: 34px; border-radius: 9px;
  display: grid; place-items: center; color: var(--amber, var(--accent));
  background: var(--lift);
}
.coll-crumb {
  display: flex; align-items: center; gap: 10px; margin-bottom: 14px;
  font-family: var(--display); font-size: 14px; font-weight: 600; color: var(--text);
}
.coll-crumb-back {
  font-family: var(--display); font-size: 13px; font-weight: 600; color: var(--accent);
  background: none; border: none; padding: 0; cursor: pointer;
}
.coll-crumb-back::after { content: '/'; color: var(--faint); margin-left: 10px; }
.coll-crumb-actions { display: flex; align-items: center; gap: 6px; margin-left: auto; }

/* who a collection is shared with */
.coll-shares { display: flex; flex-direction: column; gap: 8px; margin: 4px 0 14px; }
.coll-share { display: flex; align-items: center; gap: 10px; }
.coll-share-who {
  flex: 1; min-width: 0; display: flex; flex-direction: column;
  font-size: 13px; color: var(--text);
}
.coll-share-sub { font-family: var(--mono); font-size: 11px; color: var(--muted); }
.coll-share-note { font-size: 12.5px; color: var(--muted); }
.coll-share-x {
  width: 26px; height: 26px; border-radius: 7px; border: none; background: none;
  color: var(--faint); cursor: pointer; font-size: 16px; line-height: 1;
}
.coll-share-x:hover { color: var(--rose); background: color-mix(in srgb, var(--rose) 12%, transparent); }
.coll-share-add { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.coll-move { margin: 4px 0 14px; }

/* Said on a board somebody may read and not change — by their role, or by the
   level the collection reaches them at. Present before anything is attempted. */
.builder-readonly {
  padding: 9px 20px; border-bottom: 1px solid var(--border);
  background: var(--lift); color: var(--muted);
  font-family: var(--display); font-size: 12.5px; flex-shrink: 0;
}

.dash-card {
  display: flex; flex-direction: column; gap: 10px; text-align: left; cursor: pointer;
  min-height: 132px; padding: 16px; border-radius: var(--r-lg);
  background: var(--surface); border: 1px solid var(--border); color: var(--text);
  transition: border-color .15s, transform .15s, box-shadow .15s, background .15s;
}
.dash-card:hover {
  border-color: var(--bhi); transform: translateY(-2px);
  box-shadow: var(--shadow-md, 0 10px 28px -8px rgba(0,0,0,.5));
}
.dash-card:focus-visible { outline: none; border-color: var(--accent); box-shadow: var(--ring, 0 0 0 3px var(--accent-bg)); }
.dash-card-top { display: flex; align-items: center; justify-content: space-between; }
.dash-card-fw {
  font-family: var(--label-font, var(--mono)); font-size: max(9.5px, var(--label-min-size, 0px)); letter-spacing: var(--label-tracking, .1em); text-transform: var(--label-case, uppercase);
  padding: 3px 8px; border-radius: 999px; border: 1px solid var(--border);
  color: var(--muted); background: var(--lift);
}
.dash-card-fw.fw-dcjs { color: var(--violet); border-color: color-mix(in srgb, var(--violet) 40%, transparent); }
.dash-card-fw.fw-echarts { color: var(--cyan); border-color: color-mix(in srgb, var(--cyan) 40%, transparent); }
.dash-card-fw.fw-vega { color: var(--emerald); border-color: color-mix(in srgb, var(--emerald) 40%, transparent); }
.dash-card-del {
  display: grid; place-items: center; width: 26px; height: 26px; border-radius: 7px;
  color: var(--faint); transition: color .12s, background .12s;
}
.dash-card-del:hover { color: var(--rose); background: color-mix(in srgb, var(--rose) 12%, transparent); }
.dash-card-title {
  font-family: var(--display); font-size: 15px; font-weight: 600; line-height: 1.3;
  margin-top: auto; overflow: hidden; text-overflow: ellipsis;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
}
.dash-card-meta { font-family: var(--mono); font-size: 11px; color: var(--muted); }

/* the "new dashboard" tile */
.dash-card-new {
  align-items: center; justify-content: center; text-align: center; gap: 4px;
  border-style: dashed; background: transparent;
}
.dash-card-new:hover { border-color: var(--accent); background: var(--accent-bg); transform: translateY(-2px); }
.dash-card-plus {
  font-size: 26px; line-height: 1; color: var(--accent); font-weight: 300; margin-bottom: 2px;
}
.dash-card-newlabel { font-family: var(--display); font-size: 14px; font-weight: 600; color: var(--text); }
.dash-card-newsub { font-size: 11.5px; color: var(--muted); }

.builder-back-btn { margin-right: 4px; flex-shrink: 0; }
.builder-back-btn svg { margin-right: 3px; vertical-align: -1px; }

@media (prefers-reduced-motion: reduce) {
  .dash-card { transition: none; }
  .dash-card:hover, .dash-card-new:hover { transform: none; }
}

/* ── Power BI export dialog ───────────────────────────────────────────────── */

.pbi-modes { display: flex; flex-direction: column; gap: 10px; margin: 14px 0; }
.pbi-mode {
  display: flex; gap: 8px; align-items: flex-start; cursor: pointer;
  font-size: 12.5px; line-height: 1.5;
}
.pbi-mode input { margin-top: 3px; }
.pbi-mode small { color: var(--muted); }
.pbi-mode.is-disabled { opacity: .5; cursor: default; }

.pbi-subs {
  border: 1px solid var(--border); border-radius: var(--r-lg);
  background: var(--lift); padding: 10px 12px; margin-bottom: 12px;
  font-size: 12px; line-height: 1.5; color: var(--muted);
}
.pbi-subs-title { color: var(--text); font-weight: 600; margin-bottom: 4px; }
.pbi-subs ul { margin: 0; padding-inline-start: 16px; }

.pbi-error { font-size: 12.5px; color: var(--rose); margin: 6px 0 0; }
.pbi-done { font-size: 12.5px; color: var(--emerald); margin: 6px 0 0; }

/* ── PowerPoint deck dialog ───────────────────────────────────────────────── */

.deck-outline {
  display: flex; flex-direction: column; gap: 10px;
  max-height: 46vh; overflow-y: auto; margin: 12px 0;
}
.deck-section {
  border: 1px solid var(--border); border-radius: var(--r-lg);
  background: var(--lift); padding: 10px 12px;
}
.deck-section-hdr { display: flex; gap: 8px; align-items: center; }
.deck-section-title {
  flex: 1; min-width: 0; font-family: var(--display); font-size: 13.5px; font-weight: 600;
  color: var(--text); background: transparent; border: 1px solid transparent;
  border-radius: 7px; padding: 3px 6px;
}
.deck-section-title:focus { outline: none; border-color: var(--accent); background: var(--surface); }
.deck-section-del {
  font-size: 11.5px; color: var(--muted); padding: 3px 7px; border-radius: 7px;
}
.deck-section-del:hover { color: var(--rose); background: color-mix(in srgb, var(--rose) 12%, transparent); }
.deck-commentary { font-size: 12px; color: var(--muted); line-height: 1.5; margin: 4px 0 6px; }

.deck-items { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 4px; }
.deck-items li { display: flex; gap: 8px; align-items: center; font-size: 12.5px; }
.deck-item-kind {
  font-family: var(--label-font, var(--mono)); font-size: max(9.5px, var(--label-min-size, 0px)); letter-spacing: var(--label-tracking, .08em); text-transform: var(--label-case, uppercase);
  padding: 2px 6px; border-radius: 999px; border: 1px solid var(--border); color: var(--muted);
}
.deck-item-kind--kpi { color: var(--accent); border-color: color-mix(in srgb, var(--accent) 40%, transparent); }
.deck-item-title { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.deck-items select {
  font-size: 11.5px; color: var(--muted); background: var(--surface);
  border: 1px solid var(--border); border-radius: 7px; padding: 2px 5px; max-width: 40%;
}

.deck-ask { display: flex; gap: 8px; align-items: center; margin-bottom: 4px; }
.deck-ask input {
  flex: 1; min-width: 0; font-size: 12.5px; color: var(--text); background: var(--surface);
  border: 1px solid var(--border); border-radius: var(--r); padding: 7px 10px;
}
.deck-ask input:focus { outline: none; border-color: var(--accent); }
.deck-count { font-family: var(--mono); font-size: 11px; color: var(--muted); margin-inline-end: auto; }
.drill-foot:has(.deck-count) { display: flex; align-items: center; gap: 10px; }

/* ── generative UI in the Studio chat ─────────────────────────────────────── */
/* Theme tokens only, and no physical left/right, so a card mirrors on an RTL
   board the way every other Studio surface does. */

.gen-card {
  border: 1px solid var(--border); border-radius: var(--r-lg);
  background: var(--surface); padding: 10px 12px; margin-top: 6px;
  font-size: 12.5px; line-height: 1.5; color: var(--text);
}
.gen-card-hdr { display: flex; gap: 8px; align-items: baseline; margin-bottom: 6px; }
.gen-card-kind {
  font-family: var(--label-font, var(--mono)); font-size: max(9.5px, var(--label-min-size, 0px)); letter-spacing: var(--label-tracking, .1em);
  text-transform: var(--label-case, uppercase); color: var(--accent);
}
.gen-card-title { font-family: var(--display); font-weight: 600; font-size: 13.5px; }

.gen-plan-section + .gen-plan-section { margin-top: 8px; }
.gen-plan-section-title {
  font-family: var(--label-font, var(--mono)); font-size: max(10px, var(--label-min-size, 0px)); letter-spacing: var(--label-tracking, .08em);
  text-transform: var(--label-case, uppercase); color: var(--muted); margin-bottom: 3px;
}
.gen-plan-items { list-style: none; margin: 0; padding: 0; display: flex;
  flex-direction: column; gap: 4px; }
.gen-plan-items li { display: flex; gap: 7px; align-items: flex-start; }

.gen-item-kind {
  flex-shrink: 0; font-family: var(--label-font, var(--mono)); font-size: max(9px, var(--label-min-size, 0px)); letter-spacing: var(--label-tracking, .06em);
  text-transform: var(--label-case, uppercase); padding: 2px 6px; border-radius: 999px;
  border: 1px solid var(--border); color: var(--muted); margin-top: 2px;
}
.gen-item-kind--kpi { color: var(--accent);
  border-color: color-mix(in srgb, var(--accent) 40%, transparent); }
.gen-item-main { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.gen-item-name {
  width: 100%; font-size: 12.5px; color: var(--text); background: transparent;
  border: 1px solid transparent; border-radius: 6px; padding: 2px 5px;
}
.gen-item-name:focus { outline: none; border-color: var(--accent); background: var(--lift); }
.gen-item-name.is-static { padding-inline: 5px; }
.gen-item-note { font-size: 11.5px; color: var(--muted); padding-inline: 5px; }
.gen-item-tools { display: flex; gap: 2px; }
.gen-item-tools button {
  width: 20px; height: 20px; border-radius: 5px; font-size: 11px;
  color: var(--faint); line-height: 1;
}
.gen-item-tools button:hover { color: var(--accent); background: var(--lift); }

.gen-desc { margin: 0 0 6px; color: var(--text); }
.gen-changes { list-style: none; margin: 0 0 8px; padding: 0; color: var(--muted); }
.gen-changes li { font-size: 12px; }

.gen-stages { list-style: none; margin: 0 0 6px; padding: 0; display: flex;
  flex-wrap: wrap; gap: 6px; }
.gen-stage {
  font-size: 11.5px; color: var(--muted); padding: 2px 8px; border-radius: 999px;
  border: 1px solid var(--border);
}
.gen-stage.is-ok { color: var(--emerald);
  border-color: color-mix(in srgb, var(--emerald) 35%, transparent); }
.gen-stage.is-run { color: var(--accent);
  border-color: color-mix(in srgb, var(--accent) 45%, transparent); }

.gen-preview { height: 180px; margin: 8px 0; }

.gen-card-foot { display: flex; justify-content: flex-end; gap: 8px; margin-top: 10px; }
.gen-outcome {
  margin-top: 8px; font-family: var(--label-font, var(--mono)); font-size: max(10.5px, var(--label-min-size, 0px)); letter-spacing: var(--label-tracking, .06em);
  text-transform: var(--label-case, uppercase); color: var(--muted);
}
.builder-chip.is-picked { border-color: var(--accent); color: var(--accent); }

/* ══ Imago (spec 005h): Studio chrome ═════════════════════════════════════════
   The dashboard itself is drawn by dashboard-render.js and dressed by the
   board's skin — none of that is touched here. This is the chrome around it:
   the dashboards list, the toolbar, and the chat, which under the Imago themes
   becomes the agent's navy wing. */

/* the dashboards list */
.manage-hdr { padding: var(--page-hdr-pad, 22px 28px 18px); }
.manage-grid { padding: 26px 32px 40px; }
.manage-list, .coll-list { max-width: 1180px; }
.dash-row, .coll-row { padding: 16px 20px; gap: 16px; }
.dash-row-icon, .coll-row-icon { width: 40px; height: 40px; border-radius: 10px; }
.dash-row-title { font-size: 15.5px; }
.dash-row-meta, .coll-share-sub { font-family: var(--sans); font-size: 13px; }
.dash-row-fw { font-size: max(11px, var(--label-min-size, 0px)); }
.dash-row-btn { min-height: 36px; padding: 0 14px; font-family: var(--sans); font-size: 13.5px; font-weight: 500; border-radius: var(--r); }
.dash-row-del { width: 36px; height: 36px; }
.coll-crumb, .coll-crumb-back { font-family: var(--sans); font-size: 15px; }
.manage-empty { font-size: 15px; background: var(--surface); }
.builder-readonly { font-family: var(--sans); font-size: 14px; }

/* the toolbar over the board */
.builder-preview-hdr { padding: 12px 20px; gap: 14px; flex-wrap: wrap; }
.builder-title-input, .builder-title-static { font-family: var(--display); font-size: 19px; letter-spacing: -0.01em; min-width: 180px; }
.builder-title-input { border-radius: 6px; padding: 4px 6px; }
.builder-title-input:hover { background: var(--lift); }
.builder-title-input:focus { border-bottom: 0; outline: 2px solid var(--focus, var(--accent)); background: var(--surface); }
.builder-fw-badge { font-family: var(--sans); font-size: 12.5px; text-transform: none; letter-spacing: 0; padding: 3px 10px; border-radius: 9999px; background: var(--lift); border: 1px solid var(--border); color: var(--muted); }
.builder-preview-actions { flex-wrap: wrap; }
.export-menu button { font-family: var(--sans); font-size: 14px; min-height: 38px; }

/* the chat as the agent's wing — only under the Imago themes, by re-pointing
   the tokens inside the column so every message, card and control in it reads
   on navy without its own rule */
html[data-theme^="imago"] .builder-chat {
  --text: var(--wing-ink, #fff);
  --muted: var(--wing-muted, #c9d4ea);
  --faint: #9aa6c8;
  --surface: var(--wing-raised, #2c3352);
  --lift: #343c61;
  --depth: var(--wing-bg, #384064);
  --void: var(--wing-bg, #384064);
  --border: var(--wing-border, #5b6485);
  --bhi: #6b759a;
  --accent: var(--wing-accent, #acdbfc);
  --accent-dim: #7fb6e3;
  --accent-bg: rgba(172, 219, 252, .12);
  --accent-mid: rgba(172, 219, 252, .22);
  --on-accent: var(--wing-bg, #384064);
  --cyan: var(--wing-accent, #acdbfc);
  --gold: var(--wing-accent, #acdbfc);
  --field-bg: var(--wing-raised, #2c3352);
  --field-border: var(--wing-border, #5b6485);
  --control-bg: transparent;
  --control-border: var(--wing-border, #5b6485);
  color-scheme: dark;
  background: var(--wing-bg, #384064);
  color: var(--wing-ink, #fff);
  border-left: 0;
  margin: 10px 10px 10px 0;
  border-radius: var(--petal-lg, 32px 6px 32px 6px);
}
html[data-theme^="imago"] .builder-layout.chat-rtl .builder-chat { margin: 10px 0 10px 10px; border-radius: 6px 32px 6px 32px; }
html[data-theme^="imago"] .builder-chat-hdr { border-bottom-color: var(--wing-border, #5b6485); padding: 18px 20px 14px; }
html[data-theme^="imago"] .builder-chat-hdr-left { font-size: 16px; }
html[data-theme^="imago"] .builder-msg--user .builder-bubble { background: var(--wing-ink, #fff); color: var(--wing-bg, #384064); border-radius: 12px; }
html[data-theme^="imago"] .builder-msg--ai .builder-bubble { background: var(--wing-raised, #2c3352); border: 0; border-radius: var(--petal-sm, 18px 4px 18px 4px); }
html[data-theme^="imago"] .builder-bubble { font-size: 14px; }
html[data-theme^="imago"] .builder-input-row { border-top-color: var(--wing-border, #5b6485); }
html[data-theme^="imago"] .builder-input { font-size: 14px; min-height: 44px; border-radius: 12px; }
html[data-theme^="imago"] .builder-send-btn { width: 44px; height: 44px; border-radius: 12px; }
html[data-theme^="imago"] .builder-chat :focus-visible { outline-color: var(--wing-accent, #acdbfc); }
