/* Rock Shear Strength Parameter Evaluation — tool-scoped styling.
 *
 * The shell, cards, buttons, selects and typography come from Material
 * Dashboard 3; this file only covers what the theme has no component for —
 * the parameter spinners, the outputs grid, the plot toolbar, the data grid,
 * and the thirteen estimation aids behind the Pick buttons.
 *
 * Two rules govern everything here:
 *
 * 1. **Every rule is scoped to `.rss`.** The tool's markup was written
 *    framework-free and carries generic class names — `.field`, `.status`,
 *    `.out`, `.hint`, `.data`. Unscoped they would leak onto the rest of the
 *    platform, and `.icon` in particular is a Material Dashboard name.
 * 2. **Colour comes from tokens, never from a literal.** The `--rss-*` block
 *    below is the only place a colour is chosen, and it is redeclared under
 *    `:root.dark-version` — which is how Xplore switches theme
 *    (`documentElement.classList`), not `prefers-color-scheme`. A hard-coded
 *    `#fff` here is a bug that only shows up after the theme toggle.
 *
 * The four dialogs are `.rss-overlay` / `.rss-dialog` rather than
 * `.modal-backdrop` / `.modal`: Bootstrap owns those two names, its
 * `.modal-backdrop` paints an opaque black sheet without a `.show` class, and
 * its `.modal` is `display: none` until the Bootstrap JS opens it. These
 * dialogs are opened by the tool's own `hidden` class toggle, so they must not
 * collide with either.
 */

:root {
  /* Surfaces and ink, mapped onto Material Dashboard's own values so the tool
   * sits on the same planes as every other card in the platform. */
  --rss-surface: #fff;             /* card face — matches .card */
  --rss-inset: #f0f0f0;            /* --bs-gray-100 — inputs, spinner buttons */
  --rss-text: #0a0a0a;             /* --bs-emphasis-color */
  --rss-text-2: #737373;           /* --bs-body-color */
  --rss-muted: #a3a3a3;            /* --bs-gray-400 */
  /* Translucent on purpose, over `--bs-black-rgb`. The outputs block is a 1px
   * CSS-grid gap showing the container through, and `auto-fit` leaves the last
   * row's unfilled remainder showing it too — as a solid colour that remainder
   * reads as a second empty panel rather than as a seam. */
  --rss-border: rgba(10, 10, 10, 0.1);
  --rss-border-strong: #d4d4d4;    /* --bs-border-color */
  /* Selection and focus: the checked radio pill, the active Pick-modal tab, the
   * row hovers, the focus ring, the drop target. `--bs-primary`, the same brown
   * as `.btn-primary` and `.text-primary` — not `--bs-accent`, whose green this
   * used to be. The tool sits inside a page whose only other filled control is
   * the primary Save button, and two different "this one is selected" colours on
   * one screen read as two different meanings. */
  --rss-accent: #744c54;           /* --bs-primary */
  --rss-accent-ink: #fff;

  /* Chart series — Material Dashboard's own theme colours, not a palette
   * invented for this tool. Every hex below is copied from the `:root` block of
   * `assets/css/material-dashboard.css`, so a series line is the same ink as the
   * `.text-info` / `.text-primary` / `.text-accent` / `.text-warning` the rest of
   * the platform paints with. Four hues, four distinct lightnesses, so the
   * series stay separable in greyscale and under the common colour-vision
   * deficiencies as well as by hue.
   *
   * `--series-2` holds the same value as `--rss-accent` above, and `--series-3`
   * holds the green that token used to have. Neither is a leftover. A series
   * colour encodes *which curve this is* and means something only beside the
   * curve it names, so it is picked for separability inside the plot;
   * `--rss-accent` encodes *this control is selected*. They are free to
   * coincide, and merging them would cost the plot a hue. */
  --series-1: #1a73e8;  /* --bs-info    — failure envelope */
  --series-2: #744c54;  /* --bs-primary — equivalent Mohr-Coulomb */
  --series-3: #397d6d;  /* --bs-accent  — measured test data */
  --series-4: #fb8c00;  /* --bs-warning — instantaneous Mohr-Coulomb tangent */

  --status-info: #1a73e8;          /* --bs-info */
  --status-warning: #fb8c00;       /* --bs-warning */
  --status-critical: #f44335;      /* --bs-danger */

  --rss-radius: var(--bs-border-radius, 0.225rem);

  /* --- the parameter column's four tracks -------------------------------
   * Every row in the sidebar — typed or computed, in any of the four criteria —
   * is laid on these same tracks, which is what makes the column scannable:
   * symbols line up on one right edge, every value box starts and ends at the
   * same x, units line up, Picks line up. They are custom properties rather
   * than a literal in `.prow` so the one number that has to change when a
   * criterion arrives with a wider symbol changes in one place. */
  --rss-prow-label: 3.5rem;
  --rss-prow-unit: 2.75rem;
  --rss-prow-pick: 1.75rem;
  --rss-prow-wide-label: 5rem;

  /* --- the chart adapter's contract -------------------------------------
   * `rock-shear-strength-charts.js` builds the Plotly layout by reading these
   * names off `documentElement` — Plotly is not in Highcharts' styledMode
   * and cannot take its chrome from a stylesheet, so the colours have to be
   * handed to it in JS. They are unprefixed because that is the name the
   * adapter asks for; nothing else in the platform declares or reads them.
   *
   * Rename one of these and the plots do not break loudly: `getPropertyValue`
   * returns an empty string, Plotly silently falls back to its own white
   * defaults, and the charts sit as a light rectangle inside a dark card.
   *
   * These are deliberately NOT aliases of the `--rss-*` UI tokens above. A
   * sidebar label at 12px sits on a card the eye is resting on and can afford
   * `--bs-gray-400`; a tick label sits *inside* a busy plot next to four
   * coloured curves, and at that size and that surround the same grey reads as
   * fog. Everything the chart draws is one to three steps darker than its UI
   * equivalent, and every value is still a Material Dashboard grey. */
  --surface-1: var(--rss-surface);
  --gridline: #d4d4d4;             /* --bs-gray-300 — visible, not assertive */
  --baseline: #737373;             /* --bs-gray-500 — axis lines and zero rules */
  --text-primary: #262626;         /* --bs-dark  — the sampler's "you are here" */
  --text-secondary: #404040;       /* --bs-gray-700 — axis titles, legend */
  --text-muted: #525252;           /* --bs-gray-600 — tick labels */

  /* --- the same plot, on paper -------------------------------------------
   * `printFigure()` recolours a plot for the PDF report and the PNG export, and
   * it has to do that from a user in *either* theme. These are the light values
   * again under names `:root.dark-version` deliberately never overrides, so the
   * adapter can read "what this colour becomes on white" at any time — a report
   * built in dark mode comes out identical to one built in light mode, and the
   * hexes still live in exactly one file.
   *
   * Without them a dark-theme export puts #75abf1 and #ac9498 on white paper at
   * roughly 2:1, and paints the sampler's marker — `--text-primary`, #fff —
   * invisibly. */
  --print-paper: #fff;
  --print-series-1: #1a73e8;
  --print-series-2: #744c54;
  --print-series-3: #397d6d;
  --print-series-4: #fb8c00;
  --print-ink: #262626;
  --print-muted: #525252;
  --print-grid: #d4d4d4;
}

