/*
 * bos-comparator.css
 * Photo Comparator — desktop only.
 * Depends on: bos-common.css (CSS variables)
 */

/* ── Mobile advisory banner ──────────────────────────────────────────────── */
/* A recommendation, not a requirement — the page itself always renders (the
   multi-grid's own column-floor logic, bos-comparator.js, keeps it usable
   down to phone widths), so this is a small dismiss-free strip, not a
   blocking full-screen notice. */
.cmp-mobile-notice { display: none; }

@media (max-width: 639px) {
	.cmp-mobile-notice {
		display: block;
		padding: 8px 16px;
		text-align: center;
		color: var(--stone-dark, #7a6f62);
		background: var(--cream-dark, #ede9e2);
		border-bottom: 1px solid var(--border-strong, #c8c0b4);
	}
	.cmp-mobile-notice p { font-size: var(--fs-xs); margin: 0; }
}

/* ── Page layout ─────────────────────────────────────────────────────────── */

/* On the comparator page, hide the sticky-header spacers and un-sticky the nav.
   That "only loaded on /comparator" assumption stopped being true once other
   pages (taxon-tabs) started loading this file too, for their own "Add to
   comparator" buttons — .sticky-header is also the *site nav's own* marker
   class (header-tabs.php/style.css: header.sticky .navbar.sticky-header),
   so an unscoped !important here was clobbering position:fixed on the main
   nav everywhere this file loads, not just on /comparator. That broke the
   mobile hamburger menu elsewhere: .navbar-collapse's `top: 100%` (theme
   CSS) needs a positioned .navbar to resolve against. :has() scopes this to
   when .cmp-page is actually present, matching unstickySiteHeader()
   (comparator.php's own JS, same "only affects real comparator page" intent,
   just enforced dynamically there instead of unconditionally in CSS).
   position:relative, not static: scoping fixed the leak to other pages, but
   on the comparator page itself the hamburger menu was still broken by the
   very same mechanism it was scoped to avoid — position:static removes the
   positioned ancestor .navbar-collapse's top:100% needs entirely, same as
   before, just now only on the page this rule is actually meant to affect.
   position:relative with no offsets gets the same "stays in normal flow,
   scrolls away like static" visual result while still giving top:100% a
   valid box to resolve against.
   z-index: giving the nav position (relative, above) also gives it a real
   stacking context for the first time on this page — previously (position:
   static) it had none at all, so z-index never entered into it. The
   theme's own .navbar rule (style.css) carries z-index:99, comfortably
   under .cmp-bar's 200 (this file) — once stacking actually applies, the
   expanded #navbarNav hamburger menu (a descendant, sharing this context)
   started rendering behind the comparator's own top bar. It's a global
   site-nav affordance, not part of the comparator UI, so it should sit
   above all of it — including this file's own highest overlays (touch
   tooltips/drag indicators, up to z-index:9700) — hence the deliberately
   round, comfortably-clear-of-everything value below. */
body:has(.cmp-page) .top-space-margin { display: none !important; }
body:has(.cmp-page) .sticky-header    { position: relative !important; top: auto !important; z-index: 10000 !important; }
/* The site-wide "scroll progress" back-to-top widget (footer.php/footer-tabs.php)
   is a position:fixed sibling of .cmp-page under <body>, not a descendant of
   it — unstickySiteHeader() (comparator.php) already forces its position to
   static since it's outside .cmp-page, which is what made it render inline
   at the end of the page (in the footer area, centered) instead of floating.
   The comparator is a self-contained full-viewport UI with no page footer to
   scroll toward, so remove it outright rather than trying to keep it fixed. */
.scroll-progress { display: none !important; }
.cmp-page {
	display: flex;
	flex-direction: column;
	height: 100vh;
	overflow: hidden;
	background: #0e1520;
	margin: 0;
	padding: 0;
	/* Comparator::index() now renders through header-tabs.php/footer-tabs.php
	   (the dark site theme, for the header/footer only), whose inline <style>
	   redefines --navy/--navy-mid/etc. on body.btt-header-page — every
	   descendant under <body> inherits that, this page's own markup included.
	   This file predates that and was written against bos-common.css's
	   light-theme values (--navy: #0F2340, a genuinely dark navy) used as
	   BACKGROUND fills throughout (.cmp-bar, .cmp-add-btn, ...) — under the
	   inherited dark-theme redefinition (--navy: #5b8dee, meant only as a
	   text/icon accent colour there) those same rules paint a bright light
	   blue instead. Re-pin every token this file actually reads back to its
	   original light-theme value, scoped to .cmp-page only, so the comparator
	   's own UI (bar included) renders exactly as it did before the header/
	   footer swap — only the header and footer are meant to go dark, not
	   this. See the "no light blue backgrounds on BOS" standing rule: never
	   let --navy/--navy-mid resolve to the bright accent value anywhere a
	   background is painted. */
	--navy: #0F2340;
	--navy-mid: #1a3456;
	--navy-dark: #0e1520;
	--cream-dark: #EDE8DF;
	--stone-dark: #A09890;
	--border-strong: rgba(15, 35, 64, 0.2);
}

/* ── Control bar ─────────────────────────────────────────────────────────── */
.cmp-bar {
	flex-shrink: 0;
	display: flex;
	align-items: stretch;
	gap: 0;
	padding: 0;
	background: var(--navy, #1c2b42);
	border-bottom: 1px solid rgba(255,255,255,0.08);
	flex-wrap: nowrap;
	min-height: 52px;
	position: sticky;
	top: 0;
	z-index: 200;
}
@media (max-width: 640px) {
	.cmp-bar {
		flex-wrap: wrap;
	}
	.cmp-bar-group--centre {
		flex-wrap: wrap;
		flex-basis: 100%;
		justify-content: flex-start;
	}
}
/* Sticky-header swap while scrolled into List View — see .cmp-sort-list-header */
.cmp-in-list-view .cmp-bar { position: static; }

/* Visual group: a thin divider between groups */
.cmp-bar-group {
	display: flex;
	align-items: center;
	gap: 6px;
	padding: 8px 12px;
	flex-wrap: nowrap;
}
.cmp-bar-group + .cmp-bar-group {
	border-left: 1px solid rgba(255,255,255,0.1);
}

/* Side groups grow equally to fill width */
.cmp-bar-group--a,
.cmp-bar-group--b { flex: 1 1 0; min-width: 0; }

/* Right side group aligns its contents to the right */
.cmp-bar-group--b { justify-content: flex-end; }

/* Centre group is fixed-size, never grows */
.cmp-bar-group--centre { flex-shrink: 0; }

.cmp-bar-zoom {
	display: flex;
	align-items: center;
	gap: 6px;
	flex-shrink: 0;
}

/* ── Custom species selector ─────────────────────────────────────────────── */
.cmp-bar-select {
	/* hidden — replaced by .cmp-custom-sel overlay */
	display: none;
}
.cmp-custom-sel {
	flex: 1;
	min-width: 0;
	position: relative;
}
.cmp-custom-sel-btn {
	width: 100%;
	height: 34px;
	padding: 0 8px;
	font-size: var(--fs-sm);
	font-family: inherit;
	background: rgba(255,255,255,0.08);
	border: 1px solid rgba(255,255,255,0.15);
	border-radius: 4px;
	color: #fff;
	cursor: pointer;
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 6px;
	text-align: left;
}
.cmp-custom-sel-btn:focus,
.cmp-custom-sel--open .cmp-custom-sel-btn {
	outline: none;
	border-color: rgba(255,255,255,0.4);
}
.cmp-custom-sel-val {
	flex: 1;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}
.cmp-custom-sel-val em,
.cmp-custom-sel-val i { font-style: italic; }
.cmp-custom-sel-arrow {
	flex-shrink: 0;
	opacity: 0.5;
	transition: transform 0.15s;
}
.cmp-custom-sel--open .cmp-custom-sel-arrow { transform: rotate(180deg); }

.cmp-custom-sel-list {
	display: none;
	position: absolute;
	top: calc(100% + 4px);
	left: 0;
	right: auto;
	min-width: 100%;
	max-width: 420px;
	background: #1e2d45;
	border: 1px solid rgba(255,255,255,0.15);
	border-radius: 4px;
	list-style: none;
	margin: 0;
	padding: 4px 0;
	z-index: 100;
	max-height: 280px;
	overflow-y: auto;
	overflow-x: hidden;
	box-shadow: 0 8px 24px rgba(0,0,0,0.4);
}
.cmp-custom-sel--open .cmp-custom-sel-list { display: block; }

/* Right-side dropdown opens leftward so it doesn't overflow off-screen */
.cmp-bar-group--b .cmp-custom-sel-list {
	left: auto;
	right: 0;
}

.cmp-custom-sel-opt {
	padding: 7px 12px;
	font-size: var(--fs-sm);
	color: rgba(255,255,255,0.8);
	cursor: pointer;
	white-space: nowrap;
}
.cmp-custom-sel-opt:hover { background: rgba(255,255,255,0.08); color: #fff; }
.cmp-custom-sel-opt--selected { color: #fff; font-weight: 600; }
.cmp-custom-sel-opt--placeholder { color: rgba(255,255,255,0.4); font-style: normal; }
.cmp-custom-sel-opt--disabled {
	opacity: 0.35;
	cursor: not-allowed;
	pointer-events: none;
}
.cmp-custom-sel-opt em,
.cmp-custom-sel-opt i { font-style: italic; }

.cmp-zoom-slider {
	width: 90px;
	accent-color: #5b8dee;
	cursor: pointer;
	/* .cmp-panel (ancestor) sets touch-action:none so custom pan/pinch-zoom
	   can claim touch gestures on the photo — but that's inherited by
	   descendants too, which broke the native browser handling this range
	   input needs to drag its own thumb. Restoring normal touch-action
	   directly on the slider takes precedence over the ancestor's none for
	   touches that start on it specifically. */
	touch-action: manipulation;
}
.cmp-zoom-label {
	font-size: var(--fs-xs);
	color: rgba(255,255,255,0.6);
	width: 36px;
	text-align: right;
	font-variant-numeric: tabular-nums;
}

.cmp-sync-label {
	display: inline-flex;
	align-items: center;
	gap: 6px;
	height: 30px;
	padding: 4px 10px;
	background: rgba(255,255,255,0.08);
	border: 1px solid rgba(255,255,255,0.15);
	border-radius: 4px;
	color: rgba(255,255,255,0.75);
	font-size: var(--fs-sm);
	font-family: inherit;
	cursor: pointer;
	user-select: none;
	white-space: nowrap;
	transition: background 0.12s, color 0.12s;
}
.cmp-sync-label:hover {
	background: rgba(255,255,255,0.16);
	color: #fff;
}
.cmp-sync-label:has(input:checked) {
	background: #5b8dee;
	border-color: #5b8dee;
	color: #fff;
}
.cmp-sync-label:has(input:checked):hover {
	background: #4a7de0;
}
.cmp-sync-label input[type="checkbox"] {
	accent-color: #fff;
	width: 13px;
	height: 13px;
	cursor: pointer;
	flex-shrink: 0;
}


.cmp-ctrl-btn {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	background: rgba(255,255,255,0.08);
	border: 1px solid rgba(255,255,255,0.15);
	border-radius: 4px;
	color: rgba(255,255,255,0.75);
	font-size: var(--fs-sm);
	font-family: inherit;
	padding: 4px 10px;
	height: 30px;
	cursor: pointer;
	white-space: nowrap;
	transition: background 0.12s, color 0.12s;
}
.cmp-ctrl-btn:hover           { background: rgba(255,255,255,0.16); color: #fff; }
.cmp-ctrl-btn--active         { background: #5b8dee; border-color: #5b8dee; color: #fff; }
.cmp-ctrl-btn--active:hover   { background: #4a7de0; }

.cmp-info-btn {
	padding: 2px 6px;
	height: 24px;
	font-size: var(--fs-xs);
}

/* ── Main area ───────────────────────────────────────────────────────────── */
.cmp-main {
	display: flex;
	flex: 1;
	overflow: hidden;
}

.cmp-half {
	display: flex;
	flex-direction: column;
	flex: 1;
	min-width: 0;
	min-height: 0;
	overflow: hidden;
}

.cmp-divider {
	width: 3px;
	background: rgba(255,255,255,0.12);
	flex-shrink: 0;
}

/* ── Photo panel ─────────────────────────────────────────────────────────── */
.cmp-panel {
	flex: 1;
	min-height: 0;
	position: relative;
	/* No overflow:hidden here — that would clip cmp-meta.
	   The viewport below handles image clipping. */
	cursor: grab;
	outline: none;
	background: #111720;
	/* Custom touch pan/pinch-zoom (bos-comparator.js) replaces the
	   browser's native touch-scroll/pinch-zoom on this element entirely —
	   without this, iOS Safari in particular still performs its own page
	   pinch-zoom alongside (or instead of) our handler even with
	   preventDefault() in the touchmove listener. */
	touch-action: none;
}
.cmp-panel:focus { box-shadow: inset 0 0 0 2px #5b8dee; }

/* The whole bar is the drag handle for reordering (panning the photo is done
   from the viewport below instead), so it takes pointer events itself now. */
.cmp-panel-label {
	position: absolute;
	top: 0; left: 0; right: 0;
	z-index: 10;
	display: flex;
	align-items: center;
	gap: 6px;
	padding: 6px 10px;
	background: linear-gradient(to bottom, rgba(0,0,0,0.65) 0%, transparent 100%);
	cursor: grab;
	/* Custom touch drag-to-reorder (bos-comparator.js, multiWireDragHandle)
	   replaces the browser's native touch-scroll on this bar entirely —
	   same reasoning as .cmp-panel's own touch-action:none for pan/zoom.
	   The bar is a thin strip, not primary scrollable content — page
	   scroll remains available by touching anywhere else on the cell. */
	touch-action: none;
}
.cmp-panel-label:active { cursor: grabbing; }

.cmp-drag-handle {
	display: flex;
	align-items: center;
	flex-shrink: 0;
	color: rgba(255,255,255,0.35);
	transition: color 0.15s;
}
.cmp-panel-label:hover .cmp-drag-handle { color: rgba(255,255,255,0.75); }

.cmp-panel-name {
	font-size: var(--fs-sm);
	color: rgba(255,255,255,0.85);
	font-weight: 300;
	max-width: calc(100% - 40px);
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}
.cmp-panel-name em,
.cmp-panel-name i { font-style: italic; }
/* Tap-to-reveal (portable devices — see the click handler on nameSpan,
   multiWireDragHandle(), bos-comparator.js): no overlay — just lets the
   name wrap onto a second (or third) line in place, in the label bar's own
   normal flow. .cmp-panel-label has no fixed height, so it grows to fit;
   .cmp-drag-handle (align-items:center, base rule) re-centers itself
   against that taller row automatically. Simpler than an absolutely-
   positioned floating pill, at the cost of the bar (and whatever sits
   directly below it) shifting down for the ~2.5s the name is expanded. */
@media (max-width: 991px) {
	.cmp-panel-name--flash {
		max-width: none;
		white-space: normal;
		overflow: visible;
	}
	/* .cmp-label-link (base rule) is display:inline-flex specifically so
	   align-items:baseline lines up italic genus text with the external-
	   link icon — harmless while nowrap kept everything on one line, but
	   once wrapping is allowed each flex item (the <em> genus and the
	   plain-text remainder are SEPARATE flex items/children) wraps
	   independently in its own column instead of flowing together as one
	   sentence: e.g. "Heteropoda" wrapping on the left while
	   ‘sp. "False Black Jaw"’ wraps in a second column on the right.
	   display:inline here makes it flow as normal text instead — the icon
	   falls back to .cmp-label-ext's own vertical-align:middle (already
	   set) rather than flex baseline alignment, close enough for this
	   brief expanded state. */
	.cmp-panel-name--flash .cmp-label-link {
		display: inline;
	}
	/* .cmp-label-text (base rule) carries the actual nowrap/ellipsis now
	   (see its own rule further down) — while the name is expanded that
	   needs undoing here too, same as .cmp-panel-name--flash does for
	   itself just above, or the name would still clip to one line inside
	   the now-wrappable block. */
	.cmp-panel-name--flash .cmp-label-text {
		white-space: normal;
		overflow: visible;
		text-overflow: clip;
	}
	/* While the name is expanded, the drag-handle dot-icon should stay
	   centered against the FIRST line only, not the whole (now taller)
	   block — align-items:center on .cmp-panel-label would sink it to the
	   block's new vertical middle as more lines wrap in, and flex-start
	   alone pins it to the raw top edge of the content box, above where a
	   single line's own text actually sits. .cmp-panel-name inherits the
	   body's 1.6 line-height, so one line is 14px * 1.6 = 22.4px tall;
	   margin-top nudges the 14px-tall icon down by half the leftover
	   space, i.e. exactly where align-items:center already puts it for
	   the single-line (non-flashed) case. :has() makes this purely
	   reactive to the JS-toggled class — reverts on its own once
	   flashColorBtn's timeout removes it, no separate JS needed. */
	.cmp-panel-label:has(.cmp-panel-name--flash) .cmp-drag-handle {
		align-self: flex-start;
		margin-top: 4px; /* (14px * 1.6 - 14px) / 2, rounded */
	}
	/* Hide every other overlay control in the cell while the name is
	   expanded — a name that's wrapped to 2-3 lines has likely grown
	   past where they'd normally sit, and a deliberate "let me read this"
	   tap shouldn't compete for space/attention with them. .cmp-panel-
	   viewport is .cmp-panel-label's next sibling (both children of
	   .cmp-panel) — same :has()-driven, JS-timeout-reverts-on-its-own
	   mechanism as above. .cmp-multi-action-group covers both the move-
	   to-top/bottom group AND the category-colour group (it carries both
	   classes on the colour group specifically). */
	.cmp-panel-label:has(.cmp-panel-name--flash) + .cmp-panel-viewport .cmp-minimap,
	.cmp-panel-label:has(.cmp-panel-name--flash) + .cmp-panel-viewport .cmp-panel-corner-btns,
	.cmp-panel-label:has(.cmp-panel-name--flash) + .cmp-panel-viewport .cmp-flag-btn,
	.cmp-panel-label:has(.cmp-panel-name--flash) + .cmp-panel-viewport .cmp-multi-action-group,
	.cmp-panel-label:has(.cmp-panel-name--flash) + .cmp-panel-viewport .cmp-zoom-overlay {
		display: none;
	}
}
.cmp-label-link {
	color: inherit;
	text-decoration: none;
	display: inline-flex;
	align-items: baseline;   /* baseline keeps em/i italic visible */
	gap: 4px;
	/* .cmp-panel-name's own overflow:hidden/text-overflow:ellipsis (base
	   rule) never actually rendered a "…" — its only child is this whole
	   <a>, a single atomic inline-level box (inline-flex), so when it
	   overflowed the browser just hard-clipped it rather than ellipsizing
	   "into" it. min-width:0 + max-width:100% let the anchor itself shrink
	   to whatever room .cmp-panel-name has, instead of overflowing it; the
	   actual ellipsis now happens one level in, on .cmp-label-text below,
	   which is plain text and clips the way text-overflow expects. */
	min-width: 0;
	max-width: 100%;
}
.cmp-label-link em,
.cmp-label-link i { font-style: italic; }
.cmp-label-link:hover { color: #fff; }
.cmp-label-text {
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
	min-width: 0;
	/* An italic glyph's ink can lean past its own advance-width box —
	   "i" is the most visible case, its top overshooting to the right —
	   so when the name isn't truncated (fits exactly, no ellipsis needed)
	   and happens to end on one, overflow:hidden above clips that overhang
	   off. A couple px of right padding gives it room without adding a
	   perceptible gap for non-italic/non-overhanging endings. */
	padding-right: 2px;
}
.cmp-label-ext {
	opacity: 0;
	flex-shrink: 0;
	vertical-align: middle;
	transition: opacity 0.15s;
}
.cmp-panel-label:hover .cmp-label-ext { opacity: 0.6; }

.cmp-panel-viewport {
	width: 100%;
	height: 100%;
	display: flex;
	align-items: center;
	justify-content: center;
	overflow: hidden;
	position: relative;
}


.cmp-img {
	display: block;
	max-width: none;
	max-height: none;
	transform-origin: center center;
	user-select: none;
	-webkit-user-drag: none;
	transition: none;
}

.cmp-panel-empty {
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	gap: 12px;
	color: rgba(255,255,255,0.2);
	pointer-events: none;
	text-align: center;
}
.cmp-panel-empty p {
	font-size: var(--fs-sm);
	margin: 0;
	/* Readable (~9:1 on the panel) — it can be the final "No photos"
	   message, not just a passing "Loading…". The icon stays faint. */
	color: rgba(255,255,255,0.7);
}

/* ── Minimap ─────────────────────────────────────────────────────────────── */
.cmp-minimap {
	position: absolute;
	bottom: 10px;
	right: 10px;
	width: 120px;
	height: 90px;
	border: 1px solid rgba(255,255,255,0.3);
	border-radius: 3px;
	pointer-events: auto;
	z-index: 15;
	opacity: 0.85;
	cursor: crosshair;
}
/* 40% of desktop size (60x45 originally, then reduced a further 20% to
   48x36) on phones/tablets — at full size it covers over half the
   (already much smaller) portable cell. The canvas's drawing-buffer
   resolution (minimap.width/height, set in multiRenderPanel(),
   bos-comparator.js) is matched to this same size at the same breakpoint
   — the drag-to-pan math in multiMinimapPanTo() assumes the buffer
   resolution and this CSS display size are equal, so the two must change
   together. */
@media (max-width: 991px) {
	.cmp-minimap {
		width: 48px;
		height: 36px;
		/* .cmp-zoom-overlay is a full-width bar flush with the cell's
		   bottom edge on mobile (below), not a small floating pill like on
		   desktop — bottom:10px put the minimap's own bottom edge well
		   inside that bar's ~38px height, overlapping it. 44px clears it
		   with a small margin. The metadata-open variant of this same
		   clearance lives in the main ≤991px block further down this file
		   (after, not before, the base .cmp-panel:has(.cmp-meta--open)
		   .cmp-minimap rule below — same selector/specificity, so it has to
		   come later in source order to actually win). */
		bottom: 44px;
	}
}

.cmp-zoom-overlay {
	position: absolute;
	bottom: 14px;
	left: 50%;
	transform: translateX(-50%);
	display: flex;
	align-items: center;
	gap: 6px;
	background: rgba(10, 18, 30, 0.72);
	backdrop-filter: blur(6px);
	-webkit-backdrop-filter: blur(6px);
	border: 1px solid rgba(255,255,255,0.12);
	border-radius: 20px;
	padding: 5px 12px;
	z-index: 20;
	pointer-events: auto;
	opacity: 0;
	transition: opacity 0.15s, bottom 0.15s;
}
/* Show zoom overlay on panel hover — both dual and multi */
.cmp-panel:hover .cmp-zoom-overlay,
.cmp-multi-cell:hover .cmp-zoom-overlay,
.cmp-zoom-overlay:hover {
	opacity: 1;
}

.cmp-zoom-ovr-btn {
	width: 24px;
	height: 24px;
	padding: 0;
	font-size: 15px;
	line-height: 1;
	border-radius: 50%;
}

/* ── Metadata drawer ─────────────────────────────────────────────────────── */
/* cmp-meta sits inside cmp-panel (position:relative). The panel has no
   overflow:hidden so the drawer is not clipped. It overlays the photo only. */
.cmp-meta {
	display: none;
	position: absolute;
	bottom: 0; left: 0; right: 0;
	background: rgba(14,21,32,0.92);
	border-top: 1px solid rgba(255,255,255,0.1);
	padding: 10px 14px;
	max-height: 35%;
	overflow-y: auto;
	z-index: 30;
}
.cmp-meta--open,
.cmp-half .cmp-meta--open { display: block; }

/* ── Zoom overlay and minimap: sit just above metadata when open ─────────── */
/* Offset is set via --meta-h custom property written by JS on open/close */
.cmp-panel:has(.cmp-meta--open) .cmp-zoom-overlay {
	bottom: calc(var(--meta-h, 0px) + 8px);
	transition: bottom 0.15s;
}
.cmp-panel:has(.cmp-meta--open) .cmp-minimap {
	bottom: calc(var(--meta-h, 0px) + 8px);
	transition: bottom 0.15s;
}
.cmp-zoom-overlay { transition: bottom 0.15s; }
.cmp-minimap      { transition: bottom 0.15s; }

.cmp-meta-row {
	display: flex;
	gap: 10px;
	font-size: var(--fs-xs);
	line-height: 1.6;
	color: rgba(255,255,255,0.65);
}
.cmp-meta-key {
	flex-shrink: 0;
	width: 80px;
	font-weight: 600;
	color: rgba(255,255,255,0.35);
	font-weight: 300;
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
	font-size: 10px;
	padding-top: 2px;
}
.cmp-meta-empty {
	font-size: var(--fs-xs);
	color: rgba(255,255,255,0.3);
	margin: 0;
}

/* ── Photo strip (bottom) ────────────────────────────────────────────────── */
.cmp-strip-wrap {
	flex-shrink: 0;
	background: #111720;
	border-top: 1px solid rgba(255,255,255,0.08);
	max-height: 160px;
	overflow: hidden;
	display: flex;
	flex-direction: column;
	transition: max-height 0.2s ease;
}
.cmp-strip-wrap--collapsed {
	max-height: 32px;
}
.cmp-strip-wrap--collapsed .cmp-strip { display: none; }
.cmp-strip-wrap--collapsed .cmp-strip-toggle svg { transform: rotate(-90deg); }

.cmp-strip-toggle {
	display: flex;
	align-items: center;
	gap: 6px;
	background: none;
	border: none;
	border-bottom: 1px solid rgba(255,255,255,0.06);
	color: rgba(255,255,255,0.4);
	font-size: var(--fs-xs);
	font-family: inherit;
	font-weight: 600;
	text-transform: uppercase;
	letter-spacing: 0.08em;
	padding: 6px 12px;
	cursor: pointer;
	text-align: left;
	flex-shrink: 0;
}
.cmp-strip-toggle:hover { color: rgba(255,255,255,0.7); }
.cmp-strip-toggle svg { transition: transform 0.2s; flex-shrink: 0; }

.cmp-strip {
	flex: 1;
	overflow-y: auto;
	overflow-x: hidden;
	padding: 6px 10px 8px;
	display: flex;
	flex-direction: column;
	gap: 6px;
}

/* ── Tag filter bar ──────────────────────────────────────────────────────── */
.cmp-filter-bar {
	display: flex;
	flex-wrap: wrap;
	gap: 4px 10px;
	align-items: center;
}
.cmp-filter-cat {
	display: flex;
	align-items: center;
	gap: 4px;
	flex-wrap: wrap;
}
.cmp-filter-cat-label {
	font-size: 10px;
	font-weight: 700;
	text-transform: uppercase;
	letter-spacing: 0.06em;
	color: rgba(255,255,255,0.3);
	white-space: nowrap;
	flex-shrink: 0;
}
.cmp-filter-chip {
	display: inline-flex;
	align-items: center;
	background: rgba(255,255,255,0.07);
	border: 1px solid rgba(255,255,255,0.12);
	border-radius: 3px;
	color: rgba(255,255,255,0.55);
	font-size: var(--fs-xs);
	font-family: inherit;
	padding: 2px 7px;
	cursor: pointer;
	transition: background 0.1s, color 0.1s;
}
.cmp-filter-chip:hover { background: rgba(255,255,255,0.13); color: rgba(255,255,255,0.9); }
.cmp-filter-chip.active {
	background: #5b8dee;
	border-color: #5b8dee;
	color: #fff;
}
.cmp-filter-chip--untagged {
	border-style: dashed;
	margin-left: 6px;
}

/* ── Photo thumbnails ────────────────────────────────────────────────────── */
.cmp-thumb-row {
	display: flex;
	flex-wrap: nowrap;
	gap: 6px;
	overflow-x: auto;
	scrollbar-width: thin;
	scrollbar-color: rgba(255,255,255,0.2) transparent;
	padding-bottom: 2px;
}

.cmp-thumb {
	flex-shrink: 0;
	width: 64px;
	height: 64px;
	border: 2px solid transparent;
	border-radius: 3px;
	overflow: hidden;
	background: rgba(255,255,255,0.05);
	cursor: pointer;
	padding: 0;
	transition: border-color 0.12s;
}
.cmp-thumb:hover { border-color: rgba(255,255,255,0.4); }
.cmp-thumb--active { border-color: #5b8dee; }

.cmp-thumb img {
	width: 100%;
	height: 100%;
	object-fit: cover;
	display: block;
}

.cmp-strip-empty {
	font-size: var(--fs-xs);
	color: rgba(255,255,255,0.25);
	font-style: italic;
	padding: 4px 0;
}

/* ── Shortlist badge + drawer ────────────────────────────────────────────── */
.cmp-badge {
	position: fixed;
	bottom: 24px;
	right: 24px;
	display: flex;
	align-items: center;
	gap: 7px;
	background: var(--navy, #1c2b42);
	color: #fff;
	border: none;
	border-radius: 24px;
	padding: 10px 16px;
	font-size: var(--fs-sm);
	font-family: inherit;
	font-weight: 500;
	cursor: pointer;
	box-shadow: 0 4px 16px rgba(0,0,0,0.28);
	z-index: 500;
	transition: background 0.12s, transform 0.12s;
}
.cmp-badge:hover { background: #263d5e; transform: translateY(-1px); }
.cmp-badge--has-items { background: #2553a0; }

.cmp-badge-count {
	background: rgba(255,255,255,0.2);
	border-radius: 10px;
	padding: 0 7px;
	font-size: var(--fs-xs);
	font-weight: 700;
	min-width: 18px;
	text-align: center;
}

/* ── Inline feedback notification ───────────────────────────────────────── */
.cmp-feedback {
	position: absolute;
	display: flex;
	align-items: center;
	gap: 8px;
	background: var(--navy, #1c2b42);
	color: #fff;
	font-size: var(--fs-xs);
	padding: 7px 10px 7px 12px;
	border-radius: 5px;
	box-shadow: 0 4px 14px rgba(0,0,0,0.22);
	z-index: 700;
	opacity: 0;
	transform: translateY(-4px);
	transition: opacity 0.2s ease, transform 0.2s ease;
	pointer-events: auto;
	max-width: 260px;
	line-height: 1.4;
	white-space: normal;
}
.cmp-feedback--visible {
	opacity: 1;
	transform: translateY(0);
}
.cmp-feedback-msg {
	flex: 1;
}
.cmp-feedback-close {
	background: none;
	border: none;
	color: rgba(255,255,255,0.6);
	font-size: 16px;
	line-height: 1;
	cursor: pointer;
	padding: 0 2px;
	flex-shrink: 0;
}
.cmp-feedback-close:hover { color: #fff; }

/* ── Drawer head actions (minimise button) ───────────────────────────────── */
.cmp-drawer-head-actions {
	display: flex;
	align-items: center;
	gap: 6px;
}
.cmp-drawer-minimise {
	background: none;
	border: none;
	color: #8b96ac;
	font-size: 20px;
	line-height: 1;
	cursor: pointer;
	padding: 0 4px;
	border-radius: 3px;
}
.cmp-drawer-minimise:hover {
	background: rgba(255, 255, 255, 0.08);
	color: #eef0f4;
}


/* ── Toast notification ─────────────────────────────────────────────────── */
.cmp-toast {
	position: fixed;
	bottom: 90px;
	right: 24px;
	background: var(--navy, #1c2b42);
	color: #fff;
	font-size: var(--fs-sm);
	padding: 10px 16px;
	border-radius: 6px;
	box-shadow: 0 4px 16px rgba(0,0,0,0.25);
	z-index: 600;
	opacity: 0;
	transform: translateY(8px);
	transition: opacity 0.2s ease, transform 0.2s ease;
	pointer-events: none;
	max-width: 300px;
	line-height: 1.4;
}
.cmp-toast--visible {
	opacity: 1;
	transform: translateY(0);
}

.cmp-drawer {
	position: fixed;
	bottom: 80px;
	right: 24px;
	width: min(320px, calc(100vw - 48px));
	max-height: 60vh;
	background: #16202f;
	border-radius: 8px;
	box-shadow: 0 8px 32px rgba(0,0,0,0.5);
	z-index: 499;
	display: flex;
	flex-direction: column;
	overflow: hidden;
	/* Animate open/close via opacity + transform */
	opacity: 0;
	pointer-events: none;
	transform: translateY(8px);
	transition: opacity 0.3s ease, transform 0.3s ease;
}
.cmp-drawer--open {
	opacity: 1;
	pointer-events: auto;
	transform: translateY(0);
}

.cmp-drawer-head {
	display: flex;
	align-items: center;
	justify-content: space-between;
	padding: 12px 16px;
	border-bottom: 1px solid rgba(255, 255, 255, 0.1);
	flex-shrink: 0;
}
.cmp-drawer-title {
	font-size: var(--fs-sm);
	font-weight: 700;
	color: #eef0f4;
}
.cmp-drawer-clear {
	font-size: var(--fs-xs);
	color: #8b96ac;
	background: none;
	border: none;
	cursor: pointer;
	padding: 2px 6px;
	border-radius: 3px;
}
.cmp-drawer-clear:hover { background: rgba(255, 255, 255, 0.08); }

.cmp-drawer-empty {
	padding: 20px 16px;
	font-size: var(--fs-sm);
	color: #8b96ac;
	font-weight: 300;
	line-height: 1.6;
	text-align: center;
	flex: 1;
	display: flex;
	align-items: center;
	justify-content: center;
}

.cmp-drawer-list {
	list-style: none;
	margin: 0;
	padding: 0;
	overflow-y: auto;
	flex: 1;
	scrollbar-width: thin;
	scrollbar-color: rgba(255, 255, 255, 0.25) transparent;
}
.cmp-drawer-list::-webkit-scrollbar { width: 8px; }
.cmp-drawer-list::-webkit-scrollbar-track { background: transparent; }
.cmp-drawer-list::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, 0.25); border-radius: 4px; }
.cmp-drawer-list::-webkit-scrollbar-thumb:hover { background: rgba(255, 255, 255, 0.4); }
.cmp-drawer-item {
	display: flex;
	align-items: center;
	border-bottom: 1px solid rgba(255, 255, 255, 0.1);
	position: relative;
}
.cmp-drawer-item:last-child { border-bottom: none; }
.cmp-drawer-item:hover { background: rgba(255, 255, 255, 0.05); }

/* Full-width link covers the item; remove button floats above it */
.cmp-drawer-item-link {
	display: flex;
	align-items: center;
	gap: 10px;
	padding: 8px 12px;
	flex: 1;
	min-width: 0;
	text-decoration: none;
	color: inherit;
}
.cmp-drawer-item-link:hover { text-decoration: none; color: inherit; }

.cmp-drawer-thumb {
	width: 40px;
	height: 40px;
	object-fit: cover;
	border-radius: 3px;
	background: rgba(255, 255, 255, 0.08);
	flex-shrink: 0;
}
.cmp-drawer-thumb--empty {
	display: block;
	width: 40px;
	height: 40px;
	border-radius: 3px;
	background: rgba(255, 255, 255, 0.08);
}
.cmp-drawer-info {
	flex: 1;
	min-width: 0;
}
.cmp-drawer-name {
	display: block;
	font-size: var(--fs-sm);
	color: #eef0f4;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}
/* Preserve italic formatting from displayName HTML */
.cmp-drawer-name em,
.cmp-drawer-name i { font-style: italic; }
.cmp-drawer-common {
	display: block;
	font-size: var(--fs-xs);
	color: #8b96ac;
	font-weight: 300;
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
}
.cmp-drawer-remove {
	width: 28px;
	height: 28px;
	border-radius: 3px;
	background: none;
	border: none;
	cursor: pointer;
	color: #8b96ac;
	font-size: 16px;
	display: flex;
	align-items: center;
	justify-content: center;
	flex-shrink: 0;
	position: relative;
	z-index: 1;   /* sit above the item link */
	margin-right: 8px;
}
.cmp-drawer-remove:hover { background: rgba(217, 91, 71, 0.18); color: #f28b82; }

.cmp-drawer-footer {
	padding: 10px 12px;
	border-top: 1px solid rgba(255, 255, 255, 0.1);
	flex-shrink: 0;
}
.cmp-drawer-full {
	font-size: var(--fs-xs);
	color: #8b96ac;
	margin: 0 0 8px;
}
.cmp-drawer-btn {
	display: block;
	text-align: center;
	background: #5b8dee;
	color: #fff;
	text-decoration: none;
	border-radius: 4px;
	padding: 9px 16px;
	font-size: var(--fs-sm);
	font-weight: 600;
	transition: background 0.12s, color 0.12s;
}
.cmp-drawer-btn:hover {
	background: #4a7de0;
	color: #fff;
	text-decoration: none;
}
.cmp-drawer-btn--disabled {
	background: rgba(255, 255, 255, 0.12);
	cursor: not-allowed;
	pointer-events: none;
}

/* ── Add-to-comparator buttons on taxon pages ────────────────────────────── */
.cmp-add-btn {
	display: inline-flex;
	align-items: center;
	gap: 5px;
	font-size: var(--fs-xs);
	font-weight: 600;
	color: var(--navy, #1c2b42);
	background: var(--cream-dark, #ede9e2);
	border: 1px solid var(--border-strong, #c8c0b4);
	border-radius: 4px;
	padding: 4px 10px;
	cursor: pointer;
	white-space: nowrap;
	transition: background 0.12s, color 0.12s, border-color 0.12s;
}
.cmp-add-btn:hover { background: #d6e0f0; border-color: var(--navy, #1c2b42); }
.cmp-add-btn--active {
	background: var(--navy, #1c2b42);
	color: #fff;
	border-color: var(--navy, #1c2b42);
}
/* Needs its own hover rule — otherwise the plain :hover rule above (more specific
   than a single class) wins on background while this one's white text stays,
   giving near-invisible white-on-light-blue text. */
.cmp-add-btn--active:hover {
	background: var(--navy-mid, #1a3456);
	color: #fff;
	border-color: var(--navy, #1c2b42);
}
.cmp-add-btn:disabled { opacity: 0.4; cursor: not-allowed; }

/* Overlay icon button on thumbnail cards */
.cmp-add-btn--icon {
	position: absolute;
	top: 5px;
	right: 5px;
	width: 22px;
	height: 22px;
	padding: 0;
	border-radius: 3px;
	background: rgba(255,255,255,0.85);
	border: none;
	display: flex;
	align-items: center;
	justify-content: center;
	color: var(--navy, #1c2b42);
	opacity: 0;
	transition: opacity 0.15s;
	z-index: 2;
}
.stax-card:hover .cmp-add-btn--icon,
.srch-card:hover .cmp-add-btn--icon { opacity: 1; }
.cmp-add-btn--icon.cmp-add-btn--active { opacity: 1; background: var(--navy, #1c2b42); color: #fff; }
/* Touch/no-pointer-hover devices can never trigger :hover at all, so a
   resting opacity of 0 would leave this button permanently undiscoverable
   there (invisible AND, practically, unreachable — nothing ever reveals
   it). Shown at rest instead, on both this file's light-theme cards and
   the dark-theme override (body.btt-header-page, bos-taxon-tabs.css),
   which only recolours this button and doesn't touch opacity itself. */
@media (hover: none) {
  .cmp-add-btn--icon { opacity: 1; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   MULTI-COMPARATOR MODE
   ═══════════════════════════════════════════════════════════════════════════ */

/* 2-column grid; scrolls vertically inside cmp-page */
.cmp-multi-grid {
	display: grid;
	grid-template-columns: repeat(var(--cmp-cols, 2), 1fr);
	/* 62px static header + 52px bar + 32px collapsed strip + 8px breathing room */
	grid-auto-rows: calc(100vh - 154px);
	row-gap: 16px;
	column-gap: 16px;
	width: 100%;
	min-width: 0;
	overflow-x: hidden;
	padding: 16px;
	background: #4a5568;
	box-sizing: border-box;
}

/* One cell = panel + strip; height must match grid-auto-rows */
.cmp-multi-cell {
	display: flex;
	flex-direction: column;
	height: calc(100vh - 154px);
	/* Width fixed at 3px always (not 1px, going to 3px only when
	   .cmp-multi-cell--colored, below) — a border WIDTH change shrinks
	   .cmp-multi-cell's own content box by 2px each side, which every
	   position:absolute descendant's containing block (.cmp-panel,
	   .cmp-panel-viewport) inherits, shifting their rendered position by
	   that same 2px even though their own top/right/etc. values never
	   change. That's what was moving .cmp-flag-btn (and, less
	   noticeably, everything else layered inside the cell) on every
	   category add/remove. Only the colour now varies. */
	border: 3px solid rgba(255,255,255,0.15);
	border-radius: 4px;
	position: relative;
	min-width: 0;
	overflow: hidden;
	background: #0e1520;
}

/* 3 and 4-column Layout (desktop, ≥992px): cap the WHOLE species container
   (not just the image) at a 4:5 (width:height) aspect ratio instead of
   stretching to fill the viewport's full height. Row height follows the
   cell's own height (grid-auto-rows/align-items overridden below) rather
   than the fixed 100vh-based row used in 2-column mode.
   #cmpMultiGrid[data-cols] is set in bos-comparator.js alongside --cmp-cols. */
@media (min-width: 992px) {
	#cmpMultiGrid[data-cols="3"],
	#cmpMultiGrid[data-cols="4"] {
		grid-auto-rows: min-content;
		align-items: start;
	}
	#cmpMultiGrid[data-cols="3"] .cmp-multi-cell,
	#cmpMultiGrid[data-cols="4"] .cmp-multi-cell {
		height: auto;
		aspect-ratio: 4 / 5;
	}
}

/* Portrait-orientation screens (any width — phone, tablet, or a rotated
   monitor): the viewport-height cell (base rule above) gets absurdly tall
   once height exceeds width, regardless of how many columns are showing.
   Cap every column count at a taller-than-wide 2:3 ratio instead. Placed
   after the ≥992px block above so it wins on specificity ties whenever
   both match (e.g. a tall narrow desktop window). */
@media (orientation: portrait) {
	#cmpMultiGrid[data-cols="2"],
	#cmpMultiGrid[data-cols="3"],
	#cmpMultiGrid[data-cols="4"] {
		grid-auto-rows: min-content;
		align-items: start;
	}
	#cmpMultiGrid[data-cols="2"] .cmp-multi-cell,
	#cmpMultiGrid[data-cols="3"] .cmp-multi-cell,
	#cmpMultiGrid[data-cols="4"] .cmp-multi-cell {
		height: auto;
		aspect-ratio: 2 / 3;
	}
}

/* Colour indicator: border on all sides. Colour only — width is fixed at
   3px on the base rule above, always, so toggling this class can't shift
   the cell's content box (and thus every absolutely-positioned overlay
   inside it) by changing border width. */
.cmp-multi-cell--colored {
	border-color: var(--cell-color, transparent);
}
/* ...and the label bar itself, so the category reads clearly even when the
   border is out of view (scrolled photo, small thumbnail). Text gets a dark
   outline-style shadow since the palette spans light colours (orange, grey,
   blue, peach) through dark ones (green, red, purple) — plain white text
   alone won't hold contrast across all of them. */
.cmp-multi-cell--colored .cmp-panel-label {
	background: var(--cell-color);
}
.cmp-multi-cell--colored .cmp-panel-name {
	color: #fff;
	/* text-shadow: 0 1px 2px rgba(0,0,0,0.9), 0 0 4px rgba(0,0,0,0.7); */
}

/* ── Drag-and-drop reordering ─────────────────────────────────────────── */
/* The panel being dragged — lifted look, stays in place until dropped */
.cmp-multi-cell--dragging {
	opacity: 0.4;
	outline: 2px dashed rgba(255,255,255,0.4);
	outline-offset: -2px;
}
/* Prevent the photo/other panels from intercepting hover while dragging.
   .cmp-panel-label and .cmp-panel both declare their own cursor:grab (for
   the reorder handle and the pan-drag respectively) — that's MORE specific
   to themselves than a rule targeting their .cmp-multi-cell ancestor, so it
   would otherwise win back over this and show the wrong cursor mid-drag
   whenever the pointer passes over another cell's label/photo. */
.cmp-multi-grid--dragging .cmp-multi-cell,
.cmp-multi-grid--dragging .cmp-multi-cell .cmp-panel-label,
.cmp-multi-grid--dragging .cmp-multi-cell .cmp-panel {
	cursor: grabbing;
}

/* Shared touch-tooltip bubble (wireTouchTooltip()/showTouchTooltip(),
   bos-comparator.js) — the touch replacement for native title="" tooltips,
   which some browsers (Chrome for Android especially) show on tap but
   never reliably dismiss. One shared element, repositioned/retexted for
   whichever button was last tapped; position:fixed + JS-computed
   coordinates so it can't get clipped by an ancestor's overflow:hidden
   the way an absolutely-positioned child of the trigger button sometimes
   would (several triggers — e.g. the info button — sit inside containers
   that clip for unrelated reasons, like the photo pan viewport). */
.cmp-touch-tip {
	position: fixed;
	transform: translate(-50%, calc(-100% - 8px));
	background: rgba(10,18,30,0.92);
	color: #fff;
	font-size: 12px;
	line-height: 1.4;
	padding: 5px 9px;
	border-radius: 5px;
	border: 1px solid rgba(255,255,255,0.15);
	max-width: 220px;
	text-align: center;
	white-space: normal;
	z-index: 9700;
	pointer-events: none;
	opacity: 0;
	/* opacity only (never display/visibility) so the fade in .cmp-touch-
	   tip--visible below actually transitions both ways — this element is
	   created once and reused for every tap, so there's no first-render
	   cost to always keeping it in the render tree at rest. */
	transition: opacity 0.15s;
}
.cmp-touch-tip--visible {
	opacity: 1;
}

/* Insertion line — shows exactly where the dragged panel will land,
   positioned via JS against whichever side of the hovered panel the
   pointer is over. Fixed positioning escapes the grid's own stacking. */
.cmp-drag-indicator {
	display: none;
	position: fixed;
	width: 4px;
	background: #5b8dee;
	border-radius: 2px;
	box-shadow: 0 0 8px rgba(91,141,238,0.8);
	z-index: 9500;
	pointer-events: none;
}

/* Floating "move" badge that tracks the pointer near the native grab/
   grabbing hand cursor over any drag handle (column/grid/list view) —
   see dragMoveBadge() in bos-comparator.js. */
.cmp-drag-move-badge {
	display: none;
	position: fixed;
	align-items: center;
	justify-content: center;
	width: 16px;
	height: 16px;
	z-index: 9600;
	pointer-events: none;
}

/* Panel fills available height in cell; strip takes what's left */
.cmp-multi-cell .cmp-panel {
	flex: 1;
	min-height: 0;
	min-width: 0;
	overflow: hidden;
}

.cmp-multi-cell .cmp-strip-wrap {
	flex-shrink: 0;
	min-width: 0;
	overflow-x: hidden;
}

/* ── Action overlay groups ───────────────────────────────────────────── */
.cmp-multi-actions {
	position: absolute;
	top: 44px;
	left: 10px;
	display: flex;
	flex-direction: column;
	gap: 6px;
	z-index: 25;
	opacity: 0;
	transition: opacity 0.15s;
}
.cmp-multi-cell:hover .cmp-multi-actions { opacity: 1; }

.cmp-multi-action-group {
	display: flex;
	flex-direction: column;
	gap: 4px;
	background: rgba(10,18,30,0.6);
	border-radius: 6px;
	padding: 4px;
	width: 36px;    /* fixed — exactly wide enough for the 28px btn + 4px padding each side */
	flex-shrink: 0;
}

/* ── Move to top/bottom buttons ──────────────────────────────────────── */
.cmp-multi-action-btn {
	width: 28px;
	height: 28px;
	border-radius: 4px;
	border: 1px solid rgba(255,255,255,0.2);
	background: rgba(10,18,30,0.7);
	color: rgba(255,255,255,0.85);
	font-size: 16px;
	line-height: 1;
	cursor: pointer;
	display: flex;
	align-items: center;
	justify-content: center;
	backdrop-filter: blur(4px);
	-webkit-backdrop-filter: blur(4px);
}
.cmp-multi-action-btn:hover {
	background: rgba(255,255,255,0.18);
	color: #fff;
}
.cmp-multi-action-btn:disabled {
	opacity: 0.2;
	cursor: not-allowed;
	pointer-events: none;
}

/* ── Category colour buttons ─────────────────────────────────────────── */
.cmp-multi-color-group {
	gap: 3px;
	display: flex;
	flex-direction: column;
	align-items: flex-start;
	width: 36px;          /* 28px button + 4px padding each side */
	overflow: visible;
	background: rgba(10,18,30,0.6);
	padding: 4px;
	border-radius: 14px;  /* pill to complement the circles inside */
}

.cmp-multi-color-btn {
	width: 28px;
	height: 28px;
	border-radius: 14px;             /* circle at rest (14px = half of 28px); fixed px so it doesn't recompute against width mid-transition */
	border: 2px solid rgba(255,255,255,0.15);
	background: var(--cat-color);
	cursor: pointer;
	display: flex;
	align-items: center;
	justify-content: center;
	padding: 0;
	gap: 0;
	transition: width 0.2s ease, border-radius 0.2s ease, border-color 0.15s, box-shadow 0.15s, padding 0.2s ease;
	overflow: hidden;
	white-space: nowrap;
	position: relative;
}
/* Expand-to-pill-on-hover is real-mouse-only (hover:hover and pointer:fine).
   Touch devices don't reliably clear :hover after a tap — most mobile
   browsers apply :hover on tap and leave it "stuck" until something else
   is tapped — which left this button visibly stuck expanded even after
   tapping it removed its category (multiSetColor toggles the category off
   on the same tap that triggers :hover). Scoping the expand behavior out
   on touch avoids the stuck state outright, rather than trying to
   force-clear a pseudo-class after the fact. */
@media (hover: hover) and (pointer: fine) {
	.cmp-multi-color-btn:hover {
		/* auto (not a fixed px) — category names vary in length (built-ins
		   run "Uncertain" to "Cannot Assess"; up to 3 are user-typed custom
		   names with no length limit), so any fixed width either wastes
		   space on short names or crops long ones. Sized to fit whatever
		   text is actually there instead. */
		width: auto;
		border-radius: 14px;             /* pill — same as half the height */
		border-color: rgba(255,255,255,0.5);
		justify-content: flex-start;
		padding: 0 10px 0 8px;
		gap: 6px;
	}
	.cmp-multi-color-btn:hover .cmp-color-label {
		/* max-width, not width — needs a fixed value to be transition-
		   animatable (width:auto can't transition smoothly), but wide
		   enough that no real label ever actually hits this ceiling; the
		   button's own width:auto above is what actually governs the
		   visible size for any given label. */
		max-width: 180px;
		opacity: 1;
	}
}
/* Touch has no :hover to reveal the category name at all (the rule above
   is real-mouse-only, precisely to dodge the tap-triggered stuck-:hover
   bug) — so when a category is actually assigned, flashColorBtn()
   (multiSetColor(), bos-comparator.js) adds this class instead, driven
   entirely by its own JS timeout rather than any pseudo-class, and
   removes it again after a few seconds. Same auto width/label treatment
   as the real-mouse rule above, so it reads as "the same interaction,
   just triggered differently" rather than a visibly different size. */
@media (max-width: 991px) {
	.cmp-multi-color-btn--flash {
		/* !important: the ≤991px sizing block further down this file sets
		   .cmp-multi-color-btn { width: 20px } at equal (single-class)
		   specificity and later in source order, which otherwise wins and
		   silently keeps this rule's width at 20px — only the
		   non-conflicting properties (padding/justify-content/gap) took
		   visible effect, nudging the icon without ever actually
		   expanding the button. */
		width: auto !important;
		border-radius: 14px;
		border-color: rgba(255,255,255,0.5);
		justify-content: flex-start;
		padding: 0 10px 0 8px;
		gap: 6px;
	}
	.cmp-multi-color-btn--flash .cmp-color-label {
		max-width: 180px;
		opacity: 1;
	}
}
.cmp-multi-color-btn.active {
	border-color: #fff;
	box-shadow: 0 0 0 2px rgba(255,255,255,0.4);
}
.cmp-multi-color-btn.active {
	border-color: #fff;
	box-shadow: 0 0 0 2px rgba(255,255,255,0.4);
}
.cmp-color-icon {
	font-size: 13px;
	color: #fff;
	font-weight: 700;
	text-shadow: 0 1px 2px rgba(0,0,0,0.5);
	line-height: 1;
	flex-shrink: 0;
	position: relative;
	z-index: 1;
}
.cmp-color-label {
	font-size: 11px;
	color: #fff;
	font-weight: 600;
	line-height: 1.4;    /* line-height:1 clipped descenders (g, y) against overflow:hidden below */
	white-space: nowrap;
	max-width: 0;
	overflow: hidden;
	opacity: 0;
	transition: max-width 0.2s ease, opacity 0.15s ease 0.05s;
	text-shadow: 0 1px 2px rgba(0,0,0,0.4);
}

/* Back button group — always visible, never hidden in multi mode */
.cmp-bar-group--back { flex-shrink: 0; }

/* Remove-from-comparator button in multi panel label */
.cmp-remove-btn {
	padding: 2px 5px;
	height: 22px;
	color: rgba(255,255,255,0.4);
	flex-shrink: 0;
	margin-right: 2px;
}
.cmp-remove-btn:hover {
	color: #f87171;
	background: rgba(220,53,69,0.2);
}

/* Top-right corner button stack on multi panel viewport (remove + info) */
.cmp-panel-corner-btns {
	position: absolute;
	top: 4px;
	right: 4px;
	display: flex;
	flex-direction: column;
	gap: 4px;
	z-index: 25;
	opacity: 0;
	transition: opacity 0.15s;
}
.cmp-multi-cell:hover .cmp-panel-corner-btns { opacity: 1; }
.cmp-panel-corner-btns .cmp-ctrl-btn {
	width: 26px;
	height: 26px;
	padding: 0;
	margin: 0;   /* .cmp-remove-btn's own margin-right:2px (tuned for its other
	                usage in the horizontal panel-label bar) would otherwise
	                leak in here and shift it off the shared right edge. */
	display: flex;
	align-items: center;
	justify-content: center;
	background: rgba(10,18,30,0.75);
	backdrop-filter: blur(4px);
	-webkit-backdrop-filter: blur(4px);
}

/* Flag toggle. Independent of category colour, does not affect the panel
   border. Fades in on hover like the other corner controls, but stays
   permanently visible + coloured once flagged so it reads at a glance.
   Default (not flagged, not hovering): sits below the corner-btns stack.
   Flagged + not hovering: takes over the corner-btns' own top slot, since
   remove/info are hidden then anyway. Hovering always forces it back down
   to make room for remove/info, which are now visible too — !important so
   this always wins regardless of .active. */
.cmp-flag-btn {
	position: absolute;
	top: 64px;
	right: 4px;
	width: 26px;
	height: 26px;
	display: flex;
	align-items: center;
	justify-content: center;
	background: rgba(10,18,30,0.75);
	backdrop-filter: blur(4px);
	-webkit-backdrop-filter: blur(4px);
	border: 1px solid rgba(255,255,255,0.15);
	border-radius: 4px;
	color: rgba(255,255,255,0.4);
	cursor: pointer;
	z-index: 26;
	opacity: 0;
	transition: opacity 0.15s, color 0.15s, background 0.15s, border-color 0.15s, top 0.15s;
}
.cmp-flag-btn.active { top: 4px; }
/* Real-mouse-only, same reasoning as the category-button hover-expand
   fix elsewhere in this file: touch devices don't reliably clear :hover
   after a tap, so an unscoped version of this rule could get "stuck"
   active from tapping anywhere in the cell — and because this selector's
   specificity (.cmp-multi-cell:hover .cmp-flag-btn) is higher than the
   ≤991px block's plain .cmp-flag-btn selector, its top:64px !important
   would win over that block's own !important (specificity is compared
   before source order, once both sides are !important), permanently
   overriding the mobile position the moment :hover got stuck. */
@media (hover: hover) and (pointer: fine) {
	.cmp-multi-cell:hover .cmp-flag-btn { top: 64px !important; opacity: 1; }
}
.cmp-flag-btn:hover { color: #fff; }
.cmp-flag-btn.active {
	opacity: 1;
	color: #fff;
	background: #8B5CF6;
	border-color: #8B5CF6;
}

/* Touch/no-pointer-hover devices can never trigger :hover at all, so a
   resting opacity of 0 would leave every one of these overlay controls
   permanently undiscoverable — invisible AND, practically, unreachable.
   Each rule here just mirrors that element's existing :hover-forced state,
   applied unconditionally instead of on pointer hover. */
@media (hover: none) {
	.cmp-multi-actions,
	.cmp-panel-corner-btns,
	.cmp-zoom-overlay { opacity: 1; }
	.cmp-flag-btn { top: 64px !important; opacity: 1; }

	/* Species-name tap target: on touch, the name text itself becomes
	   non-interactive (a full-width label bar of "this whole bar drags to
	   reorder, but a tap on the name navigates" was landing accidental
	   navigations while people tried to reach the overlay controls above).
	   pointer-events:none on the link makes taps pass through it to
	   .cmp-panel-label underneath (which does nothing — it's the drag
	   handle, and touch drag-to-reorder isn't implemented yet); re-enabling
	   pointer-events:auto on just the nested .cmp-label-ext icon makes that
	   one small icon, alone, still clickable — the click still bubbles up
	   to the parent <a> and navigates normally. Padding widens its actual
	   tap area beyond its small visual size; the matching negative margin
	   keeps that padding from otherwise inflating the label bar's height. */
	.cmp-label-link { pointer-events: none; }
	.cmp-label-ext {
		opacity: 0.6;
		pointer-events: auto;
		padding: 8px;
		margin: -8px;
	}
}

/* Portable devices (≤991px): the same overlay controls above were sized
   for a desktop cell several times larger than a portable one — at full
   size they read as oversized and crowd out the photo underneath. Shrink
   them, and tighten .cmp-flag-btn's "below the corner stack" offset
   (64px) to match the corner stack's own new, smaller height. */
@media (max-width: 991px) {
	/* The whole Layout picker (2/3/4-column + Grid View) — not just the
	   3/4-column options — is hidden on mobile/tablet; the grid is forced
	   to 2 columns instead (Geo.isMobileWidth(), bos-comparator.js), with
	   no way to switch away from it. Its trailing divider goes with it,
	   so the dropdown doesn't open on an orphaned blank line. */
	#cmpLayoutRow,
	#cmpLayoutRow + .cmp-bar-dropdown-divider {
		display: none;
	}
	/* .cmp-multi-cell (base rule) has border-radius:4px, rounding its top
	   corners inward — .cmp-panel-label sits flush at top:0/left:0/right:0
	   but with square corners of its own, so where the cell's border
	   curves away from the straight edge, the label's sharp corner doesn't
	   follow it, leaving a sliver at each top corner where the photo
	   behind shows through instead of the label's own dark gradient
	   background. Matching border-radius on the label's own top corners
	   (bottom stays square — it isn't at a cell corner) closes that.
	   (The matching fix for .cmp-zoom-overlay's own edges lives further
	   down this same ≤991px block, merged into its existing full-width-bar
	   rule rather than duplicated here — a second .cmp-zoom-overlay rule at
	   this point in the file would just lose to that later, equal-
	   specificity one.) */
	.cmp-panel-label {
		border-radius: 4px 4px 0 0;
	}
	/* Base rule reserves 40px off the name's width to keep long, truncated
	   names clear of .cmp-panel-corner-btns, which used to float at the
	   label bar's own top:4px right:4px — directly overlapping this row.
	   Now that those buttons (and the flag/jump/colour buttons chained off
	   them) sit well below the label on mobile/tablet, nothing at this row
	   needs clearing any more — let the name use the row's full remaining
	   width instead (drag-handle + gap + padding still bound it via normal
	   flex layout). Explicit min-width:0 alongside — a flex item's default
	   min-width is auto (effectively its unwrapped text width, since
	   white-space:nowrap keeps min-content that wide), which otherwise lets
	   it push past the row's available space instead of shrinking to it;
	   without an enforced width there text-overflow:ellipsis (base rule)
	   has nothing to trigger against and long names just spill past the
	   cell edge instead of truncating. */
	.cmp-panel-name {
		max-width: none;
		min-width: 0;
	}
	/* A previous fix tried bleeding .cmp-panel-label 1px past its own top/
	   left/right edge (top/left/right: -1px) to paper over a hairline
	   sub-pixel rounding seam against the border. That never worked: the
	   label is a descendant of .cmp-panel, which carries its own
	   overflow:hidden in multi-cell layout (clips the photo/pan viewport)
	   — .cmp-panel's padding edge sits at the exact same coordinate as the
	   border's inner edge, so ANY negative offset on something painted
	   inside it gets clipped right back to 0 before it can reach the
	   border. Patch from .cmp-multi-cell itself instead: it's the element
	   that actually owns the border, so a strip painted from its own -3px
	   (the border's own outer edge) is clipped only by the cell's own
	   overflow — i.e. by the border-radius curve we're matching — and
	   actually reaches the seam. Sits just under the label (z-index 9 vs
	   the label's 10) so it only shows through the hairline gap, not over
	   the label's own content. */
	.cmp-multi-cell::before {
		content: '';
		position: absolute;
		top: -3px; left: -3px; right: -3px;
		height: 6px;
		border-radius: 4px 4px 0 0;
		background: rgba(0,0,0,0.65);
		z-index: 9;
		pointer-events: none;
	}
	.cmp-multi-cell--colored::before {
		background: var(--cell-color);
	}
	/* Same selector/specificity as the base (non-mobile) rule of the same
	   name above (bos-comparator.css, "Zoom overlay and minimap: sit just
	   above metadata when open") — this has to come later in the file to
	   actually win, which is why it's down here with the rest of the
	   ≤991px block rather than next to the resting-state minimap clearance
	   rule earlier in this file (that one predates this selector's base
	   rule, so a copy placed there would have lost to it). Adds the same
	   44px zoom-bar clearance on top of the metadata-open offset, rather
	   than replacing it, so both stack. */
	.cmp-panel:has(.cmp-meta--open) .cmp-minimap {
		bottom: calc(var(--meta-h, 0px) + 44px);
	}
	/* 35% (base rule) was generous enough on a wide desktop panel, where
	   metadata values rarely wrap. On a narrow mobile/tablet card the value
	   column is much tighter, so even a single long field (e.g. a catalog
	   number wrapping to 2 lines) pushes a short 3-4 field list right up
	   against that cap — the last row then gets cut off exactly at
	   .cmp-meta's own bottom edge, which sits flush against the photo-strip
	   toggle below it, reading as if the strip is covering it. More room
	   here means the common case (a handful of fields, maybe one wrapped
	   line) fits without needing to scroll at all; a genuinely long list
	   still scrolls (overflow-y:auto, base rule) rather than overflowing. */
	.cmp-meta {
		max-height: 55%;
	}
	/* 80px (base rule) was sized for a desktop-width metadata drawer — on a
	   narrow mobile/tablet cell it eats too much of the row for field names
	   like "Sex" or "Date", squeezing the actual value into a sliver.
	   text-overflow:ellipsis (base rule) already handles the couple of
	   longer keys ("Identified by", "Specimen Label") that no longer fit. */
	.cmp-meta-key {
		width: 56px;
		font-size: 9px;   /* base rule: 10px */
	}
	/* .cmp-meta-row (base rule) sets font-size:var(--fs-xs)/12px for the
	   whole row, including the value text (.cmp-meta-val has no rule of
	   its own, so it just inherits that) — knock the value down to match
	   the key's own -1px trim above, without touching --fs-xs itself
	   (that's a shared site-wide variable, not scoped to this drawer). */
	.cmp-meta-val {
		font-size: 11px;
	}
	.cmp-panel-corner-btns .cmp-ctrl-btn,
	.cmp-multi-action-btn,
	.cmp-multi-color-btn {
		width: 20px;
		height: 20px;
	}
	.cmp-multi-action-group,
	.cmp-multi-color-group { width: 28px; }
	/* Corner-btns (base rule: top:4px) sit right under the species-name bar
	   at that offset on desktop, where the cell is big enough that the two
	   don't visually collide. On a small mobile/tablet cell they do — the
	   buttons float on top of (or right against) the name text. Push them
	   down to clear the label instead: single-line .cmp-panel-label is
	   padding-top(6) + line-height(14px * 1.6 = 22.4) + padding-bottom(6)
	   ≈ 34px tall, plus a 4px gap = 38px. (Multi-line/flashed state doesn't
	   need accounting for — .cmp-panel-corner-btns is hidden entirely while
	   the name is flashed, see the :has(.cmp-panel-name--flash) rule near
	   the top of this file.) */
	.cmp-panel-corner-btns {
		top: 38px;
	}
	.cmp-flag-btn {
		width: 20px;
		height: 20px;
		/* top: the corner-btns stack above it (.cmp-panel-corner-btns:
		   top:38px, two 20px buttons + one 4px gap between them = 44px of
		   content, ending at y:82) plus the SAME 4px gap = 86px, so the
		   spacing here matches both the gap within the corner-btns stack
		   and the gap between the move-to-top/bottom buttons below.
		   !important wins over the (hover:none) block's 64px !important
		   above (same specificity, later source order) and over the plain,
		   non-important .active top override elsewhere in this file (any
		   !important beats a non-important rule regardless of specificity)
		   — covers flagged and unflagged alike. .cmp-multi-cell:hover
		   .cmp-flag-btn (higher specificity, would otherwise always win
		   regardless of !important source order) is now scoped to real-
		   mouse hover:hover/pointer:fine, so it no longer competes here at
		   all on touch — previously unscoped, it got "stuck" active from
		   tapping anywhere in the cell (same sticky-:hover mechanism as the
		   category-button bug elsewhere in this file) and permanently
		   overrode this rule's top back to 64px until the page reloaded. */
		top: 86px !important;
	}
	/* Full-width bottom bar instead of a floating centred pill — flush
	   against the cell's left/right/bottom edges rather than looking like
	   a puffy separate island over the photo. left/right/bottom sit 1px
	   past 0 (into where .cmp-multi-cell's own overflow:hidden clips it
	   anyway) to paper over a sub-pixel rounding seam against the cell's
	   own border — see the .cmp-panel-label comment earlier in this same
	   block for why. */
	.cmp-zoom-overlay {
		left: -1px;
		right: -1px;
		bottom: -1px;
		transform: none;
		width: calc(100% + 2px);
		border-radius: 0;
		border-width: 1px 0 0 0;
		padding: 2px 4px;
		gap: 4px;
	}
	/* flex-shrink:0 stops the slider's flex:1 (below) squeezing these —
	   without it the row's extra flex-shrink competition left them
	   narrower than tall (oval instead of circular); aspect-ratio is a
	   belt-and-braces backstop that forces a true circle regardless. */
	.cmp-zoom-ovr-btn {
		width: 18px;
		height: 18px;
		font-size: 12px;
		flex-shrink: 0;
		aspect-ratio: 1 / 1;
	}
	/* flex:1 (replacing the old fixed width) fills the rest of the now
	   full-width .cmp-zoom-overlay bar, between the zoom buttons and the
	   reset button. padding adds vertical hit-area around the native
	   thumb/track without widening the track's own visual thickness
	   (accent-color styling, base rule) — slimmer-looking but still
	   comfortably tappable. min-width:0 overrides the flex item default
	   of min-width:auto, which otherwise floors a <input type=range> at
	   its native intrinsic width (~150px in some browsers) regardless of
	   flex-shrink — that's what was pushing the reset button (the row's
	   last item) outside the cell: the slider refused to shrink smaller
	   than that intrinsic width even though the row had less room than
	   that to give it. */
	.cmp-zoom-slider {
		flex: 1;
		width: auto;
		min-width: 0;
		padding: 5px 0;
	}
	/* Percentage readout: taken out of the pill's horizontal flow entirely
	   (position:absolute — a flex item that's absolutely positioned no
	   longer takes up space in its flex container) and hidden at rest, so
	   it no longer costs the compact pill any width. multiApplyTransform()
	   (bos-comparator.js) adds/removes cmp-zoom-label--flash for ~900ms on
	   every zoom change, so it's still readable, just as a brief tooltip
	   above the slider instead of a permanent fixture. */
	.cmp-zoom-label {
		position: absolute;
		left: 50%;
		bottom: 100%;
		transform: translateX(-50%);
		margin-bottom: 4px;
		width: auto;
		padding: 2px 6px;
		background: rgba(10,18,30,0.85);
		border: 1px solid rgba(255,255,255,0.12);
		border-radius: 4px;
		font-size: 10px;
		opacity: 0;
		pointer-events: none;
		transition: opacity 0.15s;
	}
	.cmp-zoom-label--flash { opacity: 1; }

	/* "Move to top/bottom" and category-colour buttons used to stack
	   together in one left-side column (.cmp-multi-actions). On a portable
	   cell that crowds the photo — move the jump buttons over to sit
	   alongside the flag button instead, and let the colour buttons shift
	   up into the column the jump buttons vacated. display:contents
	   removes .cmp-multi-actions from layout/paint entirely so its two
	   children can each be positioned independently against
	   .cmp-panel-viewport (the next real positioned ancestor) instead of
	   against it. */
	.cmp-multi-actions {
		display: contents;
	}
	/* Below .cmp-flag-btn (top:86px, height 20px → bottom edge at 106px;
	   110px leaves the same 4px gap used everywhere else in this stack),
	   right-aligned to match it (right:4px, same as the flag button)
	   rather than beside it. Background/padding/radius stripped — a bare
	   pair of buttons reads lighter here than another pill. Column layout
	   (base rule) keeps the pair as narrow as the flag button above it,
	   consistent with that right-edge alignment.
	   (.cmp-flag-btn's own top, in turn, chains off .cmp-panel-corner-btns'
	   top:38px — see that rule's comment earlier in this block. Any future
	   change to either needs to walk back down this same chain.) */
	.cmp-multi-action-group:not(.cmp-multi-color-group) {
		position: absolute;
		top: 110px;
		right: 4px;
		left: auto;
		background: none;
		border-radius: 0;
		padding: 0;
		width: auto;
		/* .cmp-multi-actions (base rule, above) carried z-index:25 for both
		   groups when it was their positioned ancestor — display:contents
		   (this block) drops it out of the box tree, so that z-index no
		   longer applies to anything and each group needs its own. */
		z-index: 25;
	}
	/* A move-to-top/bottom button already at that end (idx<=1 for top,
	   idx===total-1 for bottom — multiRenderPanel(), bos-comparator.js)
	   is disabled rather than removed, so desktop shows it greyed out.
	   On a portable cell that's just a dead, oddly-shaped element taking
	   up scarce space for no reason — hide it outright instead. */
	.cmp-multi-action-btn:disabled {
		display: none;
	}
	/* Flush to the cell's left edge — the outer (left-side) corners no
	   longer sit against anything, so rounding them there now reads as
	   floating rather than intentional; only the inner corners (facing
	   into the cell) stay rounded. */
	.cmp-multi-color-group {
		position: absolute;
		/* Immediately below .cmp-panel-label (the species-name bar). */
		top: 34px;
		left: 0;
		border-radius: 0 14px 14px 0;
		/* Base rule's padding:4px is even on all sides, but the space below
		   the last button was reading noticeably tighter than the space
		   above the first one — padding-bottom alone grown a bit to even
		   that out, top/right/left left at the base rule's 4px. */
		padding-bottom: 8px;
		/* See the z-index comment on the jump-button group above — same
		   reasoning, same lost-inherited-z-index fix. Kept equal (not
		   higher) so an expanded button here still paints under
		   .cmp-flag-btn/.cmp-panel-corner-btns (z-index 26/25) if it ever
		   reaches that far right on a narrow cell, rather than covering
		   them. */
		z-index: 25;
	}
}

/* ── Remove confirmation dialog ──────────────────────────────────────── */
/* ── Toasts (showToast() in bos-comparator.js) ───────────────────────── */
.cmp-toast-wrap {
	position: fixed;
	top: 16px;
	right: 16px;
	z-index: 9600;
	display: flex;
	flex-direction: column;
	gap: 8px;
	pointer-events: none;
}
.cmp-toast {
	background: #1c2b42;
	color: #fff;
	border: 1px solid rgba(255,255,255,0.15);
	border-radius: 6px;
	padding: 10px 16px;
	font-size: 13px;
	box-shadow: 0 8px 24px rgba(0,0,0,0.4);
	max-width: 320px;
}
.cmp-toast--warning { border-color: rgba(232,114,12,0.6); background: #3a2a10; }

.cmp-dialog-backdrop {
	display: none;
	position: fixed;
	inset: 0;
	background: rgba(0,0,0,0.55);
	z-index: 9000;
	align-items: center;
	justify-content: center;
}
.cmp-dialog {
	background: #1c2b42;
	border: 1px solid rgba(255,255,255,0.12);
	border-radius: 8px;
	padding: 24px;
	min-width: 300px;
	max-width: 420px;
	box-shadow: 0 16px 48px rgba(0,0,0,0.5);
}
.cmp-dialog-body { margin-bottom: 20px; }
.cmp-dialog-msg {
	color: rgba(255,255,255,0.85);
	font-size: 14px;
	line-height: 1.5;
	margin: 0;
	white-space: pre-line;
}
.cmp-dialog-footer {
	display: flex;
	justify-content: flex-end;
	gap: 8px;
}

/* ── Custom categories dialog rows ───────────────────────────────────── */
.cmp-custom-cat-row {
	display: flex;
	align-items: center;
	gap: 8px;
}
.cmp-custom-cat-row + .cmp-custom-cat-row { margin-top: 8px; }
.cmp-custom-cat-swatch {
	flex-shrink: 0;
	width: 26px;
	height: 26px;
	border-radius: 50%;
	display: flex;
	align-items: center;
	justify-content: center;
	color: #fff;
	font-size: 12px;
	font-weight: 700;
	text-shadow: 0 1px 2px rgba(0,0,0,0.5);
}
.cmp-custom-cat-input {
	flex: 1;
	min-width: 0;
	background: rgba(255,255,255,0.06);
	border: 1px solid rgba(255,255,255,0.2);
	border-radius: 4px;
	color: #fff;
	font-size: 13px;
	padding: 6px 10px;
}
.cmp-custom-cat-input::placeholder { color: rgba(255,255,255,0.35); }
.cmp-custom-cat-input:focus { border-color: #5b8dee; outline: none; }
.cmp-custom-cat-clear {
	flex-shrink: 0;
	width: 26px;
	height: 26px;
	padding: 0;
	font-size: 16px;
	line-height: 1;
}

/* ── Trait picker dialog ─────────────────────────────────────────────── */
#cmpTraitDialog .cmp-dialog-msg { text-align: left; }
.cmp-trait-picker-list {
	max-height: 320px;
	overflow-y: auto;
	text-align: left;
}
.cmp-trait-picker-category {
	display: flex;
	align-items: center;
	gap: 6px;
	width: 100%;
	background: none;
	border: none;
	font-size: 11px;
	font-weight: 600;
	text-transform: uppercase;
	letter-spacing: .06em;
	color: rgba(255,255,255,0.45);
	padding: 10px 2px 4px;
	cursor: pointer;
}
.cmp-trait-picker-category:hover { color: rgba(255,255,255,0.7); }
.cmp-trait-picker-group:first-child .cmp-trait-picker-category { padding-top: 2px; }
.cmp-trait-picker-category-arrow {
	display: inline-block;
	font-size: 9px;
	transition: transform .15s;
}
.cmp-trait-picker-category--collapsed .cmp-trait-picker-category-arrow { transform: rotate(-90deg); }
.cmp-trait-picker-row {
	display: flex;
	align-items: center;
	justify-content: flex-start;
	width: 100%;
	box-sizing: border-box;
	gap: 8px;
	padding: 6px 2px;
	font-size: 13px;
	color: rgba(255,255,255,0.85);
	text-align: left;
	cursor: pointer;
}
.cmp-trait-picker-row input.cmp-trait-picker-check {
	flex: 0 0 15px;
	width: 15px;
	height: 15px;
	min-width: 15px;
	max-width: 15px;
	margin: 0;
	padding: 0;
	box-sizing: border-box;
}
.cmp-trait-picker-row span {
	text-align: left;
	flex: 1 1 auto;
	min-width: 0;
}
.cmp-trait-picker-row:has(.cmp-trait-picker-check:disabled) {
	color: rgba(255,255,255,0.35);
	cursor: not-allowed;
}
.cmp-trait-picker-unit { color: rgba(255,255,255,0.5); }

/* Counter (left) sits alongside Close/Save (right) in the footer */
#cmpTraitDialog .cmp-dialog-footer {
	justify-content: space-between;
	align-items: center;
}
.cmp-trait-picker-count-wrap {
	display: flex;
	align-items: center;
	gap: 10px;
}
.cmp-trait-picker-count {
	font-size: 12px;
	color: rgba(255,255,255,0.5);
}
.cmp-trait-picker-reset {
	background: none;
	border: none;
	padding: 0;
	font-size: 12px;
	color: rgba(255,255,255,0.55);
	text-decoration: underline;
	cursor: pointer;
}
.cmp-trait-picker-reset:hover { color: #fff; }
.cmp-trait-picker-footer-btns {
	display: flex;
	gap: 8px;
}

/* ── List View trait columns ─────────────────────────────────────────── */
.cmp-sort-th--trait, .cmp-sort-td--trait {
	white-space: nowrap;
	text-align: left;
}


.cmp-back-btn {
	text-decoration: none;
	white-space: nowrap;
	flex-shrink: 0;
}

/* ── Bar divider ─────────────────────────────────────────────────────── */
.cmp-bar-divider {
	width: 1px;
	background: rgba(255,255,255,0.12);
	align-self: stretch;
	margin: 8px 2px;
	flex-shrink: 0;
}

/* ── Multi controls wrapper ──────────────────────────────────────────── */
.cmp-multi-controls {
	display: flex;
	align-items: center;
	gap: 6px;
	flex-wrap: nowrap;
}

/* ── Ctrl group (label + control) ────────────────────────────────────── */
.cmp-ctrl-group {
	display: flex;
	align-items: center;
	gap: 4px;
	flex-shrink: 0;
}
.cmp-ctrl-label {
	font-size: var(--fs-xs);
	color: rgba(255,255,255,0.45);
	white-space: nowrap;
}

/* ── Layout buttons (2/3/4 column icons + grid view icon) ─────────────── */
.cmp-cols-btn--unavailable { display: none; }
.cmp-cols-btns {
	display: flex;
	align-items: center;
	gap: 2px;
}
.cmp-cols-btn {
	display: flex;
	align-items: center;
	justify-content: center;
	width: 26px;
	height: 26px;
	border: 1px solid rgba(255,255,255,0.2);
	border-radius: 3px;
	background: transparent;
	color: rgba(255,255,255,0.6);
	font-size: 12px;
	cursor: pointer;
	transition: background 0.1s, color 0.1s;
}
.cmp-cols-btn svg { width: 15px; height: 15px; display: block; }
.cmp-cols-btn:hover { background: rgba(255,255,255,0.12); color: #fff; }
.cmp-cols-btn.active {
	background: #5b8dee;
	border-color: #5b8dee;
	color: #fff;
}

/* ── Danger button variant ───────────────────────────────────────────── */
.cmp-ctrl-btn--danger {
	border-color: rgba(220,53,69,0.5);
	color: #f87171;
}
.cmp-ctrl-btn--danger:hover {
	background: rgba(220,53,69,0.25);
	color: #fca5a5;
	border-color: rgba(220,53,69,0.7);
}

/* Solid filled variants — used in confirmation dialog buttons */
.cmp-ctrl-btn--danger.cmp-ctrl-btn--solid  { background: #b91c1c; color: #fff; border-color: #b91c1c; }
.cmp-ctrl-btn--danger.cmp-ctrl-btn--solid:hover  { background: #991b1b; }
.cmp-ctrl-btn--warning { background: #92400e; color: #fff; border-color: #92400e; }
.cmp-ctrl-btn--warning:hover { background: #78350f; }
.cmp-ctrl-btn--primary { background: #1d4ed8; color: #fff; border-color: #1d4ed8; }
.cmp-ctrl-btn--primary:hover { background: #1e40af; }

/* ── Tag filter panel ────────────────────────────────────────────────── */
.cmp-tag-filter-panel {
	background: var(--navy, #1c2b42);
	border-bottom: 1px solid rgba(255,255,255,0.1);
	padding: 10px 16px;
	display: flex;
	flex-wrap: wrap;
	gap: 8px 16px;
	align-items: flex-start;
	position: sticky;
	top: 52px;
	z-index: 199;
}
.cmp-tag-filter-cat {
	display: flex;
	align-items: center;
	flex-wrap: wrap;
	gap: 4px;
}
.cmp-tag-filter-cat-label {
	font-size: 11px;
	font-weight: 600;
	text-transform: uppercase;
	letter-spacing: .06em;
	color: rgba(255,255,255,0.4);
	margin-right: 2px;
	white-space: nowrap;
}
.cmp-tag-filter-chip {
	padding: 3px 8px;
	border-radius: 20px;
	border: 1px solid rgba(255,255,255,0.2);
	background: transparent;
	color: rgba(255,255,255,0.65);
	font-size: 12px;
	cursor: pointer;
	transition: background 0.1s, color 0.1s;
}
.cmp-tag-filter-chip:hover { background: rgba(255,255,255,0.1); color: #fff; }
.cmp-tag-filter-chip.active {
	background: #5b8dee;
	border-color: #5b8dee;
	color: #fff;
}

/* ── Bar hover dropdowns ─────────────────────────────────────────────── */
.cmp-bar-dropdown {
	position: relative;
	display: inline-flex;
	align-items: center;
}
.cmp-bar-dropdown-menu {
	display: none;
	position: absolute;
	top: 100%;
	left: 0;
	min-width: 160px;
	background: #1c2b42;
	border: 1px solid rgba(255,255,255,0.12);
	border-radius: 0 0 6px 6px;
	padding: 4px 0;
	z-index: 500;
	box-shadow: 0 8px 24px rgba(0,0,0,0.4);
	overflow: hidden;
}
.cmp-bar-dropdown-menu--wide { min-width: 200px; }

/* Hover on the wrapper (trigger + menu together) keeps it open — real-
   mouse-only (hover:hover and pointer:fine). Touch devices don't reliably
   clear :hover after a tap — most mobile browsers apply :hover on tap and
   leave it "stuck" until something else is tapped — which left the
   dropdown permanently open on touch: initBarDropdowns() (bos-
   comparator.js) correctly removed the --open class on a second tap, but
   the (previously unscoped) :hover half of this selector still matched on
   its own regardless, and either half satisfies an OR selector. Same bug,
   same fix, as the category-button/flag-button/cell-hover fixes elsewhere
   in this file. */
@media (hover: hover) and (pointer: fine) {
	.cmp-bar-dropdown:hover .cmp-bar-dropdown-menu { display: block; }
}
/* Touch/no-hover toggle — initBarDropdowns() (bos-comparator.js) adds/
   removes this class on click/tap. */
.cmp-bar-dropdown--open .cmp-bar-dropdown-menu { display: block; }

/* Invisible bridge between trigger bottom and menu top to prevent gap */
.cmp-bar-dropdown-menu::before {
	content: '';
	position: absolute;
	top: -6px;
	left: 0;
	right: 0;
	height: 6px;
}

.cmp-bar-dropdown-item {
	display: flex;
	align-items: center;
	gap: 10px;
	width: 100%;
	padding: 8px 14px;
	background: none;
	border: none;
	color: rgba(255,255,255,0.75);
	font-size: var(--fs-sm);
	font-family: inherit;
	text-align: left;
	cursor: pointer;
	white-space: nowrap;
	box-sizing: border-box;
}
.cmp-bar-dropdown-item:hover { background: rgba(255,255,255,0.08); color: #fff; }
/* Coloured category items keep their bg on hover, just brighten */
.cmp-bar-dropdown-item[style*="background"]:hover { filter: brightness(1.15); }
.cmp-bar-dropdown-item--danger { color: #f87171; }
.cmp-bar-dropdown-item--danger:hover { background: rgba(220,53,69,0.15); color: #fca5a5; }

.cmp-bar-dropdown-row {
	display: flex;
	align-items: center;
	gap: 10px;
	padding: 7px 14px;
}
.cmp-bar-dropdown-label {
	font-size: 12px;
	color: rgba(255,255,255,0.45);
	white-space: nowrap;
	flex-shrink: 0;
}
.cmp-bar-dropdown-divider {
	height: 1px;
	background: rgba(255,255,255,0.08);
	margin: 4px 0;
}
.cmp-bar-dropdown-menu .cmp-bar-dropdown-check,
.cmp-bar-dropdown-check {
	display: flex !important;
	flex-direction: row !important;
	align-items: center !important;
	justify-content: flex-start !important;
	gap: 8px;
	padding: 7px 14px;
	font-size: var(--fs-sm);
	color: rgba(255,255,255,0.75);
	cursor: pointer;
	white-space: nowrap;
}
.cmp-bar-dropdown-check:hover { background: rgba(255,255,255,0.06); color: #fff; }
.cmp-bar-dropdown-menu .cmp-bar-dropdown-check input[type="checkbox"],
.cmp-bar-dropdown-check input[type="checkbox"] {
	accent-color: #5b8dee;
	cursor: pointer;
	flex-shrink: 0;
	order: 0 !important;
	margin: 0 !important;
	float: none !important;
	width: auto !important;
	height: auto !important;
}

/* ── Grid View — compact square thumbnail gallery ─────────────────────────── */
/* Alternate to .cmp-multi-grid's fixed-viewport-height panels: many small
   square tiles that wrap into multiple rows, for a quick visual overview.
   Dragging a tile reorders species (there's no zoom/pan to conflict with,
   unlike the full panel view). */
.cmp-grid-gallery {
	display: grid;
	grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
	gap: 12px;
	width: 100%;
	padding: 16px;
	background: #333;
	box-sizing: border-box;
}
.cmp-grid-tile {
	position: relative;
	display: flex;
	flex-direction: column;
	border: 1px solid rgba(255,255,255,0.15);
	border-radius: 4px;
	overflow: hidden;
	background: #0e1520;
	cursor: grab;
	user-select: none;
}
.cmp-grid-tile--colored { border: 3px solid var(--cell-color, transparent); }
/* Same tinted-bar treatment as column view's .cmp-panel-label — see there
   for why the text needs a shadow rather than relying on colour contrast. */
.cmp-grid-tile--colored .cmp-grid-tile-name-row {
	background: var(--cell-color);
}
.cmp-grid-tile--colored .cmp-grid-tile-name {
	color: #fff;
	/* text-shadow: 0 1px 2px rgba(0,0,0,0.9), 0 0 4px rgba(0,0,0,0.7); */
}
.cmp-grid-tile-photo {
	position: relative;
	width: 100%;
	aspect-ratio: 1 / 1;
	background: #1c2b42;
	overflow: hidden;
}
.cmp-grid-tile-photo img {
	width: 100%;
	height: 100%;
	object-fit: cover;
	display: block;
	pointer-events: none; /* drag/click handled by the tile, not the image itself */
}
.cmp-grid-tile-name-row {
	display: flex;
	align-items: center;
	gap: 4px;
	padding: 6px 8px 8px;
}
.cmp-grid-tile-name {
	font-size: 12px;
	color: rgba(255,255,255,0.85);
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
	min-width: 0;
}
/* Taxon-page link — inline right after the name, not a floating overlay. */
.cmp-grid-tile-ext-link {
	flex-shrink: 0;
	display: flex;
	align-items: center;
	justify-content: center;
	width: 16px;
	height: 16px;
	color: rgba(255,255,255,0.4);
}
.cmp-grid-tile-ext-link:hover { color: #fff; }

/* Top-right corner stack: remove button, then flag below it — same
   background/border/backdrop-filter/hover-and-active colours as column
   view's .cmp-panel-corner-btns .cmp-ctrl-btn / .cmp-flag-btn. Both only
   show on hover, except the flag stays visible once it's active — matching
   column view exactly, just repositioned/resized for the smaller tile. */
.cmp-grid-tile-remove,
.cmp-grid-tile-flag {
	position: absolute;
	right: 6px;
	width: 20px;
	height: 20px;
	display: flex;
	align-items: center;
	justify-content: center;
	border: 1px solid rgba(255,255,255,0.15);
	border-radius: 4px;
	background: rgba(10,18,30,0.75);
	backdrop-filter: blur(4px);
	-webkit-backdrop-filter: blur(4px);
	opacity: 0;
	transition: opacity 0.15s, background 0.15s, color 0.15s, border-color 0.15s, top 0.15s;
	z-index: 4;
}
.cmp-grid-tile-remove { top: 6px;  color: rgba(255,255,255,0.4); }
/* Default (not flagged, not hovering): sits below the remove slot.
   Flagged + not hovering: takes over the remove slot, since remove is
   hidden then anyway. Hovering always forces it back down to make room for
   remove, which is now visible too — !important so this always wins
   regardless of .active, rather than depending on a selector-specificity
   chain to land the same result. */
.cmp-grid-tile-flag { top: 30px; color: rgba(255,255,255,0.4); }
.cmp-grid-tile-flag.active { top: 6px; }
.cmp-grid-tile:hover .cmp-grid-tile-flag { top: 30px !important; }
.cmp-grid-tile:hover .cmp-grid-tile-remove,
.cmp-grid-tile:hover .cmp-grid-tile-flag { opacity: 1; }
.cmp-grid-tile-remove:hover { background: rgba(220,53,69,0.2); color: #f87171; }
.cmp-grid-tile-flag:hover   { color: #fff; }
.cmp-grid-tile-flag.active {
	opacity: 1;
	color: #fff;
	background: #8B5CF6;
	border-color: #8B5CF6;
}

/* Category colour swatches — reuses column view's .cmp-multi-color-group/
   -btn as-is (same circles-that-expand-on-hover format), just repositioned
   and hover-revealed relative to the (much smaller) grid tile instead of
   the full panel. */
.cmp-grid-tile-color-group {
	position: absolute;
	top: 8px;
	left: 8px;
	z-index: 3;
	opacity: 0;
	transition: opacity 0.15s;
}
.cmp-grid-tile:hover .cmp-grid-tile-color-group { opacity: 1; }

/* Drag-and-drop reordering — mirrors .cmp-multi-cell--dragging/.cmp-multi-grid--dragging */
.cmp-grid-tile--dragging {
	opacity: 0.4;
	outline: 2px dashed rgba(255,255,255,0.4);
	outline-offset: -2px;
}
.cmp-grid-gallery--dragging .cmp-grid-tile:not(.cmp-grid-tile--dragging) {
	cursor: grabbing;
}

/* ── List View ───────────────────────────────────────────────────────────── */
.cmp-sort-list {
	background: var(--navy-dark, #0e1520);
	border-top: 3px solid #4a5568;
	padding: 24px 24px 48px;
	color: rgba(255,255,255,0.85);
}
/* Sticky-header swap: once JS marks <body> .cmp-in-list-view (List View's
   own header has reached the top of the viewport while scrolling), the top
   .cmp-bar releases its sticky pin — it scrolls away naturally since it's
   the very first element on the page — and this header becomes the sticky
   one instead, taking over the "always visible at top" role. Negative
   margins bleed it edge-to-edge to match .cmp-bar's full-width look. */
.cmp-sort-list-header {
	display: flex;
	align-items: center;
	gap: 16px;
	flex-wrap: wrap;
	position: sticky;
	top: 0;
	z-index: 200;
	background: var(--navy-dark, #0e1520);
	margin: -24px -24px 16px;
	padding: 16px 24px;
}
.cmp-sort-list-title {
	font-size: 15px;
	font-weight: 600;
	color: rgba(255,255,255,0.7);
	text-transform: uppercase;
	letter-spacing: .08em;
	margin: 0;
	flex-shrink: 0;
}
.cmp-sort-list-actions {
	display: flex;
	align-items: center;
	gap: 8px;
	flex-wrap: wrap;
}
.cmp-sort-dropdown .cmp-bar-dropdown-menu {
	top: 100%;
}

/* Submenu */
.cmp-sort-submenu { position: relative; display: block; }
.cmp-sort-submenu-trigger { width: 100%; text-align: left; }
.cmp-sort-submenu-trigger:disabled { opacity: 0.35; cursor: not-allowed; }
.cmp-sort-submenu-menu {
	display: none;
	position: absolute;
	left: 100%;
	top: 0;
	min-width: 180px;
	background: #1c2b42;
	border: 1px solid rgba(255,255,255,0.12);
	border-radius: 6px;
	padding: 4px 0;
	z-index: 700;
	box-shadow: 0 8px 24px rgba(0,0,0,0.5);
	overflow: hidden;
}
/* Show only when hovering the wrapper AND trigger is not disabled */
.cmp-sort-submenu:hover .cmp-sort-submenu-trigger:not(:disabled) ~ .cmp-sort-submenu-menu { display: block; }
.cmp-sort-submenu-menu.cmp-submenu-locked { display: none !important; }

/* Custom category picker in sort list rows — click to open, not hover */
.cmp-sort-cat-wrap { display: block; position: relative; }
.cmp-sort-cat-trigger {
	display: flex;
	align-items: center;
	gap: 10px;
	background: transparent;
	border: 1px solid rgba(255,255,255,0.15);
	border-radius: 4px;
	color: rgba(255,255,255,0.75);
	font-size: 12px;
	padding: 4px 10px;
	cursor: pointer;
	width: 100%;
	max-width: 190px;
	text-align: left;
	transition: border-color 0.1s;
}
.cmp-sort-cat-trigger:hover { border-color: rgba(255,255,255,0.35); }

/* Flagged toggle in sort list rows — icon only, binary on/off */
.cmp-sort-flag-toggle {
	display: flex;
	align-items: center;
	justify-content: center;
	width: 26px;
	height: 26px;
	background: transparent;
	border: 1px solid rgba(255,255,255,0.15);
	border-radius: 4px;
	color: rgba(255,255,255,0.5);
	cursor: pointer;
	transition: border-color 0.1s, background 0.1s, color 0.1s;
}
.cmp-sort-flag-toggle:hover { border-color: rgba(255,255,255,0.35); }
.cmp-sort-flag-toggle.active {
	background: #8B5CF6;
	color: #fff;
	border-color: transparent;
}
.cmp-sort-cat-menu {
	display: none;
	position: fixed;   /* escape all overflow:hidden ancestors */
	min-width: 190px;
	background: #1c2b42;
	border: 1px solid rgba(255,255,255,0.12);
	border-radius: 6px;
	padding: 4px 0;
	z-index: 8000;
	box-shadow: 0 8px 24px rgba(0,0,0,0.5);
	overflow: hidden;
}
.cmp-sort-cat-menu.open { display: block; }

/* Actions menu must allow submenu to overflow */
#cmpSortActionsMenu {
	overflow: visible !important;
}

/* Table */
.cmp-sort-table-wrap {
	overflow-x: auto;
	overflow-y: visible;
	border: 1px solid rgba(255,255,255,0.1);
	border-radius: 6px;
}
/* tbody must not clip the category dropdowns */
.cmp-sort-table tbody { overflow: visible; }
.cmp-sort-table tr    { overflow: visible; }
.cmp-sort-td          { overflow: visible; position: relative; }
.cmp-sort-table {
	width: 100%;
	border-collapse: collapse;
	font-size: 13px;
}
.cmp-sort-th {
	padding: 10px 14px;
	text-align: left;
	font-size: 11px;
	font-weight: 600;
	text-transform: uppercase;
	letter-spacing: .06em;
	color: rgba(255,255,255,0.45);
	border-bottom: 1px solid rgba(255,255,255,0.1);
	background: rgba(255,255,255,0.04);
	white-space: nowrap;
}
.cmp-sort-th[data-sort] { cursor: pointer; user-select: none; }
.cmp-sort-th[data-sort]:hover { color: rgba(255,255,255,0.8); }
.cmp-sort-th--check { width: 40px; }
.cmp-sort-th--cat   { width: 200px; }
.cmp-sort-th--flag  { width: 70px; }
.cmp-sort-arrow { opacity: 0.4; font-size: 10px; margin-left: 4px; }
.cmp-sort-th.asc  .cmp-sort-arrow::after { content: '▲'; }
.cmp-sort-th.desc .cmp-sort-arrow::after { content: '▼'; }
.cmp-sort-th.asc  .cmp-sort-arrow,
.cmp-sort-th.desc .cmp-sort-arrow { opacity: 1; }

.cmp-sort-row { border-bottom: 1px solid rgba(255,255,255,0.06); cursor: grab; }
.cmp-sort-row:last-child { border-bottom: none; }
.cmp-sort-row:hover { background: rgba(255,255,255,0.04); }
.cmp-sort-row.selected { background: rgba(91,141,238,0.1); }
/* Drag-and-drop reordering, same handle-anywhere-except-controls pattern as
   the grid tiles — see multiWireSortRowDrag() in bos-comparator.js. Table
   rows don't render outline/box-shadow reliably, so opacity is the "lifted"
   cue instead of the dashed outline used for cells/tiles. */
.cmp-sort-row--dragging { opacity: 0.35; cursor: grabbing; }
.cmp-sort-table--dragging .cmp-sort-row { cursor: grabbing; }

/* Brief highlight flash after a "Copy Order" action — see flashElements()
   in bos-comparator.js. Applied to List View rows (<tr>, where outline/
   box-shadow don't render reliably — background-colour does) and to
   Layout/Grid view cells/tiles (regular divs — outline shows nicely there
   too, so it's included for the extra emphasis on those). */
.cmp-order-flash {
	background-color: rgba(91,141,238,0.35) !important;
	transition: background-color 0.15s;
}
.cmp-multi-cell.cmp-order-flash,
.cmp-grid-tile.cmp-order-flash {
	outline: 2px solid rgba(91,141,238,0.9);
	outline-offset: -2px;
}

.cmp-sort-td {
	padding: 9px 14px;
	vertical-align: middle;
}
.cmp-sort-td--check { text-align: center; }
.cmp-sort-td--flag  { text-align: center; }

.cmp-sort-name-text {
	color: rgba(255,255,255,0.85);
	display: inline;
}
.cmp-sort-name-text:hover { color: #fff; }
.cmp-sort-name-text em,
.cmp-sort-name-text i { font-style: italic; }
.cmp-sort-ext-link {
	display: inline-flex;
	align-items: center;
	margin-left: 6px;
	color: rgba(255,255,255,0.35);
	text-decoration: none;
	vertical-align: middle;
}
.cmp-sort-ext-link:hover { color: rgba(255,255,255,0.8); }
.cmp-sort-ext-icon { opacity: 0.4; flex-shrink: 0; }
.cmp-sort-name-link:hover .cmp-sort-ext-icon { opacity: 0.8; }

/* Category cell */
.cmp-sort-cat-select {
	background: transparent;
	border: 1px solid rgba(255,255,255,0.15);
	border-radius: 4px;
	color: rgba(255,255,255,0.75);
	font-size: 12px;
	padding: 3px 6px;
	cursor: pointer;
	width: 100%;
	max-width: 180px;
}
.cmp-sort-cat-select:focus { outline: none; border-color: #5b8dee; }

/* Category swatch dot */
.cmp-sort-cat-dot {
	display: inline-block;
	width: 10px;
	height: 10px;
	border-radius: 50%;
	margin-right: 5px;
	vertical-align: middle;
	flex-shrink: 0;
}

/* ── List View — mobile card layout (≤991px) ─────────────────────────────
   Below the project's standard mobile/tablet breakpoint, #cmpSortTable
   stops rendering as a table and each .cmp-sort-row becomes a stacked
   card. sortListRender() (bos-comparator.js) keeps emitting the exact same
   <tr class="cmp-sort-row"><td class="cmp-sort-td">... structure it does
   on desktop — only this block changes how that structure paints. */
.cmp-sort-mobile-select { display: none; }
.cmp-sort-drag-handle { display: none; }

@media (max-width: 991px) {
	.cmp-sort-table-wrap { border: none; overflow-x: visible; }
	.cmp-sort-table,
	.cmp-sort-table tbody,
	.cmp-sort-table td {
		display: block;
	}
	/* .cmp-sort-row (below) sets this same <tr> to display:flex — it's
	   deliberately left out of the block-display reset above, since
	   ".cmp-sort-table tr" (class+tag) would otherwise outrank the single-
	   class ".cmp-sort-row" selector and force display:block back onto the
	   row, silently breaking every order/flex rule on its children below. */
	/* The column headers are meaningless once cells stack into a card —
	   sorting instead happens through the "Sort by" dropdown below
	   (.cmp-sort-mobile-select, wired in Task 3). */
	.cmp-sort-table thead { display: none; }

	.cmp-sort-row {
		display: flex;
		flex-wrap: wrap;
		align-items: center;
		column-gap: 10px;
		row-gap: 8px;
		padding: 12px;
		border: 1px solid rgba(255,255,255,0.1);
		border-radius: 6px;
		margin-bottom: 8px;
		background: rgba(255,255,255,0.03);
	}
	.cmp-sort-row:last-child { margin-bottom: 0; }

	.cmp-sort-td {
		padding: 0;
		flex: 1 1 auto;
		min-width: 0;
	}
	/* Visual order is independent of DOM/source order in a flex container,
	   so cells are reshuffled here without touching sortListRender()'s
	   markup: row 1 = checkbox, name (grows to fill, pushing flag to the
	   far right edge), flag toggle; row 2 = category (its own
	   flex-basis:100% forces it onto a new line — the same "start a new
	   row inside a flex-wrap container" technique used for
	   .cmp-bar-group--centre elsewhere in this file); then traits. */
	.cmp-sort-td--check { order: 1; flex: 0 0 auto; }
	.cmp-sort-td--name  { order: 2; flex: 1 1 auto; min-width: 0; }
	.cmp-sort-td--flag  { order: 3; flex: 0 0 auto; }
	.cmp-sort-td--cat {
		order: 4;
		flex: 0 1 auto;
		flex-basis: 100%;
	}
	.cmp-sort-cat-trigger { max-width: none; }
	/* Trait cells: two per line. column-gap is 10px between flex items, so
	   each column is half the row width minus half that gap. A trailing
	   odd trait simply occupies the left column of its own line, the right
	   column left empty — normal behaviour for a 2-column flex-wrap grid. */
	.cmp-sort-td--trait {
		order: 5;
		flex: 0 0 calc(50% - 5px);
		font-size: 12px;
		color: rgba(255,255,255,0.65);
	}
	.cmp-sort-td--trait::before {
		content: attr(data-trait-label) ": ";
		color: rgba(255,255,255,0.4);
	}

	/* "Sort by" dropdown — the mobile stand-in for clicking a now-hidden
	   <th>. See .cmp-sort-mobile-select's change handler in initSortList(),
	   bos-comparator.js (Task 3). order:-1 keeps it visually first in the
	   toolbar regardless of where it sits in the markup. */
	.cmp-sort-mobile-select {
		display: inline-block;
		order: -1;
		background: transparent;
		border: 1px solid rgba(255,255,255,0.15);
		border-radius: 4px;
		color: rgba(255,255,255,0.85);
		font-size: 12px;
		padding: 5px 8px;
	}

	/* Dedicated touch drag-to-reorder handle — see multiWireSortRowDrag's
	   deviation note (bos-comparator.js): the row background isn't a
	   reliable drag target once squeezed to card width, so this icon is
	   the only touch drag trigger here (desktop's row-background trigger
	   is unaffected — this whole element stays display:none there). */
	.cmp-sort-drag-handle {
		display: inline-flex;
		align-items: center;
		flex-shrink: 0;
		color: rgba(255,255,255,0.35);
		margin-right: 6px;
		cursor: grab;
		touch-action: none;
	}
}
