/*
 * LabGopher site styles. Loaded after Bootstrap 5.3, the DataTables 2 Bootstrap 5
 * integrations, noUiSlider and the gopher sites' shared page shell
 * (gopher_core/web/static/shell.css: the tokens --lg-max-width, --lg-focus-color and
 * --lg-menu-empty-color, the page frame, skip link, focus outline, navbar frame, menu entries
 * and footer), and built on Bootstrap's CSS variables so both colour modes
 * (data-bs-theme="light" | "dark" on <html>) work. No !important.
 */

/* ---- Tokens ------------------------------------------------------------------ */

:root {
  --lg-handle: 1.5rem;                        /* slider handle diameter: a 24px target, even when the two
                                                 handles of a range meet */

  /* Gopher grade cell fills: the original site's colours. The word always names the grade;
     the dark text on every fill has at least 5:1 contrast. */
  --lg-great-bg: #63d863;
  --lg-good-bg: #a0e7a0;
  --lg-fair-bg: #fcf4a3;
  --lg-bad-bg: #ff6961;
  --lg-grade-fg: #1a1d1a;

  --lg-visited-bg: rgba(111, 66, 193, .07);   /* rows whose eBay link was opened */
  --lg-visited-link: #6f42c1;
}

[data-bs-theme="dark"] {
  /* Slightly deeper fills so they don't glare on the dark page; text stays dark. */
  --lg-great-bg: #3fae5a;
  --lg-good-bg: #7cc98a;
  --lg-fair-bg: #d9cc6a;
  --lg-bad-bg: #e0605a;
  --lg-grade-fg: #0b0f0c;
  --lg-visited-bg: rgba(194, 159, 250, .08);
  --lg-visited-link: #c29ffa;
}

@media (pointer: coarse) {
  :root { --lg-handle: 1.75rem; }             /* finger-sized slider handles */
}

/* ---- Focus ---------------------------------------------------------------------- */

/* The shell's one solid 2px outline (shell.css) on LabGopher's own controls: the help links,
   model links, and buttons without a Bootstrap colour class. */
.lg-help:focus-visible { outline: 2px solid var(--lg-focus-color); outline-offset: 2px; }
.lg-model-link:focus-visible { outline: 2px solid var(--lg-focus-color); outline-offset: 2px; border-radius: 2px; }
.lg-btn-quiet:focus-visible,
.lg-expand:focus-visible,
.lg-preview-btn:focus-visible { outline: 2px solid var(--lg-focus-color); outline-offset: 2px; }

/* ---- Navbar --------------------------------------------------------------------- */

[data-bs-theme="dark"] .lg-logo { filter: invert(1); }   /* black-on-transparent artwork */

/* Brand headings in the theme's secondary text colour: Bootstrap's fixed #6c757d is 3.3:1 on the
   dark menu. */
.lg-brand-menu {
  --bs-dropdown-item-padding-x: .75rem;
  --bs-dropdown-header-padding-x: .75rem;
  --bs-dropdown-header-color: var(--bs-secondary-color);
  padding-block: .25rem;
}
.lg-all-link {
  margin-bottom: .25rem;
  padding-block: .375rem;
  font-weight: 600;
  border-bottom: var(--bs-border-width) solid var(--bs-border-color-translucent);
}
.lg-model-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(10.5rem, 1fr)); margin: 0 0 .25rem; }
.lg-model-grid .dropdown-item { padding-block: .1875rem; white-space: nowrap; }
/* Entries give this site's number of listings (.lg-menu-count, styled in shell.css); one with none
   is plain dimmed text, not a link (navbar.html): still listed, so buyers see every model tracked. */
.lg-size-menu { min-width: 9rem; }

@media (min-width: 992px) {
  /* Five columns of models with their counts, so all four brands fit on a laptop screen; on a
     short window the menu scrolls (its scrollbar shows there is more). Where it would run off the
     window's right edge (up to about 1240px), the shell's shell.js moves it left. */
  .lg-brand-menu { width: min(55rem, calc(100vw - 2rem)); }
}

/* ---- Page heading and welcome card --------------------------------------------- */

.lg-page-head {
  display: flex; flex-wrap: wrap; align-items: flex-end; justify-content: space-between;
  gap: .5rem 1rem;
  margin-bottom: .75rem;
}
/* Every page's heading (listings and text pages alike). */
.lg-page-head h1,
.lg-prose h1 { font-size: clamp(1.25rem, 1.05rem + .9vw, 1.75rem); font-weight: 650; }
.lg-page-head h1 { margin: 0; }
/* The home page's line on what the grades mean. */
.lg-page-note { max-width: 75ch; margin: .375rem 0 0; font-size: .875rem; }
.lg-head-actions { display: flex; align-items: center; gap: .5rem; }
.lg-head-actions .form-select { width: auto; max-width: 15rem; }
.lg-meta { margin: .125rem 0 0; font-size: .8125rem; color: var(--bs-secondary-color); }