:root.dark-version {
  --rss-surface: #202020;          /* .dark-version body .card */
  --rss-inset: #181818;            /* --bs-dark-bg-subtle @ dark */
  --rss-text: rgba(255, 255, 255, 0.9);
  --rss-text-2: rgba(255, 255, 255, 0.7);
  --rss-muted: rgba(255, 255, 255, 0.45);
  --rss-border: rgba(255, 255, 255, 0.12);
  --rss-border-strong: rgba(255, 255, 255, 0.2);
  /* The primary at its dark-mode step — Bootstrap's own
   * `--bs-primary-text-emphasis` under `[data-bs-theme=dark]`, which is also
   * what this theme makes `--bs-link-color` in dark mode, so a filled pill here
   * is the same brown a link is three inches away.
   *
   * It lightens rather than staying #744c54 because the fill has to be told
   * apart from the `#202020` card behind it, and #744c54 sits at 2.25:1 against
   * that — a selected pill that does not read as selected. Lightening the fill
   * flips the label to near-black, hence the ink below. */
  --rss-accent: #ac9498;           /* --bs-primary-text-emphasis @ dark */
  --rss-accent-ink: #0a0a0a;       /* --bs-black */

  /* The same four theme colours at their dark-mode step. Three of them are
   * Bootstrap's own `[data-bs-theme=dark]` `*-text-emphasis` values from
   * material-dashboard.css, resolved from `rgb()` to hex; the fourth is
   * `--bs-accent-dark`, which the theme ships for exactly this purpose. At the
   * light step #744c54 and #1a73e8 fall to 2.3:1 and 3.5:1 on a `#202020` card
   * — under the 3:1 a 2px line needs, in the maroon's case badly. */
  --series-1: #75abf1;  /* --bs-info-text-emphasis    @ dark */
  --series-2: #ac9498;  /* --bs-primary-text-emphasis @ dark */
  /* `--bs-teal`, not `--bs-accent-dark`. #579183 is the accent tuned to be read
   * as a *surface* in dark mode, and as an 8px marker on a dark field it came
   * out the weakest of the four at 4.4:1. `--bs-teal` is the same hue family
   * from the theme's own `:root` palette, saturated rather than muted, and
   * carries the markers at 8.7:1. */
  --series-3: #20c997;  /* --bs-teal */
  --series-4: #fdba66;  /* --bs-warning-text-emphasis @ dark */

  --status-info: #75abf1;

  /* Chart chrome.
   *
   * The plot drops to the theme's dark *body* colour rather than staying on the
   * card's. At #202020 the plot well was the same plane as the card holding it,
   * so the chart had no edge — it read as a dim region of the card rather than
   * as a figure, which is most of what made it feel dim regardless of what was
   * drawn on it. Sitting the well one plane below the card is the same
   * relationship Material Dashboard already uses the other way round, where a
   * #202020 card sits on a #121212 body; it gives the figure a boundary and
   * hands every series about 15% more contrast for free.
   *
   * The two rules then step up to match: at the old alphas a gridline was
   * 1.7:1, which is a gridline you have to look for. */
  --surface-1: #121212;            /* --bs-body-bg @ dark (`.dark-version body`) */
  --gridline: rgba(255, 255, 255, 0.26);
  --baseline: rgba(255, 255, 255, 0.62);
  --text-primary: #fff;            /* --bs-emphasis-color @ dark */
  --text-secondary: #e5e5e5;       /* --bs-gray-200 */
  --text-muted: #d4d4d4;           /* --bs-body-color @ dark */
}

/* ------------------------------------------------------------------ shell -- */

.rss {
  font-size: 0.875rem;
  line-height: 1.5;
  color: var(--rss-text);
}

.rss .hidden,
.rss.hidden { display: none !important; }

/* Present to assistive technology, absent from the layout. Used for the field
   descriptions `applyFieldHelp` attaches — `display: none` would take them out
   of the accessibility tree along with everything else, which is exactly what
   `aria-describedby` needs them to stay in. */
.rss .sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* The sidebar tracks the plots while the user nudges a parameter and watches
   the envelope move — that pairing is the tool's central interaction, so it
   stays pinned until the viewport is too narrow to hold both columns. */
@media (min-width: 992px) {
  .rss .rss-sidebar {
    position: sticky;
    top: 1rem;
    max-height: calc(100vh - 6rem);
    overflow-y: auto;
    padding-right: 0.25rem;
  }
}

/* Cards need a stated edge here.
 *
 * Material Dashboard separates a card from the page with a box-shadow, which
 * works on the light theme's #f0f2f5 body and reads as nothing on the dark
 * one — and on this page the card and its surround are both #202020, so every
 * section boundary disappeared and the whole tool rendered as one flat field.
 * The sidebar's Project / Hoek-Brown / Test data split and the main column's
 * Rock mass / Equivalent MC / Failure envelopes split are the page's structure;
 * without them the eye has nothing to group by. */
.rss .card {
  border: 1px solid var(--rss-border);
}

