/* Chart Builder — two-pane modal body (spec §06). */

.builder {
  display: grid;
  grid-template-columns: minmax(320px, 360px) 1fr;
  /* Fill the modal body and never exceed it, so each pane scrolls internally rather
     than the whole modal growing past the viewport (otherwise a tall chart pushes the
     Create/Cancel actions below the fold). The `minmax(0, 1fr)` row lets the panes
     shrink below their content height so their own overflow kicks in. */
  grid-template-rows: minmax(0, 1fr);
  height: 100%;
  min-height: 0;
  min-width: 0;
}

/* ── No-dataset states: Carbon no-data empty state / dataset chooser ──── */

.emptyState {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-4);
  max-width: 48ch;
  padding: var(--space-7) var(--space-6);
}

.emptyTitle {
  margin: 0;
  font-size: 16px;
  font-weight: 600;
  color: var(--text);
}

.emptyBody {
  margin: 0;
  font-size: 14px;
  line-height: 1.5;
  color: var(--text-secondary);
}

/* ── Left: configuration ─────────────────────────────────────────────── */

.configPane {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
  padding: var(--space-5);
  border-right: var(--border-width) solid var(--border);
  overflow-y: auto;
  min-width: 0;
  min-height: 0;
}

/* The dataset picker pinned at the top of the config pane. */
.datasetRow {
  display: flex;
  align-items: center;
  gap: var(--space-3);
}

/* Edit-in-place banner (spec §06 → Open in builder): names the snippet the builder
   is editing, so the primary "Save changes" action reads unambiguously. */
.editingBanner {
  margin: 0;
  font-size: 12px;
  color: var(--text-secondary);
}

.editingBanner strong {
  color: var(--text-primary);
  font-weight: 600;
}

/* ── Intent front door (spec §06 → Intent) ───────────────────────────────
   A persistent "what do you want to show?" strip under the dataset picker. A
   quiet accent-soft wash marks it as the guided on-ramp without competing with
   the primary Create action (--accent stays reserved for primary actions). */
.intentStrip {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: var(--space-4);
  border: var(--border-width) solid var(--border);
  background: var(--accent-soft);
}

.intentQ {
  font-size: 13px;
  font-weight: 600;
  color: var(--text);
}

.intentSub {
  margin: 0;
  font-size: 12px;
  line-height: 1.45;
  color: var(--text-secondary);
}

.intentChips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-top: var(--space-1);
}

.intentChip {
  appearance: none;
  height: var(--control-height);
  border: var(--border-width) solid var(--border-strong);
  background: var(--bg);
  color: var(--text);
  font: inherit;
  font-size: 12px;
  padding: 0 var(--space-3);
  cursor: pointer;
  transition:
    background var(--dur-fast) var(--ease),
    box-shadow var(--dur-fast) var(--ease);
}

.intentChip:hover {
  background: var(--field);
}

/* The chip whose layout the live chart matches (council-reserved: selection uses an
   accent ring, never an accent fill — fill stays the primary-action signal). */
.intentChip[aria-pressed='true'] {
  border-color: var(--accent);
  box-shadow: inset 0 0 0 1px var(--accent);
}

/* Disabled = the dataset can't satisfy this intent: perceivable but inert (the chip's
   accessible name carries the reason). */
.intentChip[aria-disabled='true'] {
  color: var(--text-placeholder);
  border-color: var(--border);
  background: var(--bg);
  cursor: not-allowed;
}

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

/* ── Data section: filters, calculated fields, row preview (spec §06 → Data) ──── */