/* The home-page intro is plain text above the listings, separated by a rule. */
.lg-welcome {
  --bs-btn-close-opacity: .7;
  margin-bottom: 1rem;
  padding-bottom: 1rem;
  border-bottom: var(--bs-border-width) solid var(--bs-border-color);
}
.lg-welcome-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 1rem; margin-bottom: .375rem; }
/* The close button sits at the end of the text block, not at the far side of a wide window. */
.lg-welcome-head,
.lg-welcome p { max-width: 75ch; }

/* ---- Filters ------------------------------------------------------------------------
   #filters-panel is .offcanvas-lg: a drawer below lg, inline (and card-styled here) from lg up. */

#filters-panel { --bs-offcanvas-width: 22rem; }
.lg-filters { width: 100%; font-size: .875rem; }

.lg-label { display: inline-block; margin-bottom: .25rem; font-size: .75rem; font-weight: 600; color: var(--bs-secondary-color); }
.lg-label-row { display: flex; align-items: center; gap: .25rem; }

.lg-sliders { display: grid; grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr)); gap: 1.25rem 1.5rem; }
.lg-slider-head { display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between; gap: 0 .5rem; }
/* The label's line is as tall as the values' (.875rem text), so a label wrapped onto a line of its
   own sits on the same baseline as the others. */
.lg-slider-head .lg-label { line-height: 1.3125rem; }
/* Each slider's label row and track are two rows of the sliders' grid, so the tracks stay level
   when one label wraps onto a second line ("Price before shipping" and its values, on laptops). */
@supports (grid-template-rows: subgrid) {
  .lg-slider-field { display: grid; grid-row: span 2; grid-template-rows: subgrid; row-gap: 0; }
}
.lg-slider-values { font-weight: 500; font-variant-numeric: tabular-nums; white-space: nowrap; }
/* Every slider's values on the line below its label, where one slider's wouldn't fit beside its label
   (filters.js fitSliderHeads): all on one line or all below, never a mix. */
.lg-sliders-stacked .lg-slider-head { flex-direction: column; align-items: flex-start; }

.lg-filter-row { display: flex; flex-direction: column; gap: 1rem; margin-top: 1.25rem; }
.lg-field { display: flex; flex-direction: column; align-items: stretch; min-width: 0; }
.lg-field .btn-group > .btn { flex: 1 1 0; }
/* Two pressed toggles side by side would read as one solid button: draw a divider. */
.lg-field .btn-group > .btn.active + .btn.active:not(:focus-visible) { box-shadow: inset 1px 0 0 var(--bs-body-bg); }
.lg-field-reset .btn { align-self: flex-start; }
.lg-status { min-height: 1.125rem; margin-top: .25rem; font-size: .75rem; color: var(--bs-secondary-color); }
/* Drawer only: the count ("Show 29 listings") and why none match stay in view at the bottom of the
   drawer while the filters above them scroll (filters.html), below a rule. */
.lg-drawer-footer {
  flex-shrink: 0;
  padding: .75rem var(--bs-offcanvas-padding-x) calc(.75rem + env(safe-area-inset-bottom, 0px));
  border-top: var(--bs-border-width) solid var(--bs-border-color);
}
/* Why the button goes back to no listings (table.js); empty otherwise. */
.lg-drawer-status { margin: 0; font-size: .875rem; }
.lg-drawer-status:not(:empty) { margin-bottom: .625rem; }
/* A phone held sideways: a tighter footer, so the filters above it keep most of the drawer. */
@media (max-height: 30rem) {
  .lg-drawer-footer { padding-block: .5rem calc(.5rem + env(safe-area-inset-bottom, 0px)); }
  .lg-drawer-status { font-size: .8125rem; line-height: 1.35; }
  .lg-drawer-status:not(:empty) { margin-bottom: .375rem; }
  .lg-drawer-footer .btn { --bs-btn-padding-y: .25rem; }
}
/* Share: the button above the link in the drawer, where the link needs the whole width to show
   its short code; side by side from lg up. */
.lg-share-row { display: flex; flex-direction: column; align-items: flex-start; gap: .5rem; }
.lg-share-row .input-group { width: 100%; }

/* Toggles and #filters-open: Bootstrap's outline-primary text is only ~3:1 on the dark
   background, so use the theme's primary text-emphasis colour (AA in both modes). */