.rss .rss-card-title {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  margin: 0 0 0.75rem;
  font-size: 0.75rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--rss-text-2);
}

.rss .rss-card-title .hint {
  margin-left: auto;
  text-transform: none;
  letter-spacing: 0;
  font-weight: 400;
  font-size: 0.6875rem;
  color: var(--rss-muted);
}

.rss .hint.error { color: var(--status-critical); }
.rss .hint.warning { color: var(--status-warning); }

/* The tool's only live region. `role="status"` announces "converting…",
   "saved", "fit failed" without stealing focus from the field the user is in
   the middle of typing into. */
.rss .status {
  font-size: 0.75rem;
  color: var(--rss-muted);
  font-variant-numeric: tabular-nums;
  min-width: 90px;
  text-align: right;
}

.rss .status.busy { color: var(--status-info); }
.rss .status.error { color: var(--status-critical); }

.rss .spacer { flex: 1; }
.rss .btn-row { display: flex; gap: 0.5rem; flex-wrap: wrap; }

/* Material Dashboard's small button carries 16px of horizontal padding. This is
 * a parameter workbench — six buttons in one row inside a 300px column — so the
 * tool tightens the *token* rather than the rule, and the gradient, shadow,
 * hover and focus treatments all stay the theme's. */
.rss .btn { --bs-btn-padding-x: 0.625rem; }

.rss .btn .material-symbols-rounded {
  font-size: 1rem;
  line-height: 1;
  vertical-align: -0.2em;
}

/* ------------------------------------------------------------------ forms -- */

.rss .field { margin-bottom: 0.75rem; }
.rss .field:last-child { margin-bottom: 0; }

/* `.field-label` is the caption for a group of radios. It is a span rather than
   a <label> because a <label> that labels nothing is one an assistive
   technology announces as a label for whatever follows it — the group carries
   `aria-labelledby` pointing here instead. Styled identically. */
.rss .field > label,
.rss .field .field-label {
  display: block;
  font-size: 0.75rem;
  color: var(--rss-text-2);
  margin-bottom: 0.25rem;
}

.rss .field label .unit {
  color: var(--rss-muted);
  font-size: 0.6875rem;
}

/* --------------------------------------------------- the parameter row -- */

/* `.prow` is the sidebar's whole grammar: **symbol · value · unit · Pick**, on
 * one line, on four shared tracks. It replaced a stacked `.field` whose label
 * was a full sentence sitting above a full-width box.
 *
 * The reason is not density for its own sake. RocData's users read that panel
 * as a *column of quantities* — they scan down the right-aligned symbols for
 * the one they want and their eye never leaves that edge. A sentence-labelled
 * stack has no such edge: every row starts at a different word and the symbol,
 * if it appears at all, is buried mid-sentence. Halving the row height is what
 * then makes the results fit in the same column as the inputs, which is the
 * arrangement the rest of this file exists to support.
 *
 * What the sentences bought is not lost. Every one of them is still attached by
 * `applyFieldHelp()` — to the box as `title`, to the symbol as `title`, and to
 * the accessibility tree as an `aria-describedby` paragraph. The symbol is the
 * hover target because the symbol is what a user who does not recognise it
 * points at.
 *
 * `.prow-out` below is the same four tracks carrying a computed value instead
 * of an input. That is the entire trick: an input row and a result row are
 * indistinguishable in layout, so mb sits under D as naturally as GSI sits
 * under σci, and the column reads as one list rather than as a form followed by
 * a report. */
.rss .prow {
  display: grid;
  grid-template-columns:
    var(--rss-prow-label) minmax(0, 1fr) var(--rss-prow-unit) var(--rss-prow-pick);
  align-items: center;
  column-gap: 0.375rem;
  margin-bottom: 0.375rem;
}

.rss .prow:last-child { margin-bottom: 0; }

/* Explicit tracks, not auto-placement. Rows differ in which cells they carry —
 * mi has no unit, the power curve's Pa has no Pick, an output row has no
 * spinner — and under auto-placement each absence would slide everything after
 * it one column left, which is precisely the alignment being bought here. */
.rss .prow-label {
  grid-column: 1;
  justify-self: end;
  margin: 0;
  font-size: 0.8125rem;
  font-weight: 500;
  line-height: 1.2;
  color: var(--rss-text-2);
  white-space: nowrap;
}

/* A symbol carrying a sentence is worth pointing at; one that carries nothing
 * should not pretend otherwise. */
.rss .prow-label[title] { cursor: help; }

.rss .prow > .spin,
.rss .prow > input,
.rss .prow > select,
.rss .prow > .prow-value { grid-column: 2; }

.rss .prow-unit {
  grid-column: 3;
  font-size: 0.6875rem;
  line-height: 1.1;
  color: var(--rss-muted);
  white-space: nowrap;
}

/* Slightly tighter than a standalone field: four to eleven of these stack in
 * one card, so 2px a row is 20px of scroll. */
.rss .prow input[type="number"],
.rss .prow select { padding: 0.375rem 0.5rem; }

/* The stepper, not the input, was setting the row height. `.spin` is
 * `align-items: stretch` over an input of ~30px and a two-button column whose
 * height is two 18px icon em-boxes plus borders — 39px, so every spinner row
 * stood 6px taller than every row without one and the column lost the even
 * rhythm the tracks had just bought it. The icon is a small triangle in a large
 * em box, so it can lose 3px of box without losing any triangle. */
.rss .prow .spin-btns button .material-symbols-rounded { font-size: 15px; }

/* Icon-only, and square. The word "Pick" cost 20px in a column that has ~140px
 * to give the value box, and the button is one of thirteen identical ones whose
 * meaning the icon carries as well as the word did. `aria-label` and `title`
 * both still say which table it opens. */
.rss .prow > .btn.pick {
  grid-column: 4;
  display: flex;
  align-items: center;
  justify-content: center;
  width: var(--rss-prow-pick);
  height: var(--rss-prow-pick);
  min-height: 0;
  padding: 0;
  margin-left: 0;
}

.rss .prow > .btn.pick .material-symbols-rounded {
  font-size: 1rem;
  vertical-align: 0;
}

