/* Shared UI components: design tokens, fields, checkboxes, validation messages, callouts,
   setting cards and rows, buttons, tabs, segmented controls, dialogs. Linked by EVERY page
   shell before its own stylesheet, so the dashboard and the product pages render the same
   controls. Contract: docs/DESIGN_GUIDE.md. Page-level rules (html, body, canvas, h4) and
   product layout never go here. A colour belongs in a token, and a dark value in the dark
   token block, so a component needs no dark override of its own (P554). */

/* ── Tokens ─────────────────────────────────────────────────────────────── */
:root {
  --font-ui: "IBM Plex Sans", "Segoe UI", Tahoma, sans-serif;
  --font-display: "Space Grotesk", "Segoe UI", Tahoma, sans-serif;
  --font-mono: "IBM Plex Mono", ui-monospace, "SFMono-Regular", Menlo, Consolas, monospace;
  --color-ink: #0f172a;
  --color-ink-muted: #475569;
  --color-ink-soft: #64748b;
  --color-accent: #178a7a;
  --color-accent-strong: #0d6b5f;
  --color-accent-warm: #f0a23a;
  --color-accent-warm-strong: #dd7f1f;
  --color-view: #2d6cdf;
  --color-view-strong: #1e4fbd;
  --color-on-fill: #ffffff; /* text and icons on an accent, view or danger fill */
  /* Teal under white text: the SAME in both themes, because white on the brightened dark
     accent fails WCAG. Primary buttons, the active segment, icon buttons. */
  --color-fill-accent: #178a7a;
  --color-fill-accent-strong: #0d6b5f;
  --color-fill-accent-hover: #1aa58f;
  --color-fill-accent-hover-strong: #0f6f62;
  --color-fill-run: #1f8f7a; /* start of the .file / .run icon-button gradient */
  --color-fill-neutral-btn: #6c757d; /* .toolbar-button.neutral: a grey action among coloured ones */
  --color-fill-neutral-btn-strong: #545b62;
  /* Status fills (toasts, progress, the "on" state). Separated along blue-yellow and by
     lightness, not by hue alone: every pair reads under protan and deutan simulation
     (scripts/cvd_check.py, pinned by test_status_palette.py). Danger alone is a dark fill
     under white text; success, warning and info are light fills under dark text (a green
     dark enough for white text is grey to a colour-blind eye), so white text means "error". */
  --color-success: #1cbd87;
  --color-success-strong: #1aad7c;
  --color-on-success: #1f2937;
  --color-danger: #c62828;
  --color-warning-fill: #f0a23a;
  --color-on-warning: #1f2937;
  --color-info: #45b0e6;
  --color-on-info: #1f2937;
  /* Status inks and soft grounds for messages and callouts. */
  --color-success-text: #068057;
  --color-danger-text: #c62828;
  --color-danger-soft: #fff5f5;
  --color-warning: #b26a00;
  --color-warning-soft: #fff8e6;
  --color-info-text: #0d6b5f;
  --color-info-soft: rgba(23, 138, 122, 0.1);
  --color-surface: #ffffff;
  --color-surface-soft: #f6f7fb;
  --color-card: rgba(255, 255, 255, 0.85);
  --color-note: var(--color-surface-soft);
  --color-field: #ffffff;
  --color-fill-neutral: #e2e8f0;
  --color-fill-neutral-strong: #cbd5e1;
  --color-ink-on-neutral: #334155;
  --color-surface-disabled: #f0f0f0;
  --color-ink-disabled: #888888;
  --color-hover: rgba(15, 23, 42, 0.06);
  --color-glass: rgba(255, 255, 255, 0.78);
  --color-glass-strong: rgba(255, 255, 255, 0.9);
  --color-border: rgba(15, 23, 42, 0.16);
  --color-border-soft: rgba(15, 23, 42, 0.08);
  --shadow-soft: 0 8px 24px rgba(15, 23, 42, 0.12);
  --shadow-strong: 0 16px 40px rgba(15, 23, 42, 0.18);
  --shadow-card: 0 6px 14px rgba(15, 23, 42, 0.06); /* setting cards; none in dark */
  /* Icon buttons: a tint of the accent under the square, deeper on hover. */
  --shadow-control: 0 6px 14px color-mix(in srgb, var(--color-fill-accent) 35%, transparent);
  --shadow-control-hover: 0 10px 18px color-mix(in srgb, var(--color-fill-accent) 40%, transparent);
  /* One disabled look (opacity) and two motions: colour and hover, transforms and reveals. */
  --opacity-disabled: 0.5;
  --motion-fast: 0.15s;
  --motion: 0.25s;
  /* Radii: lg dialogs and panels, md cards and popovers, sm every control (toolbar buttons
     included), xs badges, chips, inline code and note edges. Circles and pills stay literal. */
  --radius-lg: 16px;
  --radius-md: 12px;
  --radius-sm: 8px;
  --radius-xs: 4px;
  /* Control sizing: the UI is tuned so 100 % browser zoom reads like the old 90 %. */
  --control-lg: 36px;
  --control-md: 32px;
  --control-sm: 28px; /* every field and every small control: one inline row height */
  --icon-lg: 16px;
  --icon-md: 14px;
  /* Field widths: Standard is the default; the other three are explicit classes
     (.field-narrow, .field-wide, .field-xwide) for content that needs them. */
  --input-w-narrow: 65px;
  --input-w: 120px;
  --input-w-wide: 180px;
  --input-w-xwide: 220px;
  /* Text scale of the controls: 13 px for labels, values, buttons and tabs, 12 px for the
     small variants (.btn-sm, .seg-sm, .tab-sm), 11 px for captions, units and messages. */
  --font-control: 13px;
  --font-caption: 11px;
  --focus-ring: 0 0 0 3px rgba(23, 138, 122, 0.18);
  --color-overlay-scrim: rgba(240, 240, 240, 0.6); /* the #spinner overlay over the page */
  /* The select's own arrow (appearance: none), inked like a muted label per theme. */
  --select-chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath d='M2.5 4.5 6 8l3.5-3.5' fill='none' stroke='%23475569' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");

  /* Drawing grounds: white paper under drawings, figures and logos. The dark theme inverts
     the drawing (filter); logos stay on white. */
  --color-canvas: #ffffff;
  --color-canvas-rule: #dddddd;
  --color-preview-ground: #ffffff;
  /* Page backdrops (dashboard, login, help and blog): a warm glow, a teal glow and three
     gradient stops each. */
  --color-page-glow-warm: rgba(240, 162, 58, 0.16);
  --color-page-glow-teal: rgba(23, 138, 122, 0.14);
  --color-page-backdrop-start: #f8fafc;
  --color-page-backdrop-mid: #eef2f7;
  --color-page-backdrop-end: #e6eef9;
  /* Help and blog pages (the blog has no dark theme). */
  --color-heading-accent: #0f6d5f;
  --color-page-rule: #e2e8f0;
  --color-body-text: #1f2937;
}