.lg-filters .btn-outline-primary,
#filters-open { --bs-btn-color: var(--bs-primary-text-emphasis); --bs-btn-bg: var(--bs-body-bg); }
/* Bootstrap's outline-button hover is the same solid fill as a pressed toggle, so a toggle just
   switched off with the pointer still on it looked on. Hover is a light tint instead; a pressed
   toggle stays solid (.btn.active wins over :hover). */
.lg-filters .btn-outline-primary {
  --bs-btn-hover-color: var(--bs-primary-text-emphasis);
  --bs-btn-hover-bg: var(--bs-primary-bg-subtle);
  --bs-btn-hover-border-color: var(--bs-primary);
}
/* Touch screens keep :hover after a tap: no tint there, so off looks off at once. */
@media (hover: none) {
  .lg-filters .btn-outline-primary {
    --bs-btn-hover-color: var(--bs-btn-color);
    --bs-btn-hover-bg: var(--bs-btn-bg);
    --bs-btn-hover-border-color: var(--bs-btn-border-color);
  }
}
/* Secondary actions (reset, share, copy): body-coloured text, unlike btn-outline-secondary (~3:1). */
.lg-btn-quiet {
  --bs-btn-color: var(--bs-body-color);             --bs-btn-bg: var(--bs-body-bg);
  --bs-btn-border-color: var(--bs-border-color);    --bs-btn-hover-border-color: var(--bs-border-color);
  --bs-btn-hover-color: var(--bs-emphasis-color);   --bs-btn-hover-bg: var(--bs-secondary-bg);
  --bs-btn-active-color: var(--bs-emphasis-color);  --bs-btn-active-bg: var(--bs-secondary-bg);
  --bs-btn-active-border-color: var(--bs-border-color);
  --bs-btn-disabled-color: var(--bs-secondary-color);
  --bs-btn-disabled-border-color: var(--bs-border-color);
}

.lg-help {
  display: inline-flex; align-items: center; justify-content: center;
  width: 1.5rem; height: 1.5rem;
  margin-block: -.375rem -.125rem;
  font-size: 1rem;
  color: var(--bs-link-color);
  cursor: pointer;
}
.lg-help:hover { color: var(--bs-link-hover-color); }
/* Narrow enough to fit inside the filter drawer (22rem, or the whole width of a smaller phone)
   with a margin; main.js keeps drawer popovers within the drawer. */
.lg-popover-wide { --bs-popover-max-width: min(21rem, calc(100vw - 2.5rem)); }
.lg-popover-wide img { display: block; max-width: 100%; height: auto; }
/* Never taller than the screen (less its 8px margins and the title): on a short phone the text
   scrolls, and main.js lets the popover cover its button to stay on screen. */
.lg-popover-wide .popover-body { max-height: calc(100vh - 4.5rem); max-height: calc(100dvh - 4.5rem); overflow-y: auto; }
/* The US regions' states under the map, whose own legend is too small to read at this size. In the
   phone drawer the list stands in for the map: its labels are unreadable there, and with it the
   popover would be taller than most phone screens. */
.lg-region-list { display: grid; grid-template-columns: auto 1fr; gap: .125rem .75rem; margin: .5rem 0 0; }
@media (max-width: 991.98px) {
  .lg-popover-wide img { display: none; }
}
.lg-region-list dt { font-weight: 600; white-space: nowrap; }
.lg-region-list dd { margin: 0; }

@media (min-width: 992px) {
  .lg-filters {
    margin-bottom: .75rem;
    padding: .75rem 1rem .5rem;
    background: var(--bs-tertiary-bg);
    border: var(--bs-border-width) solid var(--bs-border-color);
    border-radius: var(--bs-border-radius);
  }
  .lg-filter-row { flex-direction: row; flex-wrap: wrap; align-items: flex-start; gap: .75rem 1.25rem; margin-top: 1rem; }
  .lg-field .btn-group > .btn { flex: 0 0 auto; }
  .lg-field-select .form-select { min-width: 10rem; }
  .lg-share { flex: 1 1 22rem; max-width: 32rem; margin-left: auto; }
  .lg-share-row { flex-direction: row; align-items: center; gap: .375rem; }
  .lg-share-row .input-group { flex: 1 1 12rem; width: auto; min-width: 0; }
}

/* noUiSlider restyle: thin track, round handles, Bootstrap colours. Selectors are one class
   more specific than noUiSlider's own, so no !important is needed. */
