/* structureInfoPanel — extracted from JS during the CSS consolidation.
   Covers General.js, Species.js and components/{CompositionRow,
   IndividualAtomRow, IndividualBondRow, IndividualPolyhedronRow, ColorEditor,
   SpinForceEditor, MaterialEditor, utils}.js. Colours/spacing use a design
   token only where its value is an EXACT match for the JS literal — this file
   was extracted from working inline styles, so an approximate token would be a
   (forbidden) rendering change, not a refactor.

   Many editor panels below (`.atom-color-editor`, `.bond-color-editor`, …)
   are toggled open/closed by JS reading/writing `element.style.display`
   directly (General.js's captureCompositionUiState/restoreCompositionUiState
   does `el.style.display !== 'none'`), so JS still sets the initial
   `display: none` inline on creation — only the rest of each panel's chrome
   (background/border/padding/spacing) moved here. */

/* ---- Panel body width (defaultPanels.js's 'info'/Structure window) ----
   Fixed while FLOATING so the panel doesn't shrink-wrap to whichever tab's
   content is widest and visibly resize on tab switches; still shrinks on
   narrow viewports. 300px (not the old 340px): the Bonds tab's double-range
   slider row no longer carries its own redundant min/max labels (the
   combined "min - max Å" label above the slider already shows them), so the
   row needs much less width than before. Docked, the dock width wins —
   descendant selectors on the dock-state classes, same pattern as
   addStructure.css's docked overrides. */
.si-panel-body { width: min(300px, calc(100vw - 16px)); }

.cv-docked .si-panel-body,
.cv-side-docked .si-panel-body {
  width: auto;
}

/* ---- Shared "Alpha / Size / Edge alpha" row (label + range + number-box),
   used identically by the atom/bond/polyhedron/element colour editors. ---- */
.si-row {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  margin-bottom: var(--sp-2);
}
/* ColorEditor.js's element-wide alpha/size rows use margin-top instead of
   -bottom (they sit right under the picker, not stacked between siblings). */
.si-row--spaced-top {
  margin-top: var(--sp-3);
  margin-bottom: 0;
}
.si-row-label {
  font-size: var(--fs-sm);
  color: var(--fg-85); /* was 0.82 — Wave 2.6 --fg-85 */
  min-width: 34px;
}
.si-row-label--wide { min-width: 58px; } /* "Edge alpha" needs more room */
.si-row input[type="range"] { flex: 1; }
.si-row input[type="number"] {
  width: 56px;
  height: 28px;
  padding: var(--sp-1) var(--sp-2);
  border-radius: var(--radius-sm);
  background: rgb(var(--shadow-rgb) / 0.25);
  border: 1px solid var(--line-1);
  color: var(--info-tint); /* was #e7f5ff — Wave 2.6 exact */
  font-size: var(--fs-sm);
}

/* ---- Shared mini action buttons (Apply/Reset pairs) --------------------- */
.si-action-btn {
  height: 32px;
  font-size: var(--fs-sm);
  min-width: 50px;
  padding: 0 var(--sp-3-5);
  margin-right: var(--sp-1);
}
/* min-width/min-height, not fixed width/height: the class also carries the
   category Spin/Force button (see .spin-force-category-button below). The
   Apply/Reset labels are all short, so their rows are unchanged. */
.si-action-btn-narrow {
  min-height: 32px;
  font-size: var(--fs-sm);
  min-width: 50px;
  width: auto;
  padding: 0 var(--sp-1);
}
.si-action-btn-wide {
  height: 32px;
  font-size: var(--fs-sm);
  min-width: 50px;
  padding: 0 var(--sp-3);
}
.si-action-btn-wide--gap { margin-right: var(--sp-2); }

/* ---- Row-editor panels (atom/bond/poly colour, atom coord, atom spin) ---
   All five share the same chrome; only grid-column differs by layout
   (stacked-header rows use flex, so grid-column is meaningless there). */
.atom-color-editor,
.bond-color-editor,
.poly-color-editor,
.atom-coord-editor,
.atom-spin-editor {
  grid-column: 1 / -1;
  margin-top: var(--sp-2);
  padding: var(--sp-3);
  background: var(--wash-0); /* was 0.02 — Wave 2.6 --wash-0 */
  border: 1px solid var(--wash-1); /* was 0.05 — exact --wash-1 */
  border-radius: var(--radius-sm);
}
/* The editors span the full row in the default grid layout; a stacked-header
   row (Wyckoff orbits) is flex, where grid-column does nothing, so each panel
   claims a whole flex line instead (was JS: panel.style.flex='1 1 100%'). */
.individual-atom-row.stacked-header .atom-color-editor,
.individual-atom-row.stacked-header .atom-coord-editor,
.individual-atom-row.stacked-header .atom-spin-editor {
  flex: 1 1 100%;
}

/* ================================================================
   utils.js — press-and-hold popup (Apply/Reset "…to trajectory")
   ================================================================ */
