/**
 * The catalog browser.
 *
 * Every colour, size and interval is a theme.json preset, so this block takes
 * the theme's palette — including the runtime light toggle — without knowing
 * anything about it. No literal colour appears below, for the same reason none
 * appears in the theme: the palette is generated from tables that prove it at
 * WCAG AA, and a value written here would sit outside that proof.
 *
 * It also means the block degrades honestly under a theme that does not define
 * these presets: the fallbacks are the browser's own, which is legible, rather
 * than white-on-white.
 */

/* ------------------------------------------------------------------ layout
 *
 * The rail is on the RIGHT, and the results come FIRST in the document. Those
 * are one decision, not two: putting the results first lets the grid place the
 * rail in the second column without the reading order and the visual order
 * disagreeing, so a keyboard tabs through the page in the order it looks.
 *
 * It also decides the phone, where one column means source order — and source
 * order now puts products above a filter list instead of below one. A customer
 * lands on the shelf and sees the shelf. The two fragment links in the markup
 * are what makes that navigable, and the wide breakpoint hides them, where both
 * panels are on screen at once and there is nothing to jump to.
 *
 * ORDER MATTERS BELOW. The base state of those links is `inline-flex`, and the
 * rule that hides them carries no more weight than the rule that shows them, so
 * the hiding has to come last or it loses. It did lose, the first time.
 */
.ch-browser__layout {
	display: grid;
	gap: var(--wp--preset--spacing--section);
	grid-template-columns: 1fr;
}

/* Both fragment targets, so a jump does not land them under the masthead —
   the same allowance `.ch-below-masthead` makes for the same reason. */
.ch-browser__facets,
.ch-browser__results {
	scroll-margin-top: var(--wp--preset--spacing--group);
}

.ch-browser__rail-title {
	margin: 0 0 var(--wp--preset--spacing--group);
	color: var(--wp--preset--color--ink-3);
}

/* The two ways between the panels on a narrow screen. Plain links, so they
   work with scripting off and cost nothing once the wide rule hides them. */
.ch-browser__to-filters,
.ch-browser__to-results {
	display: inline-flex;
	align-items: center;
	min-height: var(--wp--custom--tap);
	font-size: var(--wp--preset--font-size--small);
	color: var(--wp--preset--color--copper-text);
}

.ch-browser__to-results {
	margin-block-end: var(--wp--preset--spacing--item);
}

/* One column: the rail follows the last card, so it needs a top edge to read
   as a panel rather than as more page. */
@media (max-width: 59.999rem) {
	.ch-browser__facets {
		padding-block-start: var(--wp--preset--spacing--group);
		border-block-start: 1px solid var(--wp--preset--color--rule);
	}
}

@media (min-width: 60rem) {
	.ch-browser__layout {
		grid-template-columns: minmax(0, 1fr) 17rem;
		/* Pins the rail to the top of the row instead of stretching it to the
		   height of the results. The `max-height` below happens to cap a
		   stretched rail too, so removing this alone does not visibly break
		   `sticky` — it is the short rail, the unfiltered view with one facet,
		   where the difference shows. `theme:rail` covers that case. */
		align-items: start;
	}

	/* A rail that scrolls away after the first screen of cards is a rail in
	   name only — these shelves run to dozens of pages. It gets its own scroll
	   because five facets stack taller than a viewport. */
	.ch-browser__facets {
		position: sticky;
		top: var(--wp--preset--spacing--group);
		max-height: calc(100vh - (2 * var(--wp--preset--spacing--group)));
		max-height: calc(100dvh - (2 * var(--wp--preset--spacing--group)));
		overflow-y: auto;
	}

	.ch-browser__to-filters,
	.ch-browser__to-results {
		display: none;
	}
}

/* While a fetch is in flight. Not a spinner — the previous results stay
   readable and simply stop accepting clicks, which is honest about the fact
   that they are still the answer to the previous question. */
.ch-browser.is-loading .ch-browser__results {
	opacity: 0.55;
	pointer-events: none;
	transition: opacity 150ms var(--wp--custom--ease);
}

/* ------------------------------------------------------------------ facets

   Every group is shut. Ten of them fit on one screen that way, and you open
   the one you want — which is what the reference shelves do and what this rail
   did not, on a shelf whose Chambering group holds 118 values. */

.ch-browser__facet {
	border: 0;
	border-block-end: 1px solid var(--wp--preset--color--rule);
	margin: 0;
	padding: 0;
}

