.toggle_group {
  display: flex;
  margin-bottom: var(--sp-3-5);
  flex-direction: column;
}

/* normal toggle rows */
.toggle_row {
  display: flex;
  align-items: center;
}

/* toggle + text */
.toggle_container {
   margin: var(--sp-5) 0px 0px 0px;
  gap: var(--sp-3-5);
  cursor: pointer;
  user-select: none;
}

/* text always to the RIGHT */
.toggle_text {
  white-space: nowrap;
}

/* The Features shared-view row keeps the lock glyph with its label while the
   switch itself remains the standard app toggle. */
.feature-lock-row .toggle_text {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
}

.feature-lock-row .feature-lock-icon {
  width: 1.1em;
  height: 1.1em;
  flex-shrink: 0;
  color: var(--fg-2);
}

/* toggle base */
.toggle_switch {
  position: relative;
  display: inline-block;
  width: 46px;
  height: 24px;
  flex-shrink: 0;
  vertical-align: middle;
  /* createMiniToggleSwitch() (StructureInfoPanel/components/utils.js) makes
     THIS element itself a <label> (so clicking anywhere on it natively
     toggles its wrapped checkbox), unlike the original "Link periodic
     copies" toggle where .toggle_switch is a plain <span> inside a separate
     outer <label class="toggle_row toggle_container"> — that container
     already overrides margin, so it never hit this. Without resetting it
     here too, the small toggle silently inherited the app's global `label`
     rule's asymmetric 15px-top/8px-bottom margin (styles.css), which threw
     off its vertical centering against the pie dot/text sitting right next
     to it in the same row (those have their own explicit margin:0).
     font-size/font-weight reset for the same reason, though harmless here
     with no visible text — see styles.css's plain `label` rule this
     overrides. */
  margin: 0;
  font-size: inherit;
  font-weight: var(--fw-normal);
}

/* hide checkbox but keep logic */
.toggle_switch input {
  position: absolute;
  opacity: 0;
  width: 0;
  height: 0;
  margin: 0;
}

/* track */
.toggle_slider {
  position: absolute;
  inset: 0;
  cursor: pointer;
  background-color: var(--switch-off);
  border-radius: var(--radius-24);
  transition: background-color 0.25s;
}

/* knob */
.toggle_slider::before {
  content: "";
  position: absolute;
  width: 18px;
  height: 18px;
  left: 3px;
  top: 3px;
  background-color: var(--switch-knob);
  border-radius: 50%;
  transition: transform 0.25s;
}




/* track */
.toggle_slider_dual {
  position: absolute;
  inset: 0;
  background-color: var(--highlight-color);
  border-radius: var(--radius-24);
  transition: background-color 0.25s;
}

/* knob */
.toggle_slider_dual::before {
  content: "";
  position: absolute;
  width: 18px;
  height: 18px;
  left: 3px;
  top: 3px;
  background-color: var(--switch-knob);
  border-radius: 50%;
  transition: transform 0.25s;
}

/* checked */
.toggle_switch input:checked + .toggle_slider {
  background-color: var(--highlight-color);
}

.toggle_switch input:checked + .toggle_slider::before {
  transform: translateX(22px);
}

/* Small variant (Bonds/Polyhedra category header show/hide toggle) — same
   pill switch, shrunk to fit a compact header row instead of a plain
   checkbox tick. */
.toggle_switch.toggle_switch--sm {
  width: 30px;
  height: 16px;
}

.toggle_switch.toggle_switch--sm .toggle_slider::before {
  width: 10px;
  height: 10px;
  left: 3px;
  top: 3px;
}

.toggle_switch.toggle_switch--sm input:checked + .toggle_slider::before {
  transform: translateX(14px);
}


/* checked */

.toggle_switch input:checked + .toggle_slider_dual::before {
  transform: translateX(22px);
}

/* camera */

.camera_section {
  display: flex;
  flex-direction: column;
  align-items: center; /* centers the title */
  gap: var(--sp-3-5);
  background: var(--chrome-1);
  border-radius: var(--radius-md);
  margin: var(--sp-5) 0px 5px 0px;
}

.camera_title {
  font-weight: bold;
  font-size: var(--fs-xl);
  text-align: center;
  margin: var(--sp-3-5) 0px calc(-1 * var(--sp-3-5));
    padding: var(--sp-3-5);
  border-radius: var(--radius-md);
}

