/* ═══════════════════════════════════════════════════════════════════════════
   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;
}

/* ── good and bad, for a KPI's delta (design 019c §7) ─────────────────────
   Here and not in the theme files: those are loaded without a ?v=, so a value
   added to them reaches a returning browser only when its cache lets go. Each
   must hold 4.5:1 for 12 px text on its theme's --surface; where a theme's own
   status colour does not, it gets a darker or lighter one of the same hue
   (run_studio_test.py resolves and checks every theme). */
#builder-canvas {
  --dash-good: var(--emerald, #10b981);
  --dash-bad:  var(--rose, #f43f5e);
}
html[data-theme="morpho-light"] #builder-canvas { --dash-good: #047857; }   /* #0E9F6E is 3.4:1 on white */
html[data-theme="morpho"] #builder-canvas       { --dash-bad:  #FB7185; }   /* #F43F5E is 3.5:1 on #243050 */

/* ── 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)); }

/* ── Studio v2 filter bar (design 019a §8) ─────────────────────────────────
   Every control re-runs the board's queries; the period shows where the data
   is, a multi-select keeps its choices as chips, a long dimension is a search.
   Same compact boxed register as the v1 select, so the two bars look alike. */
.dash-filters--v2 .dash-filter { position: relative; flex-wrap: wrap; }
.dash-filter-cover { font-size: 11px; color: var(--muted); }
.dash-period-range { display: inline-flex; align-items: center; gap: 6px; }
.dash-period-range[hidden] { display: none; }
.dash-period-sep { color: var(--faint); }
.dash-filter-date,
.dash-filter-input {
  background-color: var(--dash-card-bg, var(--lift));
  border:           1px solid var(--dash-card-border, var(--border));
  border-radius:    min(var(--dash-radius, var(--dd-r-sm, 6px)), 10px);
  color:            var(--text);
  font-family:      var(--dash-font, var(--sans));
  font-size:        12px;
  padding:          4px 8px;
}
.dash-filter-input { width: 160px; }
.dash-filter-date:focus-visible,
.dash-filter-input:focus-visible { outline: none; border-color: var(--dash-accent, var(--accent)); }
.dash-ms, .dash-search { position: relative; display: inline-flex; }
.dash-ms-trigger { cursor: pointer; text-align: start; min-width: 90px; }
.dash-ms-pop,
.dash-search-pop {
  top:              calc(100% + 4px);
  inset-inline-start: 0;
  z-index:          30;
  min-width:        210px;
  max-height:       290px;
  overflow:         auto;
}
.dash-ms-pop[hidden],
.dash-search-pop[hidden] { display: none; }
.dash-ms-search { width: 100%; margin-bottom: 4px; }
.dash-ms-item {
  display:       flex;
  align-items:   center;
  gap:           8px;
  padding:       5px 8px;
  border-radius: 6px;
  font-size:     12.5px;
  color:         var(--text);
  cursor:        pointer;
}
.dash-ms-item[hidden] { display: none; }
.dash-ms-item:hover { background: color-mix(in srgb, var(--text) 7%, transparent); }
.dash-ms-item input { accent-color: var(--dash-accent, var(--accent)); margin: 0; }
.dash-search-none { padding: 6px 10px; font-size: 12px; color: var(--muted); }
.dash-chips { display: inline-flex; flex-wrap: wrap; gap: 4px; }
.dash-chip {
  display:       inline-flex;
  align-items:   center;
  gap:           5px;
  padding:       2px 8px;
  border:        1px solid var(--dash-card-border, var(--border));
  border-radius: 999px;
  background:    color-mix(in srgb, var(--dash-accent, var(--accent)) 10%, transparent);
  color:         var(--text);
  font-family:   var(--dash-font, var(--sans));
  font-size:     11.5px;
  cursor:        pointer;
}
.dash-chip:disabled { cursor: default; }
.dash-chip-x { color: var(--muted); }
.dash-chip:hover:not(:disabled) .dash-chip-x { color: var(--text); }
.dash-filter-reset {
  margin-inline-start: auto;
  background:  none;
  border:      0;
  padding:     2px 4px;
  font-size:   12px;
  color:       var(--dash-accent, var(--accent));
  cursor:      pointer;
}
.dash-filter-reset:hover { text-decoration: underline; }
/* The offline export cannot re-query: its controls show the defaults, inert. */
.dash-filter.is-offline { opacity: .7; cursor: not-allowed; }

/* ── a tile under v2 filters ───────────────────────────────────────────────
   "Not affected by Region" is a fact about the tile, said quietly beside its
   title; an empty window and a failed query each get their own sentence in
   place of the chart, never a blank panel. */
.tile-note {
  flex-shrink:   1;
  min-width:     0;
  overflow:      hidden;
  text-overflow: ellipsis;
  white-space:   nowrap;
  padding:       1px 7px;
  border:        1px dashed var(--dash-card-border, var(--border));
  border-radius: 999px;
  font-size:     10.5px;
  color:         var(--muted);
}
.tile-note[hidden] { display: none; }
.kpi-card .tile-note { order: 4; align-self: flex-start; margin-top: 2px; }
.tile-state {
  position:        absolute;
  inset:           44px var(--dash-pad, 16px) var(--dash-pad, 16px);
  display:         flex;
  align-items:     center;
  justify-content: center;
  padding:         12px;
  text-align:      center;
  font-size:       12.5px;
  line-height:     1.45;
  color:           var(--muted);
  background:      var(--dash-card-bg, var(--surface));
}
.tile-state[hidden] { display: none; }
.tile-state.is-error { color: var(--rose, #f43f5e); }
/* every value 0 (019h §4): the sentence, not a flat chart under it — the
   card's background may be transparent (the hairline and bare chromes), so
   the chart itself is hidden, not merely covered */
.chart-panel.is-zero .chart-container { visibility: hidden; }
/* …and more rows than Imago reads at once (026 R6): the same sentence-for-chart
   rule, never `is-error` — nothing failed */
.chart-panel.is-too-many .chart-container { visibility: hidden; }
/* a map whose outlines are loading (021): a quiet placeholder, not a blank
   canvas that reads as "no data" — gone the moment the map draws */
.chart-container.is-geo-loading {
  background: repeating-linear-gradient(135deg, transparent 0 10px,
              color-mix(in srgb, var(--muted) 6%, transparent) 10px 20px);
}
/* ── click-to-filter (019e part B) ──────────────────────────────────────────
   What a click on a chart does, said on hover and on keyboard focus — never a
   permanent caption: the tile's note already says what it highlights. In the
   tile's HEADER band, never over the chart (019g fix round 4: at the panel's
   bottom corner it was drawn over the x title): the first thing on the
   controls' floating strip when the tile has controls, else after the title
   in the header's flow, not displayed at rest. Shown, it shrinks before the
   title does. Logical properties: it follows a right-to-left board. */
.tile-click-hint {
  display:        none;
  flex:           0 1000 auto;
  min-width:      0;
  /* 16 px tall in all: within the title's line, so the header — and the chart
     under it — does not move when the hint shows (at 18.7 px it took the body
     0.7 px and the chart re-laid out on hover) */
  padding:        0 7px;
  border-radius:  999px;
  font-size:      10.5px;
  line-height:    14px;
  color:          var(--muted);
  background:     var(--dash-card-bg, var(--surface));
  border:         1px solid var(--dash-card-border, var(--border));
  white-space:    nowrap;
  overflow:       hidden;
  text-overflow:  ellipsis;
  pointer-events: none;
}
.chart-panel:hover .tile-click-hint,
.chart-panel:focus-within .tile-click-hint { display: inline-block; }
/* on the controls' strip: shown with it (the strip is hidden at rest). The
   strip never reaches the grip at the header's start — the panel's padding,
   the grip (18 px) and a gap (8 px) are kept clear past its own 5 px offset;
   held to the panel's full width it covered a third-width tile's grip and
   took its pointer (fix round 4). The hint is cut, never the buttons; under a
   readable width it is a funnel (`is-icon`, set by `_v2PaintHint`). */
.chart-panel .chart-panel-actions { max-width: calc(100% - var(--dash-pad, 16px) - 31px); }
.chart-panel-actions .tile-click-hint { display: inline-block; border: 0; background: none; }
.chart-panel-actions > :not(.tile-click-hint) { flex: none; }
.tile-click-hint.is-icon { flex: none; font-size: 0; padding: 0 4px; }
/* an icon's words are its title: shown, it takes the pointer to say them */
.chart-panel:hover .tile-click-hint.is-icon,
.chart-panel:focus-within .tile-click-hint.is-icon { pointer-events: auto; }
.tile-click-hint.is-icon::before {
  content: ''; display: inline-block; width: 11px; height: 11px; vertical-align: -1px;
  background: currentColor;
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath d='M1 1.5h10L7 6.5v4l-2-1v-3z'/%3E%3C/svg%3E") center / contain no-repeat;
          mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath d='M1 1.5h10L7 6.5v4l-2-1v-3z'/%3E%3C/svg%3E") center / contain no-repeat;
}
.chart-panel .tile-click-hint[hidden] { display: none; }
.chart-container[tabindex]:focus-visible {
  outline:        2px solid var(--dash-accent, var(--accent));
  outline-offset: 2px;
  border-radius:  var(--dash-radius, 8px);
}
.dash-table tbody tr[tabindex] { cursor: pointer; }
.dash-table tbody tr[tabindex]:focus-visible td { outline: 1px solid var(--dash-accent, var(--accent)); }
.dash-table tbody tr.is-selected td { font-weight: 600; color: var(--text); }
.dash-table tbody tr.is-dimmed td { opacity: .45; }
/* the keyboard path's announcements: read, not shown */
.dash-live {
  position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
.kpi-state { order: 5; font-size: 11px; color: var(--muted); line-height: 1.35; }
.kpi-state[hidden] { display: none; }
.kpi-card.is-error .kpi-state { color: var(--rose, #f43f5e); }
.chart-panel.is-loading .chart-container,
.kpi-card.is-loading .kpi-value-row,
.kpi-card.is-loading .kpi-spark { opacity: .45; transition: opacity .15s; }
@media (prefers-reduced-motion: reduce) {
  .chart-panel.is-loading .chart-container,
  .kpi-card.is-loading .kpi-value-row,
  .kpi-card.is-loading .kpi-spark { transition: none; }
}

/* ── 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 Visual Designer's `section_style` (019c). Absent is the look above. A
   `rule` hangs a hairline under the heading; `band` sets the heading on a soft
   wash of the accent; `bare` is the heading alone, with no line at all. */
#builder-canvas[data-section-style="rule"] .dash-section-hdr {
  padding-bottom: 8px;
  border-bottom:  1px solid var(--dash-card-border, var(--border));
}
#builder-canvas[data-section-style="band"] .dash-section-hdr {
  padding:       9px 14px;
  background:    var(--dash-accent-soft, color-mix(in srgb, var(--dash-accent, var(--accent)) 10%, transparent));
  border-radius: var(--dash-radius, 8px);
}
#builder-canvas[data-section-style="bare"] .dash-section-hdr { border-bottom: 0; padding-bottom: 0; }

/* `plain` KPIs are the number alone: no sparkline (and _initSpark skips it). */
#builder-canvas[data-kpi-style="plain"] .kpi-spark { display: none; }

/* ── 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;
}

/* Reflow (019c): below 1,024 px the board is drawn on 6 columns, below 640 px
   on 2. The vendored gridstack 10.3.1 stylesheet only has the 12- and 1-column
   rules, so these are its width and offset rules for the other two. */
.gs-6 > .grid-stack-item { width: 16.667%; }
.gs-6 > .grid-stack-item[gs-x="1"] { left: 16.667%; }
.gs-6 > .grid-stack-item[gs-x="2"] { left: 33.333%; }
.gs-6 > .grid-stack-item[gs-x="3"] { left: 50%; }
.gs-6 > .grid-stack-item[gs-x="4"] { left: 66.667%; }
.gs-6 > .grid-stack-item[gs-x="5"] { left: 83.333%; }
.gs-6 > .grid-stack-item[gs-w="2"] { width: 33.333%; }
.gs-6 > .grid-stack-item[gs-w="3"] { width: 50%; }
.gs-6 > .grid-stack-item[gs-w="4"] { width: 66.667%; }
.gs-6 > .grid-stack-item[gs-w="5"] { width: 83.333%; }
.gs-6 > .grid-stack-item[gs-w="6"] { width: 100%; }
.gs-2 > .grid-stack-item { width: 50%; }
.gs-2 > .grid-stack-item[gs-x="1"] { left: 50%; }
.gs-2 > .grid-stack-item[gs-w="2"] { width: 100%; }
/* A narrow view is read-only, so it offers none of the width controls. */
#builder-canvas[data-view-cols="6"] .chart-w-btn,
#builder-canvas[data-view-cols="2"] .chart-w-btn,
#builder-canvas[data-view-cols="6"] .chart-drag-handle,
#builder-canvas[data-view-cols="2"] .chart-drag-handle { display: none; }

/* 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; }
/* Studio v2 (design 019a): the colour follows the KPI's polarity — good or bad
   for the business, not up or down — so a fall in cost is green. Neutral
   measures move without a judgement. */
.kpi-delta.is-good    { color: var(--dash-good, var(--emerald, #10b981)); }
.kpi-delta.is-bad     { color: var(--dash-bad, var(--rose, #f43f5e)); }
.kpi-delta.is-neutral { color: var(--muted); }
.kpi-compare { flex-basis: 100%; font-size: 11px; color: var(--muted); }
/* Text rows keep their height; the sparkline keeps at least 28px. In a fixed-
   height grid cell the flex column used to shrink the sparkline to 0px when the
   comparison line and the label took the room (a 3-row KPI). */
.kpi-value-row, .kpi-label { flex-shrink: 0; }
.kpi-spark { order: 3; width: 100%; height: 30px; min-height: 28px; flex: 0 0 auto;
             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); }
.insight-body { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
/* the tile a 019c callout is about — it floats between sections, so it says */
.insight-item { font-size: 11px; color: var(--muted); }

/* ── the narrator's summary (019c) ─────────────────────────────────────────
   Written by Imago, so it wears the product's AI provenance: the petal corner
   reserved for model-written content (imago.css), and a line saying where its
   numbers came from. Self-contained here because the offline file inlines this
   stylesheet and not imago.css. */
.dash-summary {
  display:        flex;
  flex-direction: column;
  gap:            6px;
  padding:        14px 18px;
  background:     var(--ai-bg, color-mix(in srgb, var(--dash-accent, var(--accent)) 6%, var(--dash-card-bg, var(--surface))));
  border:         1px solid var(--ai-border, color-mix(in srgb, var(--dash-accent, var(--accent)) 28%, transparent));
  border-radius:  var(--petal, var(--dash-radius, 8px));
}
/* the petal mirrors on a right-to-left board, as imago.css mirrors .im-ai */
html[data-theme^="imago"] #builder-canvas[dir="rtl"] .dash-summary { border-radius: 4px 24px 4px 24px; }
.dash-summary-text { margin: 0; font-size: 14px; line-height: 1.55; color: var(--text); max-width: 82ch; }
.dash-summary-by   { font-size: 11px; color: var(--ai-ink, var(--muted)); }

/* (the build's notes — 019c's `.dash-notes` — are no longer drawn: 019f) */

/* ── 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 (full-width) chart's grip sits in its header band at the inline
   start, exactly as a boxed tile's does — the title after it, the controls'
   strip capped short of it (above). It used to hang in the gutter at
   `left: -19px` so the heading lined up with the section's, but gridstack
   clips a tile's content (`overflow-x: hidden` on `.grid-stack-item-content`,
   which outranks any rule here): 0 of its 276 pixels could be reached, at
   every density (019h §5). Moved inside the tile, never fought for with a
   more specific overflow override. */

.chart-panel-title {
  /* its own width, not the row's: the click hint sits right after it (fix
     round 4); `margin-inline-start: auto` on the sub keeps "Top 10" at the end */
  flex:           0 1 auto;
  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 board's one hero tile (019c) reads first: its heading one step larger,
   in the heading face, on either chrome tier. */
.chart-panel[data-hero] .chart-panel-title {
  font-family: var(--dash-heading-font, inherit);
  font-size:   14px;
  font-weight: 600;
  color:       var(--text);
}
.chart-panel--bare[data-hero] .chart-panel-title { font-size: 17px; }
/* "Top 10" — a ranking the builder cut short with no "Other" to add up the rest */
.chart-panel-sub { flex: none; margin-inline-start: auto; font-size: 11px; color: var(--muted); white-space: nowrap; }
.chart-panel-sub[hidden] { display: none; }

/* 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;
  /* hidden, it must not take the pointer: at opacity 0 it still covered what
     lies under it — a KPI card's drag handle (019f fix round 1). A keyboard
     still reaches its buttons (focus is not a pointer event), and focus-within
     shows it. */
  pointer-events:  none;
  transition:      opacity 0.15s;
}
.chart-panel:hover .chart-panel-actions,
.chart-panel:focus-within .chart-panel-actions,
.kpi-card:hover .chart-panel-actions,
.kpi-card:focus-within .chart-panel-actions { opacity: 1; }
/* shown, only its buttons take the pointer — never the strip's own box, which
   lies over the header (fix round 4) */
.chart-panel:hover .chart-panel-actions > :not(.tile-click-hint),
.chart-panel:focus-within .chart-panel-actions > :not(.tile-click-hint),
.kpi-card:hover .chart-panel-actions > *,
.kpi-card:focus-within .chart-panel-actions > * { pointer-events: auto; }
/* a KPI card's controls (019f): the same strip as a chart's, at its top end —
   BESIDE its grip, never over it. The grip sits 6px in and is 16px wide (a
   10px glyph, 3px padding a side), so the strip starts 26px in. Logical
   offsets on both, so a right-to-left card mirrors them together. */
.kpi-card .chart-panel-actions { top: 6px; inset-inline-end: 26px; }
.tile-sql-btn { font-family: var(--mono); letter-spacing: .02em; }

/* ── the SQL behind a tile (019f) ─────────────────────────────────────────── */
.tile-sql-card { max-width: 760px; width: min(760px, calc(100vw - 32px)); }
.tile-sql-meta { margin: 2px 0 0; color: var(--muted); font-size: 12px; }
.tile-sql-note { margin: 2px 0 10px; color: var(--muted); font-size: 12px; }
.tile-sql-code {
  margin: 0 0 10px; padding: 10px 12px; max-height: 46vh; overflow: auto;
  background: var(--lift, var(--surface)); border: 1px solid var(--border);
  border-radius: 8px; font-family: var(--mono); font-size: 12px; line-height: 1.5;
  color: var(--text); white-space: pre-wrap; word-break: break-word;
  direction: ltr; text-align: left;
}
.tile-sql-more { margin: 0 0 8px; font-size: 12px; color: var(--muted); }
.tile-sql-more > summary { cursor: pointer; margin-bottom: 6px; }
.tile-sql-more > summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* 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;
  /* Anything drawn wider than its cell is clipped here rather than painted
     over the chart beside it. */
  overflow:   hidden;
}

/* A chart never scrolls inside its tile (019g fix round 4): every chart is
   drawn to the body's size (a Vega horizontal bar's content-sized step height
   overflowed a tile stored before 019g's heading row, and its x title scrolled
   out of sight). Only a table scrolls, in its own wrapper below. A canvas is a
   block: inline, its line's descender space made the body 3 px taller than
   its own height. */
.chart-container canvas,
.chart-container .vega-embed { display: block; }

/* ── the table tile (019c) ────────────────────────────────────────────────
   Up to fifty aggregated rows. It scrolls inside its cell under a header that
   stays put; numbers are right-aligned in tabular figures so they line up. */
.chart-container--table { display: flex; flex-direction: column; }
.dash-table-wrap { flex: 1; min-height: 0; overflow: auto; }
.dash-table {
  width:           100%;
  border-collapse: separate;
  border-spacing:  0;
  font-size:       12px;
  color:           var(--text);
}
.dash-table th {
  position:      sticky;
  top:           0;
  z-index:       1;
  text-align:    start;
  font-weight:   500;
  font-size:     11px;
  color:         var(--muted);
  padding:       6px 10px;
  background:    var(--dash-card-bg, var(--surface));
  border-bottom: 1px solid var(--dash-card-border, var(--border));
  white-space:   nowrap;
}
.dash-table td {
  padding:       5px 10px;
  border-bottom: 1px solid color-mix(in srgb, var(--dash-card-border, var(--border)) 55%, transparent);
  white-space:   nowrap;
}
.dash-table .is-num { text-align: end; font-variant-numeric: tabular-nums; }
.dash-table tbody tr:hover td { background: color-mix(in srgb, var(--text) 4%, transparent); }
.dash-table-note { flex: none; padding-top: 6px; font-size: 11px; color: var(--muted); }

/* A tile whose card could not be built at all (019c): the message in its cell. */
.dash-tile-broken {
  position:      relative;
  flex:          1;
  border:        1px dashed var(--dash-card-border, var(--border));
  border-radius: var(--dash-radius, 8px);
}
.dash-tile-broken > .tile-state { inset: 0; }

/* ── 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);
  /* Drawn from the theme's own ink, so the shimmer shows on a white surface as
     well as a dark one — a white sheen on white was no animation at all. */
  background: linear-gradient(100deg,
    color-mix(in srgb, var(--text) 4%, transparent) 40%,
    color-mix(in srgb, var(--text) 9%, transparent) 50%,
    color-mix(in srgb, var(--text) 4%, transparent) 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; } }

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

.chart-w-btn:focus-visible, .chart-panel-remove: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 ──────────────────────────────────────────────────────── */

/* This file loads after app.css, so this size is the one every small button
   gets; it reads the Imago token app.css's own rule reads. */
.btn-sm {
  padding:   5px 12px;
  font-size: var(--control-sm-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: var(--empty-pad, 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);
}
/* a board built by an engine Imago no longer has (019e): a sentence, not a label */
.dash-row-fw.is-retired {
  color: var(--rose); border-color: color-mix(in srgb, var(--rose) 40%, transparent);
  text-transform: none; letter-spacing: 0;
}
.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); }
/* Pinned to Home (spec 006): the state is in the fill AND in aria-pressed /
   the label, never in the colour alone. */
.dash-row-pin {
  display: grid; place-items: center; width: max(36px, var(--touch-min, 0px)); height: max(36px, var(--touch-min, 0px));
  border-radius: var(--r); color: var(--faint); background: none; border: none; cursor: pointer;
  transition: color .12s, background .12s;
}
.dash-row-pin:hover { color: var(--accent); background: var(--accent-bg); }
.dash-row-pin:focus-visible { outline: 2px solid var(--focus, var(--accent)); outline-offset: 1px; }
.dash-row-pin[aria-pressed="true"] { color: var(--accent); }
.dash-row-pin[aria-pressed="true"] svg { fill: currentColor; }

/* ── 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: var(--type-meta, 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-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: max(11px, var(--type-floor, 0px)); 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: max(11.5px, var(--type-floor, 0px)); 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); }

/* Studio v2 plan (design 019a §5): the filters, each with how much of the board
   it reaches, and the notes — what could not be built, what was replaced. A
   removed filter stays listed, struck through, so it can be kept again. */
.gen-item-kind--filter { color: var(--cyan, var(--muted));
  border-color: color-mix(in srgb, var(--cyan, var(--muted)) 40%, transparent); }
.gen-filter.is-off .gen-item-name,
.gen-filter.is-off .gen-item-note { text-decoration: line-through; color: var(--faint); }
.gen-plan-notes { list-style: none; margin: 8px 0 0; padding: 0; display: flex;
  flex-direction: column; gap: 3px; }
.gen-plan-notes li { font-size: 11.5px; line-height: 1.4; color: var(--muted);
  padding-inline-start: 8px; border-inline-start: 2px solid var(--border); }

/* Studio v2 plan, phase 2 (design 019b §6): what was understood, a sample of
   each item's real data, the person's asks and what became of each, and what
   the data steward said about the tables. Every block exists only when the
   plan carries its field, so a v1 or 019a card renders as before. */
.gen-understood .gen-desc { margin-bottom: 3px; }
.gen-questions { margin: 2px 0 0; padding-inline-start: 18px; color: var(--text); }
.gen-questions li { font-size: 12px; line-height: 1.45; }

.gen-sample { display: flex; flex-wrap: wrap; align-items: center; gap: 6px;
  padding-inline: 5px; margin-top: 2px; }
.gen-sample-label {
  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);
  color: var(--faint);
}
.gen-sample-value { font-family: var(--mono); font-size: 11.5px; color: var(--text);
  font-variant-numeric: tabular-nums; }
.gen-sample-rows { display: flex; flex-direction: column; gap: 1px; font-size: 11.5px;
  color: var(--muted); }
.gen-spark polyline { stroke: var(--accent); stroke-width: 1.5; stroke-linejoin: round;
  stroke-linecap: round; }

.gen-badges { display: flex; gap: 4px; padding-inline: 5px; margin-top: 2px; }
.gen-badge {
  display: inline-block; 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: 1px 6px; border-radius: 999px;
  border: 1px solid var(--border); color: var(--muted); margin-inline-start: 4px;
}
.gen-badges .gen-badge { margin-inline-start: 0; }
.gen-badge.is-warn { color: var(--rose, var(--muted));
  border-color: color-mix(in srgb, var(--rose, var(--muted)) 40%, transparent); }

.gen-filter.is-added .gen-item-kind { color: var(--accent);
  border-color: color-mix(in srgb, var(--accent) 40%, transparent); }
.gen-add-filter { display: flex; align-items: center; gap: 8px; margin-top: 6px;
  font-size: 11.5px; color: var(--muted); }
.gen-add-filter select {
  flex: 1; min-width: 0; font-size: 12px; color: var(--text); background: var(--lift);
  border: 1px solid var(--border); border-radius: 6px; padding: 3px 6px;
}
.gen-add-filter select:focus { outline: none; border-color: var(--accent); }

.gen-ask-status {
  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-ask-status.is-fulfilled { color: var(--emerald, var(--accent));
  border-color: color-mix(in srgb, var(--emerald, var(--accent)) 40%, transparent); }
.gen-ask-status.is-missed { color: var(--rose, var(--muted));
  border-color: color-mix(in srgb, var(--rose, var(--muted)) 40%, transparent); }

.gen-data-table { font-family: var(--mono); font-size: 11px; color: var(--text); }
.gen-data-notes li.is-unverified { border-inline-start-color: var(--rose, var(--border)); }

.gen-injection {
  margin: 8px 0 0; font-size: 11.5px; line-height: 1.4; color: var(--rose, var(--muted));
  padding-inline-start: 8px; border-inline-start: 2px solid var(--rose, var(--border));
}
.gen-card-foot .btn:disabled { opacity: .5; cursor: not-allowed; }

.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; }
/* code's sentence about a change (019d): what the board will really do */
.gen-change-note { display: block; font-size: 11.5px; color: var(--muted); margin: 1px 0 3px; }

.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: var(--page-body-pad, 26px 32px 40px); }
.manage-list, .coll-list { max-width: 1180px; }
.dash-row, .coll-row { padding: var(--row-pad-y, 16px) var(--row-pad-x, 20px); gap: var(--row-gap, 16px); }
.dash-row-icon, .coll-row-icon { width: var(--row-tile, 40px); height: var(--row-tile, 40px); border-radius: 10px; }
.dash-row-title { font-size: var(--type-body, 15.5px); font-weight: var(--weight-strong, 600); }
.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: max(36px, var(--touch-min, 0px)); padding: 0 14px; font-family: var(--sans); font-size: var(--type-secondary, 13.5px); font-weight: 500; border-radius: var(--r); }
.dash-row-del { width: max(36px, var(--touch-min, 0px)); height: max(36px, var(--touch-min, 0px)); }
.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-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); }

/* ── the export dialogs' footer ───────────────────────────────────────────────
   The engine chooser, the engine badge and the "engine · Change" pickers that
   sat here are gone: every new board is ECharts and nothing names the engine
   (owner, 2026-11-14; Vega-Lite deprecated). */

/* the action row of the Tableau, Power BI and deck export dialogs */
.drill-foot { margin-top: 12px; text-align: right; }

/* ── a board's sources (design 025) ─────────────────────────────────────────
   024's band chips on their own row under the board header (D5): the header
   holds the title and up to six actions, and 44 px chips there would wrap them.
   Positioned, so the picker (`.ask-picker`, absolute) anchors under Add source;
   `.ask-scope` (ask.css) gives the chips, the cards and the picker the Ask's
   derived colours and type. */
.builder-sources {
  position: relative; padding: 10px 20px; border-bottom: 1px solid var(--border);
  background: var(--surface); flex-shrink: 0;
}
.builder-sources .ask-picker { top: calc(100% - 4px); }
/* New dashboard: the source cards come first (025) */
.new-dash-section { display: flex; flex-direction: column; margin-top: 4px; }
.new-dash-h { margin: 10px 0 0; font-size: 15px; font-weight: 600; color: var(--text); }
.new-dash-section .modal-sub { margin: 4px 0 12px; }
.new-dash-section .ask-scope { margin-bottom: 18px; }
.new-dash-why { margin-inline-end: auto; align-self: center; font-size: 13px; color: var(--muted); }