/* Rows whose label is a word, not a symbol: the criterion and unit selects, the
 * envelope Application, the fit method. Two tracks, and the control takes the
 * rest of the row — a select showing "Generalized Hoek-Brown" cannot live in
 * the value column's width. The label keeps the same right edge as the symbols
 * so the column still has one. */
.rss .prow.wide { grid-template-columns: var(--rss-prow-wide-label) minmax(0, 1fr); }

.rss .prow.wide > .spin,
.rss .prow.wide > input,
.rss .prow.wide > select { grid-column: 2 / -1; }

.rss .prow.wide .prow-label {
  font-size: 0.75rem;
  white-space: normal;
  text-align: right;
}

/* Full width below the sidebar breakpoint would stretch one number across the
 * viewport and put its unit an inch from it. The tracks stay; only the value
 * column stops growing. */
@media (max-width: 991.98px) {
  .rss .rss-sidebar .prow {
    grid-template-columns:
      var(--rss-prow-label) minmax(0, 20rem) var(--rss-prow-unit) var(--rss-prow-pick);
    justify-content: start;
  }
  .rss .rss-sidebar .prow.wide {
    grid-template-columns: var(--rss-prow-wide-label) minmax(0, 20rem);
    justify-content: start;
  }
}

/* --- computed rows ------------------------------------------------------ */

/* Deliberately not a disabled input. A greyed-out field says "not editable
 * *yet*" and invites a click that does nothing; these are results and will
 * never take a keystroke. Full ink inside a hairline box says "displayed
 * quantity" instead — and keeps the value as legible as the ones above it,
 * which matters because these are the numbers the user came for. */
.rss .prow-list { margin: 0; }

.rss .prow-value {
  margin: 0;
  padding: 0.375rem 0.5rem;
  font-size: 0.8125rem;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  line-height: 1.4;
  color: var(--rss-text);
  background: transparent;
  border: 1px solid var(--rss-border);
  border-radius: var(--rss-radius);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* The equivalent Mohr-Coulomb c′ and φ′ — the pair most analyses are actually
 * exported for. `--rss-accent` is how this platform singles a number out. */
.rss .prow-out.accent .prow-value {
  color: var(--rss-accent);
  border-color: color-mix(in srgb, var(--rss-accent) 35%, transparent);
}

/* The criterion the constants above describe, set once in the template per
 * criterion. Centred and quiet: it is there to be recognised, not read. */
.rss .prow-equation {
  margin: 0.625rem 0 0;
  padding-top: 0.5rem;
  border-top: 1px solid var(--rss-border);
  font-size: 0.75rem;
  line-height: 1.6;
  text-align: center;
  color: var(--rss-text-2);
  overflow-x: auto;
}

/* Material Dashboard's `.form-control` is built for a floating-label input
   group and does not survive being packed into a 26px spinner row, so the
   tool's numeric fields keep their own box. Everything about it — surface,
   ink, border, radius — still comes from the theme's tokens. */
.rss input[type="number"],
.rss input[type="text"],
.rss input[type="search"],
.rss select {
  width: 100%;
  padding: 0.4375rem 0.5625rem;
  font: inherit;
  font-size: 0.8125rem;
  font-variant-numeric: tabular-nums;
  color: var(--rss-text);
  background: var(--rss-inset);
  border: 1px solid var(--rss-border-strong);
  border-radius: var(--rss-radius);
  outline: none;
}

/* Material Dashboard's `.form-select:disabled` hard-codes `background-color:
 * #e5e5e5` and ships no `.dark-version` counterpart, and at `.form-select:disabled`
 * (0,2,0) it outranks the `.rss select` rule above (0,1,1). The saved-analyses
 * select is disabled for every signed-out visitor, so without this the first
 * control in the tool is a near-white slab on a #202020 card. */
.rss select:disabled {
  color: var(--rss-muted);
  background-color: var(--rss-inset);
  cursor: not-allowed;
}

.rss input:focus-visible,
.rss select:focus-visible,
.rss button:focus-visible,
.rss [tabindex]:focus-visible {
  outline: 2px solid var(--rss-accent);
  outline-offset: 1px;
}

.rss input[type="number"] { -moz-appearance: textfield; }
.rss input[type="number"]::-webkit-outer-spin-button,
.rss input[type="number"]::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}

/* Native spinners are too small to click repeatedly, which is the whole
 * interaction this tool is built around — so we draw our own.
 *
 * No gap between the input and the stepper. The two carry the radii of a single
 * joined control — square edges facing each other — so any gap here shows the
 * card through a seam the radii say is not there, and the pair reads as clipped
 * rather than grouped. The Pick button is a separate control and keeps the gap,
 * as a margin on `.btn.pick` below. */
.rss .spin { display: flex; align-items: stretch; }
.rss .spin input { border-radius: var(--rss-radius) 0 0 var(--rss-radius); }

/* A flex item's default min-width is auto, so the input — which carries
 * width:100% from the rule above — refuses to shrink and squeezes the Pick
 * button beside it down to a clipped sliver. This is what makes room for it. */
.rss .spin input { min-width: 0; }

.rss .spin-btns {
  display: flex;
  flex-direction: column;
  width: 26px;
  flex: 0 0 26px;
}

.rss .spin-btns button {
  display: flex;
  align-items: center;
  justify-content: center;
  flex: 1;
  padding: 0;
  overflow: hidden;
  color: var(--rss-text-2);
  background: var(--rss-inset);
  border: 1px solid var(--rss-border-strong);
  /* The input's right border is the seam. Two abutting 1px borders would draw
   * it twice, at double the weight of every other edge in the group. */
  border-left: none;
  cursor: pointer;
}

/* Each half of a spinner is roughly 16px tall, so the icon's own em box is the
 * constraint — `arrow_drop_*` is a compact triangle inside a large box, which
 * is why it can be set larger than the button it sits in. */
.rss .spin-btns button .material-symbols-rounded {
  font-size: 18px;
  line-height: 1;
}

.rss .spin-btns button:first-child {
  border-radius: 0 var(--rss-radius) 0 0;
  border-bottom: none;
}
.rss .spin-btns button:last-child { border-radius: 0 0 var(--rss-radius) 0; }
.rss .spin-btns button:hover { background: var(--rss-border); color: var(--rss-text); }
.rss .spin-btns button:active { background: var(--rss-border-strong); }

