/* Hide Shiny error messages that briefly pop up (errors shown via modal instead) */
.shiny-output-error,
.shiny-output-error-validation {
  display: none !important;
}

/* Layout and navbar positioning */
.navbar {
  position: fixed;
  top: 0;
  width: 100%;
  z-index: 1000;
  margin-bottom: 0;
}

body {
  padding-top: 55px; /* Fallback; JS syncs with actual navbar height on resize */
  font-family: "Calibri", "Segoe UI", system-ui, -apple-system, sans-serif;
}

/* Remove extra top spacing from content containers */
.container-fluid,
.tab-content,
.tab-pane {
  padding-top: 0 !important;
}

.container-fluid {
  margin-top: 0 !important;
}

/* Pull content up to minimise gap below navbar */
.tab-content {
  margin-top: -12px !important;
}

/* Remove top whitespace from first elements in each tab */
.tab-pane > *:first-child {
  margin-top: 0 !important;
}

.tab-pane .row:first-child {
  margin-top: 0 !important;
}

.tab-pane .well {
  margin-top: 0 !important;
}

.tab-pane h1:first-child,
.tab-pane h3:first-child,
.tab-pane h4:first-child,
.tab-pane h5:first-child {
  margin-top: 0 !important;
}

.tab-pane .start-page-content,
.tab-pane .criteria-page-content,
.tab-pane .validation-report-page-content {
  margin-top: 0 !important;
  padding-top: 0 !important;
}

.tab-pane .row > [class*="col-"]:first-child .well {
  margin-top: 0 !important;
  padding-top: 0.25rem !important;
}

.tab-pane .row > [class*="col-"]:last-child {
  padding-top: 0 !important;
}

.tab-pane .row > [class*="col-"]:last-child > *:first-child {
  margin-top: 0 !important;
}

/* Compact checkboxes - reduce spacing between checkbox form groups */
.compact-checkboxes .form-group {
  margin-bottom: 0.25rem;
}

/* Button row - align buttons in same row with space between */
.button-row {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  align-items: center;
  margin-top: 0.5rem;
}


/* Make navbar tab labels bold in both themes */
.navbar-nav > li > a,
.navbar-nav .nav-link {
  font-weight: 600;
}

/* Floating settings button and panel */
.settings-fab {
  position: fixed;
  bottom: 20px;
  right: 20px;
  z-index: 1200;
  border-radius: 50%;
  width: 50px;
  height: 50px;
  padding: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 22px;
  box-shadow: 0 4px 10px rgba(0, 0, 0, 0.3);
}

.settings-panel {
  position: fixed;
  bottom: 80px;
  right: 20px;
  z-index: 1200;
  /* A fixed column rather than one sized by its widest control. It used to come out 640px wide -
     as wide as a dialog - because a row of inline checkboxes set the width, which left every
     other control floating in half-empty space. */
  width: 340px;
  max-width: calc(100vw - 40px);
  /* Never taller than the window: the panel is anchored to a corner, so an overflowing one has
     its last section off-screen with nothing to say so. */
  max-height: calc(100vh - 140px);
  overflow-y: auto;
  padding: 14px 16px 12px;
  border-radius: 12px;
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.22);
  display: none;
}

/* Title row, and the sign-out that sits opposite it. */
.settings-panel .settings-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding-bottom: 10px;
  margin-bottom: 4px;
}
.settings-panel .settings-head h5 {
  font-size: 1.02rem;
  font-weight: 700;
  letter-spacing: .01em;
}

/* One titled group per kind of thing. */
.settings-panel .settings-section {
  padding: 12px 0 2px;
  border-top: 1px solid rgba(128, 128, 128, 0.22);
}
.settings-panel .settings-section:first-of-type { border-top: 0; padding-top: 2px; }
.settings-panel .settings-section-title {
  font-size: 0.7rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .08em;
  opacity: .62;
  margin-bottom: 10px;
}

/* Each control keeps its own label above it, small and quiet - the choices below carry the
   emphasis. */
.settings-panel .shiny-input-container > .control-label,
.settings-panel .form-group > .control-label {
  font-size: 0.78rem;
  font-weight: 600;
  opacity: .8;
  margin-bottom: 4px;
}
.settings-panel .shiny-input-radiogroup { margin-bottom: 12px !important; }

/* Radio groups as segmented controls.
   The markup is label.radio-inline > input + span, so the SPAN is the pill and the input is
   hidden behind it - styled through `input:checked + span` rather than :has(), which keeps it
   working on older browsers. */
.settings-panel .shiny-options-group {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
}
.settings-panel .shiny-options-group .radio-inline {
  flex: 1 1 auto;
  margin: 0 !important;
  padding: 0 !important;
  cursor: pointer;
}
.settings-panel .shiny-options-group .radio-inline input[type="radio"] {
  position: absolute;
  opacity: 0;
  width: 0;
  height: 0;
  margin: 0;
}
.settings-panel .shiny-options-group .radio-inline > span {
  display: block;
  text-align: center;
  white-space: nowrap;
  padding: 6px 10px;
  font-size: 0.8rem;
  border-radius: 8px;
  border: 1px solid rgba(128, 128, 128, 0.35);
  transition: background-color .12s ease, border-color .12s ease, color .12s ease;
}
.settings-panel .shiny-options-group .radio-inline:hover > span {
  border-color: #6f2dbd;
}
.settings-panel .shiny-options-group .radio-inline input[type="radio"]:checked + span {
  background-color: #6f2dbd;
  border-color: #6f2dbd;
  color: #ffffff;
  font-weight: 600;
}
/* Keyboard users get the focus ring the hidden input would otherwise have taken with it. */
.settings-panel .shiny-options-group .radio-inline input[type="radio"]:focus-visible + span {
  outline: 2px solid #ff62f1;
  outline-offset: 2px;
}

/* The two preference tick boxes, stacked rather than in a row that set the panel's width. */
.settings-panel .settings-toggles { margin-top: 2px; }
.settings-panel .settings-toggles .checkbox { margin: 0 0 4px; }
.settings-panel .settings-toggles label { font-size: 0.8rem; }

/* Save: a full-width primary action under the field it acts on. */
/* !important throughout: `body.light-theme .btn-default` is more specific than a two-class
   selector, so without it the primary action came out as an ordinary grey button. */
.settings-panel .settings-save-btn {
  width: 100%;
  margin-top: 8px;
  background-color: #6f2dbd !important;
  border-color: #6f2dbd !important;
  color: #fff !important;
}
.settings-panel .settings-save-btn:hover,
.settings-panel .settings-save-btn:focus {
  background-color: #5a239c !important;
  border-color: #5a239c !important;
  color: #fff !important;
}
.settings-panel .session-saved-panel .shiny-input-container { width: 100%; }
.settings-panel .session-saved-panel input[type="text"] { width: 100%; font-size: 0.82rem; }
.settings-panel .session-saved-panel .shiny-html-output { margin-top: 8px; font-size: 0.78rem; }

/* Uniform spacing - match Appearance-to-Font size gap; hide br/hr that add extra space */
.settings-panel br,
.settings-panel hr {
  display: none !important;
}
.settings-panel-compact .shiny-input-container,
.settings-panel-compact .form-group,
.settings-panel-compact .shiny-input-radiogroup {
  margin-top: 0 !important;
  margin-bottom: 0 !important;
  padding-top: 0 !important;
  padding-bottom: 0 !important;
}
.settings-panel-compact .shiny-input-container:last-of-type,
.settings-panel-compact .form-group:last-of-type {
  margin-bottom: 0 !important;
}
.settings-panel-compact > h5 {
  margin-top: 0 !important;
  margin-bottom: 0.5rem !important;
  font-weight: bold !important;
}

body.dark-theme .settings-fab {
  background-color: #16002f;
  color: #ffffff;
  border: none;
}

body.light-theme .settings-fab {
  background-color: #ffffff;
  color: #212529;
  border: 1px solid #ced4da;
}

body.dark-theme .settings-panel {
  background-color: #16002f;
  color: #ffffff;
  border: 1px solid rgba(255, 255, 255, 0.12);
}
body.dark-theme .settings-panel .settings-section { border-top-color: rgba(255, 255, 255, 0.14); }
body.dark-theme .settings-panel .shiny-options-group .radio-inline > span {
  border-color: rgba(255, 255, 255, 0.25);
}
/* On the dark ground the brand purple is too close to the background to read as "selected",
   so the pill is filled with the lighter accent instead. */
body.dark-theme .settings-panel .shiny-options-group .radio-inline input[type="radio"]:checked + span {
  background-color: #8b5cf6;
  border-color: #8b5cf6;
  color: #ffffff;
}
body.dark-theme .settings-panel .shiny-options-group .radio-inline:hover > span { border-color: #8b5cf6; }
body.dark-theme .settings-panel .settings-save-btn {
  background-color: #8b5cf6 !important;
  border-color: #8b5cf6 !important;
}
body.dark-theme .settings-panel .settings-save-btn:hover,
body.dark-theme .settings-panel .settings-save-btn:focus {
  background-color: #7c3aed !important;
  border-color: #7c3aed !important;
}

body.light-theme .settings-panel {
  background-color: #ffffff;
  color: #212529;
  border: 1px solid rgba(0, 0, 0, 0.08);
}

/* Floating help button and panel - above settings on right */
.help-fab {
  position: fixed;
  bottom: 80px;
  right: 20px;
  left: auto;
  z-index: 1100;
  border-radius: 50%;
  width: 50px;
  height: 50px;
  padding: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 22px;
  box-shadow: 0 4px 10px rgba(0, 0, 0, 0.3);
}

.help-panel {
  position: fixed;
  top: 60px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 1100;
  width: 92%;
  max-width: 1400px;
  height: 88vh;
  max-height: 88vh;
  min-width: 600px;
  min-height: 400px;
  padding: 16px 20px;
  border-radius: 8px;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.25);
  display: none;
  flex-direction: column;
  overflow: hidden;
}

/* Resize handles on the outside of the help panel */
.help-panel-resize-e,
.help-panel-resize-s,
.help-panel-resize-se {
  position: absolute;
  z-index: 10;
  background: transparent;
}

.help-panel-resize-e {
  top: 0;
  right: 0;
  width: 8px;
  height: 100%;
  cursor: col-resize;
}

.help-panel-resize-s {
  bottom: 0;
  left: 0;
  width: 100%;
  height: 8px;
  cursor: row-resize;
}

.help-panel-resize-se {
  bottom: 0;
  right: 0;
  width: 14px;
  height: 14px;
  cursor: nwse-resize;
}

.help-panel-resize-e:hover,
.help-panel-resize-s:hover {
  background-color: rgba(255, 98, 241, 0.15);
}

.help-panel-resize-se:hover {
  background-color: rgba(255, 98, 241, 0.2);
  border-radius: 0 0 8px 0;
}

body.dark-theme .help-fab {
  background-color: #16002f;
  color: #ffffff;
  border: none;
}

body.light-theme .help-fab {
  background-color: #ffffff;
  color: #212529;
  border: 1px solid #ced4da;
}

body.dark-theme .help-panel {
  background-color: #16002f;
  color: #ffffff;
}

body.light-theme .help-panel {
  background-color: #ffffff;
  color: #212529;
}

/* Help panel: header (title + logo) and top section */
.help-panel-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 4px;
}

.help-panel-title {
  margin: 0;
  font-weight: 700;
  font-size: 1.25rem;
  letter-spacing: 0.02em;
}

.help-panel-logo {
  height: 40px;
  width: auto;
  flex-shrink: 0;
}

.help-panel-header + .help-panel-intro {
  margin-top: 0;
}

.help-panel-intro {
  font-size: 0.9rem;
  margin-bottom: 14px;
  font-weight: 600;
  opacity: 0.92;
  line-height: 1.4;
}

.help-panel-body {
  display: flex;
  flex: 1 1 auto;
  min-height: 0;
  gap: 0;
  margin-top: 10px;
}

.help-sidebar {
  flex: 0 0 260px;
  max-width: 260px;
  border-right: none;
  padding-right: 12px;
  overflow-y: auto;
  min-height: 0;
}

.help-resizer {
  width: 6px;
  flex-shrink: 0;
  cursor: col-resize;
  background-color: rgba(0, 0, 0, 0.12);
}

.help-resizer:hover {
  background-color: rgba(0, 0, 0, 0.2);
}

body.dark-theme .help-resizer {
  background-color: rgba(255, 255, 255, 0.12);
}

body.dark-theme .help-resizer:hover {
  background-color: rgba(255, 255, 255, 0.2);
}

.help-sidebar .help-tab-table {
  width: 100%;
  margin-bottom: 0;
  font-size: 0.9rem;
}

.help-sidebar .help-tab-table thead th {
  font-weight: 700;
  font-size: 0.8rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  padding: 8px 6px;
  border-bottom: 2px solid rgba(0, 0, 0, 0.15);
}

.help-sidebar .help-tab-table td:first-child {
  font-weight: 600;
}

.help-sidebar .help-tab-table td {
  padding: 6px 8px;
  vertical-align: middle;
}

.help-tab-row {
  cursor: pointer;
}

.help-tab-row:hover {
  background-color: rgba(0, 0, 0, 0.06);
}

.help-tab-row.active {
  background-color: rgba(255, 98, 241, 0.18);
  font-weight: 600;
}