.lg-slider { margin: .625rem calc(var(--lg-handle) / 2) .5rem; }   /* room for handle overhang */
.lg-slider.noUi-target {
  height: .375rem;
  background: var(--bs-secondary-bg);
  border: 0;
  border-radius: 999px;
  box-shadow: inset 0 0 0 1px var(--bs-border-color);
}
.lg-slider .noUi-connects { border-radius: 999px; }
.lg-slider .noUi-connect { background: var(--bs-primary); }
.lg-slider .noUi-handle {
  top: calc((.375rem - var(--lg-handle)) / 2);
  right: calc(var(--lg-handle) / -2);
  width: var(--lg-handle);
  height: var(--lg-handle);
  background: var(--bs-body-bg);
  border: 2px solid var(--bs-primary);
  border-radius: 50%;
  box-shadow: none;
  cursor: grab;
}
.lg-slider .noUi-handle::before,
.lg-slider .noUi-handle::after { display: none; }
/* A handle being dragged gets the focus outline (no halo or shadow). */
.lg-slider .noUi-handle.noUi-active { cursor: grabbing; }
.lg-slider .noUi-handle.noUi-active,
.lg-slider .noUi-handle:focus-visible { outline: 2px solid var(--lg-focus-color); outline-offset: 2px; }

/* ---- Table status line ----------------------------------------------------------- */

.lg-table-status { display: flex; flex-wrap: wrap; align-items: baseline; gap: .125rem 1rem; margin-bottom: .25rem; font-size: .875rem; }
.lg-table-status p { margin: 0; }
#listing-count:focus { outline: none; }        /* focused by "Reset filters" in the empty table */
#listing-count:focus-visible { outline: 2px solid var(--lg-focus-color); outline-offset: 2px; }
#view-notice { color: var(--bs-secondary-color); }

/* "No listings match these filters: …" with a Reset button, inside the empty table. */
.lg-no-match { max-width: 60ch; margin: 0 auto .5rem; white-space: normal; }

/* A view with no listings at all: only #empty-view (why, and where to go instead) is shown;
   the filters, sorting and the empty table would do nothing (table.js sets .lg-no-data). */
.lg-no-data #filters-panel,
.lg-no-data .lg-head-actions,
.lg-no-data .lg-table-status,
.lg-no-data div.dt-container { display: none; }
.lg-empty p { max-width: 70ch; }

/* The data didn't load: say so prominently, with a retry. */
.lg-load-error { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem 1rem; padding: .625rem 1rem; }

/* ---- DataTables 2 ------------------------------------------------------------------ */

div.dt-container { font-size: .875rem; }
div.dt-container div.dt-search input { width: 14rem; max-width: 60vw; }
div.dt-container div.dt-info { color: var(--bs-secondary-color); }
/* Below 768px DataTables centres its controls (page length, "Filter rows", the count and the
   pager); the rest of the page is left-aligned, so these are too. The pager list is centred
   with !important, so it shrinks to its buttons instead and the line aligns it. */
@media (max-width: 767.98px) {
  div.dt-container div.dt-length,
  div.dt-container div.dt-search,
  div.dt-container div.dt-info,
  div.dt-container div.dt-paging { text-align: left; }
  div.dt-container div.dt-paging ul.pagination { display: inline-flex; }
}
div.dt-container .pagination {
  --bs-pagination-padding-x: .625rem;
  --bs-pagination-padding-y: .25rem;
  --bs-pagination-font-size: .875rem;
}

/* Processing indicator: the gopher GIF replaces DataTables' animated dots. */
div.dt-processing { padding: .75rem 1rem; }
div.dt-processing:has(img) > div:last-child { display: none; }
div.dt-processing img { width: 48px; height: 48px; }

table.dataTable { font-size: .875rem; font-variant-numeric: tabular-nums; }
/* The header stays in view below the sticky navbar (table.js sets --lg-sticky-top to its
   height). No ancestor of the table may scroll or clip, or the header sticks to that instead;
   Responsive keeps the table within the page width by collapsing columns. */
table.dataTable {
  /* Separate (zero-spaced) borders belong to their cells, so the header's rules move with it
     while it sticks; collapsed borders stay behind and let the rows show through. */
  border-collapse: separate;
  border-spacing: 0;
}
table.dataTable > thead {
  position: sticky;
  top: var(--lg-sticky-top, 0px);
  z-index: 2;
  background: var(--bs-body-bg);
}
/* A control in the rows that gets focus (Tab, Shift+Tab) scrolls into view below the sticky
   header as well as the navbar (the root's scroll-padding); table.js sets --lg-thead-height. */