.ch-browser__facet > summary {
	display: flex;
	align-items: center;
	gap: var(--wp--preset--spacing--item);
	min-height: var(--wp--custom--tap);
	padding: 0 var(--wp--preset--spacing--label);
	cursor: pointer;
	list-style: none;
	color: var(--wp--preset--color--ink-2);
}

.ch-browser__facet > summary::-webkit-details-marker { display: none; }
.ch-browser__facet > summary:hover { color: var(--wp--preset--color--ink); }

/* An open group is marked by its own weight. Sportsman's runs a thick coloured
   stripe down the left edge of it; a side-stripe accent is not a device this
   system uses, and the typography already carries it. */
.ch-browser__facet[open] > summary { color: var(--wp--preset--color--ink); }

/* How many are on inside a group you cannot see into. */
.ch-browser__on {
	background-color: var(--wp--preset--color--oxide);
	border: 1px solid var(--wp--preset--color--oxide-edge);
	color: var(--wp--preset--color--on-oxide);
	padding: 0 var(--wp--preset--spacing--label);
	font-variant-numeric: tabular-nums;
}

.ch-browser__chev {
	margin-inline-start: auto;
	flex: none;
	transition: transform 150ms var(--wp--custom--ease);
}

.ch-browser__facet[open] > summary .ch-browser__chev { transform: rotate(180deg); }

/* And WHICH ones. A count says something is on; only the value says what, and
   that is the thing a customer scrolls back up to check. Emitted always and
   hidden here rather than decided in PHP — opening a disclosure does not
   re-render the page, so a server-side decision would be stale the moment it
   was made. */
.ch-browser__chosen {
	margin: 0 0 var(--wp--preset--spacing--item);
	padding: 0 var(--wp--preset--spacing--label);
	font-size: var(--wp--preset--font-size--small);
	color: var(--wp--preset--color--ink-2);
}

.ch-browser__facet[open] > .ch-browser__chosen { display: none; }

.ch-browser__facet-body { padding: 0 var(--wp--preset--spacing--label) var(--wp--preset--spacing--group); }

.ch-browser__values {
	list-style: none;
	margin: 0;
	padding: 0;
	/* Long facets scroll rather than pushing the grid down the page. The cut-off
	   row at the edge is the affordance; there is no bar under it. */
	max-height: 22rem;
	overflow-y: auto;
}

.ch-browser__value {
	display: flex;
	align-items: center;
	gap: var(--wp--preset--spacing--item);
	padding: var(--wp--preset--spacing--label) var(--wp--preset--spacing--label);
	font-size: var(--wp--preset--font-size--small);
	color: var(--wp--preset--color--ink-2);
	text-decoration: none;
	transition: color 150ms var(--wp--custom--ease);
}

/* A DRAWN box, not an `<input>`. Every control in this browser is a link,
   because the whole thing works with scripting off and each state is a
   shareable URL. The affordance people recognise is drawn on top of that. */
.ch-browser__box {
	flex: none;
	width: 16px;
	height: 16px;
	border: 1px solid var(--wp--preset--color--ink-3);
	background-color: var(--wp--preset--color--paper);
	display: inline-flex;
	align-items: center;
	justify-content: center;
	font-size: var(--wp--preset--font-size--micro);
	line-height: 1;
	color: var(--wp--preset--color--on-oxide);
}

.ch-browser__value:hover { color: var(--wp--preset--color--ink); }
.ch-browser__value:hover .ch-browser__box { border-color: var(--wp--preset--color--ink); }

/* Selection is the box, not the row. Oxide ACTS — filling a whole row of a
   list you are reading down spends the one shout on the wrong thing. */
.ch-browser__value.is-selected { color: var(--wp--preset--color--ink); font-weight: 600; }
.ch-browser__value.is-selected .ch-browser__box {
	background-color: var(--wp--preset--color--oxide);
	border-color: var(--wp--preset--color--oxide-edge);
}

/* A value that exists but this selection empties. Shown, not hidden: hiding it
   would misrepresent the shelf as smaller than it is. */
.ch-browser__value.is-empty { color: var(--wp--preset--color--ink-3); cursor: default; }
.ch-browser__value.is-empty .ch-browser__box {
	border-color: var(--wp--preset--color--rule);
	background-color: var(--wp--preset--color--sunken);
}