.camera_toggle {
  display: flex;
  align-items: center;
  margin: var(--sp-3-5) 0;
}

/* Rotate & pan speed slider row: label + value on one line, range beneath.
   Matches the vertical rhythm of the toggle rows above it. */
.camera_speed_row {
  display: block;
  margin: var(--sp-3-5) 0 0;
}
.camera_speed_row .slider-value {
  float: right;
}
/* Range + Reset button share one line, the range taking the remaining width. */
.camera_speed_controls {
  display: flex;
  align-items: center;
  gap: var(--sp-3-5);
  margin-top: var(--sp-3-5);
}
.camera_speed_controls input[type="range"] {
  flex: 1;
  min-width: 0;
}
.camera_speed_reset {
  flex: 0 0 auto;
  padding: 2px 8px;
  font-size: var(--fs-sm);
  color: var(--accent-fg);
  background: var(--wash-2);
  border: 1px solid var(--line-1);
  border-radius: var(--radius-4);
  cursor: pointer;
}
.camera_speed_reset:hover {
  background: var(--wash-3, var(--wash-2));
}

.camera_label,
.camera_label_r,
.camera_label-pnr {
  display: inline-block;
  width: 100px; /* Adjust width as needed */
  text-align: center;
  padding: 5px;
  border-radius: var(--radius-4); /* Rounded corners */
  margin: 0 var(--sp-3-5);
  font-size: var(--fs-lg); /* Adjust font size as needed */
  /* --wash-2, not --line-1: this is a chip FILL. Borrowing the hairline token
     meant that darkening hairlines for a light theme turned the chip into a
     dark slab and dropped the slider inside it to 2.1:1. */
  background: var(--wash-2);
}


.colors_section {
  display: flex;
  flex-direction: column;
  align-items: center; /* centers the title */
  gap: var(--sp-3-5);
    background: var(--chrome-1);
  border-radius: var(--radius-md);
margin: var(--sp-5) 0px 5px 0px;
}

.colors_title {
  font-weight: bold;
  font-size: var(--fs-xl);
  text-align: center;
  margin: var(--sp-3-5) 0px calc(-1 * var(--sp-3-5));
    padding: var(--sp-3-5);
  border-radius: var(--radius-md);
}


.menus_wrapper {
  justify-content: center;
  display: flex;
  gap: 20px; /* spacing between menus */
}

.menu_block {
  display: flex;
  flex-direction: column;
  align-items: center; /* centers label above select */
  margin: 0 0 20px 0px;
}

.menu_block label {
  font-size: 0.85em;
  margin-bottom: 5px; /* space below label */
}

.menu_block select {
  min-width: 100px;
  padding: 3px 5px;
}


/* ---- Full-width control rows (Visual window) ------------------------------
   One control per row: label (optionally with a value span) on the left,
   the control filling the rest. Replaces the centered narrow .menu_block
   idiom inside the Visual window; .menu_block stays for other panels. */
.control-row {
  display: grid;
  grid-template-columns: minmax(96px, 45%) 1fr;
  align-items: center;
  gap: var(--sp-3);
  margin: var(--sp-2) 0;
  width: 100%;
}

.control-row > label {
  font-size: 0.85em;
  text-align: left;
  margin: 0;
}

.control-row > select,
.control-row > input[type="range"] {
  width: 100%;
  min-width: 0;
  margin: 0;
  padding: 3px 5px;
}

/* Two adopted rows sharing one line (Scene: show-toggle + width slider). */
.control-row-pair {
  display: flex;
  align-items: center;
  gap: var(--sp-4);
}

.control-row-pair > label:first-child {
  flex: 0 0 auto;
}

.control-row-pair > label:last-child {
  flex: 1 1 auto;
  min-width: 0;
}

.control-row-pair > label:last-child input[type="range"] {
  width: 100%;
  min-width: 0;
}

/* Small explanatory note under a control group. */
.control-note {
  font-size: var(--fs-sm);
  opacity: 0.75;
  margin: var(--sp-2) 2px;
  line-height: 1.3;
}

/* Draggable color-bar legend (ui/ColorBarWidget.js, ui/ColorBarDrag.js): a
   docked grip beside a
   .cv-colorbar-values row (min input / gradient / max input) — the widget's
   wrapper (inline style) is display:flex so these two sit side by side.
   Docked in a panel, the grip sits beside the bar; floating bars use body
   drag and long-press instead. */