html[data-theme="dark"] {
  color-scheme: dark;
  --color-ink: #e7ebf3;
  --color-ink-muted: #b6c0d0;
  --color-ink-soft: #8d99ad;
  --color-accent: #2cb9a4;
  --color-accent-strong: #1a8f7e;
  --color-success-text: #3fd1a0;
  --color-danger-text: #ff8a94;
  --color-danger-soft: #3a1d22;
  --color-warning: #ffb74d;
  --color-warning-soft: rgba(255, 183, 77, 0.12);
  --color-info-text: #4fd1bd;
  --color-info-soft: rgba(44, 185, 164, 0.14);
  --color-surface: #1a2231;
  --color-surface-soft: #131a26;
  --color-card: rgba(231, 235, 243, 0.04);
  --color-note: rgba(231, 235, 243, 0.05);
  --color-field: #10161f;
  --color-fill-neutral: #232c3a;
  --color-fill-neutral-strong: #2c3646;
  --color-ink-on-neutral: #e7ebf3;
  --color-surface-disabled: #1e2634;
  --color-ink-disabled: #7b8598;
  --color-hover: rgba(231, 235, 243, 0.1);
  --color-glass: rgba(23, 30, 44, 0.82);
  --color-glass-strong: rgba(23, 30, 44, 0.95);
  --color-border: rgba(231, 235, 243, 0.18);
  --color-border-soft: rgba(231, 235, 243, 0.09);
  --shadow-soft: 0 8px 24px rgba(0, 0, 0, 0.45);
  --shadow-strong: 0 16px 40px rgba(0, 0, 0, 0.6);
  --shadow-card: none;
  --focus-ring: 0 0 0 3px rgba(44, 185, 164, 0.3);
  --color-overlay-scrim: rgba(10, 14, 20, 0.55);
  --select-chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath d='M2.5 4.5 6 8l3.5-3.5' fill='none' stroke='%23b6c0d0' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");

  /* Drawing grounds: inverted white is black. */
  --color-preview-ground: #000000;
  /* Page backdrops. */
  --color-page-glow-warm: rgba(240, 162, 58, 0.08);
  --color-page-glow-teal: rgba(44, 185, 164, 0.1);
  --color-page-backdrop-start: #10161f;
  --color-page-backdrop-mid: #131a26;
  --color-page-backdrop-end: #141d2e;
  /* Help pages. */
  --color-heading-accent: #7adfd0;
  --color-page-rule: rgba(231, 235, 243, 0.16);
  --color-body-text: #c6cfdd;
}