.ch-browser__value-label { flex: 1 1 auto; min-width: 0; }

.ch-browser__count {
	flex: none;
	font-size: var(--wp--preset--font-size--micro);
	font-variant-numeric: tabular-nums;
	color: inherit;
	opacity: 0.8;
}

/* Two named halves rather than a switch, for the reason the theme control is:
   a switch makes you work out which state the picture means. */
.ch-browser__pair { display: flex; border: 1px solid var(--wp--preset--color--rule); }

.ch-browser__pair-half {
	flex: 1 1 0;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	gap: var(--wp--preset--spacing--label);
	min-height: var(--wp--custom--tap);
	padding: 0 var(--wp--preset--spacing--item);
	font-size: var(--wp--preset--font-size--small);
	color: var(--wp--preset--color--ink-2);
	text-decoration: none;
}

.ch-browser__pair-half + .ch-browser__pair-half { border-inline-start: 1px solid var(--wp--preset--color--rule); }

.ch-browser__pair-half.is-selected {
	background-color: var(--wp--preset--color--oxide);
	color: var(--wp--preset--color--on-oxide);
	font-weight: 600;
	--focus-ring: var(--wp--preset--color--on-oxide);
}

.ch-browser__clear {
	display: inline-flex;
	align-items: center;
	margin-block-end: var(--wp--preset--spacing--group);
	font-size: var(--wp--preset--font-size--small);
	color: var(--wp--preset--color--copper-text);
}

/*
 * For text that only a screen reader needs — the chips say "× " to a sighted
 * customer and "remove the maker filter" to everyone else.
 *
 * Defined here rather than borrowed. WordPress core ships a
 * `.screen-reader-text` with the block library stylesheet, and a control whose
 * label is legible only while somebody else's CSS happens to be loaded is not
 * a control with a label.
 */
.ch-browser__chip .screen-reader-text,
.ch-browser__page .screen-reader-text {
	position: absolute;
	width: 1px;
	height: 1px;
	padding: 0;
	margin: -1px;
	overflow: hidden;
	clip-path: inset(50%);
	white-space: nowrap;
	border: 0;
}

/* ------------------------------------------------------------------- chips

   What is on, above both panels. `DESIGN.md` asked for this and the rail
   shipped without it: a restriction is never something to go looking for. */

.ch-browser__chips {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var(--wp--preset--spacing--item);
	margin-block-end: var(--wp--preset--spacing--group);
}

.ch-browser__chips-label { color: var(--wp--preset--color--ink-3); }

.ch-browser__chip {
	display: inline-flex;
	align-items: center;
	gap: var(--wp--preset--spacing--item);
	min-height: 32px;
	padding: 0 var(--wp--preset--spacing--item) 0 var(--wp--preset--spacing--group);
	background-color: var(--wp--preset--color--oxide);
	border: 1px solid var(--wp--preset--color--oxide-edge);
	color: var(--wp--preset--color--on-oxide);
	font-size: var(--wp--preset--font-size--small);
	font-weight: 600;
	text-decoration: none;
	--focus-ring: var(--wp--preset--color--on-oxide);
	transition: background-color 150ms var(--wp--custom--ease);
}

.ch-browser__chip:hover { background-color: var(--wp--preset--color--oxide-deep); }
.ch-browser__chip-x { opacity: 0.85; }

.ch-browser__chip--clear {
	background-color: transparent;
	border-color: var(--wp--preset--color--rule);
	color: var(--wp--preset--color--copper-text);
	padding-inline: var(--wp--preset--spacing--group);
	--focus-ring: var(--wp--preset--color--ink);
}

.ch-browser__chip--clear:hover { background-color: var(--wp--preset--color--sunken); }

/* ----------------------------------------------------------------- results */

.ch-browser__bar {
	display: flex;
	flex-wrap: wrap;
	align-items: baseline;
	justify-content: space-between;
	gap: var(--wp--preset--spacing--group);
	padding-block-end: var(--wp--preset--spacing--item);
	border-block-end: 1px solid var(--wp--preset--color--rule);
}

.ch-browser__total {
	margin: 0;
	font-variant-numeric: tabular-nums;
}

.ch-browser__total:focus-visible {
	outline-offset: 4px;
}

.ch-browser__sort {
	display: flex;
	flex-wrap: wrap;
	gap: var(--wp--preset--spacing--label);
}