.cv-colorbar-controls {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
}

.cv-colorbar-values {
  display: flex;
  align-items: center;
  gap: var(--sp-4);
  width: 100%;
}

.cv-colorbar-grip {
  flex: 0 0 auto;
  line-height: 1;
  cursor: pointer;
}

.cv-colorbar-grip {
  cursor: grab;
  opacity: 0.45;
  font-size: var(--fs-2xl);
  color: var(--fg-1);
  user-select: none;
  touch-action: none;
}

.cv-colorbar-grip:active {
  cursor: grabbing;
}

/* Legend (ui/ColorBarWidget.js, click-to-edit). Empty (no text typed yet, or
   cleared) shows a "Click to add legend" placeholder, but only on hover —
   invisible-but-still-there and still clickable the rest of the time, so it
   doesn't sit there as a permanent label reading "Click to add legend" on
   every bar that simply has no legend right now. */
.cv-colorbar-legend-empty {
  opacity: 0;
  transition: opacity 0.15s ease;
}

.cv-colorbar-legend-empty:hover {
  opacity: 0.45;
}

/* Editing state: a visible frame + caret, since contentEditable text with no
   box around it gave no hint you were actually in an editable field at all —
   easy to miss whether a click landed or typing would do anything. No
   compensating margin needed: positionLegend()'s centering (translateX/Y
   against the element's OWN size, see its comments) re-centers automatically
   as the box grows from the added padding/border, in every orientation. */
.cv-colorbar-legend-editing {
  padding: 1px 5px;
  border: 1px solid var(--accent-color, var(--info-bright));
  border-radius: var(--radius-4);
  background: var(--input-bg);
  caret-color: var(--fg-1);
}

/* The bar itself is also a drag handle (ui/ColorBarWidget.js passes it to
   makeColorBarDraggable as an extra handle). */
.cv-colorbar-bar-handle {
  cursor: grab;
  touch-action: none;
}

.cv-colorbar-bar-handle:active {
  cursor: grabbing;
}

/* The long-press dropdown: Horizontal / Vertical / Dock. It is positioned by
   the widget at the press point, rather than anchored to a visible button.
   --menu-bg (not --bg-color, which runs ~80% opaque as a theme accent) so
   this reads as a solid, legible dropdown regardless of what's behind it —
   the same token every other menu/modal in the app already uses. Text is
   pinned to white rather than inherited: a floating color bar's wrapper
   tracks a contrast-adaptive color for its bare tick labels against the
   arbitrary scene background (ColorBarWidget.js's currentContrastColor),
   which would otherwise leak into this menu's own always-dark chrome too. */
.cv-colorbar-menu {
  position: absolute;
  top: calc(100% + 4px);
  left: 0;
  min-width: 130px;
  display: none;
  flex-direction: column;
  padding: var(--sp-1);
  border-radius: var(--radius);
  background: var(--menu-bg);
  border: 1px solid var(--line-1);
  backdrop-filter: blur(10px);
  box-shadow: var(--shadow-md);
  z-index: 1;
}

.cv-colorbar-menu-host {
  position: fixed;
  z-index: var(--z-overlay-widget);
  pointer-events: none;
}

.cv-colorbar-menu-host .cv-colorbar-menu {
  top: 0;
  left: 0;
  pointer-events: auto;
}

.cv-colorbar-menu-open {
  display: flex;
}

.cv-colorbar-menu-item {
  padding: var(--sp-2) var(--sp-3-5);
  font-size: var(--fs-md);
  text-align: left;
  border-radius: var(--radius-5);
  border: none;
  background: none;
  color: var(--fg-1);
  cursor: pointer;
  white-space: nowrap;
}

.cv-colorbar-menu-item:hover:not(:disabled) {
  background: var(--wash-3);
}

.cv-colorbar-menu-item-active {
  font-weight: var(--fw-bold);
}

.cv-colorbar-menu-item-active::before {
  content: "✓ ";
}

.cv-colorbar-menu-item:disabled {
  opacity: 0.4;
  cursor: default;
}

.cv-colorbar-dragging {
  opacity: 0.85;
}

.cv-colorbar-floating {
  position: relative;
  padding: var(--sp-2) 3px;
  background: none;
  touch-action: none;
}