/* ── Fields ─────────────────────────────────────────────────────────────── */
/* One field look everywhere, at zero specificity (:where) so any context rule
   that sizes or places a field wins without a fight. */
:where(input:not([type]), input[type="text"], input[type="number"], input[type="password"],
  input[type="search"], input[type="email"], input[type="url"], input[type="tel"],
  input[type="date"], select, textarea) {
  font-family: inherit;
  font-size: var(--font-control);
  color: var(--color-ink);
  background-color: var(--color-field);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  padding: 5px 8px;
  box-sizing: border-box;
  transition: border-color var(--motion-fast), box-shadow var(--motion-fast);
}
:where(input:not([type]), input[type="text"], input[type="number"], input[type="password"],
  input[type="search"], input[type="email"], input[type="url"], input[type="tel"],
  input[type="date"], select, textarea):focus {
  outline: none;
  border-color: var(--color-accent);
  box-shadow: var(--focus-ring);
}
:where(textarea) {
  line-height: 1.4;
  resize: vertical;
}
/* One height for a field and the small controls beside it (.btn-sm, .seg-sm); a textarea
   grows and keeps its padding. */
:where(input:not([type]), input[type="text"], input[type="number"], input[type="password"],
  input[type="search"], input[type="email"], input[type="url"], input[type="tel"],
  input[type="date"], select) {
  height: var(--control-sm);
  padding-top: 0;
  padding-bottom: 0;
}
/* A single-choice select draws its own arrow: the native menulist adds an inset before the
   text (so it never lined up with the input beside it) and a height of its own. */
:where(select:not([multiple]):not([size])) {
  appearance: none;
  padding-right: 26px;
  background-image: var(--select-chevron);
  background-repeat: no-repeat;
  background-position: right 8px center;
  background-size: 12px 12px;
}

/* Width variants. Inside a .setting-row the row rule reads --field-w; elsewhere the
   class sets the width itself. */
.field-narrow { --field-w: var(--input-w-narrow); }
.field-std { --field-w: var(--input-w); }
.field-wide { --field-w: var(--input-w-wide); }
.field-xwide { --field-w: var(--input-w-xwide); }
:where(.field-narrow, .field-std, .field-wide, .field-xwide) {
  width: var(--field-w);
}
/* 65 px leaves no room for a spin button beside a value like 12.345: narrow number
   fields drop it (the arrow keys still step the value). */
.field-narrow[type="number"] {
  -moz-appearance: textfield;
  appearance: textfield;
}
.field-narrow[type="number"]::-webkit-outer-spin-button,
.field-narrow[type="number"]::-webkit-inner-spin-button {
  -webkit-appearance: none;
  appearance: none;
  margin: 0;
}
/* Disabled and read-only look the same: a read-out is not an editor. The
   `.setting-row` selectors outrank the row rules below that paint the field (P556). */
input:disabled:not([type="checkbox"]):not([type="radio"]),
select:disabled,
textarea:disabled,
.setting-row select:disabled,
.setting-row textarea:disabled {
  background-color: var(--color-surface-disabled);
  color: var(--color-ink-disabled);
  -webkit-text-fill-color: var(--color-ink-disabled);
  opacity: 1;
  border-color: var(--color-border);
  cursor: not-allowed;
}
.input-readonly,
input.input-readonly[type="text"],
input.input-readonly[type="number"] {
  background: var(--color-surface-disabled) !important;
  color: var(--color-ink-disabled) !important;
  -webkit-text-fill-color: var(--color-ink-disabled);
}
/* Chrome drops number spinners on :disabled but keeps them on [readonly]. */
.input-readonly::-webkit-outer-spin-button,
.input-readonly::-webkit-inner-spin-button {
  -webkit-appearance: none;
  appearance: none;
  margin: 0;
}
.input-readonly[type="number"] {
  -moz-appearance: textfield;
  appearance: textfield;
}

/* Invalid: one red, one ring, always paired with a .setting-error message. */
.setting-invalid {
  border-color: var(--color-danger) !important;
  background-color: var(--color-danger-soft) !important;
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--color-danger) 25%, transparent);
}