.rss .radios { display: flex; flex-wrap: wrap; gap: 0.375rem; }

.rss .radios label {
  flex: 1 1 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0.375rem 0.5rem;
  font-size: 0.75rem;
  border: 1px solid var(--rss-border-strong);
  border-radius: var(--rss-radius);
  cursor: pointer;
  color: var(--rss-text-2);
  margin: 0;
}

.rss .radios input { position: absolute; opacity: 0; pointer-events: none; }

.rss .radios label:has(input:checked) {
  background: var(--rss-accent);
  border-color: var(--rss-accent);
  color: var(--rss-accent-ink);
}

/* The radio itself is invisible, so its own focus ring is too — without this a
   keyboard user tabbing through "Lab / Rock mass" gets no indication at all of
   where they are. The ring goes on the label that is standing in for it. */
.rss .radios label:has(input:focus-visible),
.rss .checks label:has(input:focus-visible) {
  outline: 2px solid var(--rss-accent);
  outline-offset: 2px;
}

/* A radio the active criterion does not support is disabled rather than removed,
 * so the row of options does not reflow under the cursor mid-click. */
.rss .radios label:has(input:disabled) {
  opacity: 0.4;
  cursor: not-allowed;
}

/* A group with one option left is not a choice.
 *
 * `syncDataTypes()` hides every data type the active criterion cannot be fitted
 * in, which on Hoek-Brown leaves Triaxial alone — and a lone checked radio
 * styled as a filled pill spanning the column reads as a primary action button,
 * on a control that does nothing when clicked. The note underneath already says
 * why there is no choice, so the value is shown as the readout it is. */
.rss .radios.single-option label {
  flex: 0 0 auto;
  justify-content: flex-start;
  padding-left: 0;
  padding-right: 0;
  cursor: default;
  background: none;
  border-color: transparent;
  font-size: 0.8125rem;
  color: var(--rss-text);
}

/* The sampler's off state.
 *
 * `--rss-accent` means "this control is selected", and on a three-way group
 * whose first option is *no sampler* that painted the loudest pill on the
 * toolbar to announce that nothing is armed. Off is the resting state, so it
 * gets the resting treatment and the group shows no fill until a sampler is
 * actually picked. */
.rss .radios label[data-option-off]:has(input:checked) {
  background: none;
  border-color: var(--rss-border-strong);
  color: var(--rss-text-2);
}

.rss .checks { display: flex; flex-wrap: wrap; gap: 0.75rem; align-items: center; }

.rss .checks label {
  display: flex;
  align-items: center;
  gap: 0.375rem;
  margin: 0;
  font-size: 0.75rem;
  color: var(--rss-text-2);
  cursor: pointer;
}

.rss .checks input { accent-color: var(--rss-accent); }

/* --------------------------------------------------------------- outputs -- */

/* Seams are drawn by each cell rather than by a gap showing the container
 * through. `auto-fit` leaves the last row's unfilled remainder showing the
 * container too, and as a tinted band that remainder reads as a second, empty
 * panel — most visibly on Hoek-Brown, whose seven outputs wrap to leave six
 * empty columns beside Erm. `overflow: hidden` clips the outermost shadows so
 * the container's own border stays the outer edge. */
.rss .outputs {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(132px, 1fr));
  gap: 1px;
  margin: 0;
  background: var(--rss-surface);
  border: 1px solid var(--rss-border);
  border-radius: var(--rss-radius);
  overflow: hidden;
}

.rss .out {
  background: var(--rss-surface);
  box-shadow: 0 0 0 1px var(--rss-border);
  padding: 0.5625rem 0.6875rem;
}

.rss .out dt {
  font-size: 0.6875rem;
  color: var(--rss-muted);
  margin: 0 0 2px;
  font-weight: 400;
}

.rss .out dd {
  margin: 0;
  font-size: 0.9375rem;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--rss-text);
}

.rss .out dd .u {
  font-size: 0.6875rem;
  font-weight: 400;
  color: var(--rss-muted);
  margin-left: 2px;
}

/* The headline output. `.text-accent` is how the dashboard singles a number out,
 * so this is that colour — not a chart series' ink, which only means something
 * next to the curve it belongs to. */
.rss .out.accent dd { color: var(--rss-accent); }

/* The fit readout sits in the sidebar, so it gets narrower cells than the
 * rock-mass parameters in the main column. */
.rss .outputs.compact { grid-template-columns: repeat(auto-fit, minmax(92px, 1fr)); }
.rss .outputs.compact .out { padding: 0.4375rem 0.5625rem; }
.rss .outputs.compact .out dd { font-size: 0.8125rem; }

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

/* Three kinds of control used to sit in one flowing row of identically styled
 * pills: view *modes* (Both / σ₁-σ₃ / τ-σₙ), independent *toggles* (the six
 * checkboxes), interaction *modes* (Zoom / Pan, Sampler) and one *action*
 * (Reset extents). Nothing said which was which — a user could not tell that
 * picking Zoom deselects Pan while ticking Gridlines deselects nothing.
 *
 * The groups are separated by a rule rather than labelled. Labels would state
 * the obvious for the segmented controls and cost a line of vertical space on
 * a toolbar that already wraps; a divider is enough to say "these belong
 * together, that one does not". */
.rss .plot-toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.75rem;
}

.rss .tb-group {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.375rem;
}

/* Checkbox clusters need more room between items than a segmented control does:
 * each is an independent label, not one of a set. */
.rss .tb-group.tb-checks { gap: 0.75rem; }

/* On the first group in a wrapped row the divider would hang off the left edge
 * with nothing before it, so it is drawn only between siblings. */
.rss .tb-group + .tb-group {
  padding-left: 0.75rem;
  border-left: 1px solid var(--rss-border-strong);
}

/* Actions are not modes. `Reset extents` is the only button on this toolbar
 * that *does* something rather than selecting a state, so it drops the pill
 * and reads as the link it behaves like. */