.help-tab-row.active td:first-child {
  font-weight: 700;
}

.help-details {
  flex: 1 1 auto;
  min-width: 0;
  padding-left: 12px;
  font-size: 0.9rem;
  overflow-y: auto;
  min-height: 0;
}

.help-details h5 {
  margin-top: 0;
  margin-bottom: 8px;
  font-weight: 600;
}

.help-details h6 {
  margin-top: 16px;
  margin-bottom: 6px;
  font-weight: 600;
}

.help-controls-table {
  width: 100%;
  border-collapse: collapse;
  margin-bottom: 8px;
  font-size: 0.9rem;
}

.help-controls-table th,
.help-controls-table td {
  padding: 6px 8px;
  vertical-align: top;
}

.help-controls-table th {
  text-align: left;
  border-bottom: 1px solid rgba(0, 0, 0, 0.1);
}

.help-controls-table tr:nth-child(even) td {
  background-color: rgba(0, 0, 0, 0.01);
}

/* ---------------- Theme support ---------------- */

/* Light theme (hard-coded colours overriding the dark Bootswatch) */
body.light-theme {
  background-color: #f8f9fa;
  color: #212529;
}

body.light-theme a {
  color: #007bff;
}

body.light-theme a:hover,
body.light-theme a:focus {
  color: #0056b3;
}

/* Navbar in light mode: match page background so it blends in */
body.light-theme .navbar,
body.light-theme .navbar.navbar-default,
body.light-theme .navbar.navbar-inverse {
  background-color: #f8f9fa !important;
  border-bottom: 1px solid #dee2e6 !important;
  border-top: none !important;
  margin-bottom: 0;
}

body.light-theme .navbar-nav .nav-link,
body.light-theme .navbar-brand,
body.light-theme .navbar-default .navbar-nav > li > a,
body.light-theme .navbar-default .navbar-brand {
  color: #212529 !important;
}

body.light-theme .navbar-default .navbar-nav > li > a:hover,
body.light-theme .navbar-default .navbar-nav > li > a:focus,
body.light-theme .navbar-nav .nav-link:hover {
  color: #0056b3 !important;
}

body.light-theme .navbar-default .navbar-nav > .active > a,
body.light-theme .navbar-default .navbar-nav > .active > a:focus,
body.light-theme .navbar-default .navbar-nav > .active > a:hover,
body.light-theme .navbar-nav .nav-link.active {
  color: #0056b3 !important;
  background-color: transparent !important;
}

/* Make general UI elements readable in light mode */
body.light-theme,
body.light-theme .container-fluid,
body.light-theme .tab-content {
  background-color: #f8f9fa;
  color: #212529;
}

body.light-theme .well,
body.light-theme .panel,
body.light-theme .panel-body,
body.light-theme .card,
body.light-theme .modal-content {
  background-color: #ffffff;
  color: #212529;
}

/* Login/other modal inputs: Shiny defaults .shiny-input-container to 300px, which leaves empty
   space on the right of the 500px modal. Let them fill the modal width. */
.modal-body .shiny-input-container,
.modal-body .form-group {
  width: 100%;
  max-width: 100%;
}

body.light-theme .form-control,
body.light-theme .shiny-input-container {
  background-color: #ffffff;
  color: #212529;
}

body.light-theme .btn-default {
  background-color: #ffffff;
  color: #212529;
  border-color: #ced4da;
}

/* Tables (including DT) readable in light mode */
body.light-theme table,
body.light-theme table.dataTable,
body.light-theme .table {
  background-color: #ffffff;
  color: #212529;
}

/* Help details theming */
body.dark-theme .help-details h5,
body.dark-theme .help-details h6 {
  color: #ff62f1;
}

body.light-theme .help-details h5,
body.light-theme .help-details h6 {
  color: #16002f;
}

body.dark-theme .help-controls-table th {
  background-color: rgba(255, 98, 241, 0.12);
  border-bottom-color: rgba(255, 98, 241, 0.5);
}

body.light-theme .help-controls-table th {
  background-color: #f1eef8;
  border-bottom-color: #d0c4f0;
}

body.dark-theme .help-controls-table tr:nth-child(even) td {
  background-color: rgba(255, 255, 255, 0.02);
}

body.light-theme .help-controls-table tr:nth-child(even) td {
  background-color: #f8f9fa;
}

/* :not([style*="color"]) matters: DT::formatStyle colours a verdict cell with an inline style,
   and an !important blanket rule here beats inline - which silently turned the green/amber/red
   pass-and-flag colouring back to plain black. Cells that ask for a colour keep it. */
body.light-theme table th,
body.light-theme table td,
body.light-theme table.dataTable th,
body.light-theme table.dataTable td {
  background-color: #ffffff !important;
}
body.light-theme table th:not([style*="color"]),
body.light-theme table td:not([style*="color"]),
body.light-theme table.dataTable th:not([style*="color"]),
body.light-theme table.dataTable td:not([style*="color"]) {
  color: #212529 !important;
}

body.light-theme .table-striped > tbody > tr:nth-of-type(odd),
body.light-theme table.dataTable.stripe tbody tr.odd {
  background-color: #f2f2f2 !important;
}

body.light-theme .dataTables_wrapper .dataTables_scrollHead,
body.light-theme .dataTables_wrapper .dataTables_scrollBody {
  background-color: #ffffff;
  color: #212529;
}

/* Logged-in banner should blend with background in light mode */
body.light-theme h5 {
  background-color: transparent;
  color: #212529;
}

/* Dark theme base surfaces (Claude-like dark grey, not pure black).
   The bslib theme sets --bs-body-bg to #262624; these keep containers, panels,
   inputs and tables consistent with it and give panels a touch of depth. */
body.dark-theme,
body.dark-theme .container-fluid,
body.dark-theme .tab-content {
  background-color: #262624;
  color: #ececec;
}

body.dark-theme .well,
body.dark-theme .panel,
body.dark-theme .panel-body,
body.dark-theme .card,
body.dark-theme .modal-content {
  background-color: #30302e;   /* slightly lighter than the body for separation */
  color: #ececec;
  border-color: #3d3c39;
}

body.dark-theme .form-control,
body.dark-theme select,
body.dark-theme textarea {
  background-color: #1f1e1d;   /* inputs a touch darker, like Claude fields */
  color: #ececec;
  border-color: #4a4844;
}

body.dark-theme table,
body.dark-theme table.dataTable,
body.dark-theme .table {
  background-color: #2b2a28;
  color: #ececec;
}

body.dark-theme .table-striped > tbody > tr:nth-of-type(odd),
body.dark-theme table.dataTable.stripe tbody tr.odd {
  background-color: #2f2e2c;
}

/* Dark theme: whole top bar in the brand purple. The tabs sit inside the navbar's
   .container-fluid, which was picking up the grey page background (the "grey box") - force it
   transparent so the purple navbar shows across the entire bar. */
body.dark-theme .navbar,
body.dark-theme .navbar.navbar-default,
body.dark-theme .navbar.navbar-inverse,
body.dark-theme .navbar-default {
  background-color: #16002f !important;   /* brand purple */
  background-image: none !important;
  border-bottom: 1px solid #2a0a4a !important;
}
body.dark-theme .navbar .container-fluid,
body.dark-theme .navbar > .container-fluid,
body.dark-theme .navbar .navbar-collapse,
body.dark-theme .navbar .navbar-header {
  background-color: transparent !important;
}

/* Dark theme: dropdowns (native <select> and selectize) dark with readable white text
   (previously the selectize control rendered light/grey with low-contrast text). */
body.dark-theme select,
body.dark-theme select.form-control {
  background-color: #1f1e1d;
  color: #ffffff;
}
body.dark-theme select option {
  background-color: #1f1e1d;
  color: #ffffff;
}
body.dark-theme .selectize-input,
body.dark-theme .selectize-control.single .selectize-input {
  background-color: #1f1e1d !important;
  color: #ffffff !important;
  border-color: #4a4844 !important;
}
body.dark-theme .selectize-input input,
body.dark-theme .selectize-input > div {
  color: #ffffff !important;
}
body.dark-theme .selectize-dropdown,
body.dark-theme .selectize-dropdown-content,
body.dark-theme .selectize-dropdown .option {
  background-color: #1f1e1d;
  color: #ffffff;
  border-color: #4a4844;
}
body.dark-theme .selectize-dropdown .active {
  background-color: #16002f;
  color: #ffffff;
}

/* ---------------- Layout controls from settings ---------------- */

/* Font size controls */
body.font-normal {
  font-size: 14px;
}

body.font-large {
  font-size: 16px;
}

body.font-xlarge {
  font-size: 18px;
}

/* Table density controls */
body.table-comfortable table,
body.table-comfortable table.dataTable,
body.table-comfortable .table {
  font-size: 1rem;
}

body.table-compact table,
body.table-compact table.dataTable,
body.table-compact .table {
  font-size: 0.9rem;
}

body.table-compact table th,
body.table-compact table td,
body.table-compact table.dataTable th,
body.table-compact table.dataTable td {
  padding-top: 0.25rem;
  padding-bottom: 0.25rem;
}

/* Sidebar + main layout using flex, to let main panel fill remaining space */
.sidebar-layout-row {
  display: flex;
  flex-wrap: nowrap;
  width: 100%;
}

.sidebar-layout-sidebar {
  flex: 0 0 320px;
  max-width: 320px;
}

.sidebar-layout-main {
  flex: 1 1 auto;
  max-width: 100%;
  overflow-x: hidden;
}

/* ============================================================================================
   Sidebar height: the window, not the content
   The sidebars outgrew the screen. Calibration Review carries 36 controls and headings, the File
   Importer 27; measured in Chromium, the File Importer's panel is 1631px against a 950px window.
   So the PAGE grew to fit the sidebar and the table beside it started below the fold. Cap each
   panel at the space between the navbar and the foot of the window, and let it scroll inside
   itself.

   The cap goes on the .well - the panel - and NOT on the column. The column is a flex item that
   stretches to the row, which is what keeps the drag handle full height, and every width rule in
   the app (the resizer, the 'update-layout' handler, the File Formatter and LOD width messages,
   all in ui.R) writes to the column. None of them is touched by this.

   --navbar-h is written by updateNavbarPadding() in ui.R, which already measures the navbar for
   body's padding-top. The 100px fallback is what the rules compute to (1rem + 1rem of .navbar
   padding, a 50px brand image, .32422rem of .navbar-brand padding); the 55px at the top of this
   file is a stale fallback that the script overwrites at load.

   Both axes on purpose. Naming only overflow-y would compute overflow-x from `visible` to `auto`
   anyway, and the LOD tab's wide blank-signal table needs to scroll sideways inside the panel
   once the panel is the scroll container.
   ============================================================================================ */
:root { --navbar-h: 100px; }

@media (min-width: 901px) {
  .sidebar-layout-sidebar > .well {
    max-height: calc(100vh - var(--navbar-h) - 24px);
    overflow: auto;
    overscroll-behavior: contain;
    /* Keep the controls on screen while the main panel is scrolled: on these tabs the sidebar is
       the thing being operated. */
    position: sticky;
    top: calc(var(--navbar-h) + 8px);
  }

  /* .well is bslib's .card, so it is a flex column, and Bootstrap gives .card-body
     min-height: 1px - which defeats the automatic minimum size. Without this the card would
     shrink to fit the cap instead of overflowing it, and the panel's background would stop short
     of its own content. Pin the children so the panel scrolls. */
  .sidebar-layout-sidebar > .well > *,
  .sidebar-layout-sidebar > .well > .card > .card-body { flex: 0 0 auto; }

  /* Drop the column's right-hand gutter so the panel - and therefore its scrollbar - finishes
     against the drag handle instead of 15px short of it. Measured before: the panel ended at
     x=305 and the handle began at x=320, so the scrollbar floated in the middle of a gap.
     Only the right side: the left gutter is what holds the panel off the window edge. */
  .sidebar-layout-sidebar { padding-right: 0; }
}
/* Below 900px the sidebar stacks full width ABOVE the content (see the narrow-screen rules later
   in this file), where a capped, scrolling panel would be a scroll box inside a scrolling page.
   The media query above is load-bearing, not cosmetic. */

/* ============================================================================================
   Scrollbars on the app's own scroll containers
   The sidebar panels and the wide data tables became scroll containers; they arrived with the
   platform's default bar, which on this stack measured 15px of light grey. That is wider than
   the drag handle it sits beside, and it is the one piece of furniture in the interface that
   was never the app's colour.

   Width is the drag handle's width, from the same custom property, so the panel's scrollbar and
   the handle read as one edge - which they now are, the column's right-hand gutter having been
   dropped above.

   Scoped to these containers rather than applied to the document. A page-wide ::-webkit-scrollbar
   would also restyle the main window scrollbar, every modal, the help panel and the navbar's
   collapsed menu, and 6px is too thin for a full-page scroll.

   Two mechanisms, deliberately not both at once. `scrollbar-width` is the standard property and
   WINS over ::-webkit-scrollbar in Chromium 121+, where its `thin` is about 11px - so asking for
   both would quietly give 11px instead of the 6 that makes this flush. The webkit pseudo-elements
   carry it where they exist, and the @supports block below covers everything else.
   ============================================================================================ */
:root { --brand-scrollbar: 6px; --brand-resizer: 3px; }

