/* ──────────────────────────────────────────────────────────────────────────
   Component classes — replaces the inline-style constants in shared.js.
   Each class mirrors one of the JS style objects so JSX can switch from
   `style={sec}` to `className="dv-panel"`. New benefits unlocked here:
   :hover / :focus-visible states, single source of truth for spacing,
   future media queries.
   Naming: `dv-` prefix to stay collision-free if another stylesheet
   (e.g. a vendored chart lib) lands one day.
   ────────────────────────────────────────────────────────────────────────── */

/* Section / panel — `sec` */
.dv-panel {
  background: var(--surface-subtle);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 16px;
  margin-bottom: 16px;
}

/* Inside the plot sidebar (`tools/_shell/PlotSidebar.tsx`), the parent
   uses flex `gap: 10` for between-panel spacing. The default 16px
   margin-bottom on bare `.dv-panel` children stacks on top of that
   gap and gives 26px between bare panels but only 10px between
   ControlSections (which override the margin inline). Force every
   panel child of the sidebar to zero margin-bottom so the canonical
   between-panel spacing is uniformly the 10px flex gap. */
.dv-sidebar > .dv-panel {
  margin-bottom: 0;
}

/* Small label — `lbl` */
.dv-label {
  font-size: 12px;
  color: var(--text-faint);
  margin-bottom: 2px;
}

/* Text input — `inp` */
.dv-input {
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-radius: 4px;
  color: var(--text);
  padding: 4px 8px;
  font-size: 12px;
  font-family: inherit;
}
.dv-input:focus-visible {
  outline: 2px solid var(--accent-primary);
  outline-offset: 1px;
  border-color: var(--accent-primary);
}

/* Numeric input — `inpN` (compact, centered, fixed width).
   Used both by the NumberInput component (real number entry) and as a
   generic compact-text-input class on <input type="text"> sites (axis
   min/max, titles, labels) where the value is freeform. */
.dv-input-num {
  width: 72px;
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-radius: 4px;
  color: var(--text);
  padding: 4px 8px;
  font-size: 13px;
  text-align: center;
  font-family: inherit;
}
.dv-input-num:focus-visible {
  outline: 2px solid var(--accent-primary);
  outline-offset: 1px;
  border-color: var(--accent-primary);
}

/* ── NumberInput component ─────────────────────────────────────────────
   Replaces the native stacked up/down spinner with −/+ buttons on the
   left and right of the input. `.dv-num` is the flex wrapper,
   `.dv-num-input` the bare input (no border, spinners hidden), and
   `.dv-num-btn` the shared button look. */
.dv-num {
  display: inline-flex;
  align-items: stretch;
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-radius: 4px;
  overflow: hidden;
  height: 26px;
  box-sizing: border-box;
}
.dv-num:focus-within {
  outline: 2px solid var(--accent-primary);
  outline-offset: 1px;
  border-color: var(--accent-primary);
}
.dv-num.dv-num-disabled {
  opacity: 0.55;
}
.dv-num-input {
  flex: 1;
  min-width: 0;
  width: 100%;
  border: none;
  background: transparent;
  color: var(--text);
  font-family: inherit;
  font-size: 13px;
  text-align: center;
  padding: 0 4px;
  outline: none;
  appearance: textfield;
  -moz-appearance: textfield;
}
.dv-num-input::-webkit-outer-spin-button,
.dv-num-input::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}
.dv-num-btn {
  border: none;
  background: var(--surface-subtle);
  color: var(--text-muted);
  cursor: pointer;
  font-family: inherit;
  font-size: 15px;
  font-weight: 700;
  line-height: 1;
  padding: 0;
  width: 22px;
  flex: 0 0 22px;
  display: flex;
  align-items: center;
  justify-content: center;
  user-select: none;
  transition:
    background 140ms ease,
    color 140ms ease;
}
.dv-num-btn:hover:not(:disabled) {
  background: var(--accent-primary);
  color: var(--on-accent);
}
.dv-num-btn:active:not(:disabled) {
  filter: brightness(0.92);
}
.dv-num-btn:disabled {
  cursor: not-allowed;
  opacity: 0.5;
}
.dv-num-btn-minus {
  border-right: 1px solid var(--border);
}
.dv-num-btn-plus {
  border-left: 1px solid var(--border);
}
:root[data-theme="dark"] .dv-num-btn {
  background: var(--surface-sunken);
}