.rss .tb-group.tb-actions .btn {
  --bs-btn-color: var(--rss-text-2);
  --bs-btn-border-color: transparent;
  --bs-btn-bg: transparent;
  --bs-btn-hover-color: var(--rss-text);
  --bs-btn-hover-bg: var(--rss-border);
  --bs-btn-hover-border-color: transparent;
  text-decoration: underline;
  text-underline-offset: 2px;
}

/* ---------------------------------------------------------------- charts -- */

.rss .plots { display: grid; gap: 1rem; }
.rss .plots.dual { grid-template-columns: 1fr 1fr; }
.rss .plots.single { grid-template-columns: 1fr; }

@media (max-width: 1400px) {
  .rss .plots.dual { grid-template-columns: 1fr; }
}

/* `--surface-1`, the same plane Plotly paints the figure on, so the frame and
 * the plot are one surface with no seam where the padding ends. In light mode
 * that token resolves to `--rss-surface` and this is the rule it always was; in
 * dark it is the #121212 well, which is what gives each chart an edge against
 * the card. */
.rss .plot-wrap {
  background: var(--surface-1);
  border: 1px solid var(--rss-border);
  border-radius: var(--rss-radius);
  padding: 0.75rem;
  min-width: 0;
}

/* Flex rather than a float on the buttons: a floated child is out of flow, so
 * the heading's own box collapsed behind it and the plot sat 4px too high. */
.rss .plot-wrap h3 {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin: 0 0 0.5rem;
  font-size: 0.75rem;
  font-weight: 600;
  color: var(--rss-text-2);
}

.rss .plot-actions { margin-left: auto; display: flex; gap: 0.375rem; }

.rss .plot { width: 100%; height: 420px; }

/* Opt out of the platform's global dark-mode Plotly hack.
 *
 * `material-dashboard.css` carries
 *
 *     .dark-version .plot-container.plotly .svg-container {
 *       filter: invert(75%) hue-rotate(180deg);
 *     }
 *
 * which recolours *every* Plotly figure on the site. It exists because the
 * platform's other plots are built light-only and have no theme of their own.
 * These do: `rock-shear-strength-charts.js` reads `--surface-1`, `--gridline`,
 * `--baseline` and the four series tokens off `documentElement` and hands
 * Plotly a figure already in the page theme — so the global rule inverts a
 * dark chart back to light. `invert(75%)` takes the #121212 well to #b6b6b6
 * and the gridlines with it, and the plot ends up a pale rectangle inside a
 * #202020 card.
 *
 * Screen-only, which is why this was easy to miss: `Plotly.downloadImage`
 * serialises the SVG itself and a CSS filter never reaches it, so the PNG and
 * SVG exports were coming out correctly themed while the screen disagreed.
 *
 * Four classes and `.dark-version` is (0,4,0); this is (0,5,0). */
.rss .plot .plot-container.plotly .svg-container { filter: none; }

.rss .plot-fallback {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 100%;
  padding: 1.5rem;
  text-align: center;
  color: var(--rss-muted);
  font-size: 0.8125rem;
}

/* The two RocData formats sit apart from the three deliverables: they are a
 * hand-off to another program rather than something to put in a report. */
.rss .export-row .spacer { flex: 1; min-width: 0.75rem; }

/* ------------------------------------------------------------ data tables -- */

.rss .table-wrap { max-height: 320px; overflow: auto; }

.rss table.data {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.75rem;
  font-variant-numeric: tabular-nums;
  color: var(--rss-text);
}

.rss table.data th,
.rss table.data td {
  padding: 0.3125rem 0.625rem;
  text-align: right;
  border-bottom: 1px solid var(--rss-border);
  white-space: nowrap;
}

.rss table.data th {
  position: sticky;
  top: 0;
  z-index: 1;
  background: var(--rss-surface);
  color: var(--rss-text-2);
  font-weight: 600;
  text-align: right;
}

.rss table.data td:first-child,
.rss table.data th:first-child { text-align: left; color: var(--rss-muted); }

/* -------------------------------------------------------------- warnings -- */

.rss .warnings { display: flex; flex-direction: column; gap: 0.5rem; }

.rss .warn {
  display: flex;
  gap: 0.5625rem;
  padding: 0.5625rem 0.75rem;
  font-size: 0.78125rem;
  border-radius: var(--rss-radius);
  border: 1px solid var(--rss-border-strong);
  background: var(--rss-surface);
  color: var(--rss-text-2);
}

.rss .warn .icon { flex: 0 0 auto; font-weight: 700; }
.rss .warn.warning .icon { color: var(--status-warning); }
.rss .warn.error { border-color: var(--status-critical); }
.rss .warn.error .icon { color: var(--status-critical); }

.rss .hint-block {
  margin: 0 0 0.75rem;
  font-size: 0.75rem;
  color: var(--rss-muted);
}

.rss .warn-block {
  margin: 0;
  padding: 0.5625rem 1rem;
  font-size: 0.75rem;
  line-height: 1.5;
  color: var(--rss-text);
  background: color-mix(in srgb, var(--status-warning) 16%, transparent);
  border-bottom: 1px solid var(--rss-border);
}

.rss .refs {
  font-size: 0.6875rem;
  color: var(--rss-muted);
  line-height: 1.6;
}

/* --------------------------------------------------------------- dialogs -- */
/* Named `rss-overlay` / `rss-dialog` to stay clear of Bootstrap's `.modal`
 * family — see the header note. Each carries `.rss` itself so the tokens and
 * scoped rules above reach inside it wherever it sits in the DOM. */

.rss-overlay {
  position: fixed;
  inset: 0;
  z-index: 1055;                   /* above Bootstrap's modal layer (1050) */
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1.5rem;
  background: rgba(10, 10, 10, 0.5);   /* --bs-backdrop-bg @ --bs-backdrop-opacity */
}

.rss-dialog {
  display: flex;
  flex-direction: column;
  width: min(560px, 100%);
  max-height: 100%;
  background: var(--rss-surface);
  border: 1px solid var(--rss-border);
  border-radius: var(--bs-modal-border-radius, 0.375rem);
  box-shadow: 0 16px 48px rgba(0, 0, 0, 0.28);
  overflow: hidden;
}

