/* Chromatic tokens (--bg, --panel, --accent, ...) live in
   theme.css, auto-generated from palette.py by web/scripts/build.py.
   This file is hand-edited layout/structure that REFERENCES those
   tokens; edit palette.py to change colors, both targets pick up
   the change on the next build. */

/* Spatial tokens. One-off values that appear exactly once stay as
   literals; naming them would spread the value across grep results
   without removing a decision. */
:root {
  --space-xs: 4px;
  --space-sm: 6px;
  --space-md: 8px;
  --space-lg: 12px;
  --space-xl: 16px;
  --radius-sm: 4px;
  --radius-md: 6px;
  --radius-lg: 8px;
  --border-thin: 1px;
  --border-std: 1.5px;
  --border-thick: 2px;
  --control-h-md: 32px;   /* toolbar buttons / selects */
  --control-h-sm: 26px;   /* segment buttons */
  --control-h-xs: 22px;   /* panel clear button */
  --seg-btn-min-w: 33px;
  --feat-btn-w: 28px;
  --feat-btn-h: 24px;     /* also feat-badge height */
  --feat-badge-w: 30px;
  --font-size-base: 14px;
  --font-size-control: 13px;
  --font-size-meta: 12px;
  --font-size-label: 11px;
  --font-size-micro: 10px;
  /* `system-ui` and `ui-monospace` map to the OS's native UI fonts
     on modern browsers (SF Pro on macOS, Segoe UI Variable on
     Windows 11, Roboto on Android). The named fallbacks cover older
     browsers and Linux. */
  --font-ui:
    system-ui, -apple-system, BlinkMacSystemFont,
    "Segoe UI Variable", "Segoe UI",
    Roboto, "Helvetica Neue", Arial, sans-serif;
  --font-ipa:
    ui-monospace, "SF Mono", "Cascadia Code", "JetBrains Mono",
    "Noto Sans Mono", "Menlo", Consolas, "DejaVu Sans Mono",
    monospace;
}

/* Native UI palette (scrollbars, form controls). Paired with the
   JS-driven data-theme attribute. */
html { color-scheme: light; }
html[data-theme="dark"] { color-scheme: dark; }

* { box-sizing: border-box; }

/* Keyboard-only focus ring (mouse clicks don't trigger it). */
:focus-visible {
  outline: var(--border-thick) solid var(--accent);
  outline-offset: 2px;
}
:focus:not(:focus-visible) { outline: none; }

/* Bridge-gated toolbar controls start disabled in HTML; main.js
   enables them after Pyodide attaches. Style so the disabled state
   reads as "loading" rather than "broken". */
.toolbar :disabled {
  opacity: 0.55;
  cursor: progress;
}

html, body {
  height: 100%;
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-ui);
  font-size: var(--font-size-base);
  line-height: 1.4;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
  font-feature-settings: "kern" 1, "liga" 1, "calt" 1;
}

/* Tabular nums keep digit columns from jittering when the value
   changes (status counts, analysis tables). */
.statusbar,
.analysis-content {
  font-variant-numeric: tabular-nums;
}

/* Body grid rows: toolbar | panels (1fr) | analysis | statusbar.
   Panels gets the variable space so the analysis pane stays at a
   stable height regardless of inventory size; segments that don't
   fit are moved into .seg-spillover by main.js. */
body {
  display: grid;
  grid-template-rows: auto 1fr auto auto;
  height: 100vh;
  height: 100dvh;
}

/* ---------- toolbar ---------- */