.ch-browser__sort-option {
	display: inline-flex;
	align-items: center;
	padding: var(--wp--preset--spacing--label) var(--wp--preset--spacing--item);
	font-size: var(--wp--preset--font-size--small);
	color: var(--wp--preset--color--ink-2);
	text-decoration: none;
	border-block-end: 2px solid transparent;
}

.ch-browser__sort-option.is-selected {
	color: var(--wp--preset--color--ink);
	border-block-end-color: var(--wp--preset--color--copper);
	font-weight: 600;
}

.ch-browser__grid {
	list-style: none;
	margin: var(--wp--preset--spacing--group) 0 0;
	padding: 0;
	display: grid;
	gap: var(--wp--preset--spacing--group);
	grid-template-columns: repeat(auto-fill, minmax(16rem, 1fr));
}

.ch-browser__card {
	display: flex;
	flex-direction: column;
	gap: var(--wp--preset--spacing--label);
	padding: var(--wp--preset--spacing--group);
	background-color: var(--wp--preset--color--raised);
	border: 1px solid var(--wp--preset--color--rule);
}

.ch-card__link {
	display: flex;
	flex-direction: column;
	gap: var(--wp--preset--spacing--item);
	text-decoration: none;
	color: inherit;
}

.ch-card__image {
	width: 100%;
	height: auto;
	aspect-ratio: 4 / 3;
	object-fit: contain;
	background-color: var(--wp--preset--color--sunken);
}

.ch-card__image.is-missing {
	display: block;
	aspect-ratio: 4 / 3;
	border: 1px solid var(--wp--preset--color--rule-soft);
}

.ch-card__name {
	font-weight: 600;
	line-height: 1.25;
}

.ch-card__spec,
.ch-card__stock,
.ch-card__ffl {
	margin: 0;
	font-size: var(--wp--preset--font-size--micro);
	color: var(--wp--preset--color--ink-3);
}

.ch-card__price {
	margin: 0;
	font-size: var(--wp--preset--font-size--lg);
	font-variant-numeric: tabular-nums;
	color: var(--wp--preset--color--ink);
}

/* Sage SETTLES — it is the confirmed state, and a counted shelf is exactly
   that. Out of stock is ink-3 rather than oxide: it is a fact about the shelf,
   not an alarm, and oxide is reserved for the one shout. */
.ch-card__stock {
	color: var(--wp--preset--color--sage-text);
}

.ch-card__stock.is-out {
	color: var(--wp--preset--color--ink-3);
}

/* ------------------------------------------------------------------- empty */

.ch-browser__empty {
	margin-block-start: var(--wp--preset--spacing--section);
	padding: var(--wp--preset--spacing--section) var(--wp--preset--spacing--group);
	background-color: var(--wp--preset--color--sunken);
	border-inline-start: 2px solid var(--wp--preset--color--copper);
}

.ch-browser__relax {
	display: inline-flex;
	align-items: center;
	margin-block-start: var(--wp--preset--spacing--item);
	padding: 0 var(--wp--preset--spacing--group);
	background-color: var(--wp--preset--color--oxide);
	border: 1px solid var(--wp--preset--color--oxide-edge);
	color: var(--wp--preset--color--on-oxide);
	font-weight: 600;
	text-decoration: none;
	--focus-ring: var(--wp--preset--color--on-oxide);
}

.ch-browser__relax:hover {
	background-color: var(--wp--preset--color--oxide-deep);
}

/* ------------------------------------------------------------------- pages

   Numbered, with the middle elided. The rifle shelf is 87 pages at 48 a page,
   and Previous / Next alone is not a way to reach page 60. */

.ch-browser__pages {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	justify-content: center;
	gap: var(--wp--preset--spacing--label);
	margin-block-start: var(--wp--preset--spacing--section);
	padding-block-start: var(--wp--preset--spacing--group);
	border-block-start: 1px solid var(--wp--preset--color--rule);
}

.ch-browser__page {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	min-width: var(--wp--custom--tap);
	min-height: var(--wp--custom--tap);
	padding: 0 var(--wp--preset--spacing--item);
	font-size: var(--wp--preset--font-size--small);
	font-variant-numeric: tabular-nums;
	color: var(--wp--preset--color--ink-2);
	text-decoration: none;
	border: 1px solid transparent;
}

.ch-browser__page:hover { background-color: var(--wp--preset--color--sunken); color: var(--wp--preset--color--ink); }