/* Unit suffix inside a field (form_units.js copies the field's own width to the wrap). */
.input-unit-wrap {
  position: relative;
  flex: 0 0 var(--input-w);
  width: var(--input-w);
  min-width: var(--input-w);
  max-width: var(--input-w);
}
.input-unit-wrap > input {
  flex: 1 1 auto !important;
  width: 100% !important;
  min-width: 0 !important;
  max-width: none !important;
}
.input-unit-wrap > input[data-has-unit] {
  padding-right: 34px; /* form_units.js widens this for long units like kN/m2 */
}
.input-unit {
  position: absolute;
  right: 9px;
  top: 50%;
  transform: translateY(-50%);
  font-size: var(--font-caption);
  font-weight: 600;
  color: var(--color-ink-soft);
  pointer-events: none;
  user-select: none;
  max-width: 52px;
  overflow: hidden;
  white-space: nowrap;
}
/* A catalog swap can replace the adorned input with a <select>. */
.input-unit-wrap > select {
  flex: 1 1 auto !important;
  width: 100% !important;
  min-width: 0 !important;
  max-width: none !important;
  padding-right: 44px;
}
.input-unit-wrap > select ~ .input-unit {
  right: 26px;
}

/* ── Checkboxes ─────────────────────────────────────────────────────────── */
:where(input[type="checkbox"]) {
  width: 16px;
  height: 16px;
  accent-color: var(--color-accent);
  cursor: pointer;
}
:where(input[type="checkbox"]:disabled) {
  opacity: var(--opacity-disabled);
}
/* A state wins over every family: !important against rules that give enabled boxes a hand. */
input[type="checkbox"]:disabled {
  cursor: not-allowed !important;
}

/* ── Setting cards and rows ─────────────────────────────────────────────── */
.settings-group {
  margin-bottom: 10px;
  background: var(--color-card);
  border: 1px solid var(--color-border-soft);
  border-radius: var(--radius-md);
  padding: 10px 12px 8px;
  box-shadow: var(--shadow-card);
}
.settings-group-title {
  font-size: var(--font-caption);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--color-ink-soft);
  margin-bottom: 8px;
  padding-bottom: 6px;
  border-bottom: 1px solid var(--color-border-soft);
}
/* An uppercase label that is NOT a card title (a list heading, a meta label): the
   caption size at 600, no rule under it. */
.eyebrow {
  font-size: var(--font-caption);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--color-ink-soft);
  margin-bottom: 4px;
}
.settings-group-body {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 6px 28px;
}
/* Short-label forms fit three columns in the same grid. */
.settings-group-body.three-columns {
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 6px 20px;
}
.settings-group.company-info-group .settings-group-body {
  grid-template-columns: 1fr 1fr;
  gap: 20px;
}
.setting-row-wrap {
  display: flex;
  flex-direction: column;
}
.settings-group-body > .setting-row-wrap {
  min-width: 0;
}
/* The explanation inside a card: a soft box with a quiet left edge, tight under the
   card title. .note-after for a note placed after the fields it explains. */
.settings-group-note {
  margin: -4px 0 10px;
  padding: 6px 10px;
  background: var(--color-note);
  border-left: 3px solid var(--color-border);
  border-radius: var(--radius-xs);
  font-size: var(--font-control);
  line-height: 1.45;
  color: var(--color-ink-muted);
}
.settings-group-note.note-after { margin: 12px 0 0; }
.settings-group-note:empty { display: none; }
.settings-group-note code {
  font-family: var(--font-mono);
  font-size: 0.95em;
  background: var(--color-fill-neutral);
  padding: 1px 4px;
  border-radius: var(--radius-xs);
}

.setting-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 8px;
  max-width: 100%;
  box-sizing: border-box;
}
.setting-row.logo-row {
  flex-direction: column;
  align-items: center;
}
/* Labels wrap instead of truncating (a truncated label reads as "label … gap … field"). */
.setting-row label {
  flex: 1;
  font-weight: 500;
  color: var(--color-ink-muted);
  font-size: var(--font-control);
  line-height: 1.3;
  white-space: normal;
  overflow: hidden;
}
/* Width and layout only: the look is the field baseline above. */
.setting-row input[type="text"],
.setting-row input[type="number"],
.setting-row input[type="password"],
.setting-row select {
  flex: 0 0 var(--field-w, var(--input-w));
  width: var(--field-w, var(--input-w));
  min-width: var(--field-w, var(--input-w));
  max-width: var(--field-w, var(--input-w));
}
/* Multiline: the textarea spans the full grid width under its label. */
.setting-row.multiline-row,
.setting-row-wrap.multiline-wrap {
  flex-direction: column;
  align-items: flex-start;
  grid-column: 1 / -1;
  width: 100%;
}
.setting-row-wrap.multiline-wrap .setting-row.multiline-row {
  width: 100%;
}
/* Label above a full-width field (URL, key, free text). `flex: none`: in a column row the
   field rule's flex basis would act as a HEIGHT (P548). */