.toolbar {
  display: flex;
  align-items: center;
  gap: var(--space-md);
  padding: var(--space-md) var(--space-lg);
  background: var(--panel);
  border-bottom: var(--border-thin) solid var(--border);
}
.toolbar select,
.toolbar button {
  height: var(--control-h-md);
  padding: 0 var(--space-lg);
  border: var(--border-std) solid var(--border);
  border-radius: var(--radius-md);
  background: var(--bg);
  color: var(--text);
  font: inherit;
  cursor: pointer;
}
.toolbar select:hover,
.toolbar button:hover {
  border-color: var(--accent);
  color: var(--accent);
  background: var(--accent-light);
}
.toolbar-spacer { flex: 1; }
#theme-btn {
  width: var(--control-h-md);
  padding: 0;
  font-size: 16px;
}
/* Pencil-icon rename button. Sized like #theme-btn so square-icon
   buttons share one footprint. Sits next to the picker so the
   "edit the displayed inventory" affordance is local to its target. */
#rename-btn {
  width: var(--control-h-md);
  padding: 0;
  font-size: 14px;
}

/* ---------- dialogs (rename, future modal flows) ---------- */
dialog {
  border: var(--border-thin) solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--panel);
  color: var(--text);
  padding: 20px 24px;
  min-width: 320px;
  max-width: 480px;
  font: inherit;
}
dialog::backdrop {
  background: rgba(0, 0, 0, 0.45);
}
dialog form {
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
}
.dialog-title {
  font-weight: bold;
  font-size: 15px;
}
.dialog-label {
  font-size: var(--font-size-meta);
  color: var(--text-dim);
}
dialog input[type="text"] {
  height: var(--control-h-md);
  padding: 0 10px;
  border: var(--border-std) solid var(--border);
  border-radius: var(--radius-md);
  background: var(--bg);
  color: var(--text);
  font: inherit;
}
dialog input[type="text"]:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -1px;
  border-color: var(--accent);
}
.dialog-error {
  min-height: 18px;
  font-size: var(--font-size-meta);
  color: var(--minus);
}
.dialog-actions {
  display: flex;
  justify-content: flex-end;
  gap: var(--space-md);
  margin-top: var(--space-sm);
}
.dialog-actions button {
  height: var(--control-h-md);
  padding: 0 var(--space-lg);
  border: var(--border-std) solid var(--border);
  border-radius: var(--radius-md);
  background: var(--bg);
  color: var(--text);
  font: inherit;
  cursor: pointer;
}
.dialog-actions button:hover {
  border-color: var(--accent);
  color: var(--accent);
  background: var(--accent-light);
}
.dialog-actions button[type="submit"] {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--panel);
}
.dialog-actions button[type="submit"]:hover {
  /* Keep the high-contrast accent fill on hover; just nudge the
     border so the press affordance is still readable. */
  filter: brightness(0.92);
}

/* New-inventory setup dialog. Wider than the rename modal because
   the segments and features textareas need horizontal room for a
   typical 30+-entry feature list to read comfortably. */
.setup-dialog {
  min-width: 520px;
  max-width: 720px;
}
.setup-dialog textarea {
  width: 100%;
  box-sizing: border-box;
  padding: 8px 10px;
  border: var(--border-std) solid var(--border);
  border-radius: var(--radius-md);
  background: var(--bg);
  color: var(--text);
  font: inherit;
  font-family: var(--mono-family, ui-monospace, monospace);
  resize: vertical;
}
.setup-dialog textarea:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -1px;
  border-color: var(--accent);
}
.setup-field-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-md);
}
.setup-field-header select {
  height: var(--control-h-md);
  padding: 0 var(--space-md);
  border: var(--border-std) solid var(--border);
  border-radius: var(--radius-md);
  background: var(--panel);
  color: var(--text);
  font: inherit;
}
.setup-field-header select:hover {
  border-color: var(--accent);
}

/* ---------- two-pane split (segments | features) ---------- */

.grid {
  display: grid;
  grid-template-columns: minmax(420px, 3fr) minmax(380px, 2fr);
  gap: var(--space-md);
  padding: var(--space-md);
  min-height: 0;
  overflow: hidden;
}

/* Spillover for consonant groups that don't fit in the segments
   pane (only triggers for large inventories like General IPA).
   main.js's rebalanceSegmentSpillover moves the bottom groups
   here in 2 content-width columns. The grid stays empty for
   inventories that already fit. */