/* ── Disclosure indicator ──────────────────────────────────────────────
   Neutral collapsible marker used by StatsTile, Scatter's Filters panel,
   and Aequorin's chart / inset / replicate-table collapse buttons. A
   soft neutral-surface circle with a crisp SVG chevron that rotates 90°
   when the section is open — deliberately quiet so it melts into the
   surrounding panel chrome in both light and dark modes. The glyph is
   painted as a CSS mask so it honours `currentColor`, stays pixel-sharp
   at any DPR, and doesn't depend on which font the browser picked to
   render a ">" character. */
.dv-disclosure {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: var(--neutral-bg);
  color: var(--neutral-text);
  flex-shrink: 0;
  user-select: none;
  transition:
    transform 200ms cubic-bezier(0.4, 0, 0.2, 1),
    background 120ms ease;
}
.dv-disclosure:hover {
  background: var(--border);
}
.dv-disclosure::before {
  content: "";
  display: block;
  width: 10px;
  height: 10px;
  background-color: currentColor;
  -webkit-mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M9 5l7 7-7 7' fill='none' stroke='black' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'/></svg>");
  mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M9 5l7 7-7 7' fill='none' stroke='black' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'/></svg>");
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-position: center;
  mask-position: center;
  -webkit-mask-size: contain;
  mask-size: contain;
  /* Optical centering: the chevron's tip sits a hair left of geometric
     center because the SVG path is slightly asymmetric. */
  transform: translateX(0.5px);
}
.dv-disclosure.dv-disclosure-open {
  transform: rotate(90deg);
}

/* Select dropdown — `selStyle` */
.dv-select {
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-radius: 4px;
  color: var(--text);
  padding: 4px 8px;
  font-size: 12px;
  font-family: inherit;
  cursor: pointer;
}
.dv-select:hover {
  border-color: var(--text-faint);
}
.dv-select:focus-visible {
  outline: 2px solid var(--accent-primary);
  outline-offset: 1px;
  border-color: var(--accent-primary);
}

/* Separator selector — `sepSelect` (prominent accent border) */
.dv-select-sep {
  background: var(--surface);
  border: 2px solid var(--accent-primary);
  border-radius: 6px;
  color: var(--text);
  padding: 6px 12px;
  font-size: 13px;
  font-weight: 600;
  font-family: inherit;
  cursor: pointer;
}
.dv-select-sep:focus-visible {
  outline: 2px solid var(--accent-primary);
  outline-offset: 2px;
}

/* ── Range slider track ──────────────────────────────────────────────── */
input[type="range"] {
  -webkit-appearance: none;
  appearance: none;
  height: 6px;
  border-radius: 3px;
  outline: none;
}
input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: var(--accent-primary);
  cursor: pointer;
  border: none;
}
input[type="range"]::-moz-range-track {
  height: 6px;
  border-radius: 3px;
  border: none;
}
input[type="range"]::-moz-range-thumb {
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: var(--accent-primary);
  cursor: pointer;
  border: none;
}

/* ── Buttons ──────────────────────────────────────────────────────────── */

.dv-btn {
  cursor: pointer;
  font-family: inherit;
  border: none;
  transition:
    filter 140ms ease,
    transform 100ms ease,
    box-shadow 140ms ease,
    background 140ms ease,
    border-color 140ms ease,
    color 140ms ease;
}
.dv-btn:focus-visible {
  outline: 2px solid var(--accent-primary);
  outline-offset: 2px;
}
.dv-btn:disabled {
  cursor: not-allowed;
  opacity: 0.55;
  filter: none !important;
  transform: none !important;
  box-shadow: none !important;
}

/* Primary CTA — `btnPrimary` */
.dv-btn-primary {
  padding: 10px 28px;
  border-radius: 8px;
  font-size: 14px;
  font-weight: 700;
  background: var(--cta-primary-bg);
  color: var(--on-accent);
}
.dv-btn-primary:hover:not(:disabled) {
  filter: brightness(1.15) saturate(1.05);
  transform: translateY(-1px);
  box-shadow: var(--cta-primary-shadow);
}
.dv-btn-primary:active:not(:disabled) {
  transform: translateY(1px);
  filter: brightness(0.96);
  box-shadow: none;
}