.press-hold-popup {
  position: fixed;
  z-index: 10000;
  transform: translate(-50%, -100%);
  margin-top: -8px;
  animation: pressHoldPopupIn 0.12s ease-out;
}
@keyframes pressHoldPopupIn {
  from { opacity: 0; }
  to   { opacity: 1; }
}
/* Colors follow the app's own accent tokens (styles.css .theme-standard/
   .theme-symmetry, set on <body> per the active backend mode) instead of a
   fixed color, so the popup matches whichever theme is active. */
.press-hold-popup-btn {
  display: block;
  white-space: nowrap;
  background: var(--highlight-color, var(--accent-color, var(--ok)));
  color: var(--panel-fg, var(--fg-1)); /* was #fff — free substitution, exact */
  border: 1px solid var(--border-color, var(--popup-border)); /* was rgba(255,255,255,0.3) — exact */
  border-radius: var(--radius-sm);
  padding: 7px var(--sp-4);
  font-size: var(--fs-sm);
  font-weight: var(--fw-bold);
  cursor: pointer;
  box-shadow: 0 4px 14px rgb(var(--shadow-rgb) / 0.45);
  transition: background 0.15s ease;
}
.press-hold-popup-btn:hover { filter: brightness(1.12); }
.press-hold-popup-btn:disabled { cursor: default; filter: none; }
.press-hold-popup-arrow {
  position: absolute;
  left: 50%;
  bottom: -5px;
  width: 10px;
  height: 10px;
  transform: translateX(-50%) rotate(45deg);
  background: var(--highlight-color, var(--accent-color, var(--ok)));
  border-right: 1px solid var(--border-color, var(--popup-border));
  border-bottom: 1px solid var(--border-color, var(--popup-border));
}
/* Not enough room above the button (e.g. panel dragged near the top of the
   viewport) — flip the popup below it instead. */
.press-hold-popup.below {
  transform: translate(-50%, 0);
  margin-top: var(--sp-3);
}
.press-hold-popup.below .press-hold-popup-arrow {
  top: -5px;
  bottom: auto;
  border-right: none;
  border-bottom: none;
  border-left: 1px solid var(--border-color, var(--popup-border));
  border-top: 1px solid var(--border-color, var(--popup-border));
}

/* ================================================================
   General.js — panel chrome around the Atoms/Bonds/Poly/Wyckoff tabs
   ================================================================ */
.si-comp-wrapper {
  display: flex;
  justify-content: center;
  margin-top: 2px;
}
.si-title-row {
  display: flex;
  justify-content: space-between;
  gap: 20px;
  margin-top: 2px;
}
.si-title {
  font-size: var(--fs-lg);
  font-weight: var(--fw-medium);
  margin-bottom: var(--sp-3-5);
}
.si-comp-string {
  font-size: var(--fs-base);
  font-weight: var(--fw-medium);
  margin-bottom: var(--sp-3-5);
}
.si-add-buttons-row {
  display: flex;
  align-items: center;
  gap: var(--sp-1);
}
/* "✎" button that opens the Modify Structure panel */
.structure-edit-button {
  height: 26px;
  width: 26px;
  font-size: var(--fs-xl);
  font-weight: var(--fw-bold);
  line-height: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--wash-2); /* slightly darker square */
  border: 1px solid var(--wash-4);
  border-radius: var(--radius-4);
  color: var(--fg-1);
  cursor: pointer;
}
.si-wyckoff-badge {
  margin: 0 0 var(--sp-3-5) 0;
  padding: var(--sp-3) var(--sp-3-5);
  border-radius: var(--radius);
  /* The two stops come from different families: the badge's own locked fill,
     then the shared blue-accent fill tier. */
  background: linear-gradient(135deg, var(--locked-badge-bg), rgb(var(--blue-accent-fill-rgb) / 0.18));
  border: 1px solid rgb(var(--blue-accent-rgb) / 0.45);
  color: var(--info-tint); /* was #cfe6ff — Wave 2.6 exact */
  font-size: var(--fs-sm);
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  box-shadow: inset 0 0 0 1px var(--wash-0); /* was 0.04 — Wave 2.6 --wash-0 */
}
.si-link-copies-row { margin: var(--sp-2) 0 var(--sp-3) 0; }
.si-apply-styles-btn {
  height: 28px;
  padding: 0 var(--sp-3-5);
  font-size: var(--fs-sm);
  margin: 0 0 var(--sp-3) 0;
}
.si-reset-row {
  display: flex;
  justify-content: center;
  margin-top: var(--sp-6);
  gap: var(--sp-6);
}
/* Segmented control (Atoms/Bonds/Poly/Wyckoff) gets a blue-tinted "locked"
   look while a Wyckoff-symmetry structure is loaded — was three lines of JS
   (segmentedControl.style.boxShadow/.background, plus the active button's
   gradient/color/weight) toggled by the same hasWyckoffPanel boolean; now a
   single class carries the whole state. */