.seg-spillover {
  display: grid;
  grid-template-columns: auto auto;
  column-gap: 32px;
  row-gap: 0;
  margin-top: 0;
  justify-content: start;
}

@media (max-width: 900px) {
  /* Narrow screens stack vertically; the spillover's 2 columns
     would just split the already-narrow pane. */
  .grid {
    grid-template-columns: 1fr;
    overflow: auto;
  }
  .seg-spillover { grid-template-columns: 1fr; }
}

.panel {
  display: flex;
  flex-direction: column;
  background: var(--bg);
  border: var(--border-std) solid var(--border);
  border-radius: var(--radius-lg);
  min-height: 0;
  overflow: hidden;
  /* Isolate the panel subtree so a re-render in one panel can't
     reflow/repaint the other panel or the analysis pane. */
  contain: layout style;
}
.panel[data-active="true"] {
  background: var(--panel);
  border-color: var(--accent);
}
.panel-header {
  display: flex;
  align-items: center;
  gap: var(--space-md);
  padding: var(--space-sm) var(--space-lg);
  border-bottom: var(--border-thin) solid var(--border);
}
.panel-title {
  flex: 1;
  font-size: var(--font-size-label);
  font-weight: 600;
  letter-spacing: 0.5px;
  color: var(--text-dim);
}
.panel[data-active="true"] .panel-title {
  color: var(--text);
}
.panel-clear-btn {
  height: var(--control-h-xs);
  padding: 0 10px;
  border: var(--border-thin) solid var(--border);
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--text-dim);
  font: inherit;
  font-size: var(--font-size-label);
  cursor: pointer;
}
.panel-clear-btn:hover {
  color: var(--accent);
  border-color: var(--accent);
  background: var(--accent-light);
}
.panel-body {
  flex: 1;
  overflow: auto;
  padding: var(--space-lg);
  /* Reserve the scrollbar gutter so content doesn't shift
     horizontally when overflow appears. */
  scrollbar-gutter: stable;
}

/* ---------- segment grid ---------- */

/* Vowel chart floats top-right; consonant groups wrap around it
   like text around an image. Plain block elements (not grid) so
   the float-wrap algorithm decides each row's width. */
#seg-grid {
  display: block;
}
#seg-grid::after {
  /* Clearfix so the panel-body overflow calc includes the float. */
  content: "";
  display: block;
  clear: both;
}
.seg-vowels {
  float: right;
  margin: 0 0 var(--space-md) var(--space-xl);
  /* Cap the chart so consonants always have room to wrap left. */
  max-width: 55%;
}
.seg-group {
  margin-bottom: var(--space-md);
  min-width: 0;
}
.seg-group-header {
  font-size: var(--font-size-label);
  font-weight: 600;
  letter-spacing: 0.5px;
  color: var(--text-dim);
  padding: var(--space-xs) 2px var(--space-sm);
  overflow-wrap: anywhere;
}
.panel[data-active="true"] .seg-group-header {
  color: var(--text);
}
.seg-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-xs);
}
.seg-btn {
  min-width: var(--seg-btn-min-w);
  height: var(--control-h-sm);
  padding: 0 var(--space-sm);
  border-radius: var(--radius-lg);
  border: var(--border-std) solid var(--border);
  background: var(--seg-default);
  color: var(--text);
  font-family: var(--font-ipa);
  font-size: var(--font-size-control);
  cursor: pointer;
  /* Multi-character segments like /tʃ/, /dʒ/, /aɪ/ stay on one
     line; the button widens instead. */
  white-space: nowrap;
}
.seg-btn:hover {
  background: var(--accent-light);
  border-color: var(--accent);
}
.seg-btn[data-state="selected"],
.seg-btn[data-state="matched"] {
  background: var(--seg-selected);
  color: #FFF;
  border: var(--border-thick) solid var(--accent);
  font-weight: bold;
}
.seg-btn[data-state="unmatched"] {
  background: var(--seg-unmatched);
  color: var(--text-dim);
  border: var(--border-thin) solid var(--border);
}
.seg-btn[data-state="suggested"] {
  background: var(--accent-light);
  color: var(--accent);
  border: var(--border-std) dashed var(--accent);
}