.sidebar-layout-sidebar > .well::-webkit-scrollbar,
.dataTables_scrollBody::-webkit-scrollbar,
.stability-sidebar-table-wrapper::-webkit-scrollbar,
#table-container::-webkit-scrollbar {
  width: var(--brand-scrollbar);
  height: var(--brand-scrollbar);
}
.sidebar-layout-sidebar > .well::-webkit-scrollbar-track,
.dataTables_scrollBody::-webkit-scrollbar-track,
.stability-sidebar-table-wrapper::-webkit-scrollbar-track,
#table-container::-webkit-scrollbar-track {
  background: transparent;
}
.sidebar-layout-sidebar > .well::-webkit-scrollbar-thumb,
.dataTables_scrollBody::-webkit-scrollbar-thumb,
.stability-sidebar-table-wrapper::-webkit-scrollbar-thumb,
#table-container::-webkit-scrollbar-thumb {
  background-color: rgba(111, 45, 189, 0.45);
  border-radius: calc(var(--brand-scrollbar) / 2);
}
.sidebar-layout-sidebar > .well::-webkit-scrollbar-thumb:hover,
.dataTables_scrollBody::-webkit-scrollbar-thumb:hover,
.stability-sidebar-table-wrapper::-webkit-scrollbar-thumb:hover,
#table-container::-webkit-scrollbar-thumb:hover {
  background-color: rgba(111, 45, 189, 0.75);
}
/* The corner where a vertical and a horizontal bar meet - a wide table inside a capped panel has
   both, and left alone it paints as a light grey square. */
.sidebar-layout-sidebar > .well::-webkit-scrollbar-corner,
.dataTables_scrollBody::-webkit-scrollbar-corner,
#table-container::-webkit-scrollbar-corner {
  background: transparent;
}

body.dark-theme .sidebar-layout-sidebar > .well::-webkit-scrollbar-thumb,
body.dark-theme .dataTables_scrollBody::-webkit-scrollbar-thumb,
body.dark-theme .stability-sidebar-table-wrapper::-webkit-scrollbar-thumb,
body.dark-theme #table-container::-webkit-scrollbar-thumb {
  background-color: rgba(168, 85, 247, 0.45);
}
body.dark-theme .sidebar-layout-sidebar > .well::-webkit-scrollbar-thumb:hover,
body.dark-theme .dataTables_scrollBody::-webkit-scrollbar-thumb:hover,
body.dark-theme .stability-sidebar-table-wrapper::-webkit-scrollbar-thumb:hover,
body.dark-theme #table-container::-webkit-scrollbar-thumb:hover {
  background-color: rgba(168, 85, 247, 0.8);
}

/* Firefox, and anything else without the webkit pseudo-elements. `thin` is as narrow as the
   standard property goes; the colour is the part that matters here. */
@supports not selector(::-webkit-scrollbar) {
  .sidebar-layout-sidebar > .well,
  .dataTables_scrollBody,
  .stability-sidebar-table-wrapper,
  #table-container {
    scrollbar-width: thin;
    scrollbar-color: rgba(111, 45, 189, 0.45) transparent;
  }
  body.dark-theme .sidebar-layout-sidebar > .well,
  body.dark-theme .dataTables_scrollBody,
  body.dark-theme .stability-sidebar-table-wrapper,
  body.dark-theme #table-container {
    scrollbar-color: rgba(168, 85, 247, 0.45) transparent;
  }
}

/* Sidebar width controls (override base flex when narrow mode is enabled) */
body.sidebar-default .sidebar-layout-sidebar {
  flex: 0 0 320px;
  max-width: 320px;
}

body.sidebar-narrow .sidebar-layout-sidebar {
  flex: 0 0 260px;
  max-width: 260px;
}

/* Draggable resizer between sidebar and main */
.sidebar-resizer {
  /* flex-basis, not width: this is a flex item in .sidebar-layout-row, and a bare width was
     being shrunk to 4.6px by the row's flex algorithm, so the stated width has to be pinned to
     be the real one.

     The BAR is 3px - it is a boundary, and at 6px it read as a piece of furniture. The GRAB
     AREA is not: ::before spreads it 5px either side, so there are 13px of col-resize to aim at
     while only 3px are drawn. Pointer events on a pseudo-element are attributed to its host, so
     the mousedown handler in ui.R needs no change. It sits above the sidebar's own scrollbar
     (z-index) because the two are now adjacent with nothing between them. */
  flex: 0 0 var(--brand-resizer);
  width: var(--brand-resizer);
  position: relative;
  cursor: col-resize;
  background-color: rgba(111, 45, 189, 0.30);
  transition: background-color .12s ease;
}
.sidebar-resizer::before {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  left: -5px;
  right: -5px;
  z-index: 3;
}

.sidebar-resizer:hover,
body.light-theme .sidebar-resizer:hover {
  background-color: rgba(111, 45, 189, 0.65);
}
body.dark-theme .sidebar-resizer { background-color: rgba(168, 85, 247, 0.32); }
body.dark-theme .sidebar-resizer:hover { background-color: rgba(168, 85, 247, 0.70); }

/* Ensure plots and widgets respect main-panel width in sidebar layouts */
.sidebar-layout-main .html-widget,
.sidebar-layout-main .plotly,
.sidebar-layout-main .shiny-plot-output {
  max-width: 100% !important;
}

/* ---------------- Start page and Criteria (tile layout) ---------------- */
.start-page-content,
.criteria-page-content,
.validation-report-page-content {
  width: 100%;
  max-width: 100%;
  padding: 0 clamp(16px, 4vw, 32px);
  margin: 0 auto;
  box-sizing: border-box;
}

.greeting-banner {
  padding: 10px 16px;
  margin-bottom: 16px;
  border-radius: 8px;
}

body.dark-theme .greeting-banner {
  background-color: rgba(255, 98, 241, 0.12);
  border: 1px solid rgba(255, 98, 241, 0.3);
}

body.light-theme .greeting-banner {
  background-color: #f1eef8;
  border: 1px solid #d0c4f0;
}

.start-hero-card,
.start-intro-card,
.start-tutorial-card,
.start-downloads-card {
  margin-bottom: 20px;
}

.start-hero-card .card-body,
.start-intro-card .card-body,
.start-tutorial-card .card-body,
.start-downloads-card .card-body {
  padding-top: 0.5rem;
}

.start-hero-card .card-body h3:first-child,
.start-intro-card .card-body h3:first-child,
.start-downloads-card .card-body h3:first-child {
  margin-top: 0;
}

.start-tutorial-card .card-body > div {
  margin-top: 0;
}

.start-tutorial-card .card-body h3 {
  margin-top: 0;
}

.start-downloads-card .btn {
  margin-right: 8px;
  margin-bottom: 8px;
}

.accent-heading {
  color: #ff62f1;
}

body.light-theme .accent-heading {
  color: #16002f;
}

/* Start page tile header hierarchy - match Criteria & Definitions scale */
.start-tile-title {
  font-size: 1.75rem;
  font-weight: 600;
  margin-bottom: 0.75rem;
}

/* Sample Processing tile - smaller header for sidebar */
.sample-processing-tile-title {
  font-size: 1.1rem;
  font-weight: 600;
  margin-bottom: 0.5rem;
}

.start-tile-subtitle {
  font-size: 1.15rem;
  font-weight: 600;
  margin-top: 1rem;
  margin-bottom: 0.5rem;
}

.citation {
  font-size: 12px;
  opacity: 0.9;
}

/* ---------------- Criteria cards ---------------- */
.criterion-card {
  margin-bottom: 16px;
}

.criterion-card .card-body {
  padding-top: 0.75rem;
}

/* Criterion card inputs fill available width */
.criterion-card .form-group,
.criterion-card .shiny-input-container {
  width: 100%;
}
.criterion-card .form-control {
  width: 100%;
  max-width: 100%;
  box-sizing: border-box;
}

/* Criteria specs row: align input boxes when labels wrap to 2 lines */
.criteria-specs-row > [class*="col-"] {
  display: flex;
}
.criteria-specs-row .form-group,
.criteria-specs-row .shiny-input-container {
  display: flex;
  flex-direction: column;
  flex: 1;
  width: 100%;
}
.criteria-specs-row .form-group .form-control,
.criteria-specs-row .form-group input {
  margin-top: auto;
}

/* Equal-height tiles per row */
.criterion-tile-row {
  display: flex;
  flex-wrap: wrap;
  margin-bottom: 16px;
}

.criterion-tile-row > [class*="col-"] {
  display: flex;
}

.criterion-tile-row .criterion-card {
  flex: 1;
  display: flex;
  flex-direction: column;
  width: 100%;
  margin-bottom: 0;
}

.criterion-tile-row .criterion-card .card-body {
  flex: 1;
  display: flex;
  flex-direction: column;
}

/* Align single inputs across side-by-side tiles (e.g. Calibration, Carryover) */
.criterion-tile-row .criterion-card .card-body > .form-group,
.criterion-tile-row .criterion-card .card-body > .shiny-input-container {
  margin-top: auto;
}

.criterion-tile-row .criterion-card .criterion-help {
  /* Fixed, consistent size (no flex-stretch) so help tiles don't grow to different
     heights depending on how much content sits above them in each card. */
  flex: 0 0 auto;
  min-height: 64px;
}

.criterion-section-title {
  color: #ff62f1;
  margin-top: 0;
  margin-bottom: 12px;
}

body.light-theme .criterion-section-title {
  color: #16002f;
}

.criterion-help {
  margin-top: 12px;
  padding: 12px 14px;
  border-radius: 6px;
  font-size: 0.9rem;
  line-height: 1.5;
  /* Consistent minimum size so callouts with a similar amount of text render as
     uniform tiles; text is vertically centred within the box. */
  min-height: 64px;
  display: flex;
  flex-direction: column;
  justify-content: center;
}

body.dark-theme .criterion-help {
  background-color: rgba(255, 98, 241, 0.08);
  border-left: 4px solid rgba(255, 98, 241, 0.4);
}

body.light-theme .criterion-help {
  background-color: #f8f6fc;
  border-left: 4px solid #d0c4f0;
}

.criterion-help ul {
  margin: 8px 0 0 0;
  padding-left: 20px;
}

.criterion-help li {
  margin-bottom: 4px;
}

/* Interference tile: align input boxes when labels vary in length (same as criteria-specs-row) */
.interference-inputs-row > [class*="col-"] {
  display: flex;
}
.interference-inputs-row .form-group,
.interference-inputs-row .shiny-input-container {
  display: flex;
  flex-direction: column;
  flex: 1;
  width: 100%;
}
.interference-inputs-row .form-group .form-control,
.interference-inputs-row .form-group input {
  margin-top: auto;
}

/* Logo link in navbar */
.navbar-brand a {
  text-decoration: none;
}

.navbar-brand a:hover {
  opacity: 0.9;
}


/* ============================================================================================
   Editable grids (verification: manufacturer claims, assigned targets)
   A cell the user still has to fill is tinted and outlined, so it is obvious where to type
   rather than the grid looking like a finished read-only table. The class is applied per row by
   the rowCallback in R/verification.R and removed as soon as a value is entered.

   !important is required, not decorative: the theme rules above set every table cell background
   with !important, which silently won this fight and left the cells looking untouched.
   ============================================================================================ */
table.editable-grid td.needs-entry,
body.light-theme table.editable-grid td.needs-entry,
body.light-theme table.dataTable.editable-grid td.needs-entry,
body.dark-theme table.editable-grid td.needs-entry {
  background-color: #fff3cd !important;
  box-shadow: inset 0 0 0 2px #f0ad4e !important;
  border-radius: 3px;
  cursor: text;
  font-weight: 600;
}
body.dark-theme table.editable-grid td.needs-entry {
  background-color: #57430f !important;
  box-shadow: inset 0 0 0 2px #d99b1c !important;
  color: #ffe6a8 !important;
}
table.editable-grid td.needs-entry::after {
  content: "\270E";                /* pencil: this cell expects a value */
  float: right;
  opacity: 0.75;
  font-size: 0.95em;
  padding-left: 6px;
}
table.editable-grid td.needs-entry:hover {
  filter: brightness(0.96);
}
/* Editable cells that are already filled still invite a click, just quietly. */
table.editable-grid tbody td:nth-child(n+3) { cursor: text; }

/* ============================================================================================
   Sidebar hints
   Sidebar copy is one scannable line; the full explanation is on hover. The whole line is the
   hover target - an icon glyph beside it was both fiddly to hit and rendered as a stray letter
   wherever the tooltip JS had not loaded.
   ============================================================================================ */