.segmented-control.wyckoff-locked {
  box-shadow: 0 0 0 1px rgb(var(--blue-accent-rgb) / 0.35);
  background: var(--locked-bg); /* dark navy fill — one-off, not close enough to --blue-accent-fill-rgb to fold (cluster by role, not hue distance) */
}
.segmented-control.wyckoff-locked button.active {
  background: linear-gradient(135deg, var(--locked-grad-1), var(--locked-grad-2));
  color: var(--info-tint); /* was #f5fbff — Wave 2.6 exact */
  font-weight: 700;
}
/* Dead code, preserved as-is (moved from a JS-injected <style> block in
   General.js — never actually applied to any element, but panels are built
   at runtime and this file's job is to relocate styling, not prune it). */
.control-panel {
  display: none; /* Initially hidden */
  margin-top: var(--sp-3-5);
  padding: var(--sp-3-5);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-4);
  background-color: var(--chrome-1-9); /* was 0.95, snapped to the rung's 0.9 (dead rule, see block comment above — tokenised anyway for consistency) */
}
.si-volume {
  margin-top: var(--sp-3);
  font-size: var(--fs-md);
  color: var(--fg-75); /* was 0.8 — Wave 2.6 --fg-75 */
}

/* ================================================================
   CompositionRow.js — the Atoms tab's per-site rows
   ================================================================ */
.comp-row {
  /* .comp-row's base rule (styles.css) still supplies padding/border-bottom
     via its narrow-viewport media queries — this only adds the layout this
     component actually needs, which the base rule doesn't set. */
  display: grid;
  /* Both tracks may shrink below min-content: at the panel's 300px cap,
     count + Spin/Force + toggle beside a wide element name has no slack, and
     floored tracks pushed the button and toggle past the panel edge into a
     horizontal scrollbar. With .comp-right allowed to wrap, that case drops
     the button + toggle (one unit, .comp-actions) under the count instead. */
  grid-template-columns: minmax(0, auto) minmax(0, 1fr);
  align-items: center;
  column-gap: var(--sp-2);
  row-gap: var(--sp-2);
  cursor: pointer;
  transition: background-color 0.2s ease;
}
.comp-row:hover { background-color: var(--wash-0); } /* was 0.03 — Wave 2.6 */
.comp-row-name {
  /* Explicit, scale-respecting size matching Bonds'/Poly's category labels
     (styles.css's global `label` rule) so all three tabs render identically
     regardless of viewport width — this span isn't a <label>, so it doesn't
     inherit that rule, and previously fell through to `.comp-row`'s
     narrow-viewport override instead, causing a mismatch. */
  font-size: var(--fs-lg);
}
.comp-expand-icon {
  font-size: var(--fs-lg);
  transition: transform 0.2s ease;
  color: var(--fg-75); /* was 0.8 — Wave 2.6 --fg-75 */
  transform: rotate(0deg);
}
.comp-right {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  flex-wrap: wrap;
  min-width: 0;
  gap: var(--sp-2);
}
.comp-count-label {
  white-space: nowrap;
  font-size: var(--fs-md);
}
.comp-actions {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  flex: none;
}
.comp-pct-label {
  font-size: 0.8em;
  color: var(--fg-75); /* was 0.7 — Wave 2.6 --fg-75 */
}
/* Category-level spin/force editor button. Dense on purpose: it shares the
   row's right column with the count and the keep-toggle, and at the panel's
   300px cap every px it takes is what decides whether that group stays on
   one line. nowrap so it never becomes a two-line "Spins/ Forces" button —
   when it really doesn't fit, .comp-right wraps the whole button instead. */
.spin-force-category-button {
  min-height: 26px;
  min-width: 0;
  padding: 0 3px;
  font-size: var(--fs-xs);
  white-space: nowrap;
}
.individual-atoms {
  margin-left: var(--sp-2);
  margin-top: var(--sp-3);
  border-left: 2px solid var(--line-1);
  padding-left: var(--sp-2);
}
/* A mixed-site ("(K,Na)") group's row gets one dot PER element instead of a
   single blended swatch — .comp-mixed-dots is the wrapper around them, added
   alongside .dot (styles.css) rather than replacing it, so the "remove old
   dot" query (`left.querySelector('.dot')` in CompositionRow.js) still
   matches this wrapper the same way it matches the single-dot case. */
.comp-mixed-dots {
  display: flex;
  align-items: center;
  gap: 3px;
  margin-right: var(--sp-2);
  /* flex:none is load-bearing here, not decoration: the wrapper sits in a
     flex row with other content competing for width, and without it each
     dot's default flex-shrink squeezed it down to a ~6px sliver (visibly an
     oval, not round) rather than staying at its declared size. gap is a
     touch more than the single-dot case's 8px .comp-left gap alone (two 18px
     dots read as visually wider than the one dot they replace), but not so
     much that it starves the row's right column (count/percentage) of width
     — this is a grid row (auto 1fr), so anything spent on the left column
     here is width the right column doesn't get. align-items:center is also
     load-bearing: without it the two dot buttons default to a slight
     vertical offset from the row's other content (measured ~1.5px low
     against the label next to them) rather than sharing its center.
     width/height:auto override .dot's own fixed 15x15 size (added only so
     this wrapper picks up the rest of .dot's rules) — without them the
     wrapper was clamped to that 15px box while its two 18px, flex:none
     children refused to shrink and overflowed past its edges instead,
     visibly overlapping the "(K,Na)" label after it. */
  flex: none;
  width: auto;
  height: auto;
}
/* No `border` here for either dot class: both are <canvas> elements from
   utils/ColorModule.js's createPieDot(), which always sets its OWN inline
   `border: 1px solid #666` — an inline style, so it would silently win over
   any border declared here regardless of specificity. The actual border
   (rgba(255,255,255,0.4)) is set as an explicit inline override in JS,
   right where the canvas is created, to actually beat it. */