.dataSection {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

.transformGroup {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.transformGroupHead {
  display: flex;
  align-items: center;
  justify-content: space-between;
}

/* "Add filter" / "Add field" — a low-emphasis ghost action, like Swap X/Y. */
.addRow {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  border: none;
  background: transparent;
  color: var(--accent);
  font: inherit;
  font-size: 12px;
  cursor: pointer;
  padding: var(--space-1) var(--space-2);
  border-radius: var(--radius);
}

.addRow:hover {
  background: var(--layer-01);
}

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

/* A filter / calculated-field row, bordered like a channel block for visual rhyme. */
.transformBlock {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: var(--space-3);
  border: var(--border-width) solid var(--border);
  border-radius: var(--radius);
}

.transformBlockTop {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

.filterPredicate {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

.filterPredicate .mini {
  flex: none;
}

.valueInput {
  flex: 1;
  min-width: 0;
  height: var(--control-height);
  padding: 0 var(--space-2);
  font-size: 13px;
}

.rangeDash {
  flex: none;
  color: var(--text-placeholder);
}

/* Expression inputs (filter expression / calculate) — monospace, fill the row. */
.exprInput {
  flex: 1;
  min-width: 0;
  height: var(--control-height);
  padding: 0 var(--space-3);
  font-family: var(--font-mono);
  font-size: 12px;
}

.calcName {
  flex: 0 1 110px;
  min-width: 0;
  height: var(--control-height);
  padding: 0 var(--space-3);
  font-family: var(--font-mono);
  font-size: 12px;
}

.calcEquals {
  flex: none;
  color: var(--text-placeholder);
  font-family: var(--font-mono);
}

.exprInput[aria-invalid='true'] {
  border-color: var(--support-error);
}

/* Remove-row × is the shared IconButton (sm). */

/* Expression escape-hatch toggle — a low-emphasis link, like the hint fixes. */
.modeToggle {
  align-self: flex-start;
  border: none;
  background: transparent;
  color: var(--accent);
  font: inherit;
  font-size: 11px;
  cursor: pointer;
  padding: 2px var(--space-1);
  border-radius: var(--radius);
}

.modeToggle:hover {
  background: var(--layer-01);
}

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

.exprError,
.exprWarn {
  display: flex;
  align-items: flex-start;
  gap: var(--space-2);
  margin: 0;
  font-size: 11px;
  line-height: 1.4;
}

/* Severity reads from the glyph shape + colour, not colour alone (arch 10 §3); the
   icon inherits the line's colour via currentColor (round error / triangle warning). */
.exprFeedbackIcon {
  flex: none;
  margin-top: 1px;
}

.exprError {
  color: var(--support-error);
}

.exprWarn {
  color: var(--support-warning-fg);
}

/* Contextual pointer to the Vega expression docs (shown when an expression exists). */
.exprHelp {
  margin: 0;
  font-size: 11px;
  line-height: 1.4;
  color: var(--text-secondary);
}

.exprHelpLink {
  color: var(--accent);
  text-decoration: none;
  border-radius: var(--radius);
}

.exprHelpLink:hover {
  text-decoration: underline;
}

.exprHelpLink:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
}

/* Row preview disclosure (1D). */
.dataPreview {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.previewToggle {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  align-self: flex-start;
  border: none;
  background: transparent;
  color: var(--text-secondary);
  font: inherit;
  font-size: 12px;
  cursor: pointer;
  padding: var(--space-1) var(--space-2);
  border-radius: var(--radius);
}

.previewToggle:hover {
  background: var(--layer-01);
}

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

.previewCaret {
  font-size: 10px;
  color: var(--text-placeholder);
}

.previewMeta {
  color: var(--text-placeholder);
}

/* The per-column type chip in the source-preview header (the table structure and
   cell styling now live in the shared DataTable). */
.previewColName {
  font-weight: 600;
  color: var(--text-secondary);
}

.previewColType {
  color: var(--text-placeholder);
  font-weight: 400;
}

.previewEmptyNote {
  margin: 0;
  font-size: 11px;
  color: var(--text-secondary);
}

.field {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

/* The mark picker carries six segments — too many for one 320–360px row. Override the
   SegmentedControl's fixed single-row height so it wraps; each option keeps the control
   height so the two rows read as even segments inside the shared border. */
.markPicker {
  flex-wrap: wrap;
  height: auto;
}

.markPickerOption {
  min-height: var(--control-height);
}

.fieldLabel {
  font-size: 12px;
  font-weight: 500;
  color: var(--text-secondary);
}

.swap {
  border: none;
  background: transparent;
  color: var(--accent);
  font: inherit;
  font-size: 12px;
  cursor: pointer;
  padding: var(--space-1) var(--space-2);
  border-radius: var(--radius);
}

.swap:hover {
  background: var(--layer-01);
}

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

/* Channel name label (Marks card rows + the on-chart Columns/Rows shelves). */
.channelLabel {
  font-size: 12px;
  font-weight: 600;
  color: var(--text);
}

.transform {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

.miniLabel {
  font-size: 11px;
  color: var(--text-placeholder);
}

.toggle {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 12px;
  color: var(--text-secondary);
}

.chartControls {
  display: flex;
  gap: var(--space-5);
}

.dimInput {
  width: 100px;
  height: var(--control-height);
  padding: 0 var(--space-3);
  font-size: 13px;
}

.warnings {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  margin: 0;
  padding: var(--space-3);
  list-style: none;
  background: var(--layer-01);
  border: var(--border-width) solid var(--border);
  border-radius: var(--radius);
}

/* These take focus only programmatically (tabIndex -1) after a hint fix is applied,
   to keep focus off <body>; no visible ring for that script-driven move. */
.warnings:focus,
.configPane:focus {
  outline: none;
}

.warning {
  display: flex;
  align-items: flex-start;
  gap: var(--space-2);
  font-size: 12px;
  line-height: 1.4;
  color: var(--text-secondary);
}

/* Filled status-warning glyph (arch 09 §5.2), in the contrast-safe amber. Replaces
   the old ⚠ text character so the warning matches the toast status family. */
.warningIcon {
  flex: none;
  margin-top: 1px;
  color: var(--support-warning-fg);
}

/* The hint text and its one-click remedies stacked, growing beside the icon. */
.warningBody {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  min-width: 0;
}

/* Actionable-hint remedies: each fix is an offer, never forced — low-emphasis
   suggestions beside the advice, not commands. */
.warningFixes {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}

/* Inline link-style actions (arch 09 §4): content-sized accent text, no box —
   the smallness is the emphasis level. */
.warningFix {
  font: inherit;
  font-size: 12px;
  cursor: pointer;
  padding: var(--space-1) var(--space-2);
  border: none;
  border-radius: var(--radius);
  background: transparent;
  color: var(--accent);
}

.warningFix:hover {
  background: var(--control-hover-fill, var(--layer-01));
}

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

/* Explains the disabled Create action (contract 10: a disabled control must say
   why). `margin-top: auto` pins it just above the actions so the two read as one. */
.createHint {
  margin: auto 0 0;
  font-size: 12px;
  color: var(--text-secondary);
  text-align: right;
}

/* Cancel / Create are shared Buttons (secondary / primary, lg — modal footer). */
.actions {
  display: flex;
  justify-content: flex-end;
  gap: var(--space-3);
  margin-top: auto;
}

/* ── Right: live preview ─────────────────────────────────────────────── */

/* Right side = on-chart shelves stacked above the live preview (spec §06). */
.previewSide {
  display: flex;
  flex-direction: column;
  min-width: 0;
  min-height: 0;
  background: var(--bg);
}

.previewPane {
  display: flex;
  flex-direction: column;
  flex: 1;
  padding: var(--space-5);
  min-width: 0;
  min-height: 0;
  overflow: hidden;
  background: var(--bg);
}

.previewHint {
  margin: auto;
  font-size: 13px;
  color: var(--text-secondary);
  text-align: center;
}

.previewFrame {
  flex: 1;
  min-width: 0;
  min-height: 0;
  display: flex;
  /* A chart taller/wider than the pane scrolls *here*, inside a fixed viewport, so
     the modal keeps its shape. `safe` centring aligns to the start instead of
     clipping the top/left when the chart overflows. */
  overflow: auto;
  align-items: safe center;
  justify-content: safe center;
}

.previewHost {
  width: 100%;
}

.previewError {
  margin: auto 0;
  padding: var(--space-3);
}

.previewErrorHeadline {
  margin: 0;
  font-size: 13px;
  line-height: 1.5;
  color: var(--support-error);
}

.previewErrorDetails {
  margin-top: var(--space-2);
}

.previewErrorSummary {
  font-size: 12px;
  color: var(--text-secondary);
  cursor: pointer;
  user-select: none;
}

.previewErrorDetail {
  margin: var(--space-2) 0 0;
  padding: var(--space-3);
  font-family: var(--font-mono);
  font-size: 12px;
  line-height: 1.4;
  color: var(--text);
  background: var(--layer-01);
  border: var(--border-width) solid var(--border);
  white-space: pre-wrap;
  word-break: break-word;
}

/* ── Encoding: field shelf + Tableau-style shelves/pills (spec §06 → Encoding) ─── */

/* Field shelf — the dataset's columns as clickable, type-glyphed chips (field-first). */
.fieldShelf {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.shelfGroupHead {
  margin-top: var(--space-2);
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--text-secondary);
}

/* Scroll viewport around the field lists: a wide dataset (Superstore: 25 columns)
   must not push the Marks card and chart controls below the fold. Group headings
   pin to the top while their list scrolls under them. */
.shelfScroll {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  max-height: 280px;
  overflow-y: auto;
  /* Scroll shadows (background-attachment trick): a soft cue at the cut edge that
     appears only while there is more list in that direction. The covers (first
     two layers, attachment: local) ride with the content and hide the shadows at
     the ends; rows are transparent so the shadows show through between them. */
  background:
    linear-gradient(var(--bg) 30%, transparent),
    linear-gradient(transparent, var(--bg) 70%) 0 100%,
    linear-gradient(rgb(0 0 0 / 0.1), transparent),
    linear-gradient(transparent, rgb(0 0 0 / 0.1)) 0 100%;
  background-repeat: no-repeat;
  background-size:
    100% 24px,
    100% 24px,
    100% 8px,
    100% 8px;
  background-attachment: local, local, scroll, scroll;
}

/* An armed channel makes the shelf the live target — say so where the next click
   happens (NN/g #1): an accent ring on the list plus the status line above it. */
.shelfArmed {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: var(--radius);
}

.armedHint {
  margin: 0;
  font-size: 12px;
  color: var(--text-secondary);
}

.armedHint strong {
  color: var(--accent);
}

.shelfScroll .shelfGroupHead {
  position: sticky;
  top: 0;
  z-index: 1;
  margin-top: 0;
  padding: var(--space-2) 0;
  background: var(--layer-01);
}

.shelfList {
  display: flex;
  flex-direction: column;
}

/* Field rows are FLAT — hairline dividers and a hover fill, not per-row boxes:
   25 outlined rectangles read as noise, and flat rows are denser, so more of a
   wide dataset is visible before the shelf scrolls (arch 09 §4 — boxes are for
   inputs and drop targets, not list rows). */
.shelfField {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  width: 100%;
  padding: var(--space-2) var(--space-2);
  border: none;
  background: transparent;
  color: var(--text);
  font: inherit;
  font-size: 13px;
  text-align: left;
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease);
}

.shelfField + .shelfField {
  border-top: var(--border-width) solid var(--border);
}

.shelfField:hover {
  background: var(--layer-01);
}

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

.shelfFieldUsed {
  opacity: 0.55;
}

.shelfGlyph {
  min-width: 22px;
  padding: 1px var(--space-2);
  /* A passive type tag, not a control — subtle border (borders mark function). */
  border: var(--border-width) solid var(--border);
  background: var(--layer-01);
  color: var(--text-secondary);
  font-family: var(--font-mono);
  font-size: 11px;
  text-align: center;
}

.shelfFieldName {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Marks card — the non-positional encodings (Colour, Size). */
.marksCard {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding: var(--space-3);
  border: var(--border-width) solid var(--border);
  border-radius: var(--radius);
}

.marksRow {
  display: grid;
  grid-template-columns: 44px 1fr;
  align-items: start;
  gap: var(--space-2);
}

/* Pill — a bound channel (field/count or constant). The per-channel transform
   controls (Aggregate/Bin/Granularity) flow inline beside the pill, wrapping only
   when the row truly runs out of room (a narrow window; the Marks card). */
.pillWrap {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2) var(--space-3);
  min-width: 0;
}

.pill {
  display: inline-flex;
  align-items: stretch;
  /* One pill height, and a floor on width so "Count" and "Customer Name" read as
     the same species; names stay content-sized above the floor (truncating to
     equal widths would cost more than it buys). */
  height: 24px;
  min-width: 96px;
  max-width: 100%;
  background: var(--accent);
  color: var(--accent-contrast);
  border-radius: var(--radius);
}

.pillConst {
  background: var(--layer-02);
  color: var(--text);
}

.pillType {
  display: inline-flex;
  align-items: center;
  padding: 0 var(--space-2);
  border: none;
  background: color-mix(in srgb, #000 18%, var(--accent));
  color: var(--accent-contrast);
  font-family: var(--font-mono);
  font-size: 11px;
  line-height: 1;
  cursor: pointer;
}

.pillType:hover:not(:disabled) {
  background: color-mix(in srgb, #000 30%, var(--accent));
}

.pillType:disabled {
  cursor: default;
  opacity: 0.85;
}

.pillType[aria-expanded='true'] {
  background: color-mix(in srgb, #000 30%, var(--accent));
}

.pillType:focus-visible {
  outline: 2px solid var(--accent-contrast);
  outline-offset: -2px;
}

.pillTag {
  display: inline-flex;
  align-items: center;
  padding: 0 var(--space-2);
  color: var(--text-secondary);
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.03em;
}

.pillName {
  /* Grow into any pill slack so the remove ✕ stays at the pill's right edge. */
  flex: 1;
  min-width: 0;
  align-self: center;
  padding: 0 var(--space-3);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 12px;
}

.pillRemove {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0 var(--space-2);
  border: none;
  background: transparent;
  color: inherit;
  cursor: pointer;
}

.pillRemove:hover {
  background: color-mix(in srgb, var(--text) 14%, transparent);
}

.pillRemove:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: -2px;
}

/* Sizing + chrome come from ColorField (size="sm"); only the pill margin is local. */
.constColor {
  margin: 0 var(--space-2);
}

.constNumber {
  width: 64px;
  margin: 0 var(--space-2);
  padding: var(--space-1) var(--space-2);
  font-size: 12px;
}

.pillControls {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
}

/* Empty assign target — armed by click, then filled by a clicked field. */
.slot {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-height: var(--control-height);
  padding: var(--space-1) var(--space-2);
  border: var(--border-width) dashed var(--border-strong);
  background: var(--layer-01);
}

.slotActive {
  border-style: solid;
  border-color: var(--accent);
  box-shadow: inset 0 0 0 1px var(--accent);
}

.slotAssign {
  flex: 1;
  min-width: 0;
  padding: var(--space-1);
  border: none;
  background: transparent;
  color: var(--text-placeholder);
  font: inherit;
  font-size: 12px;
  text-align: left;
  cursor: pointer;
}

.slotAssign:hover {
  color: var(--text-secondary);
}

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

/* Ghost button (Carbon: links navigate, buttons act — this one acts). */
.slotConst {
  padding: var(--space-1) var(--space-2);
  border: none;
  border-radius: var(--radius);
  background: none;
  color: var(--accent);
  font: inherit;
  font-size: 11px;
  white-space: nowrap;
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease);
}

.slotConst:hover {
  background: var(--layer-02);
  color: var(--accent-hover);
}

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

/* On-chart Columns / Rows shelves (X/Y), stacked above the preview. */
.shelves {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding: var(--space-5) var(--space-5) 0;
}

/* Swap sits beside the heading — an action flushed to the far edge of a very wide
   pane reads as unrelated to the shelves it acts on. */
.shelvesHead {
  display: flex;
  align-items: baseline;
  gap: var(--space-4);
}

.shelfStrip {
  display: flex;
  align-items: stretch;
  /* One fixed minimum for BOTH strips — sized to the tallest occupant (a 32px
     control + the slot padding), so Columns (which carries the Aggregate select)
     and Rows stay the same height instead of each hugging its own content. */
  min-height: 48px;
  border: var(--border-width) solid var(--border);
}

.shelfName {
  flex: 0 0 84px;
  display: flex;
  align-items: center;
  padding: 0 var(--space-3);
  border-right: var(--border-width) solid var(--border);
  background: var(--layer-01);
  color: var(--text-secondary);
  font-size: 12px;
}

.shelfSlots {
  flex: 1;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
  min-width: 0;
  padding: var(--space-2) var(--space-3);
}

/* Empty slots keep a minimum presence; a filled pill hugs its content so the
   remove ✕ sits at its right edge rather than adrift in dead pill background. */
.shelfSlots .slot {
  min-width: 190px;
}

/* Chart properties — title/subtitle/size as a slim strip pinned under the preview
   (chart-level properties live on the chart side; council 2026-06-12). */
.chartProps {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-4);
  padding: var(--space-3) var(--space-5);
  border-top: var(--border-width) solid var(--border);
}

.propField {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font-size: 12px;
  color: var(--text-secondary);
  white-space: nowrap;
}

.propInput {
  width: 200px;
  height: var(--control-height);
  padding: 0 var(--space-2);
  font-size: 13px;
}

.propInput:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}