/* ---------- IPA vowel trapezoid ----------
   6 height rows × 6 backness-rounding columns. Placement comes
   from Python (gui.vowel_layout.vowel_grid_pos) so the chart
   matches the desktop's VowelChartWidget. */

.vowel-chart-group {
  break-inside: avoid;
  margin-top: var(--space-xs);
}
.vowel-chart {
  display: grid;
  /* Column 1: row labels. Columns 2-7 pair unrounded + rounded
     per backness (front, central, back). */
  grid-template-columns: minmax(60px, auto) repeat(6, minmax(28px, 1fr));
  /* Min height keeps single-button rows compact; auto lets a row
     expand to fit a tall collision-cell stack (e.g. ə / ɜ / ɚ all
     landing in open-mid central for the General inventory).
     Mirrors the desktop's QGridLayout, where the row height grows
     to fit its tallest cell. */
  grid-auto-rows: minmax(var(--control-h-sm), auto);
  column-gap: var(--space-xs);
  row-gap: 2px;
  padding: var(--space-xs) 2px;
  align-items: center;
}
.vowel-chart-corner {
  grid-row: 1;
  grid-column: 1;
}
.vowel-chart-col-label {
  grid-row: 1;
  text-align: center;
  font-size: var(--font-size-micro);
  font-weight: 600;
  letter-spacing: 0.5px;
  color: var(--text-dim);
  padding-bottom: 2px;
}
.vowel-chart-row-label {
  text-align: right;
  padding-right: var(--space-sm);
  font-size: var(--font-size-micro);
  font-weight: 600;
  letter-spacing: 0.3px;
  color: var(--text-dim);
}
.panel[data-active="true"] .vowel-chart-col-label,
.panel[data-active="true"] .vowel-chart-row-label {
  color: var(--text);
}
.vowel-chart-cell {
  /* Each grid cell is wider than a button so the trapezoid keeps
     its proportions; center the button within the cell. */
  justify-self: center;
}
/* Vertical stack for cells holding multiple vowels (e.g. ə / ɜ / ɚ
   all landing in open-mid central). Mirrors the desktop's
   QVBoxLayout collision handling so each vowel stays independently
   visible and clickable instead of overlapping at the same
   CSS-grid coordinates. */
.vowel-chart-cell-stack {
  display: flex;
  flex-direction: column;
  gap: 1px;
  background: transparent;
}

/* ---------- feature panel ----------
   Two columns of named cards. JS decides which card goes in which
   column (gui.layout.distribute_feature_groups); we just space
   them. Narrow viewports stack vertically. */

#feat-list {
  display: flex;
  gap: var(--space-xl);
  align-items: flex-start;
}
.feat-col {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: var(--space-lg);
  min-width: 0;
}
@media (max-width: 600px) {
  #feat-list { flex-direction: column; }
}
.feat-group {
  margin-bottom: 0;
}
.feat-group-header {
  font-size: var(--font-size-label);
  font-weight: 600;
  letter-spacing: 0.5px;
  color: var(--text-dim);
  padding: var(--space-xs) 2px var(--space-sm);
  overflow-wrap: anywhere;
}
.panel[data-active="true"] .feat-group-header {
  color: var(--text);
}
.feat-row {
  display: flex;
  align-items: center;
  gap: var(--space-xs);
  padding: 2px var(--space-md);
  border-radius: var(--radius-md);
}

/* Row backgrounds are mode-dependent. SEG mode (feat panel
   inactive) encodes the analysis result via data-shared /
   data-value / data-contrastive. FEAT mode (feat panel active)
   echoes the user's query via data-query-value, mirroring the
   desktop's _apply_query_style. The selectors gate by panel
   active state so a stale data attribute from one mode can't
   bleed into the other. */