.comp-mixed-dot {
  width: 18px;
  height: 18px;
  border-radius: 50%;
  cursor: pointer;
  flex: none;
}
.comp-single-dot {
  width: 20px;
  height: 20px;
  /* No margin of its own: .comp-left's gap already spaces it, and the 6px it
     added on top came straight out of the right column's one-line budget. */
  margin-right: 0;
  cursor: pointer;
}

/* ================================================================
   IndividualAtomRow.js / IndividualBondRow.js / IndividualPolyhedronRow.js
   — the per-atom/bond/polyhedron rows inside an expanded category
   ================================================================ */
.individual-atom-row,
.individual-bond-row,
.individual-polyhedron-row {
  align-items: center;
  font-size: var(--fs-sm);
  cursor: pointer;
  transition: background-color 0.2s ease;
}
.individual-atom-row:hover,
.individual-bond-row:hover,
.individual-polyhedron-row:hover {
  /* Selection (SelectAndHighlightModule.js highlightAtomRow) sets an inline
     background-color on the selected row, which — being inline — always
     outranks this stylesheet rule regardless of :hover, so a selected row's
     amber highlight is never overwritten by hovering it. */
  background-color: var(--wash-0); /* was 0.03 — Wave 2.6 */
}
.individual-bond-row,
.individual-polyhedron-row {
  display: grid;
  grid-template-columns: 1fr auto;
  column-gap: var(--sp-4);
  padding: var(--sp-1) 0;
}
/* A bond too short to render has nothing to highlight in 3D, so its row
   isn't clickable. */
.individual-bond-row.not-renderable { cursor: default; }
/* Default (non-stacked) atom row: two lines. Identity (name + coordinate
   triple) on the first, the editor buttons and the keep-toggle on the second.
   At the panel's 300px cap the triple, three buttons and the toggle never fit
   on one line — the triple ended up wrapped one number per line and the
   buttons still spilled past the panel edge. */
.individual-atom-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  column-gap: var(--sp-2);
  row-gap: var(--sp-1);
  padding: var(--sp-1) 0;
}
.individual-atom-row .iar-name-container { grid-column: 1 / -1; }
.individual-atom-row .cv-tiny-toggle-wrap { grid-column: 2; align-self: center; }
/* Wyckoff orbit rows carry a long label (site symmetry, orbit size, DOF)
   that has no room next to three buttons in a docked panel, so they stack:
   identity on its own line, buttons underneath. */
.individual-atom-row.stacked-header {
  display: flex;
  flex-wrap: wrap;
  column-gap: var(--sp-3-5);
  row-gap: var(--sp-2);
  padding: var(--sp-2) 0;
}
/* Periodic-image rows (imageOffset != (0,0,0)) sit under the atom they copy,
   not beside it, and their label reads dimmer to flag them as a copy. */
.individual-atom-row.periodic-image { margin-left: 14px; }
.individual-atom-row.periodic-image .iar-name { color: var(--fg-2); }

/* Name and coordinate triple share a line ("Si1  (0.000, 0.000, 0.000)");
   the triple drops to its own line only when the label is long. Everything
   else the container carries — species lines, vacancy, meta — is a full line
   of its own, so a disordered site still reads label -> species -> where. */
.iar-name-container {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  column-gap: var(--sp-2);
  row-gap: 2px;
  /* Without this its min-content width becomes the grid's floor again. */
  min-width: 0;
}
.individual-atom-row.stacked-header .iar-name-container {
  flex: 1 1 100%;
  min-width: 0;
}
.iar-name {
  color: var(--ink-4);
}
.iar-charge {
  font-size: var(--fs-xs);
  color: var(--fg-2);
  margin-left: 5px;
}
.iar-coords,
.bond-dist,
.poly-meta {
  font-size: var(--fs-xs);
  color: var(--fg-75); /* was 0.8 — Wave 2.6 --fg-75 */
  font-family: var(--font-mono);
}
/* The coordinate triple keeps wrapping rather than being trimmed — it is the
   row's actual information. This only stops one long token from becoming the
   name column's min-content floor, which is what the grid above needs. */