.setting-row.stacked {
  flex-direction: column;
  align-items: stretch;
  gap: 5px;
}
.setting-row.stacked label {
  flex: none;
}
.setting-row.stacked input,
.setting-row.stacked select,
.setting-row.stacked textarea {
  flex: none;
  width: 100%;
  min-width: 0;
  max-width: none;
}
.setting-row textarea {
  width: 100%;
  max-width: 100%;
  box-sizing: border-box;
  padding: 6px 8px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--color-border);
  background-color: var(--color-field);
  color: var(--color-ink);
  font-size: var(--font-control);
  font-family: inherit;
  resize: vertical;
  min-height: 60px;
}
/* A row whose value belongs to the ORGANISATION (orgLocks.js): the field reads as
   disabled, the label says why with its "?" badge. */
.setting-row.org-locked > label,
.setting-row-wrap.org-locked > .setting-row > label {
  color: var(--color-ink-muted);
}
.org-help {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 16px;
  height: 16px;
  flex: none;
  border-radius: var(--radius-xs);
  background: var(--color-fill-accent);
  color: var(--color-on-fill);
  font-size: var(--font-caption);
  font-weight: 600;
  line-height: 1;
  cursor: help;
  vertical-align: middle;
  user-select: none;
}

/* ── Validation messages and callouts ───────────────────────────────────── */
/* The message sits directly under its field, aligned with the field's edge: right
   under a field at the right of its label, .align-start under a full-width field.
   An empty message takes no space; .reserve keeps one line so a single-field dialog
   does not jump. Scripts may also toggle style.display or the hidden attribute. */
.setting-error {
  display: block;
  color: var(--color-danger-text);
  font-size: var(--font-caption);
  font-weight: 600;
  line-height: 1.3;
  margin: 1px 0 2px;
  padding-left: 2px;
  text-align: right;
}
.setting-error:empty,
.setting-error[hidden] {
  display: none;
}
.setting-error.reserve {
  min-height: 14px;
}
.setting-error.reserve:empty {
  display: block;
}
.setting-error.align-start {
  text-align: left;
  padding-left: 0;
}
.setting-error.is-warning { color: var(--color-warning); }
.setting-error.is-warning::before {
  content: "\f06a";
  font: var(--fa-font-solid);
  margin-right: 4px;
}
.setting-error.is-ok { color: var(--color-accent); }

/* A message about a whole section rather than one field. */
.callout {
  margin: 8px 0;
  padding: 7px 10px;
  border-left: 3px solid var(--color-border);
  border-radius: var(--radius-sm);
  font-size: var(--font-control);
  line-height: 1.45;
}
.callout[hidden] { display: none; }
.callout.is-error {
  border-left-color: var(--color-danger);
  background: var(--color-danger-soft);
  color: var(--color-danger-text);
  font-weight: 600;
}
.callout.is-warning {
  border-left-color: var(--color-warning);
  background: var(--color-warning-soft);
  color: var(--color-warning);
}
.callout::before {
  font: var(--fa-font-solid);
  margin-right: 7px;
}
.callout.is-error::before { content: "\f071"; }
.callout.is-warning::before { content: "\f06a"; }
.callout.is-info::before { content: "\f05a"; }
.callout.is-info {
  border-left-color: var(--color-accent);
  background: var(--color-info-soft);
  color: var(--color-info-text);
}
/* A plain status line under a toolbar or list. */
.status-line { font-size: var(--font-control); color: var(--color-ink-muted); min-height: 16px; }
.status-line.is-error { color: var(--color-danger-text); }
.status-line.is-ok { color: var(--color-accent); }

/* ── Buttons ────────────────────────────────────────────────────────────── */
/* .btn-primary, .btn-secondary, .btn-danger, .btn-ghost; sizes .btn-sm and .btn-block.
   A class-less button in a dialog footer is primary. */