#feat-panel:not([data-active="true"]) .feat-row[data-shared="true"][data-value="+"] {
  background: var(--shared-plus);
}
#feat-panel:not([data-active="true"]) .feat-row[data-shared="true"][data-value="-"] {
  background: var(--shared-minus);
}
#feat-panel:not([data-active="true"]) .feat-row[data-contrastive="true"] {
  background: var(--accent-light);
}
#feat-panel[data-active="true"] .feat-row[data-query-value="+"] {
  background: var(--shared-plus);
}
#feat-panel[data-active="true"] .feat-row[data-query-value="-"] {
  background: var(--shared-minus);
}

.feat-name {
  flex: 1;
  color: var(--text);
  /* User-uploaded inventories may carry long feature names; wrap
     on any character rather than blowing out the row. */
  overflow-wrap: anywhere;
}
#feat-panel:not([data-active="true"]) .feat-row[data-contrastive="true"] .feat-name {
  color: var(--accent);
  font-weight: bold;
}
#feat-panel:not([data-active="true"]) .feat-row[data-shared="true"] .feat-name {
  font-weight: bold;
}
#feat-panel[data-active="true"] .feat-row[data-query-value="+"] .feat-name,
#feat-panel[data-active="true"] .feat-row[data-query-value="-"] .feat-name {
  font-weight: bold;
}

.feat-badge {
  width: var(--feat-badge-w);
  height: var(--feat-btn-h);
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-sm);
  font-weight: bold;
  background: var(--tag-gray);
  color: var(--text-dim);
}
.feat-row[data-shared="true"][data-value="+"] .feat-badge {
  background: var(--plus-bg); color: var(--plus);
}
.feat-row[data-shared="true"][data-value="-"] .feat-badge {
  background: var(--minus-bg); color: var(--minus);
}
.feat-row[data-contrastive="true"] .feat-badge {
  background: var(--accent-light); color: var(--accent);
}

.feat-btn {
  width: var(--feat-btn-w);
  height: var(--feat-btn-h);
  border: var(--border-std) solid var(--border);
  border-radius: 5px;
  background: var(--analysis-bg);
  color: var(--text-dim);
  font-weight: bold;
  cursor: pointer;
}
.feat-btn[data-polarity="+"]:hover,
.feat-btn[data-polarity="+"][data-active="true"] {
  background: var(--plus-bg);
  color: var(--plus);
  border-color: var(--plus);
}
.feat-btn[data-polarity="-"]:hover,
.feat-btn[data-polarity="-"][data-active="true"] {
  background: var(--minus-bg);
  color: var(--minus);
  border-color: var(--minus);
}

/* The inactive panel is display-only: badge visible, buttons
   hidden; reversed in the active panel. */
.panel:not([data-active="true"]) .feat-btn { display: none; }
.panel[data-active="true"] .feat-badge { display: none; }

/* ---------- analysis pane ----------
   Fixed at 220 px (min == max) so a long multi-segment analysis
   can't eat enough vertical space to crowd the segments pane.
   Inside, .analysis-content's overflow:auto handles content
   that exceeds the visible area. The expand button overrides
   min-height to 55vh; min-height wins over max-height per CSS. */