/* Plot CTA — `btnPlot` (teal accent) */
.dv-btn-plot {
  padding: 10px 28px;
  border-radius: 8px;
  font-size: 14px;
  font-weight: 700;
  background: var(--cta-plot-bg);
  color: var(--on-accent);
}
.dv-btn-plot:hover:not(:disabled) {
  filter: brightness(1.15) saturate(1.05);
  transform: translateY(-1px);
  box-shadow: var(--btn-plot-hover-shadow);
}
.dv-btn-plot:active:not(:disabled) {
  transform: translateY(1px);
  filter: brightness(0.96);
  box-shadow: none;
}

/* Secondary / quiet button — `btnSecondary` */
.dv-btn-secondary {
  padding: 6px 14px;
  border-radius: 6px;
  font-size: 12px;
  background: var(--surface);
  border: 1px solid var(--border-strong);
  color: var(--text-muted);
}
.dv-btn-secondary:hover:not(:disabled) {
  background: var(--surface-subtle);
  border-color: var(--accent-primary);
  color: var(--text);
  transform: translateY(-1px);
  box-shadow: var(--btn-secondary-hover-shadow);
}
.dv-btn-secondary:active:not(:disabled) {
  transform: translateY(1px);
  box-shadow: none;
}

/* Unified download chip — icon + short filetype label (SVG / PNG / CSV / TXT).
   Used in ActionsPanel and standalone (per-replicate CSV, stats report TXT,
   molarity prep sheet). Ghost style off the shared `--success-*` triplet so it
   themes cleanly in dark mode (pale mint chip in light mode → deep-green chip
   with mint text in dark mode) instead of a neon-green fill. */
.dv-btn-dl {
  padding: 8px 10px;
  border-radius: 6px;
  font-size: 12px;
  font-weight: 600;
  background: var(--step-ready-bg);
  border: 1px solid var(--step-ready-border);
  color: var(--step-ready);
  white-space: nowrap;
  min-width: 78px;
}
.dv-btn-dl:hover:not(:disabled) {
  filter: brightness(0.96);
  border-color: var(--step-ready);
  transform: translateY(-1px);
  box-shadow: var(--btn-dl-hover-shadow);
}
.dv-btn-dl:active:not(:disabled) {
  transform: translateY(1px);
  box-shadow: none;
}

/* Destructive button — `btnDanger` (full width) */
.dv-btn-danger {
  width: 100%;
  padding: 8px 14px;
  border-radius: 6px;
  font-size: 12px;
  background: var(--danger-bg);
  border: 1px solid var(--danger-border);
  color: var(--danger-text);
}
.dv-btn-danger:hover:not(:disabled) {
  filter: brightness(0.94);
  border-color: var(--danger-text);
  transform: translateY(-1px);
  box-shadow: var(--btn-danger-hover-shadow);
}
.dv-btn-danger:active:not(:disabled) {
  transform: translateY(1px);
  box-shadow: none;
}

/* ── Segmented toggle ──────────────────────────────────────────────────
   Pill-bar where one option is active at a time. Wrap a row of buttons
   in `.dv-seg` and give the active button `.dv-seg-btn-active`. Used
   across power + molarity for mode/alpha/tails/solveFor/separator pickers.
   Replaces the inline-styled flex+overflow:hidden pattern that each tool
   previously re-implemented. */
.dv-seg {
  display: flex;
  border-radius: 6px;
  overflow: hidden;
  border: 1px solid var(--border-strong);
}
.dv-seg-btn {
  flex: 1;
  padding: 4px 0;
  font-size: 11px;
  font-weight: 400;
  font-family: inherit;
  cursor: pointer;
  border: none;
  background: var(--surface);
  color: var(--text-muted);
  transition:
    background 120ms ease,
    color 120ms ease;
}
.dv-seg-btn:hover:not(:disabled):not(.dv-seg-btn-active) {
  background: var(--surface-subtle);
  color: var(--text);
}
.dv-seg-btn:focus-visible {
  outline: 2px solid var(--accent-primary);
  outline-offset: -2px;
}
.dv-seg-btn:disabled {
  cursor: not-allowed;
  opacity: 0.55;
}
.dv-seg-btn-active {
  background: var(--accent-primary);
  color: var(--on-accent);
  font-weight: 700;
}

/* Facet-mode statistics summary: fill the scroll wrapper so its bottom
   aligns with the plot + display-tile stack on the left. */