.rss-dialog-head,
.rss-dialog-foot {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.75rem 1rem;
  border-bottom: 1px solid var(--rss-border);
}

.rss-dialog-foot {
  border-bottom: none;
  border-top: 1px solid var(--rss-border);
  flex-wrap: wrap;
}

.rss-dialog-foot .spacer { flex: 1; }
.rss-dialog-foot .hint { font-size: 0.75rem; color: var(--rss-muted); }

.rss-dialog-head h2 {
  flex: 1;
  margin: 0;
  font-size: 0.8125rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--rss-text-2);
}

.rss-dialog-body {
  padding: 0.875rem 1rem;
  overflow-y: auto;
}

/* Buttons in a dialog's head or foot must not shrink. Material Dashboard's
 * `.btn` carries `overflow: hidden`, and a flex item whose overflow is not
 * `visible` loses the automatic minimum size that would otherwise hold it at
 * its content width — so under the title's `flex: 1` the close button collapsed
 * to 16px with 8px of padding each side and clipped its own icon to nothing. */
.rss-dialog-head .btn,
.rss-dialog-foot .btn,
.rss .checks > .btn,
.rss .plot-actions .btn,
.rss .btn-row > .btn:not(.flex-fill) { flex: 0 0 auto; }

/* `.icon` is the platform's name too: `nucleo-svg.css` pins it to
 * `width: 1em; height: 1em; color: #111`, which is right for an inline SVG
 * sprite and wrong for the two places the tool uses the name — the dialogs'
 * close button, whose glyph was clipped to nothing inside a 15px box, and the
 * warning glyph the JS emits, which would have been painted near-black on a
 * dark card. Undo the sizing and let the ink cascade; the rules below and the
 * `.warn` rules above are more specific and still win on colour. */
.rss .icon,
.rss-dialog .icon {
  width: auto;
  height: auto;
  color: inherit;
}

/* The dialogs' close/remove control. */
.rss .btn.icon,
.rss-dialog .btn.icon {
  flex: 0 0 auto;
  padding: 0.125rem 0.5rem;
  min-height: 0;
  font-size: 0.9375rem;
  line-height: 1.2;
  color: var(--rss-muted);
  background: none;
  border: none;
  box-shadow: none;
}

.rss .btn.icon:hover,
.rss-dialog .btn.icon:hover { color: var(--status-critical); }

/* ------------------------------------------------------------- data grid -- */

.rss .grid-wrap { max-height: min(46vh, 380px); }

.rss table.data.grid td { padding: 0.1875rem 0.375rem; }
.rss table.data.grid td:first-child { width: 36px; }
.rss table.data.grid td:last-child { width: 36px; text-align: center; }

.rss table.data.grid input[type="text"] {
  padding: 0.25rem 0.4375rem;
  font-size: 0.75rem;
  text-align: right;
  background: var(--rss-surface);
}

/* A flagged row is outlined as well as tinted — colour is never the only
 * signal, and the message below the grid names the row numbers too. */
.rss table.data.grid tr.bad input[type="text"] {
  border-color: var(--status-critical);
  background: color-mix(in srgb, var(--status-critical) 8%, var(--rss-surface));
}

.rss .switch-remember {
  display: flex;
  align-items: center;
  gap: 0.4375rem;
  font-size: 0.75rem;
  color: var(--rss-text-2);
  cursor: pointer;
}

.rss .switch-remember input { accent-color: var(--rss-accent); }

.rss-drop-overlay {
  position: fixed;
  inset: 0;
  z-index: 1060;
  display: flex;
  align-items: center;
  justify-content: center;
  background: color-mix(in srgb, var(--rss-accent) 12%, rgba(10, 10, 10, 0.5));
  pointer-events: none;
}

.rss-drop-overlay div {
  padding: 1.25rem 1.75rem;
  font-size: 0.875rem;
  font-weight: 500;
  color: var(--rss-text);
  background: var(--rss-surface);
  border: 2px dashed var(--rss-accent);
  border-radius: var(--rss-radius);
}

/* ------------------------------------------------------------ pick modal -- */
/* The estimation aids. One shell, three body variants (table / grid /
 * profiles) — see `domain/rock_strength_picks.py`. */

/* Its own control, not part of the spinner — so it keeps the gap the spinner
 * gave up, rather than inheriting one from `.spin`.
 *
 * Accent-tinted, not `btn-outline-secondary`. That variant sets
 * `--bs-btn-color: #616964`, which on a #202020 card lands near 3:1 — dimmer
 * than the input it sits beside, so the thirteen estimation aids, which are
 * most of why this tool exists, all read as greyed out. The tokens are
 * overridden rather than the properties so Material Dashboard keeps its own
 * hover, active and focus machinery.
 *
 * Tinted rather than filled: a solid accent here would compete with Save, the
 * page's one primary action, and there are up to four Picks visible at once. */
.rss .btn.pick {
  --bs-btn-color: var(--rss-accent);
  --bs-btn-border-color: color-mix(in srgb, var(--rss-accent) 45%, transparent);
  --bs-btn-bg: color-mix(in srgb, var(--rss-accent) 10%, transparent);
  --bs-btn-hover-color: var(--rss-accent-ink);
  --bs-btn-hover-bg: var(--rss-accent);
  --bs-btn-hover-border-color: var(--rss-accent);
  --bs-btn-active-color: var(--rss-accent-ink);
  --bs-btn-active-bg: var(--rss-accent);
  --bs-btn-active-border-color: var(--rss-accent);
  flex: 0 0 auto;
  margin-left: 0.375rem;
  padding: 0 0.625rem;
  min-height: 0;
  font-size: 0.6875rem;
}

/* `.btn-outline-secondary:hover:not(.active)` (0,3,0) hard-sets transparent /
 * 0.75 opacity / #616964 and would otherwise beat the tokens above on hover —
 * which is the one moment the button has to look live. This is (0,4,0). */
.rss .btn.pick:hover {
  opacity: 1;
  color: var(--rss-accent-ink);
  background-color: var(--rss-accent);
  border-color: var(--rss-accent);
}

/* Wider than the other dialogs: the GSI grid carries a sketch and its wording
 * in the row header before the five surface-condition columns start. */