.analysis {
  background: var(--analysis-bg);
  border-top: var(--border-thin) solid var(--border);
  padding: var(--space-lg) var(--space-xl);
  display: flex;
  flex-direction: column;
  min-height: 220px;
  max-height: 220px;
}
.analysis.expanded {
  min-height: 55vh;
  min-height: 55dvh;
}
.analysis-header {
  display: flex;
  align-items: center;
  gap: var(--space-md);
  font-size: var(--font-size-label);
  font-weight: 600;
  letter-spacing: 0.5px;
  color: var(--text-dim);
}
.analysis-title { flex: 1; }
#expand-btn {
  width: var(--feat-btn-h);
  height: 20px;
  background: transparent;
  border: none;
  color: var(--text-dim);
  font-size: var(--font-size-base);
  cursor: pointer;
}
#expand-btn:hover { color: var(--text); }
.analysis-content {
  margin-top: var(--space-md);
  background: var(--panel);
  border: var(--border-thin) solid var(--border);
  border-radius: var(--radius-md);
  padding: var(--space-md) var(--space-lg);
  overflow: auto;
  scrollbar-gutter: stable;
  flex: 1;
  font-size: var(--font-size-control);
  color: var(--text);
  /* Innerhtml swaps are the most frequent DOM update; contain so
     they don't reflow the panels or status bar. */
  contain: layout style;
}
.analysis-content p { margin: 0 0 var(--space-md); }
.analysis-content b { color: var(--text); }
.analysis-content table { border-collapse: collapse; }
.analysis-content td { padding-bottom: 3px; }

/* ---------- status bar ---------- */

.statusbar {
  background: var(--panel);
  border-top: var(--border-thin) solid var(--border);
  padding: var(--space-sm) var(--space-lg);
  font-size: var(--font-size-meta);
  color: var(--text-dim);
}

/* ---------- loading overlay ---------- */

.loading {
  position: fixed;
  inset: 0;
  background: var(--bg);
  z-index: 1000;
  display: flex;
  align-items: center;
  justify-content: center;
}
.loading.hidden { display: none; }
.loading-card {
  background: var(--panel);
  border: var(--border-thin) solid var(--border);
  border-radius: var(--radius-lg);
  padding: 24px 32px;
  text-align: center;
  max-width: 360px;
}
.loading-title {
  font-weight: bold;
  font-size: 16px;
  margin-bottom: var(--space-md);
}
.loading-status {
  color: var(--text-dim);
  margin-bottom: var(--space-lg);
  min-height: 20px;
}
progress {
  width: 100%;
}

/* ---------- builder editor (full-screen overlay) ---------- */

/* Full-viewport overlay so the viewer chrome stays in the DOM
   (no rebuild on toggle) but is visually hidden while the editor
   is up. Display rows: toolbar | meta | grid (1fr) | status. */
.editor-view {
  position: fixed;
  inset: 0;
  z-index: 100;
  background: var(--bg);
  display: grid;
  grid-template-rows: auto auto 1fr auto;
}
.editor-view[hidden] { display: none; }
.editor-toolbar {
  display: flex;
  align-items: center;
  gap: var(--space-md);
  padding: var(--space-md) var(--space-lg);
  background: var(--panel);
  border-bottom: var(--border-thin) solid var(--border);
}
.editor-toolbar button {
  height: var(--control-h-md);
  padding: 0 var(--space-lg);
  border: var(--border-std) solid var(--border);
  border-radius: var(--radius-md);
  background: var(--bg);
  color: var(--text);
  font: inherit;
  cursor: pointer;
}
.editor-toolbar button:hover {
  border-color: var(--accent);
  color: var(--accent);
  background: var(--accent-light);
}
.editor-toolbar-spacer { flex: 1; }
.editor-toolbar-sep {
  width: 1px;
  align-self: stretch;
  background: var(--border);
  margin: 0 var(--space-sm);
}
.editor-toolbar button[disabled] {
  background: var(--btn-disabled-bg);
  color: var(--btn-disabled-text);
  border-color: var(--btn-disabled-border);
  cursor: not-allowed;
}
.editor-toolbar button[disabled]:hover {
  background: var(--btn-disabled-bg);
  color: var(--btn-disabled-text);
  border-color: var(--btn-disabled-border);
}
.editor-meta {
  display: flex;
  align-items: center;
  gap: var(--space-md);
  padding: var(--space-sm) var(--space-lg);
  background: var(--bg);
  border-bottom: var(--border-thin) solid var(--border);
}
.editor-meta-label {
  font-weight: bold;
  color: var(--text-dim);
  font-size: var(--font-size-meta);
}
.editor-meta input {
  flex: 1;
  height: var(--control-h-md);
  padding: 0 10px;
  border: var(--border-std) solid var(--border);
  border-radius: var(--radius-md);
  background: var(--panel);
  color: var(--text);
  font: inherit;
}
.editor-meta input:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -1px;
  border-color: var(--accent);
}
.editor-meta-file {
  color: var(--text-dim);
  font-size: var(--font-size-meta);
  padding: 0 var(--space-sm);
}
/* Four-pane frozen-header layout mirroring Qt's QTableWidget:
   corner / column-headers / row-headers / data each live in their
   own pane so the headers can't overlay the data the way CSS sticky
   does. Only the data pane scrolls; the header panes have their
   scrollLeft/scrollTop driven from the data pane via JS so columns
   and rows stay aligned. */