.iar-coords {
  overflow-wrap: anywhere;
}
.iar-species-line,
.iar-vacancy-line,
.iar-meta {
  flex: 1 1 100%;
}
.iar-species-line {
  display: flex;
  align-items: center;
  gap: 5px;
  margin: 1px 0;
}
.iar-species-swatch {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  flex: none;
  border: 1px solid var(--fg-3); /* was 0.4 — exact --fg-3 */
}
.iar-species-text {
  font-size: var(--fs-xs);
  color: rgb(var(--vacancy-amber-rgb) / 0.85);/* amber species tint — no matching token (info/ok tints are blue/green only) */
  font-family: var(--font-mono);
}
.iar-vacancy-line {
  display: block;
  font-size: var(--fs-xs);
  color: rgb(var(--vacancy-amber-rgb) / 0.6);/* same amber family as .iar-species-text — no matching token */
  font-family: var(--font-mono);
  margin-left: 13px;
}
.iar-meta {
  font-size: var(--fs-xs);
  color: var(--fg-2); /* was 0.55 — documented --fg-2 near-duplicate */
}

.iar-buttons,
.ibr-buttons,
.ipr-buttons {
  display: flex;
  gap: var(--sp-3-5);
}
.individual-atom-row .iar-buttons {
  gap: var(--sp-2);
  justify-content: center;
}
.individual-atom-row.stacked-header .iar-buttons {
  gap: var(--sp-3);
  flex: 1 1 auto;
  flex-wrap: wrap;
}

/* Row-level "Edit"/"Color"/"Position"/"Spin/Force" buttons — the bond/poly
   row "Edit" buttons use this same .atom-editor-button class (no separate
   class of their own; they never get the .active state atom rows do).
   The inactive border/box-shadow (below) doubles as this base style, since
   an atom row's color/coord/spin buttons always carry one or the other —
   bond/poly row buttons, which never gain .active, get it as their only
   state. */
.atom-editor-button {
  color: var(--fg-1);
  padding: var(--sp-1) var(--sp-3);
  border-radius: var(--radius-4);
  cursor: pointer;
  font-size: var(--fs-xs);
}
/* Round pie-dot swatch (Color button on an atom row): a circle split into
   wedges is the app's established "this is a fractional split" visual (the
   composition header's per-element dots use the same language), where a flat
   rectangle or gradient reads ambiguously. */
.atom-editor-button.iar-color-btn {
  width: 26px;
  height: 26px;
  border-radius: 50%;
  padding: 0;
  border: 1px solid var(--fg-3); /* was 0.35 — documented --fg-3 near-duplicate */
  flex: none;
  overflow: hidden;
}
/* No `border` here either, for the same createPieDot inline-style reason as
   .comp-single-dot/.comp-mixed-dot above — JS sets it explicitly. */
.iar-pie-canvas {
  width: 100%;
  height: 100%;
  display: block;
  pointer-events: none;
}
.atom-editor-button.iar-coord-btn,
.atom-editor-button.iar-spin-btn {
  background: var(--bg-color);
  border: 1px solid var(--line-2);
  color: var(--fg-1);
  padding: var(--sp-1) var(--sp-2);
  border-radius: var(--radius-sm);
  cursor: pointer;
  font-size: var(--fs-xs);
  white-space: nowrap;
  line-height: 1.15;
  text-align: center;
}
.atom-editor-button.iar-coord-btn:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}
/* Active/inactive border+glow, shared by all three editor-toggle buttons
   (color/coord/spin) — was setButtonActive()'s two inline style writes. */
.atom-editor-button {
  border: 1px solid var(--line-2); /* was 0.2 — exact --line-2 */
  box-shadow: none;
}
.atom-editor-button.active {
  border: 1px solid rgb(var(--active-green-rgb) / 0.95);
  box-shadow: 0 0 0 1px rgb(var(--active-green-rgb) / 0.35), inset 0 0 0 1px rgb(var(--active-green-rgb) / 0.15);
}

/* Disordered-site colour editor: one small square swatch per species */
.iar-species-color-row {
  display: flex;
  gap: var(--sp-2);
  margin-bottom: var(--sp-2);
  flex-wrap: wrap;
}
.iar-species-swatch-box {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
}
.iar-species-swatch-btn {
  width: 26px;
  height: 26px;
  border-radius: var(--radius-5);
  padding: 0;
  cursor: pointer;
  border: 1px solid var(--fg-3); /* was 0.35 — documented --fg-3 near-duplicate */
}
.iar-species-swatch-label {
  font-size: 8px;
  color: var(--fg-2);
}

.iar-top-row {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  margin-bottom: var(--sp-2);
}
.iar-button-row { display: flex; align-items: center; gap: var(--sp-2); }
.ibr-button-row,
.ipr-button-row {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  margin-top: var(--sp-2);
}

/* Bond/polyhedron name + meta text */
.ibr-name,
.ipr-name {
  color: var(--ink-4);
}
.poly-edge-header {
  font-size: var(--fs-sm);
  color: var(--fg-85); /* was 0.82 — Wave 2.6 --fg-85 */
  margin-top: var(--sp-3);
}
.ibr-name-container,
.ipr-name-container {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

/* Coordinate editor (Position button) */
.coord-editor-title {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--sp-3);
  font-size: var(--fs-sm);
  color: var(--fg-75); /* was 0.8 — Wave 2.6 --fg-75 */
  margin-bottom: var(--sp-2);
  font-weight: var(--fw-medium);
}
.coord-editor-note {
  font-size: var(--fs-xs);
  font-weight: var(--fw-normal);
  color: var(--fg-3); /* was 0.45 — documented --fg-3 near-duplicate */
}
/* A Wyckoff move that would collapse symmetry-equivalent atoms onto each
   other is refused — the note flips to this warning colour to say so. */