.btn-primary,
.btn-secondary,
.btn-danger,
.btn-ghost,
.modal-footer button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  box-sizing: border-box; /* an <a> button is content-box by default and grows by its border */
  height: var(--control-md);
  padding: 0 16px;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  font-family: inherit;
  font-size: var(--font-control);
  font-weight: 600;
  line-height: 1;
  white-space: nowrap;
  text-decoration: none;
  cursor: pointer;
  transition: background var(--motion-fast) ease, border-color var(--motion-fast) ease, color var(--motion-fast) ease;
}
.modal-footer button:not(.btn-secondary):not(.btn-danger):not(.btn-ghost),
.btn-primary {
  background-color: var(--color-accent);
  color: var(--color-on-fill);
}
.modal-footer button:not(.btn-secondary):not(.btn-danger):not(.btn-ghost):hover:not(:disabled),
.btn-primary:hover:not(:disabled) {
  background-color: var(--color-accent-strong);
}
/* Dark: keep the DEEPER teal fill; white on the brightened dark accent fails WCAG. */
html[data-theme="dark"] .modal-footer button:not(.btn-secondary):not(.btn-danger):not(.btn-ghost),
html[data-theme="dark"] .btn-primary {
  background: linear-gradient(145deg, var(--color-fill-accent), var(--color-fill-accent-strong));
  background-color: var(--color-fill-accent);
}
html[data-theme="dark"] .modal-footer button:not(.btn-secondary):not(.btn-danger):not(.btn-ghost):hover:not(:disabled),
html[data-theme="dark"] .btn-primary:hover:not(:disabled) {
  background: linear-gradient(145deg, var(--color-fill-accent-hover), var(--color-fill-accent-hover-strong));
  background-color: var(--color-fill-accent-strong);
}
/* !important: secondary must beat the footer's class-less primary rule and id rules. */
#cancelSettings,
.btn-secondary {
  background: var(--color-fill-neutral) !important;
  color: var(--color-ink-on-neutral) !important;
}
#cancelSettings:hover:not(:disabled),
.btn-secondary:hover:not(:disabled) {
  background: var(--color-fill-neutral-strong) !important;
}
/* The `background` SHORTHAND for every state colour: the dark primary paints a
   gradient, which covers any background-color underneath (P436). */
.btn-danger,
#cancelSettings.confirm-discard,
.modal-footer button.confirm-discard {
  background: var(--color-danger) !important;
  color: var(--color-on-fill) !important;
}
.btn-danger:hover:not(:disabled) {
  filter: brightness(0.92);
}
.btn-ghost {
  background: transparent;
  color: var(--color-ink-muted);
}
.btn-ghost:hover:not(:disabled) {
  background: var(--color-hover);
  color: var(--color-ink);
}
.btn-secondary.danger,
.btn-ghost.danger {
  color: var(--color-danger-text) !important;
}
.btn-sm {
  height: var(--control-sm);
  padding: 0 10px;
  gap: 5px;
  font-size: 12px;
}
.btn-block {
  width: 100%;
}
/* A display rule on a class defeats the hidden attribute (P514): restore it for every
   control that sets its own display. */
:is(.btn-primary, .btn-secondary, .btn-danger, .btn-ghost, .toolbar-button, .tab-button,
  .seg-btn, .seg-control, .setting-row, .setting-row-wrap, .settings-group)[hidden] {
  display: none;
}
.btn-primary:disabled,
.btn-secondary:disabled,
.btn-danger:disabled,
.btn-ghost:disabled,
.modal-footer button:disabled {
  opacity: var(--opacity-disabled);
  cursor: not-allowed;
}
.btn-primary:focus-visible,
.btn-secondary:focus-visible,
.btn-danger:focus-visible,
.btn-ghost:focus-visible,
.modal-footer button:focus-visible,
.tab-button:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}

/* ── Tabs and segmented controls ────────────────────────────────────────── */
.tab-button {
  padding: 7px 14px;
  border: none;
  border-bottom: 2px solid transparent;
  background: transparent;
  cursor: pointer;
  font-family: inherit;
  font-size: var(--font-control);
  font-weight: 500;
  color: var(--color-ink-soft);
  transition: color var(--motion-fast) ease, background var(--motion-fast) ease, border-color var(--motion-fast) ease;
}
.tab-button:hover {
  color: var(--color-accent);
  background: var(--color-info-soft);
}
.tab-button.active {
  color: var(--color-accent);
  border-bottom-color: var(--color-accent);
  font-weight: 600;
}
.tab-button.tab-sm {
  padding: 4px 8px;
  font-size: 12px;
  font-weight: 600;
}
/* The row the tabs sit in: a hairline under it. `.is-framed` for an edge-to-edge dialog,
   where the bar runs to the frame with the soft fill and the frame's 18 px inset. */
.tab-bar {
  display: flex;
  gap: 0;
  border-bottom: 1px solid var(--color-border-soft);
}
.tab-bar.is-framed {
  padding: 0 18px;
  background: var(--color-surface-soft);
}

/* Two to four mutually exclusive options on one line, one `.active`; replaces radios. */
/* No overflow clip: it would cut the buttons' ::after tooltips; the end buttons round
   their own corners instead. */