.ch-browser__page.is-here {
	background-color: var(--wp--preset--color--oxide);
	border-color: var(--wp--preset--color--oxide-edge);
	color: var(--wp--preset--color--on-oxide);
	font-weight: 700;
}

/* Present but not offered, so the row does not change width at the ends. */
.ch-browser__page.is-off { color: var(--wp--preset--color--ink-3); }

.ch-browser__gap {
	color: var(--wp--preset--color--ink-3);
	padding: 0 var(--wp--preset--spacing--label);
}

/* ------------------------------------------------- buy, and tick to compare

   Two controls under every card, STACKED rather than side by side, and that is
   a measurement rather than a preference. Side by side was the first version:
   a flexible button with the tick beside it, wrapping when they did not fit.
   At 1440px this catalog's cards come out 264px wide, and `npm run theme:cart`
   measured what that does over eight real products — the two longest names
   wrapped their tick onto a second line and the two shortest did not, so four
   buy buttons in one row sat at two different heights, 52px apart. A row of
   buttons that does not line up reads as a rendering fault, and it is harder
   to scan than the ragged text underneath it ever was.

   Stacked, the block is the same height on every card whatever the name does,
   so the buttons line up by construction rather than by luck of the wording.
   Both reference shelves stack them the same way.

   `DESIGN.md` reserves oxide for the one shout and names primary action as
   what it is spent on. On a card the primary action is Add to cart, so it
   takes the fill and Compare stays a quiet link beneath it. */

.ch-card__actions {
	display: flex;
	flex-direction: column;
	align-items: stretch;
	gap: var(--wp--preset--spacing--hair);
	margin-block-start: auto;
	padding-block-start: var(--wp--preset--spacing--label);
}

/* The form is a layout participant, not a box: it exists only because adding
   to a cart is a POST, and it should take exactly the space its button does. */
.ch-card__buy {
	margin: 0;
	flex: none;
}

.ch-card__add {
	width: 100%;
	min-height: var(--wp--custom--tap);
	padding: 0 var(--wp--preset--spacing--group);
	font: inherit;
	font-weight: 600;
	cursor: pointer;
	background-color: var(--wp--preset--color--oxide);
	border: 1px solid var(--wp--preset--color--oxide-edge);
	color: var(--wp--preset--color--on-oxide);
	--focus-ring: var(--wp--preset--color--on-oxide);
	transition: background-color 150ms var(--wp--custom--ease);
}

.ch-card__add:hover { background-color: var(--wp--preset--color--oxide-deep); }

/* The same drawn box the facet values use, for the same reason: this is a
   link, and the affordance is drawn on top of it. Out of stock leaves no Add
   button, so Compare takes the whole row on its own. */
.ch-card__cmp {
	flex: none;
	/* `align-self` rather than `width: auto`: the row stretches its children,
	   and a Compare link as wide as the card is a 264px target for a word. */
	align-self: flex-start;
	display: inline-flex;
	align-items: center;
	gap: var(--wp--preset--spacing--label);
	min-height: var(--wp--custom--tap);
	font-size: var(--wp--preset--font-size--small);
	color: var(--wp--preset--color--ink-2);
	text-decoration: none;
	transition: color 150ms var(--wp--custom--ease);
}

.ch-card__cmp:hover { color: var(--wp--preset--color--ink); }
.ch-card__cmp:hover .ch-browser__box { border-color: var(--wp--preset--color--ink); }

.ch-card__cmp.is-on { color: var(--wp--preset--color--ink); font-weight: 600; }
.ch-card__cmp.is-on .ch-browser__box {
	background-color: var(--wp--preset--color--oxide);
	border-color: var(--wp--preset--color--oxide-edge);
}

/* Four is the cap and this says so rather than going quiet. Dimmed like a
   zero-count facet value, because it is the same thing: present, explained,
   not offered. */
.ch-card__cmp.is-full { color: var(--wp--preset--color--ink-3); cursor: default; }
.ch-card__cmp.is-full .ch-browser__box {
	border-color: var(--wp--preset--color--rule);
	background-color: var(--wp--preset--color--sunken);
}

/* ------------------------------------------------------------------- tray

   What is ticked, and the way into the table. Sticky rather than fixed: it
   rides the bottom of the screen while the shelf is on it and then stops,
   instead of covering the footer for the rest of the page. It is last in the
   source so a keyboard reaches it after the cards it summarises. */