.coord-editor-note.refused { color: var(--warn-soft); }
.coord-axis-rows {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  margin-bottom: var(--sp-3);
}
.coord-axis-row {
  display: grid;
  grid-template-columns: 10px minmax(0, 1fr) 72px;
  align-items: center;
  gap: var(--sp-2);
}
.coord-axis-row[data-free="false"] { opacity: 0.4; }
.coord-axis-row-label {
  font-size: var(--fs-xs);
  color: var(--fg-2); /* was 0.65 — documented --fg-2 near-duplicate */
  font-family: var(--font-mono);
}
.coord-axis-slider {
  width: 100%;
  min-width: 0;
  accent-color: rgb(var(--active-green-rgb) / 0.95); /* same active-state green as .atom-editor-button.active */
}
.coord-num-input {
  width: 100%;
  min-width: 0;
  padding: 3px var(--sp-1);
  background: rgb(var(--shadow-rgb) / 0.3);
  border: 1px solid var(--line-2);
  border-radius: var(--radius-4);
  color: var(--fg-1);
  font-size: var(--fs-xs);
  font-family: var(--font-mono);
  /* The spinner arrows eat a third of a 72px box at this font size; the
     slider is the coarse control, so the box is text-only. Chrome ignores
     the -moz property's Firefox effect for its own spinner, hence both. */
  -moz-appearance: textfield;
  appearance: textfield;
}
.coord-buttons-row {
  display: flex;
  align-items: center;
  gap: var(--sp-1);
}

/* ================================================================
   ColorEditor.js — createElementColorEditor (the category-wide editor)
   ================================================================ */
.element-color-editor {
  grid-column: 2;
  padding: var(--sp-3);
  background: var(--wash-0); /* was 0.03 — Wave 2.6 --wash-0 */
  border: 1px solid var(--wash-2); /* was 0.08 — exact --wash-2 */
  border-radius: var(--radius);
}
.spin-force-category-editor {
  grid-column: 2;
  padding: var(--sp-3);
  background: var(--wash-0);
  border: 1px solid var(--wash-2);
  border-radius: var(--radius);
}
.ece-top-row {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
}
.ece-button-row {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  margin-top: var(--sp-2);
}
/* Reset button's background/text colour are computed from the element's
   default colour (JS, data-dependent) — only the border stays fixed. */
.ece-reset-btn { border-color: rgb(var(--shadow-rgb) / 0.15); }

/* ================================================================
   SpinForceEditor.js — createSpinForceEditor
   ================================================================ */
.spin-mode-switch {
  display: inline-flex;
  background: var(--chrome-2); /* was #2d2d2d — Wave 2.6 near-dup fold */
  border-radius: var(--radius-md);
  padding: var(--sp-1);
  gap: var(--sp-1);
  font-size: var(--fs-sm);
  min-width: 98%;
  margin-bottom: var(--sp-3-5);
}
.spin-mode-switch-btn {
  padding: var(--sp-2) 14px;
  border-radius: var(--radius-sm);
  cursor: pointer;
  transition: 0.15s ease;
  user-select: none;
  font-weight: var(--fw-medium);
  min-width: 40%;
  justify-content: center;
  display: flex;
}
/* Active/inactive state — was setSwitchActive()'s four inline style writes. */
.spin-mode-switch-btn.active {
  background: var(--seg-active-bg);
  color: var(--fg-1);
}
.spin-mode-switch-btn:not(.active) {
  background: transparent;
  color: var(--fg-75); /* was 0.8 — Wave 2.6 --fg-75 */
}
.spin-no-data {
  font-size: var(--fs-sm);
  color: var(--fg-50); /* was 0.5 — Wave 2.6 --fg-50 */
  text-align: center;
  padding: var(--sp-2) 0;
}
.spin-vector-input {
  width: 60px;
  padding: var(--sp-1) var(--sp-2);
  background: rgb(var(--shadow-rgb) / 0.3);
  border: 1px solid var(--line-2);
  border-radius: var(--radius-4);
  color: var(--fg-1);
  font-size: var(--fs-sm);
  margin-right: var(--sp-1);
  appearance: textfield;
  -webkit-appearance: none;
  -moz-appearance: textfield;
}
.spin-vector-input:last-child { margin-right: 0; }
.spin-vector-row {
  display: flex;
  align-items: center;
  gap: var(--sp-1);
  margin-bottom: var(--sp-2);
  justify-content: center;
}
.spin-hide-label {
  display: flex;
  align-items: center;
  gap: var(--sp-1);
  font-size: var(--fs-sm);
  color: var(--fg-75); /* was 0.8 — Wave 2.6 --fg-75 */
  cursor: pointer;
  justify-content: center;
  margin-bottom: var(--sp-3);
}
.spin-buttons-row {
  display: flex;
  align-items: center;
  gap: var(--sp-1);
  justify-content: center;
}
.spin-color-picker-section {
  margin-top: var(--sp-3);
  padding-top: var(--sp-3);
  border-top: 1px solid var(--wash-2);
  align-items: center;
  flex-direction: column;
  gap: var(--sp-2);
}