.editor-grid-frame {
  display: grid;
  grid-template-columns: max-content 1fr;
  grid-template-rows: max-content 1fr;
  overflow: hidden;
  min-height: 0;
  min-width: 0;
  background: var(--panel);
}
.editor-grid-frame:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}
.editor-grid-pane-corner { grid-column: 1; grid-row: 1; }
.editor-grid-pane-corner > table { width: 100%; height: 100%; }
.editor-grid-pane-cols   { grid-column: 2; grid-row: 1; overflow: hidden; }
.editor-grid-pane-rows   { grid-column: 1; grid-row: 2; overflow: hidden; }
.editor-grid-pane-data   { grid-column: 2; grid-row: 2; overflow: auto; }
.editor-grid {
  border-collapse: collapse;
  font-family: var(--mono-family, ui-monospace, monospace);
}
.editor-grid th,
.editor-grid td {
  border: 1px solid var(--border);
  padding: 4px 8px;
  min-width: 32px;
  text-align: center;
  box-sizing: border-box;
  white-space: nowrap;
}
.editor-grid td { background: var(--panel); }
.editor-grid th {
  background: var(--bg);
  color: var(--text);
  font-weight: bold;
}
.editor-grid-pane-rows .editor-grid th { text-align: right; }
/* Cell value styling. Same semantics as the desktop's
   _cell_brushes: + on green, minus on red, 0 on a dim neutral.
   Read-only this iteration; the click-to-cycle wiring lands next. */
.editor-grid td[data-value="+"] {
  background: var(--plus-bg); color: var(--plus); font-weight: bold;
}
.editor-grid td[data-value="-"] {
  background: var(--minus-bg); color: var(--minus); font-weight: bold;
}
.editor-grid td[data-value="0"] {
  color: var(--text-dim);
}
/* Multi-select highlight. Painted over the value-colored bg so the
   selection reads regardless of which value the cell holds. The
   ``box-shadow inset`` draws a single accent overlay inside the
   cell border without breaking the grid's gridline alignment. */
.editor-grid td.is-selected {
  box-shadow: inset 0 0 0 2px var(--accent), inset 0 0 0 100px var(--accent-light);
}
/* Keyboard focus indicator. A dashed inset outline so it reads as
   "cursor here" without competing with the solid-outline selection
   highlight. Applied independently of is-selected so a selected
   focused cell gets both. */
.editor-grid td.is-focused {
  outline: 2px dashed var(--accent);
  outline-offset: -2px;
}
/* Header click affordance: cursor change so the user knows
   headers are interactive (the desktop builder gets this for
   free via Qt's QPushButton-haptic header subclass). */
.editor-grid thead th,
.editor-grid tbody th {
  cursor: pointer;
}
.editor-grid thead th[data-corner],
.editor-grid thead th[data-corner]:hover {
  cursor: pointer;
}
.editor-status {
  padding: var(--space-sm) var(--space-lg);
  background: var(--panel);
  border-top: var(--border-thin) solid var(--border);
  font-size: var(--font-size-meta);
  color: var(--text-dim);
  min-height: 24px;
}