.ch-browser__tray {
	position: sticky;
	bottom: 0;
	z-index: 2;
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var(--wp--preset--spacing--group);
	margin-block-start: var(--wp--preset--spacing--group);
	padding: var(--wp--preset--spacing--label) var(--wp--preset--spacing--group);
	background-color: var(--wp--preset--color--raised);
	border: 1px solid var(--wp--preset--color--rule);
}

.ch-browser__tray-n {
	margin: 0;
	flex: 1 1 auto;
	font-size: var(--wp--preset--font-size--small);
	font-weight: 600;
	color: var(--wp--preset--color--ink);
}

.ch-browser__tray-go {
	display: inline-flex;
	align-items: center;
	min-height: var(--wp--custom--tap);
	padding-inline: var(--wp--preset--spacing--group);
	background-color: var(--wp--preset--color--oxide);
	border: 1px solid var(--wp--preset--color--oxide-edge);
	color: var(--wp--preset--color--on-oxide);
	font-size: var(--wp--preset--font-size--small);
	font-weight: 600;
	text-decoration: none;
	--focus-ring: var(--wp--preset--color--on-oxide);
	transition: background-color 150ms var(--wp--custom--ease);
}

.ch-browser__tray-go:hover { background-color: var(--wp--preset--color--oxide-deep); }

/* One ticked is not a comparison. Saying so beats an enabled button that
   produces a table with a single column in it. */
.ch-browser__tray-hint {
	font-size: var(--wp--preset--font-size--small);
	color: var(--wp--preset--color--ink-3);
}

.ch-browser__tray-clear {
	display: inline-flex;
	align-items: center;
	min-height: var(--wp--custom--tap);
	font-size: var(--wp--preset--font-size--small);
	color: var(--wp--preset--color--copper-text);
}

/* -------------------------------------------------------------- comparison

   It replaces the shelf, so there is no rail and no pager competing with it.
   The table scrolls sideways inside its own box rather than making the page
   do it: a horizontally scrolling PAGE loses the back link and the masthead,
   and four columns of specification will not fit a phone however they are set. */

.ch-browser__compare {
	padding: var(--wp--preset--spacing--label);
}

.ch-browser__compare-head {
	display: flex;
	flex-wrap: wrap;
	align-items: baseline;
	justify-content: space-between;
	gap: var(--wp--preset--spacing--item);
	margin-block-end: var(--wp--preset--spacing--group);
}

.ch-browser__compare-title {
	margin: 0;
	font-size: var(--wp--preset--font-size--xl);
}

/* `tabindex="0"` is on this in the markup, because a region that scrolls and
   cannot be reached by a keyboard is content a keyboard cannot read at all.
   The focus ring is the theme's, so it is visible on both grounds. */
.ch-browser__compare-scroll {
	overflow-x: auto;
	-webkit-overflow-scrolling: touch;
}

/* SEPARATE, not collapsed, and that is not a preference. The row labels below
   are sticky, and a collapsed table's borders belong to the table rather than
   to the cell — so a sticky cell scrolls out from under its own left border
   and the column bleeds through. Separate borders travel with the cell. Each
   cell draws its end and block-end edge and the table draws the two the cells
   cannot, which comes out as one 1px line everywhere. */
.ch-browser__compare-table {
	width: 100%;
	border-collapse: separate;
	border-spacing: 0;
	border-block-start: 1px solid var(--wp--preset--color--rule);
	border-inline-start: 1px solid var(--wp--preset--color--rule);
	font-size: var(--wp--preset--font-size--small);
}

.ch-browser__compare-table th,
.ch-browser__compare-table td {
	padding: var(--wp--preset--spacing--label);
	border-block-end: 1px solid var(--wp--preset--color--rule);
	border-inline-end: 1px solid var(--wp--preset--color--rule);
	text-align: start;
	vertical-align: top;
	/* The Working Density Rule: a spec table runs tighter than the prose, and
	   a column narrower than this stops being readable at four across. */
	min-width: 9rem;
}

/* The row labels. They stay put while the columns scroll, which is the whole
   reason a sideways-scrolling comparison is usable at all. Opaque, because a
   sticky cell has the next column passing underneath it. */
.ch-browser__compare-table tbody th {
	position: sticky;
	inset-inline-start: 0;
	background-color: var(--wp--preset--color--sunken);
	color: var(--wp--preset--color--ink-2);
	font-weight: 600;
	min-width: 7rem;
}