/* ================================================================
   MaterialEditor.js — createMaterialEditor (ray/path-tracing knobs)
   ================================================================ */
.material-editor {
  margin-top: var(--sp-3-5);
  padding-top: var(--sp-3);
  border-top: 1px solid var(--wash-2);
}
.material-editor-header {
  font-size: var(--fs-sm);
  color: var(--fg-2);
  margin-bottom: var(--sp-2);
}
.material-editor-type-row {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
}
.material-type-select {
  flex: 1;
  height: 28px;
  font-size: var(--fs-sm);
}
/* Per-property slider rows (Gloss/Tint/Rough/Frost/IoR/…) */
.material-prop-row {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  margin-top: var(--sp-2);
}
.material-prop-row input[type="range"] { flex: 1; }
.material-prop-value {
  font-size: var(--fs-sm);
  color: var(--fg-75); /* was 0.7 — Wave 2.6 --fg-75 */
  min-width: 34px;
  text-align: right;
}

/* ---------------------------------------------------------------------------
   Composition Display (ui/CompositionLegendWidget.js) — the movable colour
   legend. Swatches are canvases painted by a shared WebGL renderer with the
   main view's atom material; the text sizes lean large because the legend's
   whole purpose is being screenshotted next to the structure.

   It is a scene widget on ui/ColorBarDrag.js, like the floating colour bars,
   so it wears their menu/resize classes (styles/toggle_styles.css) and only the box itself
   is styled here.
   ------------------------------------------------------------------------ */

.comp-legend-widget {
  /* Shrink-wraps .comp-legend-body, which is the box the user actually
     resizes. Sizing/placement otherwise comes from ColorBarDrag (inline
     position:fixed + left/top) and .cv-colorbar-floating. */
  width: fit-content;
  touch-action: none;
}

.comp-legend-widget [contenteditable="true"] {
  touch-action: auto;
}

/* .cv-colorbar-floating's own padding frames a gradient strip against its
   tick labels — the legend's surface has to sit flush against its edges
   instead, or transparent mode leaves an inset shadow of the old box. */
.comp-legend-widget.cv-colorbar-floating {
  padding: 0;
}

.comp-legend-body {
  /* Written by the widget's ResizeObserver as the box is resized; every
     row dimension below derives from it so the whole legend scales as one. */
  --legend-scale: 1;
  padding: var(--sp-3-5);
  /* Never sized: the box is exactly what the rows need at the current scale,
     so a two-symbol legend is a chip rather than a wide empty rectangle, and
     nothing can be cropped. The resize handle drives --legend-scale. */
  width: fit-content;
  border-radius: var(--radius-md);
  background: var(--float-panel-bg);
  border: 1px solid var(--line-1);
  box-shadow: var(--shadow-glow);
  backdrop-filter: blur(10px);
}

/* The resize frame and its handle are visually hover-revealed so they stay
   out of a figure. The frame encloses the box instead of a
   gradient strip and its labels, so it's a plain inset rather than the
   measured extent ColorBarWidget computes; the handle keeps the bar's
   ew-resize cursor because, as there, the drag sets width only. */
.comp-legend-widget .cv-colorbar-resize-frame {
  inset: -4px;
}

.comp-legend-widget .cv-colorbar-resize-handle {
  right: -12px;
  bottom: -12px;
  cursor: ew-resize;
}

.comp-legend-widget:hover .cv-colorbar-resize-frame,
.comp-legend-widget:hover .cv-colorbar-resize-handle {
  opacity: 1;
}
.comp-legend-widget.cv-colorbar-locked .cv-colorbar-resize-frame,
.comp-legend-widget.cv-colorbar-locked .cv-colorbar-resize-handle {
  display: none;
  pointer-events: none;
}

.comp-legend-list {
  display: flex;
  flex-direction: column;
  gap: calc(6px * var(--legend-scale));
  min-width: 0;
}

.comp-legend-row {
  display: flex;
  align-items: center;
  gap: calc(10px * var(--legend-scale));
}

.comp-legend-swatch {
  width: calc(30px * var(--legend-scale));
  height: calc(30px * var(--legend-scale));
  flex: none;
}

/* Raw px on purpose, not the --fs-* rungs: the legend's type tracks the box
   scale, and the style guide forbids wrapping a font-size token in another
   calc() (it would re-apply --cv-font-scale). */
.comp-legend-label {
  font-size: calc(16px * var(--legend-scale));
  font-weight: var(--fw-medium);
  color: var(--fg-1);
  line-height: var(--lh-tight);
}