.seg-control {
  display: inline-flex;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  background: var(--color-surface);
}
.seg-btn {
  height: var(--control-md);
  padding: 0 14px;
  border: none;
  border-right: 1px solid var(--color-border);
  background: var(--color-surface);
  color: var(--color-ink);
  font-family: inherit;
  font-size: var(--font-control);
  font-weight: 600;
  cursor: pointer;
}
.seg-btn:first-child {
  border-top-left-radius: calc(var(--radius-sm) - 1px);
  border-bottom-left-radius: calc(var(--radius-sm) - 1px);
}
.seg-btn:last-child {
  border-right: none;
  border-top-right-radius: calc(var(--radius-sm) - 1px);
  border-bottom-right-radius: calc(var(--radius-sm) - 1px);
}
/* Hover must read against the white control: the tab hover tint, not a surface grey. */
.seg-btn:hover:not(.active):not(:disabled) {
  background: var(--color-info-soft);
}
.seg-btn.active {
  background: var(--color-fill-accent);
  color: var(--color-on-fill);
}
.seg-btn:disabled {
  opacity: var(--opacity-disabled);
  cursor: not-allowed;
}
.seg-btn:focus-visible {
  outline: none;
  box-shadow: var(--focus-ring);
}
.seg-control.seg-sm .seg-btn {
  height: var(--control-sm);
  padding: 0 10px;
  font-size: 12px;
}
/* Full-width variant: the options share the row equally. */
.seg-control.seg-fill {
  display: flex;
  width: 100%;
}
.seg-control.seg-fill .seg-btn {
  flex: 1 1 0;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ── Icon buttons (toolbars) ────────────────────────────────────────────── */
/* One square icon button: 36 px, or 32 px with .md. Colour families: default (teal),
   .file and .run (green, "this computes"), .settings (warm), .view (blue toggles). */
.toolbar-button {
  width: var(--control-lg);
  height: var(--control-lg);
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: none;
  border-radius: var(--radius-sm);
  background: linear-gradient(145deg, var(--color-fill-accent), var(--color-fill-accent-strong));
  color: var(--color-on-fill);
  cursor: pointer;
  position: relative;
  box-shadow: var(--shadow-control);
  transition: filter var(--motion-fast) ease, transform var(--motion-fast) ease, box-shadow var(--motion) ease;
}
.toolbar-button i {
  font-size: var(--icon-lg);
  pointer-events: none;
}
.toolbar-button.md {
  width: var(--control-md);
  height: var(--control-md);
}
.toolbar-button.md i {
  font-size: var(--icon-md);
}
.toolbar-button:hover:not(:disabled) {
  filter: brightness(1.08);
  transform: translateY(-2px) scale(1.02);
  box-shadow: var(--shadow-control-hover);
}
.toolbar-button:active:not(:disabled) {
  transform: scale(0.95);
  box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
}
.toolbar-button:disabled {
  opacity: var(--opacity-disabled);
  cursor: not-allowed;
}
.toolbar-button:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}
.toolbar-button.file,
.toolbar-button.run {
  background: linear-gradient(145deg, var(--color-fill-run), var(--color-fill-accent-strong));
}
.toolbar-button.settings {
  background: linear-gradient(145deg, var(--color-accent-warm), var(--color-accent-warm-strong));
}
.toolbar-button.view {
  background: linear-gradient(145deg, var(--color-view), var(--color-view-strong));
}
/* The .active glow is the family's own fill at half strength, so a palette change reaches it. */
.toolbar-button.active {
  box-shadow: 0 0 14px 4px color-mix(in srgb, var(--color-fill-accent) 50%, transparent);
  animation: togglePulse 0.35s ease;
}
.toolbar-button.file.active,
.toolbar-button.run.active {
  box-shadow: 0 0 14px 4px color-mix(in srgb, var(--color-fill-run) 55%, transparent);
}
.toolbar-button.settings.active {
  box-shadow: 0 0 14px 4px color-mix(in srgb, var(--color-accent-warm) 55%, transparent);
}
.toolbar-button.view.active {
  box-shadow: 0 0 14px 4px color-mix(in srgb, var(--color-view) 50%, transparent);
}
/* .neutral: a grey action (the 2D editor's move / copy / delete) or an on/off option that is
   grey when off; its .active state is the success fill ("on"). .success: a two-second "done"
   flash after a send, added and removed by the caller. */
.toolbar-button.neutral {
  background: linear-gradient(145deg, var(--color-fill-neutral-btn), var(--color-fill-neutral-btn-strong));
  box-shadow: 0 6px 14px color-mix(in srgb, var(--color-fill-neutral-btn-strong) 35%, transparent);
}
.toolbar-button.neutral.active,
.toolbar-button.success {
  background: linear-gradient(145deg, var(--color-success), var(--color-success-strong));
  color: var(--color-on-success);
}
.toolbar-button.neutral.active {
  box-shadow: 0 0 14px 4px color-mix(in srgb, var(--color-success) 55%, transparent);
}
.toolbar-button.success {
  animation: pulse-success 0.5s ease;
}
@keyframes pulse-success {
  0%, 100% { transform: scale(1); }
  50% { transform: scale(1.1); }
}
@keyframes togglePulse {
  0%   { transform: scale(1); }
  40%  { transform: scale(1.12); }
  100% { transform: scale(1); }
}