.rss-dialog.pick-modal { width: min(1020px, 100%); }

.rss .pick-controls {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  align-items: center;
  padding: 0.625rem 1rem;
  border-bottom: 1px solid var(--rss-border);
}

.rss .pick-controls select { width: auto; min-width: 130px; }
.rss .pick-controls input[type="search"] { flex: 1 1 180px; width: auto; }

.rss .pick-tabs { display: flex; gap: 0.25rem; flex: 1 1 100%; flex-wrap: wrap; }

.rss .pick-tab {
  padding: 0.375rem 0.75rem;
  font: inherit;
  font-size: 0.75rem;
  color: var(--rss-text-2);
  background: var(--rss-inset);
  border: 1px solid var(--rss-border-strong);
  border-radius: var(--rss-radius);
  cursor: pointer;
}

.rss .pick-tab:hover { background: var(--rss-border); }

.rss .pick-tab.active {
  color: var(--rss-accent-ink);
  background: var(--rss-accent);
  border-color: var(--rss-accent);
}

.rss .pick-body { max-height: min(58vh, 520px); }
.rss .pick-body .table-wrap { max-height: none; }

/* A blocked aid stays readable — only its rows stop responding. */
.rss .pick-blocked-body { opacity: 0.55; pointer-events: none; }

.rss .pick-citation {
  flex: 1 1 100%;
  margin: 0;
  font-size: 0.6875rem;
  line-height: 1.5;
  color: var(--rss-muted);
}

.rss #pick-note { padding: 0.625rem 1rem 0; margin: 0; }

/* ---- table variant */
.rss table.data.pick-table td,
.rss table.data.pick-table th { text-align: left; white-space: normal; }
.rss table.data.pick-table td.num,
.rss table.data.pick-table th.num { text-align: right; white-space: nowrap; }
.rss table.data.pick-table td:first-child { color: var(--rss-muted); }

.rss table.data.pick-table tbody tr { cursor: pointer; }
.rss table.data.pick-table tbody tr:hover,
.rss table.data.pick-table tbody tr:focus-visible {
  background: color-mix(in srgb, var(--rss-accent) 14%, transparent);
  outline: none;
}

/* ---- grid variant (the GSI charts) */
.rss table.data.pick-grid th,
.rss table.data.pick-grid td { text-align: center; white-space: normal; }

.rss table.data.pick-grid .pick-corner,
.rss table.data.pick-grid .pick-rowhead {
  text-align: left;
  min-width: 150px;
  font-weight: 500;
  color: var(--rss-text-2);
}

.rss table.data.pick-grid .pick-rowhead {
  position: sticky;
  left: 0;
  background: var(--rss-surface);
  min-width: 290px;
}

/* The flex box lives inside the cell, not on it: `display: flex` on a <th>
 * takes it out of the table layout and the columns stop lining up.
 *
 * No width here. A cell's `max-width` is advisory under auto table layout and
 * is ignored, and capping the inner box instead only moves the slack — the
 * column keeps the width either way and the cap just opens a gap before the
 * first condition column. The min-width above is the constraint that holds. */
.rss .pick-rowinner { display: flex; gap: 0.625rem; align-items: flex-start; }

/* The generated structure sketch, stroked in `currentColor` like the JRC
 * comparator artwork below — it follows the theme with no dark-mode variant. */
.rss .pick-sketch {
  flex: 0 0 84px;
  color: var(--rss-text);
}

.rss .pick-sketch svg { display: block; width: 84px; height: auto; }

.rss .pick-rowtext {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.rss .pick-rowname { color: var(--rss-text); }

.rss .pick-rowdesc {
  font-size: 0.6875rem;
  font-weight: 400;
  line-height: 1.35;
  color: var(--rss-muted);
}

.rss table.data.pick-grid abbr {
  text-decoration: underline dotted var(--rss-border-strong);
  cursor: help;
}

/* `nowrap` against the rule above, which lets the *header* wrap. Without it the
 * row header's sketch and description win the auto layout, the five condition
 * columns are squeezed to the width of one digit, and every band breaks as
 * "70–" over "80". */
.rss table.data.pick-grid .pick-cell {
  cursor: pointer;
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}

.rss table.data.pick-grid .pick-cell:hover,
.rss table.data.pick-grid .pick-cell:focus-visible {
  background: color-mix(in srgb, var(--rss-accent) 20%, transparent);
  outline: none;
}

.rss table.data.pick-grid .pick-na { color: var(--rss-muted); }

/* ---- profiles variant (JRC) */
.rss .pick-profiles { display: flex; flex-direction: column; gap: 2px; }

.rss .pick-profile {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  width: 100%;
  padding: 0.25rem 0.5rem;
  font: inherit;
  font-size: 0.75rem;
  color: var(--rss-text);
  background: none;
  border: 1px solid transparent;
  border-radius: var(--rss-radius);
  cursor: pointer;
  text-align: left;
}

.rss .pick-profile:hover,
.rss .pick-profile:focus-visible {
  background: color-mix(in srgb, var(--rss-accent) 12%, transparent);
  border-color: var(--rss-border-strong);
  outline: none;
}

.rss .pick-profile-label {
  flex: 0 0 96px;
  color: var(--rss-text-2);
  font-variant-numeric: tabular-nums;
}

.rss .pick-profile-art { flex: 1; min-width: 0; max-width: 420px; }

/* The generated comparator artwork is stroked in `currentColor`, so it follows
 * the theme without the renderer knowing anything about it. */
.rss .pick-profile-art svg { display: block; width: 100%; height: auto; }

.rss .pick-profile-z2 {
  flex: 0 0 62px;
  text-align: right;
  font-size: 0.6875rem;
  color: var(--rss-muted);
  font-variant-numeric: tabular-nums;
}

/* -------------------------------------------------------- theory / about -- */

.rss-about h6 { margin: 1.125rem 0 0.5rem; }
.rss-about h6:first-child { margin-top: 0; }
.rss-about ul { margin: 0; padding-left: 1.25rem; }
.rss-about li { margin-bottom: 0.375rem; }
.rss-about .about-caveats li { color: var(--status-critical); }