table.dataTable > tbody :is(a, button, [tabindex]) { scroll-margin-top: calc(var(--lg-thead-height, 0px) + .25rem); }
/* table-sm's .25rem side padding leaves a right-aligned number touching the left-aligned
   text of the next column ("10,348 2.5″"): widen just those gaps. */
table.dataTable.table-sm > :not(caption) > tr > .lg-num + :not(.lg-num):not(.lg-grade-col) { padding-left: .875rem; }
/* Likewise after the solid grade fill ("Great" then "2U"). The fill's edge already separates
   it from the column before; from 576px the word gets room inside the fill too (phones keep
   .25rem so the grade fits beside the prices). */
table.dataTable.table-sm > :not(caption) > tr > .lg-grade-col + * { padding-left: .75rem; }
@media (min-width: 576px) {
  table.dataTable.table-sm > :not(caption) > tr > .lg-grade-col { padding-inline: .5rem; }
  /* An ungraded dash right after "Time left" (also a dash for Buy It Now) read as one "— —" cell. */
  table.dataTable.table-sm > tbody > tr > .lg-num + td.lg-grade-col:has(> .lg-muted) { padding-left: .875rem; }
}
table.dataTable > tbody > tr > td { white-space: nowrap; }
table.dataTable > tbody > tr > td.child { white-space: normal; }
/* Phones: header titles ("Price w/ shipping") wrap rather than push the price columns out of
   the row. Model cells stay on one line (name and photo button), so every row is the same
   height (very long names only wrap where the grade wouldn't fit otherwise), and drop the brand word ("R720": still read out) and the "Just listed" label (shown in
   the row's details instead, table.js), so the model, the sort column, the price with shipping
   and the grade fit on a 360px screen. */
@media (max-width: 575.98px) {
  table.dataTable > thead > tr > th .dt-column-title { white-space: normal; }
  .lg-model-link .lg-brand {
    position: absolute;
    width: 1px; height: 1px;
    margin: -1px; padding: 0;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
  }
  .lg-just-listed { display: none; }
  /* Where one line would leave no room for the grade (five-digit prices; table.js fitColumns sets
     .lg-wrap-long), a very long model name ("UCS C240 M5", "ThinkStation S30") may wrap before its
     last word, which stays with the photo button. Cut short with an ellipsis, "ThinkStation S20"
     and "S30" would look the same. */
  table.dataTable.lg-wrap-long .lg-model-wrap { white-space: normal; }
  /* Its first line stays a 24px-tall target above the photo button on the second. */
  table.dataTable.lg-wrap-long .lg-model-link:has(.lg-model-wrap) { line-height: 1.75rem; }
  table.dataTable.table-sm > * > tr > .lg-control { width: 1.5rem; padding-inline: 0; }   /* the page gutter is beside it */
  /* The "Sort by" select above the table is the phone's sort control, so a column the rows
     aren't sorted by shows no arrows; its header stays a sort button (the whole cell is the
     target and gets the focus ring), and the sorted column keeps its arrow. */
  table.dataTable > thead > tr > th { position: relative; }
  table.dataTable.table-sm > thead > tr > th.dt-orderable-asc:not(.dt-ordering-asc):not(.dt-ordering-desc) .dt-column-order {
    position: absolute;
    inset: 0;
    width: auto; height: auto;
    margin: 0; padding: 0;
  }
  table.dataTable.table-sm > thead > tr > th.dt-orderable-asc:not(.dt-ordering-asc):not(.dt-ordering-desc) .dt-column-order::before,
  table.dataTable.table-sm > thead > tr > th.dt-orderable-asc:not(.dt-ordering-asc):not(.dt-ordering-desc) .dt-column-order::after { display: none; }
}
table.dataTable > thead > tr > th { font-size: .75rem; font-weight: 600; line-height: 1.25; vertical-align: bottom; }
/* A title on two lines ("Ships from", "Drive bays" on laptops) keeps its sort arrows beside its last
   line, the line every header's title ends on, rather than halfway up. */
table.dataTable > thead { --dt-header-align-items: flex-end; }
table.dataTable > thead > tr > th:focus-visible { outline: 2px solid var(--lg-focus-color); outline-offset: -2px; }
/* The sort button (the arrows) gets a 24px box, the minimum target size, without moving the
   header text: padding grows it, negative margins give the space back. Its content box is one line
   of the title tall, so the arrows are centred on the title's last line (--dt-header-align-items). */
table.dataTable.table-sm > thead > tr > th .dt-column-order { box-sizing: content-box; height: .9375rem; padding: 4.5px 6px; margin: -4.5px -6px; }
/* The title row is tall enough for a 24px button inside its bottom rule: on phones a header without
   arrows is a sort button the size of its cell. */