.sidebar-hint.has-hint {
  display: block;
  margin-bottom: 8px;
  line-height: 1.4;
  cursor: help;
  border-bottom: 1px dotted rgba(255, 98, 241, 0.55);
  padding-bottom: 2px;
}
.sidebar-hint.has-hint:hover { color: #ff62f1; }
body.light-theme .sidebar-hint.has-hint {
  border-bottom-color: rgba(140, 20, 130, 0.45);
}
body.light-theme .sidebar-hint.has-hint:hover { color: #8c1482; }
.tooltip-inner { max-width: 340px; text-align: left; font-size: 0.85em; line-height: 1.45; padding: 8px 10px; }

/* ============================================================================================
   Accent button - the primary action in a group of small buttons (e.g. Load ACB example)
   ============================================================================================ */
.btn-accent, .btn-accent:focus {
  background-color: #6f2dbd !important;
  border-color: #6f2dbd !important;
  color: #ffffff !important;
  font-weight: 600;
}
.btn-accent:hover { background-color: #8b46d9 !important; border-color: #8b46d9 !important; }
body.dark-theme .btn-accent, body.dark-theme .btn-accent:focus {
  background-color: #a855f7 !important;
  border-color: #a855f7 !important;
  color: #1a0b2e !important;
}
body.dark-theme .btn-accent:hover { background-color: #c084fc !important; border-color: #c084fc !important; }

/* ============================================================================================
   Report export buttons + header fields
   ============================================================================================ */
.report-download-group { display: flex; flex-direction: column; gap: 10px; margin-bottom: 6px; }
.report-dl-btn {
  display: block !important;
  width: 100%;
  text-align: left;
  padding: 9px 12px !important;
  border-radius: 8px !important;
  font-weight: 600;
  border: 1px solid transparent !important;
  transition: transform 0.08s ease, box-shadow 0.15s ease;
}
.report-dl-btn i { margin-right: 8px; opacity: 0.95; }
.report-dl-btn:hover { transform: translateY(-1px); box-shadow: 0 3px 10px rgba(0, 0, 0, 0.18); }
.report-dl-word { background-color: #005EB8 !important; border-color: #005EB8 !important; color: #fff !important; }
.report-dl-word:hover { background-color: #0072e0 !important; }
.report-dl-html { background-color: #6f2dbd !important; border-color: #6f2dbd !important; color: #fff !important; }
.report-dl-html:hover { background-color: #8b46d9 !important; }
/* Excel's own green, so the button is recognisable next to the Word one. */
.report-dl-excel { background-color: #1d6f42 !important; border-color: #1d6f42 !important; color: #fff !important; }
.report-dl-excel:hover { background-color: #248a52 !important; }
.report-dl-note { margin-top: 8px; display: block; line-height: 1.35; }

/* ============================================================================================
   Sign-in modal
   Small, centred and quiet: two fields and a button, with no wasted vertical space.
   ============================================================================================ */
/* Scoped to the sign-in dialog (the only modal opened at size "s"): a global .modal-dialog width
   also squeezed the source-data dialog, which needs to be wide. */
.modal-sm { max-width: 400px; }
.modal-sm .modal-content { border-radius: 14px; border: none; overflow: hidden; }
.modal-sm .modal-body { padding: 0 !important; }
/* The source-data dialog carries the pooled import table, which is wider than a default modal. */
.modal-lg { max-width: 940px; }
.login-modal { padding: 30px 32px 24px; text-align: center; }
.login-logo { height: 46px; margin-bottom: 14px; }
.login-title { margin: 0 0 4px; font-weight: 700; letter-spacing: 0.2px; }
.login-sub { margin: 0 0 18px; opacity: 0.7; font-size: 0.92em; }
.login-modal .form-group { margin-bottom: 10px; text-align: left; }
.login-modal .form-control {
  height: 42px; border-radius: 8px; padding: 8px 12px; font-size: 0.98em;
}
.login-start {
  width: 100%; height: 42px; margin-top: 6px; border-radius: 8px;
  font-weight: 600; letter-spacing: 0.3px;
  background-color: #6f2dbd !important; border-color: #6f2dbd !important; color: #fff !important;
}
.login-start:hover { background-color: #8b46d9 !important; border-color: #8b46d9 !important; }
.login-note { margin: 16px 0 0; font-size: 0.78em; opacity: 0.6; line-height: 1.4; }
.login-error {
  display: none; margin: 2px 0 6px; font-size: 0.85em; color: #b3261e; text-align: left;
}
.login-error.visible { display: block; }
body.dark-theme .modal-content { background: #2f2f2d; color: #ececec; }
body.dark-theme .login-error { color: #ff8a80; }
body.dark-theme .login-start,
body.dark-theme .login-start:focus { background-color: #a855f7 !important; border-color: #a855f7 !important; color: #1a0b2e !important; }
body.dark-theme .login-start:hover { background-color: #c084fc !important; border-color: #c084fc !important; }

/* Source-data button: sits at the foot of each analysis tab's sidebar, opening the imported rows
   that tab is working from. Quiet by default - it is a reference, not a primary action. */
.show-data-btn {
  margin-top: 4px;
  /* Centred, like every other block button in the sidebar. It was left-aligned to read as a
     quiet reference rather than an action, but sitting in a column of centred buttons that
     read as a misalignment rather than as emphasis. */
  text-align: center;
  border: 1px solid rgba(111, 45, 189, 0.45) !important;
  background: rgba(111, 45, 189, 0.07) !important;
  color: #6f2dbd !important;
  font-weight: 600;
}
.show-data-btn:hover { background: rgba(111, 45, 189, 0.16) !important; }
.show-data-btn i { margin-right: 6px; }
body.dark-theme .show-data-btn {
  border-color: rgba(168, 85, 247, 0.55) !important;
  background: rgba(168, 85, 247, 0.12) !important;
  color: #c084fc !important;
}
body.dark-theme .show-data-btn:hover { background: rgba(168, 85, 247, 0.25) !important; }
/* The pooled table is wider than a dialog, so let it scroll rather than crush the columns. */
.modal-lg .dataTables_wrapper { overflow-x: auto; }

/* Source-data dialog: keep every row on one line and let the table scroll sideways instead.
   Long source filenames otherwise wrap to three lines and the ten rows fill the screen. */
.modal-lg table.dataTable td,
.modal-lg table.dataTable th { white-space: nowrap; }

/* ============================================================================================
   Verification sidebars
   The section headings read as body text and the buttons ran together. Give the headings weight
   and a rule, and space the button rows so each is a separate target.
   ============================================================================================ */
.criterion-section-title {
  font-weight: 700;
  font-size: 0.95rem;
  letter-spacing: 0.02em;
  padding-bottom: 6px;
  border-bottom: 1px solid rgba(255, 98, 241, 0.25);
}
body.light-theme .criterion-section-title { border-bottom-color: rgba(111, 45, 189, 0.25); }
.criterion-section-title ~ .criterion-section-title { margin-top: 18px; }
/* Button rows: lay them out as a wrapping flex row with real gaps rather than inline neighbours. */
.compact-checkboxes {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin: 10px 0 4px;
}
.compact-checkboxes .btn { margin: 0 !important; }
.compact-checkboxes .form-group { margin-bottom: 0; }
.criterion-card .card-body > .btn,
.criterion-card .card-body > .shiny-download-link { margin-top: 8px; }
/* The sidebar's own card: a little more breathing room inside it. */
.criterion-card .card-body { padding: 16px 16px 18px; }
.sidebar-hint.has-hint + .criterion-section-title { margin-top: 18px; }

/* ============================================================================================
   Light-theme dropdowns
   The native <select> and the selectize control both inherited a light background with pale text,
   so the chosen option and the open list were hard to read.
   ============================================================================================ */
body.light-theme select.form-control,
body.light-theme select {
  background-color: #ffffff !important;
  color: #212529 !important;
  border: 1px solid #ced4da !important;
}
body.light-theme select.form-control option,
body.light-theme select option {
  background-color: #ffffff !important;
  color: #212529 !important;
}
body.light-theme .selectize-input,
body.light-theme .selectize-control.single .selectize-input,
body.light-theme .selectize-input input,
body.light-theme .selectize-input > div {
  background-color: #ffffff !important;
  color: #212529 !important;
  border-color: #ced4da;
}
body.light-theme .selectize-dropdown,
body.light-theme .selectize-dropdown-content,
body.light-theme .selectize-dropdown .option {
  background-color: #ffffff !important;
  color: #212529 !important;
  border-color: #ced4da;
}
body.light-theme .selectize-dropdown .active,
body.light-theme .selectize-dropdown .option:hover {
  background-color: #6f2dbd !important;
  color: #ffffff !important;
}

/* ============================================================================================
   Table paging and export buttons, in the app's own colours
   DataTables here renders through its Bootstrap 4 integration, so the pager is
   li.paginate_button.page-item > a.page-link and the buttons are .btn.btn-secondary - not the
   .paginate_button.current / .dt-button of the plain build.
   ============================================================================================ */
.dataTables_paginate .page-item .page-link {
  color: #6f2dbd !important;
  background: transparent !important;
  border-color: rgba(111, 45, 189, 0.25) !important;
}
.dataTables_paginate .page-item .page-link:hover {
  background: rgba(111, 45, 189, 0.12) !important;
  border-color: rgba(111, 45, 189, 0.4) !important;
}
.dataTables_paginate .page-item.active .page-link,
.dataTables_paginate .page-item.active .page-link:hover {
  background: #6f2dbd !important;
  border-color: #6f2dbd !important;
  color: #ffffff !important;
}
.dataTables_paginate .page-item.disabled .page-link {
  color: #adb5bd !important;
  background: transparent !important;
  border-color: rgba(0, 0, 0, 0.08) !important;
}
body.dark-theme .dataTables_paginate .page-item .page-link {
  color: #c084fc !important;
  border-color: rgba(168, 85, 247, 0.35) !important;
}
body.dark-theme .dataTables_paginate .page-item .page-link:hover {
  background: rgba(168, 85, 247, 0.22) !important;
}
body.dark-theme .dataTables_paginate .page-item.active .page-link,
body.dark-theme .dataTables_paginate .page-item.active .page-link:hover {
  background: #a855f7 !important;
  border-color: #a855f7 !important;
  color: #1a0b2e !important;
}
body.dark-theme .dataTables_paginate .page-item.disabled .page-link {
  color: #6c6a67 !important;
  border-color: rgba(255, 255, 255, 0.08) !important;
}
/* Copy / CSV buttons, which arrive as Bootstrap secondary buttons. */
.dt-buttons .btn,
.dt-buttons .btn-secondary {
  background: rgba(111, 45, 189, 0.10) !important;
  border: 1px solid rgba(111, 45, 189, 0.35) !important;
  color: #6f2dbd !important;
  font-weight: 600;
  box-shadow: none !important;
}
.dt-buttons .btn:hover, .dt-buttons .btn-secondary:hover {
  background: rgba(111, 45, 189, 0.2) !important;
  color: #6f2dbd !important;
}
body.dark-theme .dt-buttons .btn,
body.dark-theme .dt-buttons .btn-secondary {
  background: rgba(168, 85, 247, 0.16) !important;
  border-color: rgba(168, 85, 247, 0.45) !important;
  color: #c084fc !important;
}
body.dark-theme .dt-buttons .btn:hover,
body.dark-theme .dt-buttons .btn-secondary:hover {
  background: rgba(168, 85, 247, 0.3) !important;
  color: #e9d5ff !important;
}

/* ============================================================================================
   First paint
   The workflow routing runs in R, so for a moment on load the browser showed the validation tabs
   and start page before they were hidden again. A cover sits over the page until the routing has
   run. Deliberately NOT done by hiding the individual tabs in CSS: the routing reveals them with
   jQuery .show() on the parent <li>, which would not override a rule on the <a> inside, and a
   validation build would then have permanently invisible tabs.
   ============================================================================================ */
#app-loading {
  position: fixed;
  inset: 0;
  z-index: 2000;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 18px;
  background: #f8f9fa;
  transition: opacity 0.25s ease;
}
body.dark-theme #app-loading { background: #262624; }
body.workflow-ready #app-loading { opacity: 0; pointer-events: none; visibility: hidden; }
#app-loading img { height: 54px; opacity: 0.95; }
#app-loading .app-loading-bar {
  width: 180px; height: 3px; border-radius: 2px; overflow: hidden;
  background: rgba(111, 45, 189, 0.18);
}
#app-loading .app-loading-bar span {
  display: block; height: 100%; width: 40%; border-radius: 2px; background: #6f2dbd;
  animation: app-loading-slide 1.1s ease-in-out infinite;
}
body.dark-theme #app-loading .app-loading-bar { background: rgba(168, 85, 247, 0.2); }
body.dark-theme #app-loading .app-loading-bar span { background: #a855f7; }
@keyframes app-loading-slide {
  0%   { transform: translateX(-100%); }
  100% { transform: translateX(250%); }
}
/* Third report action: builds the interactive report and hands over a link to open it. */
.report-dl-view {
  background: transparent !important;
  border: 1px solid rgba(111, 45, 189, 0.5) !important;
  color: #6f2dbd !important;
}
.report-dl-view:hover { background: rgba(111, 45, 189, 0.1) !important; }
body.dark-theme .report-dl-view {
  border-color: rgba(168, 85, 247, 0.55) !important;
  color: #c084fc !important;
}
body.dark-theme .report-dl-view:hover { background: rgba(168, 85, 247, 0.2) !important; }

/* ============================================================================================
   Autosave prompt
   The first thing a returning user sees, so it gets the same card treatment as the sign-in
   modal rather than a bare Bootstrap dialog. Scoped to .autosave-modal so no other dialog is
   affected, and every colour is stated for both themes - the viewer's theme is a body class,
   so a rule defined only once inherits the wrong ground in the other.
   ============================================================================================ */
.autosave-modal { padding: 28px 30px 22px; text-align: center; }
.autosave-icon {
  width: 52px; height: 52px; margin: 0 auto 14px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  background: rgba(111, 45, 189, 0.12); color: #6f2dbd; font-size: 1.35em;
}
.autosave-title { margin: 0 0 6px; font-weight: 700; letter-spacing: 0.2px; }
.autosave-sub { margin: 0 0 14px; opacity: 0.72; font-size: 0.92em; line-height: 1.45; }
.autosave-when {
  display: inline-block; margin-bottom: 18px; padding: 5px 12px; border-radius: 999px;
  font-size: 0.82em; font-variant-numeric: tabular-nums;
  background: rgba(111, 45, 189, 0.08); border: 1px solid rgba(111, 45, 189, 0.22); color: #6f2dbd;
}
.autosave-actions { display: flex; gap: 10px; justify-content: center; flex-wrap: wrap; }
.autosave-actions .btn { min-width: 132px; font-weight: 600; border-radius: 8px; padding: 9px 16px; }
.autosave-actions .btn-primary,
.autosave-actions .btn-primary:focus {
  background-color: #6f2dbd !important; border-color: #6f2dbd !important; color: #fff !important;
}
.autosave-actions .btn-primary:hover { background-color: #8b46d9 !important; border-color: #8b46d9 !important; }
.autosave-note { margin: 16px 0 0; font-size: 0.76em; opacity: 0.55; line-height: 1.4; }

body.dark-theme .autosave-icon { background: rgba(168, 85, 247, 0.18); color: #c084fc; }
body.dark-theme .autosave-when {
  background: rgba(168, 85, 247, 0.14); border-color: rgba(168, 85, 247, 0.35); color: #c084fc;
}
body.dark-theme .autosave-actions .btn-primary,
body.dark-theme .autosave-actions .btn-primary:focus {
  background-color: #a855f7 !important; border-color: #a855f7 !important; color: #1a0b2e !important;
}
body.dark-theme .autosave-actions .btn-primary:hover { background-color: #c084fc !important; border-color: #c084fc !important; }

/* ============================================================================================
   Inputs rendered inside a table cell
   selectInput() emits a form-group with an empty <label> and a bottom margin. In a DT cell that
   margin pushed the control to the bottom of the row and left a white band above it - which is
   what the per-row model selector on the model table looked like in light theme, where the band
   contrasts with the striping.
   ============================================================================================ */
table.dataTable td .form-group,
table.dataTable td .shiny-input-container { margin-bottom: 0 !important; margin-top: 0 !important; }
table.dataTable td .control-label { display: none; }
table.dataTable td .control-label:not(:empty) { display: block; margin-bottom: 2px; }
table.dataTable td .selectize-control { margin-bottom: 0; }

/* ============================================================================================
   Limits of detection / quantitation: which guidance is in force
   The three methods are different definitions rather than variants of one, so the tab states
   which is being applied before it shows any numbers.
   ============================================================================================ */
.limits-method-note {
  border-left: 4px solid #6f2dbd;
  background: rgba(111, 45, 189, 0.06);
  padding: 10px 14px;
  border-radius: 0 8px 8px 0;
  margin: 4px 0 18px;
}
.limits-method-note p { margin: 0; line-height: 1.5; }
.limits-method-guideline {
  display: inline-block; margin-right: 8px; padding: 2px 9px; border-radius: 999px;
  background: #6f2dbd; color: #fff; font-weight: 700; font-size: 0.82em; white-space: nowrap;
}
body.dark-theme .limits-method-note {
  border-left-color: #a855f7; background: rgba(168, 85, 247, 0.10);
}
body.dark-theme .limits-method-guideline { background: #a855f7; color: #1a0b2e; }

/* ============================================================================================
   Settings popover
   A panel that opens from a sidebar button. Deliberately not a modal: the controls inside are
   Shiny inputs, and an input that exists only while a modal is open has no value until the modal
   is first opened - every reactive reading it would see NULL on a fresh session.
   ============================================================================================ */
.settings-popover {
  margin: 8px 0 12px;
  padding: 12px 14px;
  border: 1px solid rgba(111, 45, 189, 0.28);
  border-radius: 10px;
  background: rgba(111, 45, 189, 0.05);
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.06);
}
.settings-popover-title {
  font-size: 0.78em; font-weight: 700; letter-spacing: 0.04em; text-transform: uppercase;
  opacity: 0.65; margin: 2px 0 6px;
}
.settings-popover .compact-checkboxes + .settings-popover-title { margin-top: 12px; }
.settings-popover .checkbox { margin: 3px 0; }
.settings-popover-note { margin: 10px 0 0; font-size: 0.78em; opacity: 0.7; line-height: 1.4; }
body.dark-theme .settings-popover {
  border-color: rgba(168, 85, 247, 0.35);
  background: rgba(168, 85, 247, 0.08);
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.25);
}

/* Show-source-data button on a tab with no sidebar to put it in (Calibration Acceptance). */
.review-data-inline { margin: 0 0 14px; }

/* Breathing room between the calibration plot and the residual plot below it. */
.plot-separator { height: 28px; border-bottom: 1px solid rgba(128, 128, 128, 0.22); margin: 0 0 28px; }
body.dark-theme .plot-separator { border-bottom-color: rgba(200, 200, 200, 0.18); }

/* ============================================================================================
   Criteria & Definitions tile grid
   One tile per criterion, on a grid that reflows. The page previously packed unrelated limits
   into shared cards - Calibration with Carryover, Dilution with Stability - which made the
   headings misleading and left the cards at different heights beside each other.
   ============================================================================================ */
.criteria-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
  gap: 18px;
  align-items: stretch;
  margin-top: 8px;
}
/* conditionalPanel wraps each tile in a plain div; make that wrapper a grid item that stretches. */
.criteria-grid > div { display: flex; }
.criteria-grid .criteria-tile { flex: 1 1 auto; display: flex; flex-direction: column; margin: 0; }
.criteria-grid .criteria-tile .card-body { display: flex; flex-direction: column; height: 100%; }
/* Tiles carrying three or more inputs get the full width when the grid has room for it. */
@media (min-width: 1100px) {
  .criteria-grid > div:has(.criteria-tile-wide) { grid-column: span 2; }
}
.criteria-grid .criterion-help { margin-top: auto; padding-top: 10px; }
.criteria-grid .criterion-section-title { margin-top: 0; }

/* ============================================================================================
   Sidebar buttons: one width
   The sidebar mixes two generations of button. The newer ones are btn-block and fill the
   sidebar; the older pairs sit in a flex .button-row and size themselves to their label, so
   "Update exclusions" and "Reset exclusions" came out narrower than everything above them and
   different widths from each other. Inside a sidebar every button now fills the width.

   Scoped to the sidebar deliberately: the report page has .button-row pairs in its MAIN panel
   ("Add row" / "Remove row"), where stacking them full width across a nine-column panel would
   look absurd.
   ============================================================================================ */
.sidebar-layout-sidebar .button-row {
  flex-direction: column;
  align-items: stretch;
}
.sidebar-layout-sidebar .button-row > .btn,
.sidebar-layout-sidebar .btn:not(.btn-xs) {
  display: block;
  width: 100%;
  margin-left: 0;
  margin-right: 0;
}
/* fileInput's own browse button is part of an input group and must not be stretched by the
   block-button rule above. The one deliberate exception is .file-input-block, which lays the
   input out as a block on purpose; it out-specifies this at the foot of the file rather than
   being carved out here with :not(), which reads as an exclusion but is not one - an ancestor
   :not() matches on any ancestor that lacks the class, so :not(.file-input-block) .btn-file
   matches every .btn-file there is, at a higher specificity than the rule it was meant to spare. */
.sidebar-layout-sidebar .input-group .btn,
.sidebar-layout-sidebar .btn-file,
.sidebar-layout-sidebar .input-group-btn .btn {
  display: inline-block;
  width: auto;
}

/* ============================================================================================
   Small screens
   The app was built for a desktop and, on a phone, showed almost nothing but chrome: fifteen
   navbar tabs wrapping onto six or seven lines, then a 320px sidebar sitting beside a main
   panel with 60px left to render into. navbarPage(collapsible = TRUE) deals with the tabs; the
   rules below deal with everything that was pinned to a fixed pixel width.

   One breakpoint at 900px, which is where the sidebar plus a usable main panel stops fitting -
   not a device width. Tablets in portrait get the stacked layout too, which is the right answer
   for them as well.
   ============================================================================================ */
@media (max-width: 900px) {
  /* Sidebar above content rather than beside it. The three width rules below all have to be
     overridden together: the base, the two body-class variants, and the stability tab's wider
     one - miss any and that tab keeps its fixed width while the rest stack. */
  .sidebar-layout-row { flex-wrap: wrap; }
  .sidebar-layout-sidebar,
  body.sidebar-default .sidebar-layout-sidebar,
  body.sidebar-narrow .sidebar-layout-sidebar,
  #stability-tab-layout .sidebar-layout-sidebar {
    flex: 1 1 100%;
    max-width: 100%;
    margin-bottom: 14px;
  }
  .sidebar-layout-main { flex: 1 1 100%; max-width: 100%; }

  /* Criterion tiles: 320px is wider than a phone once padding is taken off, so auto-fit never
     got below one column and overflowed instead. */
  .criteria-grid { grid-template-columns: repeat(auto-fit, minmax(min(100%, 260px), 1fr)); gap: 12px; }
  @supports not (width: min(100%, 260px)) {
    .criteria-grid { grid-template-columns: 1fr; }
  }
  .criteria-grid > div:has(.criteria-tile-wide) { grid-column: auto; }

  /* The help panel had min-width: 600px, so on a 390px screen it hung off both edges. */
  .help-panel {
    width: 96%;
    min-width: 0;
    left: 2%;
    right: 2%;
    transform: none;
    top: 56px;
    height: 86vh;
  }
  .help-panel-resize-e, .help-panel-resize-s, .help-panel-resize-se { display: none; }

  .settings-panel {
    left: 12px;
    right: 12px;
    bottom: 74px;
    min-width: 0;
    max-height: 70vh;
    overflow-y: auto;
  }

  /* Wide content scrolls inside its own box rather than pushing the page sideways. */
  .dataTables_wrapper { overflow-x: auto; -webkit-overflow-scrolling: touch; }
  .shiny-html-output table { max-width: 100%; }

  /* The brand logo is 50px tall inline; alongside a hamburger it crowds the bar. */
  #app-logo { height: 34px !important; margin-right: 4px !important; }

  /* Collapsed navbar: let the tab list scroll if it is taller than the screen. */
  .navbar-collapse { max-height: 78vh; overflow-y: auto; }
}

/* Phones proper: tighten the padding that a desktop can afford. */
@media (max-width: 560px) {
  .tab-pane .well, .card-body { padding: 10px 12px; }
  h3.accent-heading { font-size: 1.35rem; }
  .limits-method-note { padding: 8px 10px; }
  .limits-method-guideline { display: inline-block; margin-bottom: 4px; }
}

/* ============================================================================================
   Sidebar chrome: one box, not two
   Every sidebar is a Shiny .well wrapping a .card.criterion-card, so each one drew two nested
   borders with a colour change between them. The outer .well keeps the background (it is what
   makes the sidebar read as a sidebar) and loses its border; the inner card loses its own
   background and border entirely, so the panel is a single surface.
   ============================================================================================ */
.sidebar-layout-sidebar .well {
  border: none !important;
  box-shadow: none !important;
}
.sidebar-layout-sidebar .well .card,
.sidebar-layout-sidebar .well .criterion-card {
  background: transparent !important;
  border: none !important;
  box-shadow: none !important;
  margin-bottom: 0;
}

/* ============================================================================================
   Checkbox and input-label backgrounds
   Bootstrap 4 + the bootswatch theme give .checkbox label and .control-label their own
   background, which shows as a tinted box behind every tick box and every input label - most
   visible in the light theme, where it reads as a stray grey slab. The controls should sit on
   whatever surface they are placed on.
   ============================================================================================ */
/* The white slab was on the WRAPPER, not the checkbox. Shiny renders a checkbox as
   div.form-group.shiny-input-container > div.checkbox > label > input, and the theme paints the
   form-group - so clearing .checkbox alone left a white rectangle the width of the panel behind
   every row, which is what showed against the tinted settings panels.

   Safe to clear on the container generally: the visible surface of a text or select input is the
   .form-control / .selectize-input inside it, which keeps its own background. */
.form-group,
.shiny-input-container,
/* div., not bare: `.checkbox` and `.radio` are Bootstrap WRAPPER classes, but three exclusion
   tables put class="checkbox" on the <input> itself, and this block's `background-color:
   transparent !important` plus `box-shadow: none !important` then stripped the branded control of
   both its white ground and its keyline. A 17px transparent square on a white table cell is
   invisible, so the Calibration Review, Bias and Precision and Stability exclusion tables all
   looked as though their tick boxes had stopped being drawn. */
div.checkbox,
div.checkbox label,
div.radio,
div.radio label,
.shiny-input-checkboxgroup label,
.shiny-input-radiogroup label,
.form-group > label,
.control-label,
.shiny-input-container > label,
.selectize-control .selectize-input > .item,
.shiny-input-container .checkbox-inline,
.shiny-input-container .radio-inline {
  background: transparent !important;
  background-color: transparent !important;
  box-shadow: none !important;
}
/* The label element itself, not just its text, in both themes. */
body.light-theme .control-label,
body.light-theme .checkbox label,
body.light-theme .form-group > label { color: #212529; }
body.dark-theme .control-label,
body.dark-theme .checkbox label,
body.dark-theme .form-group > label { color: #ececec; }

/* Verdicts: one pair of colours, everywhere, in both themes. Applied by a DT column render
   (see dt_verdict_coldefs) rather than per call site, which is how three tables ended up
   coloured and six did not. */
.verdict-pass { color: #1a7f37; font-weight: 700; }
.verdict-fail { color: #b3261e; font-weight: 700; }
/* A criterion that could not be judged is neither a pass nor a failure, and must not be coloured
   as one - it used to render in the failure colour because "Not evaluated" begins with "No". */
.verdict-none { color: #6b7280; font-weight: 600; font-style: italic; }
.verdict-review { color: #b45309; font-weight: 700; }
body.dark-theme .verdict-pass { color: #4ade80; }
body.dark-theme .verdict-fail { color: #ff6b6b; }
body.dark-theme .verdict-none { color: #9ca3af; }
body.dark-theme .verdict-review { color: #fbbf24; }

/* ============================================================================================
   Selected table rows
   ============================================================================================ */
/* The class is `active`, not `selected`, and that is why the earlier version of this block did
   nothing at all. DT takes the name from the table style it was handed - datatables.js has
   `var selClass = inArray(data.style, ['bootstrap', 'bootstrap4']) ? 'active' : 'selected';`
   - and this is a bslib Bootstrap 4 page, so a selected row is marked <tr class="odd active">
   and every `tr.selected` selector here matched zero elements. Measured in Chromium: clicking a
   row of the File Importer table sets input$Compiled_data_rows_selected while
   $('tr.selected').length stays 0 and $('tr.active').length is 1.

   It also explains why only the light theme looked broken. The one rule that paints an `active`
   row is DT's own dt-core-bootstrap4 sheet - `.table.dataTable tbody tr.active td
   { background-color: #007bff }` - and it carries no !important, so the blanket
   `body.light-theme table.dataTable td { background-color: #ffffff !important }` above beats it
   and the selected cell measures rgb(255,255,255), identical to its neighbours. Dark mode has no
   such blanket rule, DT's blue survives, and the row reads as selected - which is why selection
   "worked in dark mode". Nothing about the alphas below was ever the problem.

   Both names are kept: `selected` is what DT emits again the moment the page becomes Bootstrap 5.

   DataTables paints selection with `box-shadow: inset 0 0 0 9999px <colour>` on the row's cells
   rather than with a background-color - a shadow large enough to flood the cell. So the flood is
   what has to be replaced, with the same mechanism. */
table.dataTable tbody tr.selected > *,
table.dataTable tbody tr.selected td,
table.dataTable tbody tr.active > *,
table.dataTable tbody tr.active td,
table.dataTable tbody tr > .selected,
table.dataTable tbody tr > .active {
  box-shadow: inset 0 0 0 9999px rgba(111, 45, 189, 0.30) !important;
  background-color: transparent !important;   /* kills DT's #007bff on the dark theme */
  color: #1f1235 !important;
}
body.dark-theme table.dataTable tbody tr.selected > *,
body.dark-theme table.dataTable tbody tr.selected td,
body.dark-theme table.dataTable tbody tr.active > *,
body.dark-theme table.dataTable tbody tr.active td,
body.dark-theme table.dataTable tbody tr > .selected,
body.dark-theme table.dataTable tbody tr > .active {
  box-shadow: inset 0 0 0 9999px rgba(168, 85, 247, 0.28) !important;
  color: #ffffff !important;
}
/* The light theme needs its own, and needs it stated at THIS specificity. A 0.30-alpha violet
   over white lands at about #d4beeb, which on a table that already alternates white and #f2f2f2
   rows looks like one more stripe; and `body.light-theme table.dataTable td:not([style*="color"])`
   above is (0,3,4) and would beat the (0,2,4) colour in the base rule. A stronger flood, and a
   text colour and weight specific enough to actually win. */
body.light-theme table.dataTable tbody tr.selected > *,
body.light-theme table.dataTable tbody tr.selected td,
body.light-theme table.dataTable tbody tr.active > *,
body.light-theme table.dataTable tbody tr.active td,
body.light-theme table.dataTable tbody tr > .selected,
body.light-theme table.dataTable tbody tr > .active {
  box-shadow: inset 0 0 0 9999px rgba(111, 45, 189, 0.42) !important;
  color: #2a1147 !important;
  font-weight: 600 !important;
}
/* A bar marking the START of the row, over the flood. Two shadows on one declaration because the
   flood has to stay - splitting them into two rules would mean the second replaced the first. */
table.dataTable tbody tr.selected td:first-child,
table.dataTable tbody tr.active td:first-child {
  box-shadow: inset 0 0 0 9999px rgba(111, 45, 189, 0.30), inset 4px 0 0 #6f2dbd !important;
}
body.light-theme table.dataTable tbody tr.selected td:first-child,
body.light-theme table.dataTable tbody tr.active td:first-child {
  box-shadow: inset 0 0 0 9999px rgba(111, 45, 189, 0.42), inset 4px 0 0 #4a1d80 !important;
}
body.dark-theme table.dataTable tbody tr.selected td:first-child,
body.dark-theme table.dataTable tbody tr.active td:first-child {
  box-shadow: inset 0 0 0 9999px rgba(168, 85, 247, 0.28), inset 4px 0 0 #a855f7 !important;
}
/* Hover, so it is obvious the rows are clickable at all. On the CELLS, not the row, and for the
   same reason the selection flood is: in the light theme
   `body.light-theme table.dataTable td { background-color: #ffffff !important }` above paints
   every cell opaque white over any row-level colour, so a `tr` background here was invisible and
   the only hover cue a light-theme user ever got was DataTables' own faint grey.

   Deliberately WITHOUT !important, so that hovering a selected row leaves the selection flood
   showing. Adding !important here would make a hovered selected row look unselected, which is
   the state this whole block exists to make visible. */
table.dataTable.hover tbody tr:hover > td,
table.dataTable tbody tr:hover > td {
  box-shadow: inset 0 0 0 9999px rgba(111, 45, 189, 0.07);
}
body.dark-theme table.dataTable tbody tr:hover > td {
  box-shadow: inset 0 0 0 9999px rgba(168, 85, 247, 0.12);
}

/* ============================================================================================
   Modal headings and branding
   Every dialog the app opens is a place where a decision gets made, so the title should read as
   a title, and it should be obvious which application is asking. Warnings and short messages get
   the same treatment - there is no separate class for them and adding one would mean tagging
   every showModal call site.
   ============================================================================================ */
.modal-header {
  align-items: center;
  border-bottom: 1px solid rgba(128, 128, 128, 0.25);
}
.modal-title {
  font-weight: 700;
  font-size: 1.12rem;
  letter-spacing: 0.1px;
}
/* The logo sits at the top right of the dialog, opposite the title. Added by JS on modal open
   (see ui.R) because modalDialog builds its own header and offers no hook for extra content. */
.modal-header .modal-brand {
  margin-left: auto;
  height: 26px;
  width: auto;
  opacity: 0.9;
  flex: 0 0 auto;
}
@media (max-width: 560px) { .modal-header .modal-brand { height: 20px; } }

/* ============================================================================================
   Lighter dark mode
   The dark ground is #262624, which is close to black on a bright monitor and was reported as
   too dark to work against for long. Rather than move it for everyone, the gear panel's
   developer tools carry a switch that adds .dark-lighter to <body>; these are the same surfaces
   the dark theme defines above, two steps up the grey scale, and nothing else about the theme
   changes. Selectors are one class more specific than the rules they override, so no !important.
   ============================================================================================ */
body.dark-theme.dark-lighter,
body.dark-theme.dark-lighter .container-fluid,
body.dark-theme.dark-lighter .tab-content {
  background-color: #35342f;
}
body.dark-theme.dark-lighter .well,
body.dark-theme.dark-lighter .panel,
body.dark-theme.dark-lighter .panel-body,
body.dark-theme.dark-lighter .card,
body.dark-theme.dark-lighter .modal-content {
  background-color: #403f39;
  border-color: #4c4b44;
}
body.dark-theme.dark-lighter #app-loading { background: #35342f; }
/* Tables and inputs move with the ground they sit on, or they turn into dark holes in a lighter
   page. The navbar is deliberately NOT included: it is the brand purple in both, not a grey. */
body.dark-theme.dark-lighter table,
body.dark-theme.dark-lighter table.dataTable,
body.dark-theme.dark-lighter .table {
  background-color: #3a3934;
}
body.dark-theme.dark-lighter .table-striped > tbody > tr:nth-of-type(odd),
body.dark-theme.dark-lighter table.dataTable.stripe tbody tr.odd {
  background-color: #403f39;
}
body.dark-theme.dark-lighter .form-control,
body.dark-theme.dark-lighter select,
body.dark-theme.dark-lighter textarea {
  background-color: #2c2b27;
  border-color: #56554d;
}

/* ============================================================================================
   Branded checkboxes, everywhere
   The ValidateLabs mark reads as a tick, so it is the tick. This started life scoped to
   .brand-checks on the File Importer as one tab's experiment; it is now the app's checkbox.
   appearance:none replaces the browser's native box entirely, which is the only way to control
   what a checked box draws - ::before/::after do not apply to input elements in every engine.

   The mark is a coloured logo and needs a light ground to read against, so the box stays white
   in both themes. What does change per theme is the EMPTY box: white on a white card is
   invisible, so the light theme gets a violet keyline, while on the dark ground the white box is
   its own outline.
   ============================================================================================ */
input[type="checkbox"] {
  appearance: none;
  -webkit-appearance: none;
  width: 17px;
  height: 17px;
  border: none;                 /* the mark is the affordance; a box around it is noise */
  border-radius: 4px;
  background-color: #ffffff;
  box-shadow: inset 0 0 0 1px rgba(111, 45, 189, 0.45);
  vertical-align: middle;
  position: relative;
  top: -1px;
  cursor: pointer;
  transition: box-shadow .12s ease;
}
body.dark-theme input[type="checkbox"] { box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.25); }
input[type="checkbox"]:hover { box-shadow: 0 0 0 2px rgba(111, 45, 189, .28); }
body.dark-theme input[type="checkbox"]:hover { box-shadow: 0 0 0 2px rgba(168, 85, 247, .40); }
input[type="checkbox"]:focus-visible { outline: 2px solid #ff62f1; outline-offset: 1px; }
input[type="checkbox"]:checked {
  background-image: url("favicon-32.png");
  background-size: 15px 15px;
  background-repeat: no-repeat;
  background-position: center;
  box-shadow: none;
}
/* Shiny puts the label text after the input inside <label>; give the mark room, and centre the
   text against it for the same reason. */
.checkbox label,
.shiny-input-container > label + input[type="checkbox"] { padding-left: 5px; }
.checkbox label > span,
.checkbox label { vertical-align: middle; }

/* ============================================================================================
   Full-width file input
   Shiny lays a fileInput out as an input-group: a "Browse..." button and a read-only field for
   the chosen filenames, side by side. In a sidebar of full-width block buttons that button is a
   third of the width and reads as something lesser than the ones under it.

   Stacked instead: the button takes the row, the filename field sits beneath it. The field is
   kept rather than hidden - it is the only confirmation the upload names the right files, and on
   a multi-file import it is the only place the count appears. Applied by wrapper class, not to
   every fileInput: the session loader and the verification uploader sit in dialogs where the
   side-by-side layout is right.
   ============================================================================================ */
.file-input-block .shiny-label-null { display: none; }
.file-input-block .form-group { margin-bottom: .5rem; }
.file-input-block .input-group { display: block; width: 100%; }
.file-input-block .input-group > .input-group-btn,
.file-input-block .input-group > .input-group-prepend {
  display: block;
  width: 100%;
  /* Bootstrap gives the prepend a -1px margin so it can overlap the field it sits beside; stacked,
     that is 5px of the row the button does not fill. */
  margin: 0;
  padding: 0;
}
/* Four classes, to sit above `.sidebar-layout-sidebar .input-group .btn` - measured, not
   assumed: at three it lost and the button rendered 164px inside a 227px panel. */
.file-input-block .btn-file,
.sidebar-layout-sidebar .file-input-block .input-group .btn-file {
  display: block;
  width: 100%;
  /* btn-sm metrics, to sit level with the block buttons above and below it. */
  padding: .25rem .5rem;
  font-size: .875rem;
  line-height: 1.5;
  border-radius: .2rem;
  white-space: normal;
}
.file-input-block .input-group > .form-control {
  display: block;
  width: 100%;
  margin-top: 4px;
  border-radius: .2rem;
  font-size: .8rem;
}

/* ============================================================================================
   Branded radio buttons, everywhere
   The checkbox above became the ValidateLabs mark; the radio could not follow it, because a
   radio is not a tick - it is one of a set, and "the mark on the chosen one" reads as a list of
   ticks. So it keeps the shape a radio has always had, a filled dot in a ring, and takes the
   app's colour instead.

   Same construction as the checkbox for the same reason: appearance:none replaces the native
   control entirely, which is the only way to control what a checked one draws - ::before and
   ::after do not apply to input elements in every engine, so the dot is drawn as a
   radial-gradient background rather than as a child.

   This was the last native control left in the app. The Concentration pair on the File Importer
   made it obvious: two violet-keylined checkboxes directly above two grey system circles.
   ============================================================================================ */
input[type="radio"] {
  appearance: none;
  -webkit-appearance: none;
  width: 16px;
  height: 16px;
  border: none;
  border-radius: 50%;
  background-color: #ffffff;
  box-shadow: inset 0 0 0 1px rgba(111, 45, 189, 0.45);
  vertical-align: middle;
  position: relative;
  top: -1px;
  cursor: pointer;
  transition: box-shadow .12s ease;
}
body.dark-theme input[type="radio"] { box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.25); }
input[type="radio"]:hover { box-shadow: inset 0 0 0 1px rgba(111, 45, 189, 0.45), 0 0 0 2px rgba(111, 45, 189, .28); }
body.dark-theme input[type="radio"]:hover { box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.25), 0 0 0 2px rgba(168, 85, 247, .40); }
input[type="radio"]:focus-visible { outline: 2px solid #ff62f1; outline-offset: 1px; }
/* The dot: a hard-stopped radial gradient rather than a child element, and a ring kept around it
   so the control still reads as a radio when selected rather than as a plain filled disc. */
input[type="radio"]:checked {
  background-image: radial-gradient(circle at center, #6f2dbd 0 4.5px, transparent 4.5px);
  box-shadow: inset 0 0 0 1px rgba(111, 45, 189, 0.75);
}
body.dark-theme input[type="radio"]:checked {
  background-image: radial-gradient(circle at center, #a855f7 0 4.5px, transparent 4.5px);
  box-shadow: inset 0 0 0 1px rgba(168, 85, 247, 0.75);
}
/* Shiny puts the label text after the input inside <label>, exactly as it does for a checkbox. */
.radio label,
.radio-inline { vertical-align: middle; }
.radio label > span,
.radio label { vertical-align: middle; }
.radio label input[type="radio"],
.radio-inline input[type="radio"] { margin-right: 5px; }

/* ============================================================================================
   Build stamp
   The version and commit the running copy came from, at the foot of the settings panel. Quiet
   by design - it is reference information for a support conversation, not something to read.
   ============================================================================================ */
.app-version-stamp {
  margin-top: 1rem;
  padding-top: .6rem;
  border-top: 1px solid rgba(128, 128, 128, 0.25);
  font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
  font-size: .68rem;
  letter-spacing: .02em;
  opacity: .62;
  user-select: all;   /* so it can be copied in one gesture when someone asks for it */
}

/* ============================================================================================
   Sample Submission Portal
   Everything below is specific to this app. Colours follow SHINY_APP_STYLE.md section 2: violet
   #6f2dbd for things you act on in light theme, #a855f7 (with #1a0b2e text) in dark.
   ============================================================================================ */

/* Sign-in: which kind of visit this is, as a segmented control. */
.login-role { margin: 0 0 14px; }
.login-role .form-group, .login-role .shiny-input-container { margin: 0 !important; width: 100%; }
.login-role .shiny-options-group { display: flex; gap: 6px; flex-wrap: wrap; }
.login-role .radio-inline { flex: 1 1 0; margin: 0 !important; padding: 0 !important; cursor: pointer; }
.login-role .radio-inline input[type="radio"] { position: absolute; opacity: 0; width: 0; height: 0; margin: 0; }
.login-role .radio-inline > span {
  display: block; text-align: center; white-space: nowrap; padding: 7px 8px; font-size: 0.82rem;
  border-radius: 8px; border: 1px solid rgba(128, 128, 128, 0.35);
  transition: background-color .12s ease, border-color .12s ease, color .12s ease;
}
.login-role .radio-inline:hover > span { border-color: #6f2dbd; }
.login-role .radio-inline input[type="radio"]:checked + span {
  background-color: #6f2dbd; border-color: #6f2dbd; color: #ffffff; font-weight: 600;
}
.login-role .radio-inline input[type="radio"]:focus-visible + span { outline: 2px solid #ff62f1; outline-offset: 2px; }
body.dark-theme .login-role .radio-inline:hover > span { border-color: #a855f7; }
body.dark-theme .login-role .radio-inline input[type="radio"]:checked + span {
  background-color: #a855f7; border-color: #a855f7; color: #1a0b2e;
}
.login-modal .shiny-input-container { width: 100%; }

/* Sign-out link in the settings header. */
.settings-signout { font-size: 0.8rem; font-weight: 600; color: #6f2dbd !important; }
body.dark-theme .settings-signout { color: #c084fc !important; }

/* Status, as a pill. Violet family only: these are stages, not verdicts. */
.status-chip {
  display: inline-block; padding: 2px 10px; border-radius: 999px; font-size: 0.78em; font-weight: 700;
  white-space: nowrap; vertical-align: middle;
}
body.light-theme .status-chip.status-submitted { background: rgba(111, 45, 189, 0.08); color: #6f2dbd; border: 1px solid rgba(111, 45, 189, 0.35); }
body.light-theme .status-chip.status-assigned { background: rgba(111, 45, 189, 0.16); color: #4a1d80; border: 1px solid rgba(111, 45, 189, 0.45); }
body.light-theme .status-chip.status-data-available { background: #6f2dbd; color: #ffffff; border: 1px solid #6f2dbd; }
body.dark-theme .status-chip.status-submitted { background: rgba(168, 85, 247, 0.10); color: #c084fc; border: 1px solid rgba(168, 85, 247, 0.40); }
body.dark-theme .status-chip.status-assigned { background: rgba(168, 85, 247, 0.22); color: #e9d5ff; border: 1px solid rgba(168, 85, 247, 0.55); }
body.dark-theme .status-chip.status-data-available { background: #a855f7; color: #1a0b2e; border: 1px solid #a855f7; }

.project-id-line { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 4px; font-variant-numeric: tabular-nums; }
.project-title-line { font-weight: 600; margin: 0 0 8px; line-height: 1.35; }

/* Three-step progress: done, current, still to come. */
.progress-steps { display: flex; gap: 12px; flex-wrap: wrap; margin: 0 0 18px; }
.progress-step {
  flex: 1 1 165px; padding: 12px 14px; border-radius: 10px; position: relative; padding-left: 58px; min-height: 64px;
}
.progress-step-badge {
  position: absolute; left: 14px; top: 14px; width: 32px; height: 32px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center; font-weight: 700; font-size: 0.9em;
}
.progress-step-title { font-weight: 700; }
.progress-step-desc { font-size: 0.85em; opacity: 0.75; }
body.light-theme .progress-step { background: #ffffff; border: 1px solid rgba(111, 45, 189, 0.22); }
body.light-theme .progress-step.current { background: #f8f6fc; border-color: #6f2dbd; }
body.light-theme .progress-step.done .progress-step-badge,
body.light-theme .progress-step.current .progress-step-badge { background: #6f2dbd; color: #ffffff; }
body.light-theme .progress-step.todo .progress-step-badge { background: rgba(111, 45, 189, 0.08); color: #6f2dbd; }
body.light-theme .progress-step.todo { opacity: 0.7; }
body.dark-theme .progress-step { background: #30302e; border: 1px solid #3d3c39; }
body.dark-theme .progress-step.current { background: rgba(168, 85, 247, 0.10); border-color: #a855f7; }
body.dark-theme .progress-step.done .progress-step-badge,
body.dark-theme .progress-step.current .progress-step-badge { background: #a855f7; color: #1a0b2e; }
body.dark-theme .progress-step.todo .progress-step-badge { background: rgba(168, 85, 247, 0.14); color: #c084fc; }
body.dark-theme .progress-step.todo { opacity: 0.7; }
body.dark-theme.dark-lighter .progress-step { background: #403f39; border-color: #4c4b44; }

/* The submission, read back as questions and answers. */
.summary-table { margin: 0; }
.summary-table th { width: 34%; font-weight: 600; opacity: 0.8; vertical-align: top; border-top: 0; }
.summary-table td { vertical-align: top; border-top: 0; overflow-wrap: anywhere; }
.summary-table tr + tr th, .summary-table tr + tr td { border-top: 1px solid rgba(128, 128, 128, 0.22); }
body.dark-theme .summary-table tr + tr th, body.dark-theme .summary-table tr + tr td { border-top-color: rgba(255, 255, 255, 0.12); }
body.light-theme .summary-table, body.light-theme .summary-table th, body.light-theme .summary-table td { background: transparent !important; }
.summary-detail { opacity: 0.72; }
@media (max-width: 560px) {
  .summary-table th, .summary-table td { display: block; width: 100%; }
  .summary-table td { padding-top: 0; }
  .summary-table tr + tr td { border-top: 0; }
}

/* Project ID and access code, shown once after submitting and after reissuing a code. */
.access-details { display: flex; flex-direction: column; gap: 8px; margin: 0 0 18px; }
.access-details > div {
  display: flex; justify-content: space-between; align-items: center; gap: 12px;
  padding: 8px 12px; border-radius: 8px;
}
.access-label { font-size: 0.8em; font-weight: 700; text-transform: uppercase; letter-spacing: .06em; opacity: .65; }
.access-value {
  font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace; font-weight: 700; font-size: 1.05em;
  user-select: all; font-variant-numeric: tabular-nums;
}
body.light-theme .access-details > div { background: #f8f6fc; border: 1px solid #d0c4f0; }
body.light-theme .access-value { color: #16002f; }
body.dark-theme .access-details > div { background: rgba(168, 85, 247, 0.10); border: 1px solid rgba(168, 85, 247, 0.35); }
body.dark-theme .access-value { color: #e9d5ff; }

/* Counts above the projects table. */
.count-chips { display: flex; gap: 8px; flex-wrap: wrap; margin: 0 0 14px; }
.count-chip {
  padding: 4px 12px; border-radius: 999px; font-size: 0.85em; font-variant-numeric: tabular-nums;
}
body.light-theme .count-chip { background: rgba(111, 45, 189, 0.06); border: 1px solid rgba(111, 45, 189, 0.22); }
body.dark-theme .count-chip { background: rgba(168, 85, 247, 0.10); border: 1px solid rgba(168, 85, 247, 0.30); }

.empty-note { margin: 0; opacity: 0.7; font-style: italic; }
.files-actions { margin-top: 12px; }
.sidebar-layout-main > .card { margin-bottom: 18px; }
.sidebar-layout-main .accent-heading { margin-top: 0; }

/* Delete: outlined in the failure colour, because it cannot be undone. */
.btn-danger-ghost {
  background: transparent !important; border: 1px solid rgba(179, 38, 30, 0.55) !important;
  color: #b3261e !important; font-weight: 600;
}
.btn-danger-ghost:hover { background: rgba(179, 38, 30, 0.08) !important; }
body.dark-theme .btn-danger-ghost { border-color: rgba(255, 107, 107, 0.6) !important; color: #ff6b6b !important; }
body.dark-theme .btn-danger-ghost:hover { background: rgba(255, 107, 107, 0.12) !important; }

/* Radio groups inside the form tiles wrap their long questions rather than overflow. */
.criteria-tile .shiny-input-radiogroup > .control-label { white-space: normal; }
.criteria-tile textarea { resize: vertical; }

/* Bootstrap's `.btn-default:not(.btn-primary):not(...)` selector carries eight classes of
   specificity, so the light-theme FAB and default-button rules above never won (it is the
   "ordinary buttons are dark grey in light theme" inconsistency in SHINY_APP_STYLE.md 12).
   Settled here deliberately: the FABs are white as specified, and the remaining plain buttons
   (dialog Cancel/Close) take the ghost style. */
body.light-theme .settings-fab,
body.light-theme .help-fab {
  background-color: #ffffff !important; color: #212529 !important; border: 1px solid #ced4da !important;
}
body.light-theme .modal-footer .btn-default {
  background: rgba(111, 45, 189, 0.07) !important; color: #6f2dbd !important;
  border: 1px solid rgba(111, 45, 189, 0.45) !important; font-weight: 600;
}
body.light-theme .modal-footer .btn-default:hover { background: rgba(111, 45, 189, 0.16) !important; }

/* Notifications: Darkly's are dark brown in both themes. A light card with a coloured edge in
   light theme; the type colour is the semantic one from section 2.4. */
body.light-theme .shiny-notification {
  background: #ffffff; color: #212529; border: 1px solid #ced4da; border-left: 4px solid #6f2dbd;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12); opacity: 1;
}
body.light-theme .shiny-notification-warning { border-left-color: #b45309; }
body.light-theme .shiny-notification-error { border-left-color: #b3261e; }
body.light-theme .shiny-notification-message { border-left-color: #1a7f37; }
body.light-theme .shiny-notification-close { color: #212529; }
body.dark-theme .shiny-notification { border-left: 4px solid #a855f7; }
body.dark-theme .shiny-notification-warning { border-left-color: #fbbf24; }
body.dark-theme .shiny-notification-error { border-left-color: #ff6b6b; }
body.dark-theme .shiny-notification-message { border-left-color: #4ade80; }

/* The file-picker button is a .btn-default too, so the same specificity fix: ghost violet. */
body.light-theme .file-input-block .btn,
body.light-theme .input-group-btn .btn-file {
  background: rgba(111, 45, 189, 0.07) !important; color: #6f2dbd !important;
  border: 1px solid rgba(111, 45, 189, 0.45) !important; font-weight: 600;
}
body.light-theme .file-input-block .btn:hover { background: rgba(111, 45, 189, 0.16) !important; }

/* Stacked layout: there is nothing to drag, so no resizer bar beside the sidebar. */
@media (max-width: 900px) { .sidebar-resizer { display: none !important; } }

table.dataTable td.dt-nowrap, table.dataTable th.dt-nowrap { white-space: nowrap; }

/* Fields inside a centred card-style prompt still read left to right. */
.login-modal-fields { text-align: left; margin-bottom: 4px; }
.login-modal-fields .shiny-input-container { width: 100%; }

/* ============================================================================================
   Round 2: partner logo, admin colours, completion, tile spacing
   ============================================================================================ */

/* ValidateLabs at the right-hand end of the navbar, in the ink for the theme. */
.navbar-partner { margin-left: auto; display: flex; align-items: center; padding-left: 12px; order: 3; line-height: 0; }
.navbar-partner:hover .partner-logo { opacity: 0.85; }
.partner-logo { height: 36px; width: auto; display: block; }
body.dark-theme .partner-logo-light,
body:not(.dark-theme) .partner-logo-dark { display: none; }
@media (max-width: 767.98px) {
  /* Collapsed navbar: brand, partner logo, hamburger on one line; the menu drops below. */
  .navbar > .container-fluid { flex-wrap: wrap; }
  .navbar-partner { order: 1; padding-left: 8px; }
  .navbar > .container-fluid > .navbar-toggler,
  .navbar > .container-fluid > .navbar-header .navbar-toggle { order: 2; }
  .navbar > .container-fluid > .navbar-collapse { order: 3; flex-basis: 100%; }
  .partner-logo { height: 22px; }
}
@media (max-width: 900px) { #app-logo { height: 34px; } }

/* An admin's name, marked with the colour they chose. */
.admin-chip {
  display: inline-flex; align-items: center; gap: 6px; padding: 1px 9px 1px 6px; border-radius: 999px;
  white-space: nowrap; font-weight: 600; font-size: 0.92em; vertical-align: middle;
  border: 1px solid color-mix(in srgb, var(--admin-colour) 55%, transparent);
  background: color-mix(in srgb, var(--admin-colour) 12%, transparent);
}
.admin-dot { width: 9px; height: 9px; border-radius: 50%; background: var(--admin-colour); flex: 0 0 auto; }
.admin-user { font-weight: 400; opacity: 0.65; font-size: 0.88em; }
.admin-chip-none { border: 1px dashed rgba(128, 128, 128, 0.5); background: transparent; font-weight: 400; opacity: 0.7; padding-left: 9px; }

/* Colour picker in Settings: the options are swatches. */
.colour-picker { margin-top: 12px; }
.colour-picker .shiny-options-group { display: flex; flex-wrap: wrap; gap: 6px; }
.colour-picker .radio-inline { margin: 0 !important; padding: 0 !important; cursor: pointer; }
.colour-picker .radio-inline input[type="radio"] { position: absolute; opacity: 0; width: 0; height: 0; margin: 0; }
.colour-swatch {
  display: block; width: 24px; height: 24px; border-radius: 50%; background: var(--swatch);
  border: 2px solid transparent; box-shadow: inset 0 0 0 2px rgba(255, 255, 255, 0.9);
  transition: transform .12s ease;
}
.colour-picker .radio-inline:hover .colour-swatch { transform: scale(1.1); }
.colour-picker input[type="radio"]:checked + span .colour-swatch { border-color: #212529; }
body.dark-theme .colour-picker input[type="radio"]:checked + span .colour-swatch { border-color: #ffffff; }
.colour-picker input[type="radio"]:focus-visible + span .colour-swatch { outline: 2px solid #ff62f1; outline-offset: 2px; }
.sr-only { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }

/* Completed: the tick column and status. Green here means "finished", as in ManualPipetting. */
.projects-table td.tick-col { color: #1a7f37; font-weight: 700; font-size: 1.05em; }
body.dark-theme .projects-table td.tick-col { color: #4ade80; }
.projects-table tbody tr { cursor: pointer; }
.status-chip .fa, .status-chip .fas, .status-chip svg { margin-right: 4px; font-size: 0.9em; }
body.light-theme .status-chip.status-completed { background: rgba(26, 127, 55, 0.10); color: #1a7f37; border: 1px solid rgba(26, 127, 55, 0.45); }
body.dark-theme .status-chip.status-completed { background: rgba(74, 222, 128, 0.12); color: #4ade80; border: 1px solid rgba(74, 222, 128, 0.45); }
.btn-complete {
  background: transparent !important; border: 1px solid rgba(26, 127, 55, 0.55) !important;
  color: #1a7f37 !important; font-weight: 600; margin-top: 8px;
}
.btn-complete:hover { background: rgba(26, 127, 55, 0.08) !important; }
body.dark-theme .btn-complete { border-color: rgba(74, 222, 128, 0.55) !important; color: #4ade80 !important; }
body.dark-theme .btn-complete:hover { background: rgba(74, 222, 128, 0.12) !important; }
.progress-step { flex-basis: 150px; }

/* Submission tiles: the callout follows its inputs, and any spare height falls below it, so the
   tiles in a row stay the same height without a gap opening up inside the shorter ones. */
.criteria-grid .criterion-help { margin-top: 12px; }
.criteria-tile .form-group,
.criteria-tile .shiny-input-container { margin-bottom: 10px; }
.criteria-tile .shiny-input-radiogroup { margin-bottom: 4px; }
.criteria-tile .shiny-input-radiogroup .shiny-options-group { margin-top: 2px; }
.criteria-tile .shiny-input-radiogroup + .shiny-panel-conditional .form-group { margin-top: 2px; }
.criteria-tile .control-label { margin-bottom: 4px; }
.criteria-tile #dl_template { margin-top: 2px; }
.criteria-tile .file-input-block .form-group { margin-bottom: 6px; }

/* Checkboxes: a plain violet box with a white tick, the same accent as the radio dot, instead
   of the brand-mark tick inherited from ValidateLabs. */
input[type="checkbox"]:checked {
  background-color: #6f2dbd !important;
  background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23ffffff' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round' d='M3.5 8.5l3 3 6-7'/%3e%3c/svg%3e") !important;
  background-size: 13px 13px !important;
  box-shadow: none;
}
body.dark-theme input[type="checkbox"]:checked {
  background-color: #a855f7 !important;
  background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%231a0b2e' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round' d='M3.5 8.5l3 3 6-7'/%3e%3c/svg%3e") !important;
}

/* Swatches are not segmented pills: undo the settings-panel pill styling for this group. */
.settings-panel .colour-picker .shiny-options-group .radio-inline { flex: 0 0 auto; }
.settings-panel .colour-picker .shiny-options-group .radio-inline > span,
.settings-panel .colour-picker .shiny-options-group .radio-inline input[type="radio"]:checked + span {
  padding: 0 !important; border: 0 !important; background: transparent !important; display: inline-block;
}
.settings-panel .colour-picker .shiny-options-group .radio-inline input[type="radio"]:checked + span .colour-swatch {
  border-color: #212529; transform: scale(1.12);
}
body.dark-theme .settings-panel .colour-picker .shiny-options-group .radio-inline input[type="radio"]:checked + span .colour-swatch {
  border-color: #ffffff;
}

@media (max-width: 560px) {
  #app-logo { height: 28px; margin-right: 4px !important; }
  .partner-logo { height: 17px; }
}
.cell-sub { font-size: 0.85em; opacity: 0.65; }
.projects-table td:nth-child(3) { min-width: 180px; }
@media (max-width: 767.98px) {
  .navbar > .container-fluid { position: relative; }
  .navbar-partner { position: absolute; top: 14px; right: 62px; padding: 0; height: 30px; }
}
/* The collapsed-menu button was white on the light navbar. */
body.light-theme .navbar .navbar-toggle,
body.light-theme .navbar .navbar-toggler { border-color: #ced4da !important; background-color: transparent !important; }
body.light-theme .navbar .navbar-toggle .icon-bar { display: none !important; }
body.light-theme .navbar .navbar-toggle {
  width: 44px; height: 38px; background-color: transparent !important;
  background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='30' height='30' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%2833,37,41,0.85%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e") !important;
  background-repeat: no-repeat !important; background-position: center !important; background-size: 24px 24px !important;
}
body.light-theme .navbar .navbar-toggler-icon {
  background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='30' height='30' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%2833,37,41,0.85%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e") !important;
}

/* Projects table: each row tinted by where the project is. Red = nobody has taken it on, amber =
   taken on but not finished, green = completed. Pale enough that the text keeps its contrast;
   set on the cells, because the striping and the light-theme table rules paint the cells. */
body.light-theme table.dataTable.projects-table tbody tr.row-unassigned > td { background-color: rgba(179, 38, 30, 0.09) !important; }
body.light-theme table.dataTable.projects-table tbody tr.row-open > td       { background-color: rgba(230, 159, 0, 0.14) !important; }
body.light-theme table.dataTable.projects-table tbody tr.row-done > td       { background-color: rgba(26, 127, 55, 0.10) !important; }
body.dark-theme table.dataTable.projects-table tbody tr.row-unassigned > td  { background-color: rgba(255, 107, 107, 0.13) !important; }
body.dark-theme table.dataTable.projects-table tbody tr.row-open > td        { background-color: rgba(251, 191, 36, 0.12) !important; }
body.dark-theme table.dataTable.projects-table tbody tr.row-done > td        { background-color: rgba(74, 222, 128, 0.11) !important; }
/* The zebra striping and the row hover are both drawn as an inset shadow on the cells, which
   greyed every other tinted row. The tint carries the rows now, so the stripe goes; hover
   deepens the tint instead. */
table.dataTable.projects-table tbody tr[class*="row-"] > td { box-shadow: none !important; }
/* The stripe is also painted on the row itself, under the translucent tint; give every tinted row
   the same solid ground so alternate rows do not come out greyer. */
body.light-theme table.dataTable.projects-table tbody tr[class*="row-"] { background-color: #ffffff !important; }
body.dark-theme table.dataTable.projects-table tbody tr[class*="row-"] { background-color: #2b2a28 !important; }
body.dark-theme.dark-lighter table.dataTable.projects-table tbody tr[class*="row-"] { background-color: #3a3934 !important; }
body.light-theme table.dataTable.projects-table tbody tr.row-unassigned:hover > td { background-color: rgba(179, 38, 30, 0.17) !important; }
body.light-theme table.dataTable.projects-table tbody tr.row-open:hover > td       { background-color: rgba(230, 159, 0, 0.25) !important; }
body.light-theme table.dataTable.projects-table tbody tr.row-done:hover > td       { background-color: rgba(26, 127, 55, 0.18) !important; }
body.dark-theme table.dataTable.projects-table tbody tr.row-unassigned:hover > td  { background-color: rgba(255, 107, 107, 0.22) !important; }
body.dark-theme table.dataTable.projects-table tbody tr.row-open:hover > td        { background-color: rgba(251, 191, 36, 0.20) !important; }
body.dark-theme table.dataTable.projects-table tbody tr.row-done:hover > td        { background-color: rgba(74, 222, 128, 0.19) !important; }
/* A coloured bar at the start of each row says the same thing to anyone who can't tell the tints apart. */
table.dataTable.projects-table tbody tr.row-unassigned > td:first-child { box-shadow: inset 4px 0 0 #b3261e !important; }
table.dataTable.projects-table tbody tr.row-open > td:first-child       { box-shadow: inset 4px 0 0 #E69F00 !important; }
table.dataTable.projects-table tbody tr.row-done > td:first-child       { box-shadow: inset 4px 0 0 #1a7f37 !important; }
body.dark-theme table.dataTable.projects-table tbody tr.row-unassigned > td:first-child { box-shadow: inset 4px 0 0 #ff6b6b !important; }
body.dark-theme table.dataTable.projects-table tbody tr.row-open > td:first-child       { box-shadow: inset 4px 0 0 #fbbf24 !important; }
body.dark-theme table.dataTable.projects-table tbody tr.row-done > td:first-child       { box-shadow: inset 4px 0 0 #4ade80 !important; }

.row-key { display: flex; gap: 14px; flex-wrap: wrap; font-size: 0.85em; margin: -4px 0 10px; opacity: 0.85; }
.row-key span::before {
  content: ""; display: inline-block; width: 12px; height: 12px; border-radius: 3px;
  margin-right: 6px; vertical-align: -1px; border-left: 3px solid var(--key);
  background: color-mix(in srgb, var(--key) 22%, transparent);
}

.whoami { margin: 0 0 4px; }
.whoami .whoami-label { display: block; font-size: 0.9em; opacity: 0.7; margin-bottom: 4px; }

/* What the attached sample list says about concentrations. */
.conc-check { font-size: 0.85em; margin: -2px 0 6px; line-height: 1.4; opacity: 0.85; }
.conc-check .fa, .conc-check svg { margin-right: 5px; }
body.light-theme .conc-check-ok { color: #1a7f37; opacity: 1; }
body.light-theme .conc-check-warn { color: #b45309; opacity: 1; }
body.dark-theme .conc-check-ok { color: #4ade80; opacity: 1; }
body.dark-theme .conc-check-warn { color: #fbbf24; opacity: 1; }