.dv-facet-stats-scroll > .dv-panel {
  flex: 1 1 0;
  min-height: 0;
  margin: 0 !important;
  box-sizing: border-box;
  overflow-y: auto;
}

/* Control-tile title — `dv-tile-title`.

   Single source of truth for the typography of every plot-tool sidebar
   control tile heading (heatmap "Display / Style / Cluster", aequorin
   "Conditions / Axes / Style", boxplot "Shape & fill / Data points",
   etc.) and shared panel title (Actions, Prefs picker open buttons,
   inline Sets / Display / Variables / Filters cards on venn / scatter
   / upset). Heatmap was already using this style inline; the rest of
   the app drifted to plain mixed-case 12 px or 13 px regular weights,
   making the same control look louder in heatmap than elsewhere.

   Apply with `className="dv-tile-title"` on whatever element wraps
   the title text (`<button>` for collapsible ControlSection headers,
   `<p>` for static panel titles). Drop the corresponding fontSize /
   fontWeight / color / textTransform / letterSpacing from any inline
   `style={…}` — inline styles win against external rules, so leaving
   them in keeps the old look. */
.dv-tile-title {
  font-size: 12px;
  font-weight: 600;
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.5px;
  font-family: inherit;
}

/* RLU timecourse — chart column responsive toggles.

   The right side of the plot step has two widgets at the top: the
   sticky "🔬 Sample selection" pill on the left (must keep its
   differential stickiness so it stays visible while scrolling), and
   an absolute-positioned Series + Layout toggle group on the right
   (intentionally non-sticky so it scrolls away with the page). When
   the chart column is wide both share one row comfortably; when
   narrow (e.g. window < ~1280 px with the controls panel taking its
   ~360 px share), the two horizontally-laid toggle groups crowd into
   the sticky sample pill on the left.

   Fix: a container query on the chart column flips Series + Layout
   from a horizontal row into a right-aligned vertical stack below
   ~620 px of inline-size. Preserves the wide-screen look and the
   per-widget stickiness contract. */
.dv-aeq-chart-area {
  container-type: inline-size;
  container-name: aeq-chart;
}
.dv-aeq-toggles-group {
  position: absolute;
  top: 0;
  right: 0;
  z-index: 19;
  display: flex;
  align-items: flex-start;
  gap: 18px;
}
@container aeq-chart (max-width: 620px) {
  /* Narrow column: drop the absolute positioning so the toggles enter
     normal flow and push the chart (and any sticky neighbours below
     them) downward instead of overlapping. They still scroll away
     normally — Sample selection alone retains its `position: sticky`. */
  .dv-aeq-toggles-group {
    position: static;
    flex-direction: column;
    align-items: flex-end;
    gap: 8px;
    margin-bottom: 8px;
  }
}

/* ── Chunk-load spinner ──────────────────────────────────────────────
   Shown by the SPA's <Suspense> fallback while a per-tool chunk is
   fetching (each tool's `App` is wrapped in `React.lazy(() => import(…))`
   in `tools/_app/tool-registry.ts`). Themed via the same accent colour
   the rest of the chrome uses; the universal `prefers-reduced-motion`
   block below caps the spin to ~zero so vestibular-sensitive users
   see a static ring instead of an animated one. */
.dv-chunk-spinner {
  width: 28px;
  height: 28px;
  border-radius: 50%;
  border: 2.5px solid var(--border);
  border-top-color: var(--accent-primary);
  animation: dv-chunk-spinner-spin 0.9s linear infinite;
}
@keyframes dv-chunk-spinner-spin {
  to {
    transform: rotate(360deg);
  }
}

/* ── prefers-reduced-motion ──────────────────────────────────────────
   Honour the user's OS-level "reduce motion" preference (vestibular
   disorders, motion-triggered nausea). Sets transitions / animations
   to ~zero across every element so hover scales, button transforms,
   prefetch-bar slides, and disclosure-arrow rotations don't trigger
   motion. 0.01ms (not 0) keeps `transitionend` event listeners firing
   so any code that depends on them (none currently, but future-proofs
   the rule) doesn't break. The PlotToolShell.tsx StepFade component
   does its own JS-side check for the same media query and skips the
   opacity fade entirely — both layers are kept in sync.

   Covers components.css, index.html's inline <style>, and any future
   stylesheet because the universal selector + !important takes
   precedence over more specific transition declarations. */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}