.comp-legend-sub {
  font-size: calc(11px * var(--legend-scale));
  color: var(--fg-2);
  margin-top: 2px;
}

/* Labels are contenteditable; show it on hover/focus only, so nothing
   extra prints into a figure capture. */
.comp-legend-label,
.comp-legend-sub {
  outline: none;
  border-radius: var(--radius-3);
}

.comp-legend-label:hover,
.comp-legend-sub:hover {
  box-shadow: 0 0 0 1px var(--line-2);
  cursor: text;
}

.comp-legend-label:focus,
.comp-legend-sub:focus {
  box-shadow: 0 0 0 1px var(--border-color);
}

.comp-legend-empty {
  font-size: var(--fs-base);
  color: var(--fg-2);
  padding: var(--sp-2);
}

/* Transparent mode (long-press menu): strip the surface so only swatches and
   text overlay the scene. */
.comp-legend-transparent .comp-legend-body {
  background: transparent;
  border-color: transparent;
  box-shadow: none;
  backdrop-filter: none;
}

/* With no surface under it the text reads against the SCENE, so it takes the
   wrapper's colour — which CompositionLegendWidget.js polls to the same
   contrast-safe value the floating colour bars' ticks use. --fg-1 there is a
   near-white that vanishes on a light background (and in a light-background
   PNG export, which copies these computed colours). */
.comp-legend-transparent .comp-legend-label,
.comp-legend-transparent .comp-legend-sub {
  color: inherit;
}

.comp-legend-transparent .comp-legend-sub {
  opacity: 0.78;
}

/* ---- Selection bulk-visuals bar (ui/StructureInfoPanel/SelectionActionBar.js)
   -------------------------------------------------------------------------
   Docked at the top of the Structure panel; visible only while atoms are
   selected (JS toggles display). An accent left-border + faint accent wash
   flag it as "your current selection", distinct from the per-species rows
   below. */
.si-selection-bar {
  display: none; /* JS switches to block when the selection is non-empty */
  margin-bottom: var(--sp-3);
  padding: var(--sp-2) var(--sp-3);
  border-radius: var(--radius-md);
  border: 1px solid var(--line-1); /* neutral hairline — no coloured accent bar */
  background: var(--wash-1);
}
.si-selbar-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-2);
  margin-bottom: var(--sp-2);
}
.si-selbar-summary {
  font-size: var(--fs-sm);
  color: var(--fg-1);
  font-weight: 600;
}
/* Compact Clear/Reset — smaller than the shared .btn-mini so they read as
   secondary actions on the bar rather than primary buttons. Scoped through
   .si-selection-bar to out-rank .cv-panel-body button's font-size (panelWindow.css). */
.si-selection-bar .si-selbar-clear,
.si-selection-bar .si-selbar-reset,
.si-selection-bar .si-selbar-coordinates-toggle,
.si-selection-bar .si-selbar-copy {
  flex: none;
  padding: 1px var(--sp-2);
  font-size: var(--fs-xs);
  line-height: 1.4;
}
.si-selbar-header-actions,
.si-selbar-coordinate-heading {
  display: flex;
  align-items: center;
  gap: var(--sp-1);
}
.si-selbar-coordinates {
  display: grid;
  gap: var(--sp-2);
  margin-bottom: var(--sp-2);
  padding: var(--sp-2);
  border: 1px solid var(--line-1);
  border-radius: var(--radius-sm);
  background: var(--group-bg);
}
.si-selbar-coordinate-heading {
  justify-content: space-between;
  margin-bottom: var(--sp-1);
  font-size: var(--fs-xs);
}
.si-selbar-coordinates textarea {
  box-sizing: border-box;
  width: 100%;
  resize: vertical;
  padding: var(--sp-1) var(--sp-2);
  font: var(--fs-xs)/1.45 var(--font-mono, monospace);
  white-space: pre;
}
.si-selbar-color-row {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  margin-bottom: var(--sp-2);
}
.si-selbar-swatch {
  flex: none;
  width: 24px;
  height: 24px;
  padding: 0;
  border-radius: 50%;
  border: 1px solid var(--line-1);
  background: transparent;
  cursor: pointer;
}
.si-selbar-dot {
  width: 100%;
  height: 100%;
  display: block;
  border-radius: 50%;
  pointer-events: none; /* clicks belong to the button, not the canvas */
}
.si-selbar-reset {
  margin-left: auto; /* push Reset to the right, dot stays next to the label */
}

/* Locked composition legend is read-only: drop the editing affordance
   (text cursor + focus/hover outline). Labels' contentEditable is also set
   false in CompositionLegendWidget.js. */
.comp-legend-widget.cv-colorbar-locked .comp-legend-label:hover,
.comp-legend-widget.cv-colorbar-locked .comp-legend-sub:hover,
.comp-legend-widget.cv-colorbar-locked .comp-legend-label:focus,
.comp-legend-widget.cv-colorbar-locked .comp-legend-sub:focus {
  box-shadow: none;
  cursor: default;
}