.ch-browser__compare-table thead th {
	background-color: var(--wp--preset--color--raised);
	vertical-align: top;
}

.ch-compare__link {
	display: flex;
	flex-direction: column;
	gap: var(--wp--preset--spacing--item);
	text-decoration: none;
	color: inherit;
}

.ch-compare__image {
	width: 100%;
	height: auto;
	aspect-ratio: 4 / 3;
	object-fit: contain;
	background-color: var(--wp--preset--color--sunken);
}

.ch-compare__image.is-missing {
	display: block;
	aspect-ratio: 4 / 3;
	border: 1px solid var(--wp--preset--color--rule-soft);
}

.ch-compare__name {
	font-weight: 600;
	line-height: 1.25;
}

.ch-compare__drop {
	display: inline-flex;
	align-items: center;
	min-height: var(--wp--custom--tap);
	font-size: var(--wp--preset--font-size--micro);
	color: var(--wp--preset--color--copper-text);
}

.ch-compare__price {
	font-size: var(--wp--preset--font-size--lg);
	color: var(--wp--preset--color--ink);
}

/* An em dash, and dimmed, so a blank cell reads as "the feed did not say"
   rather than as a rendering fault. Nothing is ever filled in for it. */
.ch-compare__none { color: var(--wp--preset--color--ink-3); }

/* The buy row's label is for screen readers only — a visible "Buy" above a
   row of buttons is a caption for something that captions itself. Defined here
   for the same reason the chips define theirs: a label that is legible only
   while core's block library stylesheet happens to be loaded is not a label. */
.ch-browser__compare-table .screen-reader-text {
	position: absolute;
	width: 1px;
	height: 1px;
	padding: 0;
	margin: -1px;
	overflow: hidden;
	clip-path: inset(50%);
	white-space: nowrap;
	border: 0;
}

/* Inside a cell the Add button should not stretch to a column's full width and
   dominate the table; it is one of several things being compared. */
.ch-browser__compare-table .ch-card__add { width: auto; }

/* ------------------------------------------------------ can it reach you?

   The one group in the rail that asks the customer a question instead of
   offering them the shelf's own values. A form and a button, because fifty
   places will not fit as links and a select that only works with scripting is
   the one control here that would not. */

.ch-browser__ship {
	margin: 0 0 var(--wp--preset--spacing--label);
}

.ch-browser__ship-label {
	display: block;
	margin-block-end: var(--wp--preset--spacing--hair);
	font-size: var(--wp--preset--font-size--micro);
	color: var(--wp--preset--color--ink-2);
}

.ch-browser__ship-row {
	display: flex;
	gap: var(--wp--preset--spacing--hair);
}

/* MEASURED, and not what I first wrote here.
   
   The comment this replaces said that without `min-width: 0` the select would
   take the width of its longest option — District of Columbia — and push the
   rail wider than its column. `npm run theme:cart` says otherwise: Chromium
   gives a `<select>` a fixed intrinsic width of 190px whatever is inside it,
   and removing both declarations overflows nothing at either 320px or 1440px.
   The rationale was invented and the mutation run caught it.

   What `flex: 1 1 auto` actually does is make the picker fill its row — 228px
   of a 272px rail rather than sitting at 190px with a gap after it. That is
   worth having and is the honest reason for the rule. `min-width: 0` is
   insurance for a narrower rail or a longer button label, where a flex item's
   automatic minimum size would stop it shrinking; nothing measured today
   reaches that, and no check covers it. */
.ch-browser__ship-pick {
	flex: 1 1 auto;
	min-width: 0;
	font: inherit;
	font-size: var(--wp--preset--font-size--small);
	padding: 0 var(--wp--preset--spacing--hair);
	color: var(--wp--preset--color--ink);
	background-color: var(--wp--preset--color--paper);
	border: 1px solid var(--wp--preset--color--rule);
}

/* The `min-height` is insurance rather than the thing that works: the row is a
   flex row, so the button already stretches to the select's own 44px, and
   breaking this rule to 24px changed no measurement. It stays because a font
   change that shrank the select would otherwise take the button with it, and
   the Daylight Rule is not a thing to leave to a side effect. */