table.dataTable > thead > tr:last-child > th { height: calc(1.5rem + var(--bs-border-width)); }
table.dataTable.table-sm > thead > tr > th .dt-column-order::before,
table.dataTable.table-sm > thead > tr > th .dt-column-order::after { left: 6px; }
table.dataTable > thead > tr > th .dt-column-order:focus-visible { outline: 2px solid var(--lg-focus-color); outline-offset: 0; border-radius: 2px; }
/* A wrapped left-aligned title fills the column's width and its arrows would sit at the column's
   edge: table.js (alignSortArrows) gives the gap to the end of its last line as --lg-order-shift,
   so they sit beside the text as on one-line titles (DataTables' own right: .25rem included). */
table.dataTable.table-sm > thead > tr > th.lg-order-shifted .dt-column-order { left: calc(var(--lg-order-shift, 0px) - .25rem); }
/* Sort icons sit right after their title (numeric headers right-aligned), not at the far
   edge of the cell, where they read as belonging to the neighbouring column. */
table.dataTable thead > tr > th div.dt-column-header { justify-content: flex-start; }
table.dataTable thead > tr > th div.dt-column-header .dt-column-title { flex-grow: 0; }
table.dataTable thead > tr > th.dt-type-numeric div.dt-column-header,
table.dataTable thead > tr > th.lg-num div.dt-column-header { flex-direction: row; justify-content: flex-end; }

/* Group header row: "CPU" and "Storage" over their columns; empty cells beside them draw no rule. */
table.dataTable > thead .lg-group {
  padding-bottom: .125rem;
  font-size: .75rem;
  text-align: center;
  color: var(--bs-secondary-color);
  border-bottom: 2px solid var(--bs-border-color);
}
table.dataTable > thead .lg-group div.dt-column-header { justify-content: center; }
table.dataTable > thead > tr:has(> .lg-group) > :not(.lg-group) { border-bottom-color: transparent; }

/* Responsive child rows (collapsed columns): compact label/value pairs that flow into as many
   columns as fit. :nth-child(n) matches the specificity of Responsive's :first-child and
   :last-child padding rules so every item gets the same padding. */
table.dataTable > tbody > tr.child ul.dtr-details {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr));
  column-gap: 1.5rem;
  width: 100%;
}
table.dataTable > tbody > tr.child ul.dtr-details > li:nth-child(n) { display: flex; gap: .75rem; padding-block: .25rem; }
table.dataTable > tbody > tr.child span.dtr-title { min-width: 8rem; font-weight: 600; color: var(--bs-secondary-color); }
.lg-no-details { margin: 0; color: var(--bs-secondary-color); }

/* Expand column (Responsive's control column, shown only while columns are collapsed): one
   chevron button per row, turned down while the row's details are open. Tapping elsewhere
   on a row that isn't a link or a button toggles it too (table.js). */
table.dataTable > * > tr > .lg-control { width: 1.75rem; padding-inline: .25rem 0; text-align: center; }
table.dataTable.collapsed > tbody > tr:not(.child) { cursor: pointer; }
.lg-expand {
  --bs-btn-color: var(--bs-secondary-color);
  --bs-btn-hover-color: var(--bs-link-hover-color);
  --bs-btn-active-color: var(--bs-link-hover-color);
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 1.5rem; min-height: 1.5rem;              /* 24px target */
  line-height: 1;
  vertical-align: middle;
}
tr.dtr-expanded .lg-expand { --bs-btn-color: var(--bs-emphasis-color); }
tr.dtr-expanded .lg-expand .lg-icon { transform: rotate(90deg); }
@media (prefers-reduced-motion: no-preference) {
  .lg-expand .lg-icon { transition: transform .15s ease-out; }
}

/* ---- Cell content emitted by table.js ------------------------------------------------ */