/* ── Spinner ────────────────────────────────────────────────────────────── */
/* One ring: the track is a border token (lighter than the ground in dark, darker in light),
   the arc the accent. 36 px by default, .spinner-lg 48, .spinner-xl 70 (the #spinner overlay). */
.spinner {
  width: 36px;
  height: 36px;
  box-sizing: border-box;
  border: 3px solid var(--color-border);
  border-top-color: var(--color-accent);
  border-radius: 50%;
  animation: spin 0.8s linear infinite;
}
.spinner.spinner-lg {
  width: 48px;
  height: 48px;
  border-width: 4px;
}
.spinner.spinner-xl {
  width: 70px;
  height: 70px;
  border-width: 8px;
}
@keyframes spin {
  to { transform: rotate(360deg); }
}
/* The overlay every app shell carries, driven by spinner.js: a scrim over its positioned
   parent (the body in a shell), the ring, a label, the elapsed clock, an optional progress
   line and Cancel. */
#spinner {
  position: absolute;
  inset: 0;
  display: none;
  z-index: 10;
  background: var(--color-overlay-scrim);
  backdrop-filter: blur(2px);
}
#spinner-inner {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  text-align: center;
}
#spinner-inner > .spinner {
  margin: 0 auto;
}
#spinner-text {
  display: block;
  margin-top: 10px;
  font-weight: 600;
  color: var(--color-ink);
  font-size: 1.1em;
  text-align: center;
}
#spinner-elapsed {
  display: block;
  margin-top: 6px;
  font-size: var(--font-control);
  font-weight: 600;
  color: var(--color-ink-soft);
  font-variant-numeric: tabular-nums;
  text-align: center;
}
/* Precompute phase + remaining-time line; empty (and so invisible) for every other job,
   which is why it carries no reserved height. */
#spinner-progress {
  display: block;
  margin-top: 4px;
  font-size: 12px;
  color: var(--color-ink-muted);
  text-align: center;
}
#spinner-cancel {
  margin-top: 14px;
}

/* ── Dialogs ────────────────────────────────────────────────────────────── */
.modal {
  display: none;
  position: fixed;
  z-index: 50;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  background-color: rgba(0, 0, 0, 0.6);
}
.modal-content {
  background-color: var(--color-surface);
  margin: 3vh auto 0 auto;
  padding: 16px 18px;
  border-radius: var(--radius-lg);
  width: 80%;
  max-width: 800px;
  max-height: 90vh;
  overflow-y: auto;
  box-shadow: var(--shadow-strong);
}
.modal-content.cfd-modal {
  max-width: 1020px;
  width: 94%;
}
.modal-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 10px;
}
.modal-header h3 {
  margin: 0;
  padding: 4px 0;
  font-size: 16px;
  font-weight: 700;
  color: var(--color-ink);
  font-family: var(--font-display);
}
.modal-footer {
  display: flex;
  justify-content: flex-end;
  align-items: center;
  margin-top: 15px;
  gap: 10px;
}
/* The dialog frame: a hairline under the header and over the footer, both running to the
   dialog edge through the content padding. Dialogs whose content is already an edge-to-edge
   frame (padding 0: the girder, truss and MIDAS API dialogs) set their own margins. */
.modal-content > .modal-header:first-child {
  margin: -16px -18px 12px;
  padding: 12px 18px;
  border-bottom: 1px solid var(--color-border-soft);
}
.modal-content > .modal-footer:last-child {
  margin: 16px -18px -16px;
  padding: 12px 18px;
  border-top: 1px solid var(--color-border-soft);
}
.modal-footer.is-dirty .modal-dirty-note {
  visibility: visible;
}
/* Settings JSON export/import: icon buttons at the left of the footer. */
.modal-footer .settings-json-btn {
  width: 32px;
  padding: 0;
  flex: 0 0 32px;
}
.modal-footer .settings-json-btn:first-of-type {
  margin-right: -4px;
}
.modal-footer #downloadSettingsJson { order: -3; }
.modal-footer #uploadSettingsJson { order: -2; }
.modal-footer .modal-dirty-note { order: -1; }

.close-button {
  cursor: pointer;
  font-size: 20px;
  line-height: 1;
  color: var(--color-ink-soft);
  padding: 4px 6px;
  border: none;
  background: none;
  border-radius: var(--radius-sm);
  transition: color var(--motion-fast), background var(--motion-fast);
}
.close-button:hover {
  color: var(--color-ink);
  background: var(--color-hover);
}