.ch-browser__ship-go {
	flex: none;
	min-height: var(--wp--custom--tap);
	padding-inline: var(--wp--preset--spacing--label);
	font: inherit;
	font-size: var(--wp--preset--font-size--small);
	font-weight: 600;
	cursor: pointer;
	color: var(--wp--preset--color--ink);
	background-color: var(--wp--preset--color--sunken);
	border: 1px solid var(--wp--preset--color--rule);
	transition: background-color 150ms var(--wp--custom--ease);
}

.ch-browser__ship-go:hover { background-color: var(--wp--preset--color--raised); }

/* Nothing on this shelf is flagged for where they are. Sage SETTLES — it is
   the confirmed state, and this is the one place in the rail that confirms
   something rather than counting it. */
.ch-browser__ship-clear {
	margin: 0;
	font-size: var(--wp--preset--font-size--micro);
	color: var(--wp--preset--color--sage-text);
}

/* The disclaimer, and it is not decoration. Every word of the restriction data
   is the distributor's; this shop does not interpret statute, and the note has
   to be legible rather than technically present. Ink 3 is the dimmest the
   palette proves at AA, and the note carries no colour of its own beyond it. */
.ch-browser__ship-note {
	margin: var(--wp--preset--spacing--label) 0 0;
	font-size: var(--wp--preset--font-size--micro);
	line-height: 1.45;
	color: var(--wp--preset--color--ink-3);
}

/* On a card. Oxide ACTS, and this is the one thing on a shelf card that is
   genuinely a stop: the customer cannot have this one. It is the only card
   line that gets the pigment. */
.ch-card__blocked {
	margin: 0;
	font-size: var(--wp--preset--font-size--micro);
	font-weight: 600;
	color: var(--wp--preset--color--oxide-mark);
}

/* ------------------------------------------------ the plate under a photo

   THE BARS ARE THE POINT. Product photography of a rifle is wide — three or
   four to one — and the card frame is 4:3, so `object-fit: contain` letterboxes
   every one of them. On the live dark shelf that reads as a bright white band
   floating between two dark bars, three tones stacked in one card. Seen on the
   real site 2026-09-23, which is the first time anybody on this project had
   seen a Copper Hill product image at all.

   `DESIGN.md` already had the answer and the card was not using it:

     "A photograph sits on a white plate, because manufacturer cut-outs arrive
      on pure white and this site's paper is warm — matching the plate to the
      file turns a mismatched rectangle into a deliberate edge."

   `plate` is the token for it, and the reason it is the right one is that it
   **stays light in the dark theme** — 0.898 where `raised` drops to 0.258. It
   is the one surface in this palette that does not invert, because a
   manufacturer cut-out does not invert either.

   SO THE PHOTO IS MULTIPLIED AGAINST THE PLATE, NOT THE CARD. White ground
   times plate is plate; the letterbox is plate because the image's own ground
   is transparent; and the two are the same colour, so **there are no bars.**
   One tile, whatever shape the photograph is.

   AND IT NEEDS NO DARK-THEME BRANCH. An earlier version of this blended
   against the card and had to switch itself off in dark, where multiplying
   against near-black deletes the product. Blending against a surface that is
   light in both themes removes the special case entirely — which also means
   nothing here breaks on the day the dark theme is dropped.

   `isolation` is what confines the blend to the plate. Without it the backdrop
   is whatever stacking context happens to be nearest, which is the card or the
   page, and the dark-theme problem comes straight back.

   WHAT IT STILL CANNOT DO. A ground that is not pure white only fades: multiply
   is a ratio, so a ground at 95% of white leaves the plate at 95% of itself —
   measured, 242/240/236 lands about 5-7% dark, a faint edge rather than a
   rectangle. Only real transparency removes that, and that costs money per
   image. `npm run theme:blend` renders both cases side by side. */

.ch-card__plate {
	display: block;
	background-color: var(--wp--preset--color--plate);
	isolation: isolate;
}

.ch-card__image:not(.is-missing),
.ch-compare__image:not(.is-missing) {
	mix-blend-mode: multiply;
	background-color: transparent;
}

/* The drawn placeholder is not a photograph and `DESIGN.md` separates the two
   deliberately: "a drawing sits on the sunken tone with the rest of the
   furniture, where it reads as part of the page rather than as a picture of
   anything. Never style one to pass for the other." */
.ch-card__image.is-missing,
.ch-compare__image.is-missing {
	background-color: var(--wp--preset--color--sunken);
}