.cv-colorbar-floating input,
.cv-colorbar-floating [contenteditable="true"] {
  touch-action: auto;
}

/* Resize affordance: a full frame around the bar's whole visual footprint
   (the color strip plus its tick labels and legend, wherever those land —
   ui/ColorBarWidget.js's labeledExtent()/positionResizeHandle size both
   every render), the same visual language as the PNG export crop tool's
   selection rectangle (.cv-crop-rect/.cv-crop-handle, styles/styles.css),
   with a single grab handle at its far corner (JS toggles which one —
   orientation/flipSide) — sitting right at that corner, outside the
   labeled content the frame itself encloses. Docked-only-visible-when-
   floating, and visually hover-revealed on the bar itself so it doesn't
   clutter the bar at rest. The frame is purely decorative (pointer-events: none,
   always): an earlier version made it pointer-events: auto so the gap
   between the bar and the handle stayed continuously hoverable, but since
   the frame also encloses Min/Max, that ended up swallowing clicks meant for
   both instead. The handle stays
   small and isolated at the corner instead — safe to leave pointer-events:
   auto permanently (below), since nothing else needs that spot. */
.cv-colorbar-resize-frame,
.cv-colorbar-resize-handle {
  display: none;
}

.cv-colorbar-floating .cv-colorbar-resize-frame,
.cv-colorbar-floating .cv-colorbar-resize-handle {
  display: block;
  position: absolute;
  opacity: 0;
  transition: opacity 0.15s ease;
}

.cv-colorbar-floating .cv-colorbar-resize-frame {
  pointer-events: none;
}

/* Always interactive once floating, not gated behind a hover reveal first
   (only opacity — its visibility — is): the handle is a small, isolated
   target at the frame's corner, away from anything else that needs clicks,
   so there's no risk in it just being clickable the moment the cursor
   reaches it rather than requiring the bar to already be hovered first. */
.cv-colorbar-floating .cv-colorbar-resize-handle {
  pointer-events: auto;
}

.cv-colorbar-floating:hover .cv-colorbar-resize-frame,
.cv-colorbar-floating:hover .cv-colorbar-resize-handle,
.cv-colorbar-resize-handle:hover {
  opacity: 1;
}

/* currentColor, not a hardcoded white: both live inside the
   .cv-colorbar-floating wrapper, whose `color` ColorBarWidget.js's
   tickContrastSync polls to the same contrast-safe color the tick labels
   and legend title already render in (currentContrastColor(), synced
   against the live scene background) — the frame/handle should read as
   part of that same text, not a fixed white mark that can wash out on a
   light background. */
.cv-colorbar-floating .cv-colorbar-resize-frame {
  border: 2px solid currentColor;
  border-radius: var(--radius-4);
  z-index: 2;
}

.cv-colorbar-floating .cv-colorbar-resize-handle {
  width: 24px;
  height: 24px;
  z-index: 3;
  touch-action: none;
}

/* A plain small solid square, centered in the handle's (bigger) hit box —
   matching a standard iOS-style crop/resize corner mark, simpler than an
   L-shaped bracket (and sidesteps having to fight a same-element base
   rule's `border` shorthand on specificity for it, the way the L design
   used to). */
.cv-colorbar-floating .cv-colorbar-resize-handle::before {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  width: 12px;
  height: 12px;
  transform: translate(-50%, -50%);
  background: currentColor;
  border-radius: var(--radius-3);
  opacity: 0.9;
}
.cv-colorbar-floating.cv-colorbar-locked .cv-colorbar-resize-handle,
.cv-colorbar-floating.cv-colorbar-locked .cv-colorbar-resize-frame {
  display: none;
  pointer-events: none;
}

/* Which corner (JS toggles exactly one of these, based on orientation +
   flipSide — the growing end, on the labeled side) — same -11px inset as
   the crop tool's own handles. */
.cv-colorbar-resize-handle-nw { left: -12px; top: -12px; }
.cv-colorbar-resize-handle-ne { right: -12px; top: -12px; }
.cv-colorbar-resize-handle-sw { left: -12px; bottom: -12px; }
.cv-colorbar-resize-handle-se { right: -12px; bottom: -12px; }
.cv-colorbar-floating .cv-colorbar-resize-handle { cursor: ew-resize; }
.cv-colorbar-floating.cv-colorbar-vertical .cv-colorbar-resize-handle { cursor: ns-resize; }