.lg-num { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
.lg-muted { color: var(--bs-secondary-color); }
.lg-time { font-variant-numeric: tabular-nums; white-space: nowrap; }

.lg-model-link { font-weight: 600; text-decoration: none; }
.lg-model-link:hover { text-decoration: underline; }
/* Bootstrap's link blue is exactly 4.5:1 on white and 3.8:1 on a hovered row: use its darker
   hover blue for links in the table (5.4:1 on a hovered row). */
[data-bs-theme="light"] table.dataTable { --bs-link-color-rgb: 10, 88, 202; --bs-link-hover-color-rgb: 8, 66, 152; }
table.dataTable > tbody > tr.lg-visited > * { --bs-table-bg-type: var(--lg-visited-bg); }
tr.lg-visited .lg-model-link { color: var(--lg-visited-link); }

/* The photo button: a 24px target (5px padding around the 14px icon) whose padding overlaps
   its neighbours instead of widening the model column, so phones keep room for the prices.
   The icon sits .375rem after the name. */
.lg-preview-btn {
  --bs-btn-color: var(--bs-secondary-color);
  --bs-btn-hover-color: var(--bs-link-hover-color);
  --bs-btn-active-color: var(--bs-link-hover-color);
  --bs-btn-padding-x: 5px;
  --bs-btn-padding-y: 5px;
  --bs-btn-border-width: 0;
  display: inline-flex; align-items: center; justify-content: center;
  margin: -5px -5px -5px calc(.375rem - 5px);
  vertical-align: middle;
  line-height: 1;
}
.lg-preview-btn .lg-icon { width: 14px; height: 14px; }

/* Plain inline annotations: "Just listed" (in the last 24 hours) after the model, drive speed
   ("15K", "SSD") after storage. */
.lg-just-listed { margin-left: .25rem; font-size: .75rem; font-weight: 600; color: var(--bs-primary-text-emphasis); }
.lg-speed { margin-left: .25rem; font-size: .75rem; color: var(--bs-secondary-color); }

/* Explained values (.lg-tip, table.js): the auction gavel, the ≈ shipping marker, a price without
   shipping, the grade, "est." after an estimated Passmark score and an unknown value's dash. Each
   shows a tooltip on hover, tap or focus; padding gives it at least a 24px target and matching
   negative margins keep the text where it was. */
.lg-tip { display: inline-block; cursor: help; border-radius: 2px; }
/* A tooltip fading out (focus or the pointer has moved on) no longer catches the pointer: placed
   beside a value at the window's edge, it would cover the next value for its 150ms fade. */
.tooltip:not(.show) { pointer-events: none; }
.lg-tip:focus-visible { outline: 2px solid var(--lg-focus-color); outline-offset: 0; }
.lg-muted.lg-tip { padding: 3px 5px; margin: -3px -5px; }
/* An estimated Passmark score: "19,679 est.". When some scores are estimates, the others get an
   invisible "est." (.lg-pm-est-space), so the column's digits line up. */
.lg-pm-est {
  padding: 3px 4px;
  margin: -3px -4px -3px calc(.25rem - 4px);
  font-size: .75rem;
  color: var(--bs-secondary-color);
}
.lg-pm-est-space { visibility: hidden; }
/* The gavel sits before the price, so auction prices line up with the others. */
.lg-auction {
  padding: 5px;
  margin: -5px calc(.25rem - 5px) -5px -5px;
  line-height: 1;
  vertical-align: -.125em;
  color: var(--bs-secondary-color);
}
.lg-auction .lg-icon { display: block; }
/* The same gavel in "Price w/ shipping", shown only while the Price column is collapsed (table.js
   PRICE_HIDDEN), so an auction's row always says its price is the current bid. */
.lg-auction.lg-auction-ship { display: none; }
table.dataTable.lg-price-hidden .lg-auction.lg-auction-ship { display: inline-block; }
.lg-ship-approx {
  padding: .25rem .5rem;
  margin: -.25rem calc(.125rem - .5rem) -.25rem -.5rem;
  color: var(--bs-secondary-color);
  text-decoration: underline dotted;
  text-underline-offset: .2em;
}
.lg-ship-unknown {
  min-width: 1.5rem;
  padding-block: .25rem;
  margin-block: -.25rem;
  font-style: italic;
  color: var(--bs-secondary-color);
}

/* Gopher grade: the whole cell is filled with the grade's colour, as on the original site.
   Setting Bootstrap's table colour variables on the cell keeps the fill solid under row hover
   and visited-row tints. In Responsive child rows (collapsed columns) the word gets the fill. */
.lg-grade { font-weight: 600; }
.lg-grade-great { --lg-grade-bg: var(--lg-great-bg); }
.lg-grade-good { --lg-grade-bg: var(--lg-good-bg); }
.lg-grade-fair { --lg-grade-bg: var(--lg-fair-bg); }
.lg-grade-bad { --lg-grade-bg: var(--lg-bad-bg); }
table.dataTable > tbody > tr > td:has(> .lg-grade-great) { --lg-cell-bg: var(--lg-great-bg); }
table.dataTable > tbody > tr > td:has(> .lg-grade-good) { --lg-cell-bg: var(--lg-good-bg); }
table.dataTable > tbody > tr > td:has(> .lg-grade-fair) { --lg-cell-bg: var(--lg-fair-bg); }
table.dataTable > tbody > tr > td:has(> .lg-grade-bad) { --lg-cell-bg: var(--lg-bad-bg); }
table.dataTable > tbody > tr > td:has(> .lg-grade) {
  --bs-table-bg: var(--lg-cell-bg);
  --bs-table-bg-type: var(--lg-cell-bg);
  --bs-table-bg-state: var(--lg-cell-bg);
  --bs-table-color: var(--lg-grade-fg);
  --bs-table-color-type: var(--lg-grade-fg);
  --bs-table-color-state: var(--lg-grade-fg);
}
/* DataTables tints a hovered row with its own inset shadow (7.5% black, white in dark mode), which
   replaces Bootstrap's fill: the grade cell keeps its own colour instead. */
table.table.dataTable.table-hover > tbody > tr:hover > td:has(> .lg-grade) { box-shadow: inset 0 0 0 9999px var(--lg-cell-bg); }
table.dataTable > tbody > tr.child .dtr-data > .lg-grade {
  padding: 0 .375rem;
  color: var(--lg-grade-fg);
  background: var(--lg-grade-bg);
}
/* The grade word is focusable (its meaning is a tooltip). On the fills the focus blue is under 3:1
   (2.5:1 on Great's green), so its outline takes the grade text's colour, 6:1 or more on every fill. */
.lg-grade.lg-tip:focus-visible { outline-color: var(--lg-grade-fg); }

/* ---- Photo preview modal ------------------------------------------------------------ */

/* Tablets and up: a sideways strip of photos of one height, with previous/next buttons
   (preview.js). Phones: the photos are stacked at the dialog's width, each at its natural
   height, and the full-screen dialog scrolls. */
.lg-gallery {
  position: relative;                        /* preview.js measures photos from here */
  display: flex;
  gap: .5rem;
  padding-bottom: .5rem;
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory;
}
.lg-gallery:has(> img:only-child) { justify-content: center; }   /* a lone photo isn't left at one side */
.lg-gallery-hint { margin: .25rem 0 0; font-size: .8125rem; color: var(--bs-secondary-color); }
.lg-gallery img {
  flex: 0 0 auto;
  width: auto;
  max-width: 100%;
  height: min(60vh, 32rem);
  aspect-ratio: auto 4 / 3;                  /* placeholder width until the photo loads */
  object-fit: contain;
  scroll-snap-align: center;
  background: var(--bs-tertiary-bg);
  border-radius: var(--bs-border-radius);
}
.lg-gallery-nav { display: flex; align-items: center; justify-content: center; gap: .75rem; margin-top: .25rem; font-size: .875rem; }
.lg-gallery-nav .btn { display: inline-flex; align-items: center; justify-content: center; min-width: 2rem; min-height: 2rem; }
.lg-gallery-prev .lg-icon { transform: rotate(180deg); }
.lg-gallery-position { min-width: 9rem; text-align: center; font-variant-numeric: tabular-nums; }
.lg-gallery-nav + .lg-gallery-hint { display: none; }   /* the position says it */

@media (max-width: 767.98px) {
  .lg-gallery { flex-direction: column; gap: .75rem; padding-bottom: 0; overflow-x: visible; scroll-snap-type: none; }
  .lg-gallery img { width: 100%; height: auto; }
  .lg-gallery-nav { display: none; }
  .lg-gallery-nav + .lg-gallery-hint { display: block; }
}

/* ---- About / Contact / Terms ------------------------------------------------------- */

/* Left-aligned with the logo like every other page (main keeps the site's width); the text keeps
   a readable measure. */
.lg-prose > * { max-width: 48rem; }
.lg-prose h1 { margin-bottom: 1rem; }
.lg-prose h2 { margin-top: 1.5rem; font-size: 1.125rem; }
.lg-prose li { margin-bottom: .375rem; }
/* The 404 page's models: in columns, each with this site's number of listings; a model with none
   is dimmed text, not a link, as in the brand menu. */
.lg-suggestions { columns: 13rem auto; column-gap: 2rem; }
.lg-suggestions li { break-inside: avoid; }
.lg-suggestion-count { color: var(--bs-secondary-color); font-variant-numeric: tabular-nums; white-space: nowrap; }
.lg-suggestion-empty,
.lg-suggestion-empty .lg-suggestion-count { color: var(--lg-menu-empty-color); }

/* ---- Motion ------------------------------------------------------------------------- */

@media (prefers-reduced-motion: no-preference) {
  .lg-gallery { scroll-behavior: smooth; }
}
@media (prefers-reduced-motion: reduce) {
  .lg-slider .noUi-origin,
  .lg-slider .noUi-connect { transition: none; }   /* noUiSlider animates taps on the track */
}
