/**
 * Shop Archive — the chassis. (T-SA-1)
 *
 * Serves `templates/archive-product.html`, which WooCommerce's block template
 * hierarchy resolves for THREE surfaces: `/shop/`, `/product-category/…/` and
 * `/product-tag/…/`. `taxonomy-product_cat` falls back to `archive-product`, so
 * there is deliberately no second template.
 *
 * ⚠ FOUR SINCE 2026-09-27: `/brands/<slug>/` (the `pwb-brand` taxonomy) is
 * routed here by `includes/brand-archive.php`. On a brand page the band shows
 * the brand name with no strip, and the rail has no Brand group. No rule in
 * this file names brands; every "three surfaces" note below predates them.
 *
 * ⚠ WHY THIS IS IN assets/css/ AND NOT patterns/shop-archive/shop-archive.css.
 *
 * Every other section's CSS in this theme is co-located with a pattern and
 * reaches the page through `ftm_enqueue_pattern_styles()`, which globs
 * `patterns/{name}/{name}.css` (includes/enqueue.php:70). This build ships NO
 * pattern, by an approved deviation from the standing rule: the archive is
 * query-bound, and `tools/create-pattern-library.php` renders every library
 * pattern on one page with no query context, where this one would draw an
 * unbound product grid or an empty box. `firsttracks-theme/blog-archive` is the
 * existing precedent for template furniture that is correctly absent from the
 * library — but that one is still a pattern, so it still gets the glob. This one
 * is not, so it needs an enqueue of its own. See includes/enqueue.php.
 *
 * It is loaded GLOBALLY and unconditionally, exactly as pattern CSS is, rather
 * than gated on `is_shop() || is_product_taxonomy()`. That is not laziness: the
 * same stylesheet has to reach the Site Editor canvas, where neither of those
 * conditionals is true and where the template still has to look like itself.
 * Every selector below is scoped under `.tc-shop-archive`, so the cost of
 * shipping it everywhere is bytes, not cascade.
 *
 * ⚠ THE CARD IS NOT DEFINED HERE. It is `.tc-product-cards` in
 * patterns/product-grid/product-grid.css, opted into by the `tc-product-cards`
 * class on the product-collection block (archive-product.html:125). That file
 * is enqueued on every page whether or not its pattern renders, so opting in
 * costs nothing and duplicates nothing. This file owns the TRACK the cards sit
 * in, and the one place the archive's card genuinely differs from the shared
 * one — the horizontal row below 48rem.
 *
 * ⚠ BREAKPOINTS ARE WRITTEN AS LITERAL REM VALUES WITH THE TOKEN NAMED IN A
 * COMMENT. CSS custom properties cannot be used inside an @media condition, and
 * the design system says so itself at
 * plugins/ftm-blocks/design-system/tokens/primitive.json:765.
 *
 * Measured from three Figma boards: 43324:19027 (1920), 43324:18324 (768),
 * 43324:17626 (320).
 *
 * @package WordPress
 * @subpackage FTM_Theme
 */

.tc-shop-archive {
	/* Chevrons for pagination prev/next. Defined on this root, not consumed
	   from a shared layer: there is no global `--tc-icon-*` layer in this theme
	   (build-knowledge §6 — the contract layer is dead), so every consumer
	   defines its own. Same stroked glyphs as blog-archive's pagination, which
	   is the control this one is a sibling of. */
	--tc-icon-chevron-left: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='m15 18-6-6 6-6'/></svg>");
	--tc-icon-chevron-right: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='m9 18 6-6-6-6'/></svg>");

	/*
	 * T-SA-3's two, both consumed through the contract slug with a local
	 * fallback — `var(--ftps-icon-x, var(--tc-icon-x))`, the house idiom
	 * (patterns/header/header.css:508). The artwork below is what actually
	 * draws, and the route it takes is worth writing down because it is not the
	 * one the syntax suggests.
	 *
	 * `assets/css/theme-tokens.css` pins these slugs on `html:root` as
	 * `--ftps-icon-x: var(--tc-icon-x)`. ⚠ THAT IS NOT A RE-POINT, IT IS A
	 * BLANKING. `--tc-icon-*` is not defined on `:root` anywhere — every
	 * consumer declares its own in its own scope — so the pin resolves to
	 * guaranteed-invalid ON <html> and inherits nothing down. Each slug is
	 * therefore EMPTY, `var()` falls through to the second argument, and the
	 * fallback below is what paints. Measured in-browser 2026-08-07:
	 * `getPropertyValue('--ftps-icon-chevron-up')` on `document.documentElement`
	 * returns the empty string, and the rendered mask is this file's data URI.
	 *
	 * ⚠ SO THE FALLBACKS ARE NOT DORMANT AND MUST NOT BE TIDIED AWAY. Written as
	 * a bare `mask: var(--ftps-icon-chevron-up)` the declaration is invalid and
	 * the pseudo-element renders as a solid currentColor SQUARE. Same trap
	 * header.css:475 records from the other direction.
	 *
	 * `chevron-up` was ADDED to that pin by this build (2026-08-07). Before it,
	 * the slug fell through to the design system's own glyph — a FILLED chevron
	 * on a 1024 viewBox — which would have sat beside the STROKED 24-viewBox
	 * chevron-down on the Show All chip, in the same 268px column, and beside the
	 * two stroked pagination arrows twenty lines up. Four chevrons, two weights,
	 * one page. Checked before adding it that nothing else on the site reads
	 * `--ftps-icon-chevron-up`; the rail's group headings are its only consumer.
	 */
	--tc-icon-chevron-up: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='m18 15-6-6-6 6'/></svg>");
	--tc-icon-chevron-down: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M5.29289 8.29289C5.65338 7.93241 6.22061 7.90468 6.6129 8.2097L6.70711 8.29289L12 13.585L17.2929 8.29289C17.6534 7.93241 18.2206 7.90468 18.6129 8.2097L18.7071 8.29289C19.0676 8.65338 19.0953 9.22061 18.7903 9.6129L18.7071 9.70711L12.7071 15.7071C12.3466 16.0676 11.7794 16.0953 11.3871 15.7903L11.2929 15.7071L5.29289 9.70711C4.90237 9.31658 4.90237 8.68342 5.29289 8.29289Z'/%3E%3C/svg%3E");

	/*
	 * T-SA-4's one — the Filters trigger's glyph (43324:18995 draws it at 16px).
	 *
	 * ⚠ THIS SLUG NEEDED THE PIN FOR A DIFFERENT REASON FROM THE CHEVRONS ABOVE,
	 * AND THE DIFFERENCE IS WORTH KNOWING. Those two were pinned because nothing
	 * consumed them and the design system's artwork would have arrived by
	 * default. `filters` is a slug the design system ACTIVELY DECLARES — and so
	 * are `filter` and `sliders`. Measured 2026-08-07 at 1280 on /shop/, all
	 * three return a `;charset=utf-8,` data URL at `:root`, i.e. the design
	 * system's own glyph, where a pinned slug returns the empty string. So
	 * without the theme-tokens.css line this fallback would never have painted
	 * and the trigger would have drawn a filled glyph beside the sheet's stroked
	 * chevrons — silently, and looking deliberate.
	 *
	 * Two tracks with offset handles, stroked, 24 viewBox, matching the family.
	 */
	--tc-icon-filters: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='M8 3v12M8 19v2M16 3v2M16 9v12'/><circle cx='8' cy='17' r='2'/><circle cx='16' cy='7' r='2'/></svg>");

	/* T-SA-4 — the filter sheet's inline padding, 16 at 320 and 40 from md. One
	   property, set here and stepped in the md query at the foot of this file,
	   so the sheet's header, group list and footer cannot drift apart. */
	--tc-sheet-pad-inline: var(--wp--preset--spacing--normal, 1rem); /* 16 */
}

/* ══ Toolbar row ════════════════════════════════════════════════════════════
   Full content-band width at every breakpoint — it is NOT inside the two-column
   body, so the sort control stays hard right against 1400 rather than against
   the 1068 grid column.

   ⚠ THE LEFT SLOT IS NO LONGER EMPTY — T-SA-3 PUT "Filter by" IN IT, which is
   what T-SA-1 reserved it for. It still holds the left end of a `space-between`
   and T-SA-4's tablet/mobile Filters button still lands here, so the note below
   about not removing it stands unchanged. The `:empty` rule twenty lines down is
   now dormant rather than dead: it is what keeps the slot holding its end of the
   row on any surface where the label is edited away.
   ────────────────────────────────────────────────────────────────────────── */

.tc-shop-archive .tc-shop-archive__toolbar {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--wp--preset--spacing--normal, 1rem); /* 16 */
	padding-block-end: var(--wp--preset--spacing--normal, 1rem); /* 16 */
	border-block-end: 1px solid var(--wp--custom--color--neutral--200, #dee2e6);

	/* DERIVED — the boards draw the divider but not the space below it. 24 is
	   the theme's standing gap between a rule and the content under it, the
	   same value blog-archive's pagination uses above its own. */
	margin-block-end: var(--wp--preset--spacing--large, 1.5rem); /* 24 */
}

/* The reserved slot, still holding its end of the row if the label is removed. */

.tc-shop-archive .tc-shop-archive__toolbar-filters:empty {
	display: block;
	min-inline-size: 0;
}

/*
 * "Filter by" — T-SA-3.
 *
 * ⚠ SAME TYPE TREATMENT AS THE PAGINATION NUMBERS, DELIBERATELY, AND THE BRIEF
 * SAYS SO. Inter Bold 16/24 at 0.032px tracking. That is `label-x-large` (1rem
 * = 16, theme.json:279) at weight 700 with `letter-spacing: 0.002em`, and
 * 0.002 x 16 = 0.032px exactly. It is NOT `label-large`, which theme.json:285
 * sets to 0.9375rem/15 — the size the sort control beside it takes.
 *
 * So the two ends of this row are deliberately DIFFERENT sizes: 16 bold on the
 * left, 15 medium on the right. Both are board values.
 */

.tc-shop-archive .tc-shop-archive__filter-by {
	margin-block: 0;
	font-family: var(--wp--preset--font-family--body);
	font-size: var(--wp--preset--font-size--label-x-large, 1rem); /* 16 */
	font-weight: 700;
	line-height: 1.5; /* 24 */
	letter-spacing: 0.002em; /* 0.032px */
	color: var(--wp--custom--color--base--black, #0a205b);
}

/*
 * ⚠ margin-block: 0 IS LOAD-BEARING — I14 follow-up, 2026-10-01. The toolbar is
 * a flex row on a group that still carries `is-layout-flow`, so core's block gap
 * (`:root :where(.wp-block-group-is-layout-flow) > *`) gave this second child a
 * 24 margin-block-start. With Woo's 13 form margin below, the row measured 78
 * against the board's 40 + 1 divider (43324:19742).
 */
.tc-shop-archive .tc-shop-archive__toolbar-sort {
	margin-block: 0;
	margin-inline-start: auto;
}

/*
 * ── THE SORT CONTROL IS THE BOARD'S BARE LABEL — I14, 2026-09-30 ────────────
 *
 * Board 43324:19027, "Input" 43324:19744: 168 × 24, radius 4, NO border, NO
 * padding, "Newest Products" Inter Bold 15/24, then an 8 gap and a 20px chevron
 * (43324:19746, the same path as `--tc-icon-chevron-down` scaled 24 → 20).
 *
 * This replaces a DERIVED skin: 1px neutral-200, 2px radius, 8 padding and
 * weight 500. Those were house consistency with the pagination and never
 * measurements, and the comment that shipped with them asked for a later board
 * reading to override them. Superseded, DECISIONS.md 2026-09-30.
 *
 * ⚠ STILL A NATIVE <select>. `appearance: none` drops only the UA arrow. The
 * control, its options, keyboard operation and the accessible name
 * ("Shop order") are Woo's own, unchanged. The chevron cannot be a pseudo on
 * the select (replaced element), so it sits on the form as a mask filled with
 * currentcolor, and `pointer-events: none` passes clicks through to the select.
 *
 * `field-sizing: content` sizes the select to the SELECTED option, so the 8 gap
 * holds whatever is chosen. Where it is unsupported the select sizes to its
 * longest option, and `text-align-last: end` still keeps the label against
 * the chevron.
 *
 * Desktop only in practice: the whole toolbar is `display: none` below lg
 * (see the 63.9375rem block), and the smartphone board draws no sort either.
 */

/* `.wc-block-catalog-sorting` lifts this to (0,3,0). At (0,2,0) it tied Woo's
   `.woocommerce .woocommerce-ordering { margin: 0 0 1em }` (woocommerce.css),
   which loads later and won: a 13 margin under the select. Woo's
   woocommerce-layout.css also floats the form right; `float: none` lets the
   wrapper hold its 24 instead of collapsing to 0 around a float. */
.tc-shop-archive .wc-block-catalog-sorting .woocommerce-ordering,
.tc-shop-archive .woocommerce-ordering {
	position: relative;
	float: none;
	margin: 0;
	color: var(--wp--custom--color--base--black, #0a205b);
}

.tc-shop-archive .woocommerce-ordering::after {
	content: "";
	position: absolute;
	inset-block-start: 2px; /* (24 - 20) / 2 */
	inset-inline-end: 0;
	width: 20px;
	height: 20px;
	background-color: currentcolor;
	pointer-events: none;

	/* stylelint-disable-next-line property-no-vendor-prefix -- mask needs the prefix pair; scoped exception per the header/media/blog-archive precedent */
	-webkit-mask: var(--ftps-icon-chevron-down, var(--tc-icon-chevron-down)) center / contain no-repeat;
	mask: var(--ftps-icon-chevron-down, var(--tc-icon-chevron-down)) center / contain no-repeat;
}

.tc-shop-archive .woocommerce-ordering select {
	appearance: none;
	field-sizing: content;
	display: block;
	block-size: 24px;
	border: 0;
	border-radius: 4px;
	background: none;

	/* 28 at the end is the chevron's 20 plus the board's 8 gap, not padding: the
	   board's own padding is 0 on every side. */
	padding: 0 28px 0 0;
	font-family: var(--wp--preset--font-family--body);
	font-size: var(--wp--preset--font-size--label-large, 0.9375rem); /* 15 */
	font-weight: 700;
	line-height: 1.6; /* 24 */
	letter-spacing: 0.002em; /* 0.03px */
	color: inherit;
	text-align: end;
	text-align-last: end;
	cursor: pointer;
}

/*
 * One ring, not two. The ftm-blocks design system also answers
 * `select:focus-visible` with `box-shadow: var(--ftm-focus-ring)` and an accent
 * border (elements.css:142-148). This rule's outline wins its own property but
 * never cleared the shadow, so the old select drew two concentric rings.
 */
.tc-shop-archive .woocommerce-ordering select:focus-visible {
	outline: 2px solid var(--wp--custom--color--secondary--600, #057db9);
	outline-offset: 2px;
	box-shadow: none;
}

/* ══ The two-column body ════════════════════════════════════════════════════
   ⚠ A CSS GRID ON A core/group, NOT core/columns. `core/columns` is
   grid-with-inert-flex-basis site-wide (environment-facts §14) and cannot
   express a fixed 268px track beside a fluid one.

   Below 64rem the body is a single column, so the rail is not a column there
   and cannot be allowed to eat a 268px track.

   ⚠ THE RULE BELOW USED TO BE `display: none` AND T-SA-4 REWROTE IT. That is
   the single change that makes the drawer possible, so it is recorded here
   rather than only in the section that consumes it.

   T-SA-1 and T-SA-3 hid this element outright below lg, which was right while
   there was nothing below lg to show. The sheet's contents ARE this rail, so
   `display: none` would open an empty sheet — the whole element is Woo's
   `product-filters` block, trigger and overlay together.

   ⚠ AND IT DOES NOT NEED TO BECOME CONDITIONAL ON THE OVERLAY'S OPEN STATE,
   WHICH IS WHAT THE OBVIOUS FIX WOULD BE. WooCommerce already hides the
   contents itself, in `product-filters-style.css`: below 601px the overlay is
   `position: fixed` and `translateY(100vh)`. Nothing here has to hide them.
   What DOES have to happen is undoing Woo's own `min-width: 601px` block for
   the 601-1023 window, where it switches the overlay back to a static column —
   see the T-SA-4 section at the foot of this file.

   So below lg this element stops being a rail and becomes the TOOLBAR ROW the
   tablet and mobile boards draw (43324:18994 / 43324:18291): the trigger left,
   the result count right. Its box is set in that section, with the rest of the
   sheet, rather than split across two places.
   ────────────────────────────────────────────────────────────────────────── */

.tc-shop-archive .tc-shop-archive__body {
	display: grid;
	grid-template-columns: minmax(0, 1fr);
}

/* ══ THE FILTER RAIL — T-SA-3 ═══════════════════════════════════════════════
   Board 43324:19694, the ONLY board that draws the rail open. There is no frame
   PNG for it in docs/design/, so every number below is from the task brief's
   measurements and NOT from a compared screenshot.

   ⚠ THE RAIL IS BUILT FOR lg AND UP ONLY. `.tc-shop-archive__rail` is
   `display: none` below 64rem (above), so nothing in this section renders
   there — including WooCommerce's own `position: fixed` overlay, which it
   switches to below 601px. The tablet/mobile Filters button and drawer are
   T-SA-4's, and the tablet and mobile boards draw a button and NO panel.

   ── WHAT WOOCOMMERCE OWNS AND WHAT THIS FILE OWNS ─────────────────────────

   ⚠ NO FILTERING LOGIC IS WRITTEN HERE OR ANYWHERE IN THIS BUILD. Filtering,
   sorting, pagination and the query stay WooCommerce's. Verified end to end
   rather than assumed — see the report's §2:
     /shop/                                 1,167 results, Page 1 of 25
     /shop/?filter_pwb-brand=aja-video-systems   37 results, 37 cards
     /shop/?min_price=0&max_price=50            Page 1 of 16
   The binding works because `MainQueryController` adds Woo's filter params to
   `query_vars` and clauses them onto the MAIN query, and T-SA-1's collection
   is `inherit: true`. That is also why the filters can sit OUTSIDE the
   collection, in the rail: `ProductCollection\Utils::get_query_vars()` falls
   through to the global `$wp_query` when there is no `query` context, and
   `ProductFilters::get_filter_params()` reads REQUEST_URI directly.

   ── WHY ALL THREE GROUPS ARE INSIDE woocommerce/product-filters ────────────

   Brand and Price have no choice: both declare
   `ancestor: ["woocommerce/product-filters"]`, and both need the `filterParams`
   context only that block provides. Category is a theme block and could have
   sat outside it. It does not, for two reasons that are structural rather than
   stylistic:

     1. THE DIVIDERS NEED ONE SIBLING CHAIN. "Divider between groups, not above
        the first" is an adjacent-sibling rule. Split across two parents it
        stops being expressible in CSS at all.
     2. T-SA-4 INHERITS THE WHOLE RAIL FOR FREE. Below 601px WooCommerce moves
        its inner blocks into a real drawer; anything left outside would have to
        be moved there by hand.

   ⚠ THE COST, RECORDED RATHER THAN HIDDEN: WooCommerce wraps its inner blocks
   in `role="dialog" aria-label="Product Filters"`, and at desktop that dialog is
   permanently open and not modal. So the whole rail — Category included — is
   announced as a dialog. That is Woo's own markup for Brand and Price either
   way; putting Category inside widens it by one group rather than introducing
   it. Not fixable from a stylesheet, and on the maintainer list.
   ────────────────────────────────────────────────────────────────────────── */

/*
 * Woo's own container is `display: inline-flex`, switching to `flex` at 601px
 * (product-filters-style.css). Both shrink-to-fit inside the 268 track, so the
 * rail is told to fill it. Its overlay button, header and footer are already
 * `display: none` above 600 by Woo itself and are NOT re-hidden here — a second
 * copy of a rule someone else already ships is a rule that goes stale silently.
 *
 * `--wc-product-filter-block-spacing` is Woo's own gap variable between the
 * groups. It goes to ZERO because the rhythm is the groups' own 24 padding plus
 * the divider; leaving Woo's `--wp--style--block-gap` fallback in place would
 * add a fourth, invisible space to a spacing spec that names three.
 */

.tc-shop-archive .tc-shop-archive__rail .wc-block-product-filters {
	--wc-product-filter-block-spacing: 0;

	inline-size: 100%;
}

/* ── The shared group chrome ─────────────────────────────────────────────────
 * padding-block 24, gap 16 between the header row and the group's body.
 * ─────────────────────────────────────────────────────────────────────────── */

.tc-shop-archive .tc-shop-filter-group {
	display: flex;
	flex-direction: column;
	gap: var(--wp--preset--spacing--normal, 1rem); /* 16 */
	padding-block: var(--wp--preset--spacing--large, 1.5rem); /* 24 */
}

/*
 * ⚠ THE 16 GAP ABOVE WAS NOT THE GAP THAT RENDERED — I15, 2026-09-30. The
 * taxonomy and price blocks each bring a 24px top margin of their own, and a
 * flex gap adds to a margin rather than absorbing it, so the desktop rail drew
 * the heading row and the first option 40 apart where board 43324:19694
 * draws 16 (Frame 173 ends at 48, Frame 79 starts at 64). The spec only ever
 * read the computed `row-gap`, so it passed at 16 while the rail rendered 40.
 * The drawer already cancelled the same margin in its own block (below lg).
 * This is that fix at every width.
 */
.tc-shop-archive .tc-shop-filter-group__title + * {
	margin-block-start: 0;
}

/*
 * The divider: between groups, never above the first.
 *
 * ⚠ "THE FIRST" IS NOT ALWAYS THE FIRST ELEMENT, which is the whole reason
 * there are three rules here rather than one. Category renders NOTHING on
 * `/shop/` — one template, three surfaces, and only a term archive has children
 * to list. An adjacent-sibling selector still matches a `display: none`
 * element, so `+` alone would draw a divider above Brand on every `/shop/` page
 * with nothing above it.
 *
 * Each suppressed-head case is therefore written out explicitly rather than
 * inferred, and the selectors are ANCHORED ON CATEGORY rather than on "any
 * suppressed group". ⚠ The looser version is wrong and the difference is not
 * academic: `.tc-shop-filter-group:has(--hidden) + .tc-shop-filter-group` would
 * strip Price's divider whenever BRAND is suppressed — including on a term
 * archive where Category is right there above it, visible, needing the rule.
 * "No group above me" is the condition, not "the group directly above me is
 * hidden".
 *
 * With only three groups in a fixed order the whole truth table is three rules:
 * Category never takes one (nothing precedes it), Brand loses it when Category
 * is suppressed, and Price loses it only when Category AND Brand both are.
 */

.tc-shop-archive .tc-shop-filter-group + .tc-shop-filter-group {
	border-block-start: 1px solid var(--wp--custom--color--neutral--200, #dee2e6);
}

.tc-shop-archive
	.tc-shop-filter-group--category:not(:has(.tc-category-filter))
	+ .tc-shop-filter-group {
	border-block-start: 0;
}

/*
 * `wc-block-product-filter--hidden` is the class WooCommerce puts on a taxonomy
 * or price filter that resolved to nothing — no terms in the current loop, or a
 * price range with min === max. It carries `hidden` too, so Woo's own filter is
 * gone; only the theme's heading and padding would be left behind.
 *
 * Both links of the chain are required here, so this fires only when there is
 * genuinely nothing visible above Price.
 */

.tc-shop-archive
	.tc-shop-filter-group--category:not(:has(.tc-category-filter))
	+ .tc-shop-filter-group:has(.wc-block-product-filter--hidden)
	+ .tc-shop-filter-group {
	border-block-start: 0;
}

/* Suppress the whole group — heading, padding and all — when its filter is. */

.tc-shop-archive .tc-shop-filter-group:has(.wc-block-product-filter--hidden),
.tc-shop-archive
	.tc-shop-filter-group--category:not(:has(.tc-category-filter)) {
	display: none;
}

/*
 * The header row: label left, chevron right, space-between.
 *
 * ⚠ THE DESKTOP CHEVRON IS GONE AS OF 2026-08-07 (T-SA-5), AND THE RULE BELOW
 * IS NOW SCOPED BELOW lg RATHER THAN DELETED. Read this before touching either.
 *
 * T-SA-3 drew a decorative chevron-up in every rail heading because the board
 * does, and flagged it: the rail's groups are ALWAYS expanded, so it was a
 * control-shaped thing that is not a control. Gary ruled it dropped — it now
 * reads identical to the working chevron in the tablet/mobile sheet, which IS a
 * control. That closes T-SA-3 open item 4 and T-SA-4 open item 3.
 *
 * The rule was UNSCOPED, so it reached every width. It is now scoped: below lg
 * unchanged, at lg and up not drawn at all. The max-width query is the whole of
 * the change.
 *
 * ⚠ CORRECTION, MEASURED 2026-08-07 — T-SA-4 §10.2 OVERSTATED WHAT SCOPING
 * SAVES, AND THE T-SA-5 BRIEF INHERITED THE OVERSTATEMENT. That section says
 * that with JS off below lg "the <h3>'s own chevron-up SHOWS over a list that
 * really is expanded", which read as: deleting this rule removes the only no-JS
 * expansion indicator there is.
 *
 * Measured at 768 with `javaScriptEnabled: false`: the `::after` IS still
 * generated (`content: ""`, 24px, this theme's artwork) — but it is NOT ON
 * SCREEN. The whole rail lives inside WooCommerce's overlay, which below lg is a
 * closed sheet computing `visibility: hidden` with its dialog parked at
 * `translateY(100vh)`, and with JS off the trigger is inert so the sheet cannot
 * be opened at all (T-SA-3 §5). Nobody sees this glyph on that path.
 *
 * ⚠ SCOPING IS STILL RIGHT, FOR A WEAKER AND HONESTER REASON. It costs one
 * media query, it keeps T-SA-4's stated intent intact, and it becomes genuinely
 * load-bearing the moment either the closed sheet's `visibility` changes or a
 * no-JS path for opening the sheet ships. What it did NOT do is rescue a
 * visible affordance. Both facts are pinned by assertion 11 of
 * e2e/product-card-badge-verify.spec.ts, including the `visibility: hidden`, so
 * that if either premise moves the reasoning gets re-read rather than inherited.
 *
 * Type: Inter Bold 16/24 at 0.032px tracking — `label-x-large` at 700 with
 * `letter-spacing: 0.002em`, the same treatment as the pagination numbers and
 * as "Filter by". ⚠ NOT `label-large`, which is 15px (theme.json:285). The type
 * is untouched by the chevron removal; only the `::after` moved.
 */

.tc-shop-archive .tc-shop-filter-group__title {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--wp--preset--spacing--xsmall, 0.5rem); /* 8 */
	margin-block: 0;
	font-family: var(--wp--preset--font-family--body);
	font-size: var(--wp--preset--font-size--label-x-large, 1rem); /* 16 */
	font-weight: 700;
	line-height: 1.5; /* 24 */
	letter-spacing: 0.002em; /* 0.032px */
	color: var(--wp--custom--color--base--black, #0a205b);
}

@media (max-width: 63.9375rem) { /* below breakpoint/lg */

	.tc-shop-archive .tc-shop-filter-group__title::after {
		content: "";
		flex: 0 0 auto;
		width: 24px;
		height: 24px;
		background-color: currentcolor;

		/* stylelint-disable-next-line property-no-vendor-prefix -- mask needs the prefix pair; scoped exception per the header/media/blog-archive precedent */
		-webkit-mask: var(--ftps-icon-chevron-up, var(--tc-icon-chevron-up)) center / contain no-repeat;
		mask: var(--ftps-icon-chevron-up, var(--tc-icon-chevron-up)) center / contain no-repeat;
	}
}

/* ── Collapse ───────────────────────────────────────────────────────────────
 * assets/js/shop-filters-sheet.js upgrades each heading to a real button, and
 * injects nothing with JS off — the same shape as patterns/footer/footer.js.
 *
 * ⚠ HOISTED OUT OF THE BELOW-lg MEDIA QUERY, 2026-08-31. These five rules
 * used to live inside `@media (max-width: 63.9375rem)` because T-SA-4 only
 * built the collapse for the drawer; a design review asked for the same
 * affordance on the desktop rail, so the button now exists at every width and
 * its appearance has to as well. Nothing about the rules themselves changed —
 * they were de-indented and moved, and the diff should read that way.
 *
 * The DEFAULT STATE still differs by width and that is handled in the JS, not
 * here: the drawer opens closed (board 43869:24354), the rail opens open. See
 * the comment on apply() in shop-filters-sheet.js.
 *
 * ⚠ THE CHEVRON MOVES ONTO THE BUTTON, and that is not tidiness. T-SA-3
 * draws it as `::after` on the <h3>, outside where the button goes, which
 * would leave a 24px target that looks like the control and is not. On the
 * button the whole row is the target.
 *
 * ⚠ AND IT POINTS DOWN, NOT UP. Chevron-down is the "can be opened" glyph and
 * rotating it 180° is the "is open" one, so one glyph carries both states.
 * The <h3>'s own decorative chevron-up survives below lg for the JS-off case
 * only, where the list really is permanently expanded — and it is suppressed
 * by the `content: none` rule below the moment a button appears, at any width.
 *
 * ⚠ DEVIATION, RECORDED: THE COMP DOES NOT ROTATE IT. On 43869:24352 Brand
 * is expanded and still draws chevron-down, identical to collapsed Category
 * beside it. Shipping a static chevron would mean shipping a control whose
 * only state indicator is the presence of the list itself.
 */
.tc-shop-archive
	.tc-shop-filter-group__title:has(.tc-shop-filter-group__toggle)::after {
	content: none;
}

.tc-shop-archive .tc-shop-filter-group__toggle {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--wp--preset--spacing--xsmall, 0.5rem);
	border: 0;
	background: none;
	padding: 0;
	inline-size: 100%;
	font: inherit;
	letter-spacing: inherit;
	color: inherit;
	text-align: start;
	cursor: pointer;
}

.tc-shop-archive .tc-shop-filter-group__toggle::after {
	content: "";
	flex: 0 0 auto;
	width: 24px;
	height: 24px;
	background-color: currentcolor;
	transition: transform 0.2s var(--wp--custom--easing--standard, ease);

	/* stylelint-disable-next-line property-no-vendor-prefix -- mask needs the prefix pair; scoped exception per the header/media/blog-archive precedent */
	-webkit-mask: var(--ftps-icon-chevron-down, var(--tc-icon-chevron-down)) center / contain no-repeat;
	mask: var(--ftps-icon-chevron-down, var(--tc-icon-chevron-down)) center / contain no-repeat;
}

.tc-shop-archive .tc-shop-filter-group__toggle[aria-expanded="true"]::after {
	transform: rotate(180deg);
}

.tc-shop-archive .tc-shop-filter-group__toggle:focus-visible {
	outline: 2px solid var(--wp--custom--color--secondary--600, #057db9);
	outline-offset: 2px;
}

/* ── CATEGORY — navigation, not a filter ─────────────────────────────────────
 * ⚠ PLAIN LINKS WITH COUNTS AND NO CHECKBOXES, AND THAT IS THE POINT. The
 * board's Category group lists the CHILDREN of the category being viewed and
 * navigates to them; it does not narrow the current result set. Verified
 * against real data: the board's "Analog (14)" is term 582, parent 581 = "Video
 * Signal Processing", count 14 — and the board's description panel reads "About
 * Signal Processing". The other three labels it draws (Digital 57, Video 100,
 * Racking 233) match no term on this site and are comp filler; the four real
 * siblings are Accessories (68), Analog (14), CMTS (3), Conditional Access (3)
 * and Digital / IPTV (256).
 *
 * It is `firsttracks-theme/category-filter` with `source: "children"` and
 * `showCount: true` — an ADDITIVE extension, defaults untouched, so the live
 * blog tab bar at patterns/blog-archive/blog-archive.php:92 is unchanged. The
 * horizontal tab bar's own styling lives in blog-archive.css scoped under
 * `.tc-blog-archive`, so nothing there reaches here and nothing here reaches
 * there. This is the `is-style-rail` variation and it starts from nothing.
 * ─────────────────────────────────────────────────────────────────────────── */

.tc-shop-archive .tc-category-filter__list {
	display: flex;
	flex-direction: column;
	gap: var(--wp--preset--spacing--normal, 1rem); /* 16 between links */
	margin: 0;
	padding: 0;
	list-style: none;
}

.tc-shop-archive .tc-category-filter__item {
	margin: 0;
}

.tc-shop-archive .tc-category-filter__link {
	display: block;
	font-family: var(--wp--preset--font-family--body);
	font-size: var(--wp--preset--font-size--label-medium, 0.875rem); /* 14 */
	font-weight: 500;
	line-height: 1.4286; /* 20 */
	color: var(--wp--custom--color--base--black, #0a205b);
	text-decoration: none;
}

.tc-shop-archive .tc-category-filter__link:hover,
.tc-shop-archive .tc-category-filter__link.is-current {
	text-decoration: underline;
}

.tc-shop-archive .tc-category-filter__link:focus-visible {
	outline: 2px solid var(--wp--custom--color--secondary--600, #057db9);
	outline-offset: 2px;
}

/* ── BRAND — woocommerce/product-filter-taxonomy on pwb-brand ────────────────
 * ⚠ pwb-brand, NOT product_brand, AND THE DIFFERENCE IS DATA. Measured
 * 2026-08-07: `pwb-brand` has 76 terms and carries every product's brand;
 * WooCommerce 9.4's own `product_brand` has ONE term with ONE product and is an
 * empty artifact of the core taxonomy being registered. A non-Woo taxonomy is
 * fine here because `ProductFilterTaxonomy::render()` takes its taxonomy from an
 * attribute and resolves it through `get_taxonomy()` — and because
 * `Internal\ProductFilters\Params::get_taxonomy_params()` enrols EVERY public,
 * show_ui taxonomy attached to `product`. Verified: the param map contains
 * `pwb-brand => filter_pwb-brand`.
 *
 * ⚠ 68 BRANDS APPEAR ON /shop/, NOT 76. `hideEmpty` is on and the counts come
 * from the current loop, so a term with no product in the query is not drawn.
 * That is correct, and it is why the "Show All" arithmetic below is stated
 * against 68 rather than 76.
 *
 * ⚠ THE LIST SITS IN A RAW UA `fieldset`, AND WooCommerce THINKS IT DOES NOT.
 * `product-filter-checkbox-list-style.css` opens with
 * `.wc-block-product-filter-checkbox-list fieldset { display: contents }` —
 * and THAT CLASS IS NEVER EMITTED. `ProductFilterCheckboxList::render()` builds
 * its wrapper class by reading the class off the block's own save content with
 * `WP_HTML_Tag_Processor`, that content is empty, so the wrapper ships with
 * `wp-block-woocommerce-product-filter-checkbox-list` alone. Measured in the
 * browser 2026-08-07: `fieldset` computes `display: block`, `border-top-width:
 * 2px`, `padding: 6.3px 13.5px`, `margin-inline: 2px` — every one of them the
 * UA default, and 17.5px of unasked-for inset with a groove border around the
 * brand list.
 *
 * Several of Woo's own rules for this block are dead for the same reason. This
 * is a WooCommerce 10.9.4 defect, reported in the build notes; the theme cannot
 * wait for it.
 *
 * ⚠ SO THE RESET IS HUNG ON `.tc-shop-filter-group fieldset` AND NOT ON EITHER
 * WooCommerce CLASS. Anchoring it to `wc-block-…` would inherit the bug, and
 * anchoring it to `wp-block-woocommerce-…` would break the day Woo fixes its own
 * selector and the intended class starts shipping. The rail's own group class is
 * the one thing here the theme controls, and the only `fieldset` inside it is
 * this one.
 *
 * It becomes the flex column Woo meant `display: contents` to produce: `__items`
 * and `__show-more` are its only in-flow children — the `legend` is
 * `.screen-reader-text`, which is `position: absolute` — so the 16 to the chip
 * is one `gap` rather than a margin.
 * ─────────────────────────────────────────────────────────────────────────── */

.tc-shop-archive .tc-shop-filter-group fieldset {
	display: flex;
	flex-direction: column;
	gap: var(--wp--preset--spacing--normal, 1rem); /* 16 to the Show All chip */
	min-inline-size: 0;
	margin: 0;
	border: 0;
	padding: 0;
}

.tc-shop-archive .wc-block-product-filter-checkbox-list__items {
	display: flex;
	flex-direction: column;
	gap: var(--wp--preset--spacing--normal, 1rem); /* 16 between rows */
}

.tc-shop-archive .wc-block-product-filter-checkbox-list__label {
	align-items: center;
	gap: var(--wp--preset--spacing--xsmall, 0.5rem); /* 8, over Woo's .625em */
	margin: 0;
	cursor: pointer;
}

/*
 * ⚠ 18px IN PIXELS, NOT IN em. Woo sizes the box, its tick and the tinted
 * backplate as `1em` of the wrapper's inherited font size, so a type change
 * anywhere above would silently resize the control. The board draws 18 and 18
 * is what ships.
 */

.tc-shop-archive .wc-block-product-filter-checkbox-list__input-wrapper,
.tc-shop-archive input[type="checkbox"].wc-block-product-filter-checkbox-list__input,
.tc-shop-archive svg.wc-block-product-filter-checkbox-list__mark {
	width: 18px;
	height: 18px;
	flex: 0 0 auto;
}

/*
 * ⚠ WooCommerce'S TINTED BACKPLATE IS SUPPRESSED, AND IT IS WHY THE 2026-08-31
 * WHITE FILL BELOW NEVER PAINTED. This rule used to size that backplate to 18
 * (and its drawer copy to 24) and leave it drawing.
 *
 * `product-filter-checkbox-list-style.css` ships
 *
 *   .wc-block-product-filter-checkbox-list__input-wrapper:before {
 *     background: currentColor; opacity: .1; position: absolute;
 *     inset-block-start: 0; inset-inline-start: 0; width: 1em; height: 1em }
 *
 * — an ABSOLUTELY POSITIONED box over the input, so it paints above in-flow
 * content whatever the source order. `currentColor` is `#0A205B` here, so it
 * washed 10% navy across the whole control: measured `rgb(230, 233, 240)` on
 * the rail at 1440 and in the sheet at 768/390, with the neutral-300 border
 * completely covered because the backplate is the same 18/24 box.
 *
 * So the control read as a flat grey chip, and the fix recorded below —
 * "All four properties are now stated … white fill" — was correct in the
 * cascade and invisible on the page. Nothing about the input needed changing;
 * the thing on top of it did.
 *
 * `content: none` rather than `display: none`: it stops the pseudo-element
 * being generated at all, where `display: none` leaves a generated box the
 * spec still has to blockify (build-knowledge §33, same call and same reason).
 * (0,2,0) against Woo's (0,1,0) — specificity, not order.
 *
 * Woo's own escape hatch is `.has-option-element-color`, a server-side class
 * this block never emits, so CSS is the only seam.
 */

.tc-shop-archive .wc-block-product-filter-checkbox-list__input-wrapper::before {
	content: none;
}

/*
 * ⚠ THE BOX WAS TRANSPARENT, NOT WHITE — fixed 2026-08-31 on a design review.
 *
 * This rule used to set `border-color` and `border-radius` only, leaving
 * `border-width`, `border-style` and the FILL to WooCommerce's own sheet.
 * Measured before the change: `1px solid rgb(206, 212, 218)` — the border half
 * was arriving correctly and by luck — and `background-color: rgba(0, 0, 0, 0)`.
 *
 * On the desktop rail that reads as white because the rail behind it is white,
 * which is exactly why it survived: it is invisible until something is painted
 * behind it, and the drawer at mobile is that something.
 *
 * ⚠ THAT LAST PARAGRAPH IS WRONG AND WAS WRONG WHEN WRITTEN — corrected
 * 2026-09-05. The rail did NOT read as white. Woo's `__input-wrapper::before`
 * paints a 10% `currentColor` backplate OVER the input on every surface, so
 * both the rail and the sheet drew `rgb(230, 233, 240)` with the border hidden,
 * and this fix computed correctly while painting nothing. The suppression is
 * the rule above; measurements in `docs/reports/review-batch-5.md` §2.
 *
 * All four properties are now stated, matching the contact form's checkbox
 * contract at contact-form.css:560 — 18px box, 1px neutral-300 `#CED4DA`, 2px
 * radius, white fill — so the two controls on this site agree and neither
 * depends on a vendor default. The 18px sizing stays in the rule above; only
 * the border and the fill move here.
 *
 * The CHECKED state keeps secondary-600 rather than adopting the contact
 * form's secondary-700: this control sits on white in a filter rail where
 * secondary-600 is the interactive colour throughout (the buttons, the focus
 * ring two rules down), and the contact form's 700 is a contrast decision made
 * against a tinted panel. Same shape, different ground.
 */
.tc-shop-archive input[type="checkbox"].wc-block-product-filter-checkbox-list__input {
	border: 1px solid var(--wp--custom--color--neutral--300, #ced4da);
	border-radius: 2px;
	background-color: var(--wp--custom--color--base--white, #fff);
}

.tc-shop-archive
	input[type="checkbox"].wc-block-product-filter-checkbox-list__input:checked {
	background-color: var(--wp--custom--color--secondary--600, #057db9);
	border-color: var(--wp--custom--color--secondary--600, #057db9);
	color: var(--wp--custom--color--base--white, #fff);
}

.tc-shop-archive
	input[type="checkbox"].wc-block-product-filter-checkbox-list__input:focus-visible {
	outline: 2px solid var(--wp--custom--color--secondary--600, #057db9);
	outline-offset: 2px;
}

/* Woo's own `.875em`; stated in the token so it does not float with the parent. */

.tc-shop-archive .wc-block-product-filter-checkbox-list__text-wrapper {
	gap: 4px;
	font-family: var(--wp--preset--font-family--body);
	font-size: var(--wp--preset--font-size--label-medium, 0.875rem); /* 14 */
	font-weight: 500;
	line-height: 1.4286; /* 20 */
	color: var(--wp--custom--color--base--black, #0a205b);
}

/* ── "Show All" ──────────────────────────────────────────────────────────────
 * ⚠ THE BRIEF SAYS WooCommerce HAS NO SHOW-MORE AND NO TERM LIMIT. IT HAS BOTH,
 * AND THEY ARE IN THE SIBLING BLOCK. `ProductFilterTaxonomy.php` really does
 * carry neither — that reading is correct as far as it goes — but
 * `ProductFilterCheckboxList.php` hard-codes `DISPLAY_LIMIT = 15`, renders the
 * first 15 terms server-side, and ships a `showAll` button for the rest. So the
 * behaviour the brief asked to be BUILT already exists and is Woo's.
 *
 * That changes the shape of the job in a way worth stating precisely:
 *
 *   - "Render all terms and hide the overflow in CSS" IS NOT POSSIBLE. Woo puts
 *     15 in the server DOM and no attribute or filter moves that number. Items
 *     16-68 arrive only through the `<template data-wp-each--item="state.items">`
 *     the Interactivity API hydrates.
 *   - SO THERE IS NO NO-JS PATH, AND NOT ONLY FOR THE OVERFLOW. Woo's checkbox
 *     carries `data-wp-on--change="actions.toggle"` inside a bare `fieldset` —
 *     no form, no submit. WITHOUT JAVASCRIPT NO BRAND FILTERS AT ALL, whether
 *     it is the first row or the sixty-eighth. The brief's "without JS all terms
 *     must be reachable" cannot be met while using the block the brief
 *     specifies, and it is reported rather than silently dropped.
 *   - WHAT THIS FILE THEREFORE DOES is skin Woo's control to the board's chip
 *     and clamp the collapsed list to the board's SIX rows. It adds no second
 *     toggle. A theme-built reveal beside Woo's own would be two controls
 *     disagreeing about the same list.
 *
 * ⚠ THE CLAMP IS GATED ON THE CONTROL EXISTING, `:not([hidden])` AND ALL. On a
 * narrow category archive the brand list can be shorter than Woo's 15, Woo
 * renders no button, and a bare `:nth-child(n + 7)` would hide rows 7 and 8 with
 * nothing anywhere to reveal them. Expanded, the button takes `hidden` from
 * `data-wp-bind--hidden="context.isExpanded"`, the `:has()` stops matching, and
 * every row falls back to its own state — which is why nothing here has to
 * re-show anything with `display: block` and fight Woo's own `[hidden]`.
 *
 * ⚠ THE GATE HANGS OFF `__items` AND A SIBLING `:has()`, NOT OFF THE LIST
 * WRAPPER — for the reason spelled out above the fieldset reset. The obvious
 * `.wc-block-product-filter-checkbox-list:has(…)` NEVER MATCHES ANYTHING on
 * WooCommerce 10.9.4, and it fails SILENTLY: the clamp simply does not apply and
 * fifteen rows render where the board draws six. That is exactly how this was
 * found — by measuring the row count, not by reading the selector.
 *
 * The label reads "Show All" because includes/woocommerce.php replaces Woo's
 * "Show N more": with the list clamped to 6, Woo's N counts against ITS 15 and
 * is wrong by nine. Full reasoning at that filter.
 * ─────────────────────────────────────────────────────────────────────────── */

.tc-shop-archive
	.wc-block-product-filter-checkbox-list__items:has(
		~ .wc-block-product-filter-checkbox-list__show-more
			.wc-block-product-filter-checkbox-list__show-more-button:not(
				[hidden]
			)
	)
	> .wc-block-product-filter-checkbox-list__item:nth-child(n + 7) {
	display: none;
}

/*
 * ⚠ THE `:not([hidden])` IS NOT DECORATION — WITHOUT IT THE CHIP NEVER GOES
 * AWAY. `display: inline-flex` is an AUTHOR declaration and the `hidden`
 * attribute is a UA `display: none`, so the author rule wins and the control
 * stays on screen reading "Show All" beside an already-expanded list. Caught by
 * assertion 10 in e2e/shop-archive-filters-verify.spec.ts on its first run;
 * nothing about it is visible in the markup, which is why the test exists.
 */

.tc-shop-archive
	.wc-block-product-filter-checkbox-list__show-more-button:not([hidden]) {
	display: inline-flex;
	align-items: center;
	gap: 4px;
	box-sizing: border-box;
	border: 1px solid var(--wp--custom--color--neutral--200, #dee2e6);
	border-radius: 4px;
	background-color: var(--wp--custom--color--neutral--100, #e9ecef);
	padding: 6px;
	font-family: var(--wp--preset--font-family--body);
	font-size: var(--wp--preset--font-size--label-small, 0.75rem); /* 12 */
	font-weight: 500;
	line-height: 1.3333; /* 16 */
	color: var(--wp--custom--color--base--black, #0a205b);
	text-decoration: none;
}

.tc-shop-archive .wc-block-product-filter-checkbox-list__show-more-button::after {
	content: "";
	flex: 0 0 auto;
	width: 16px;
	height: 16px;
	background-color: currentcolor;

	/* stylelint-disable-next-line property-no-vendor-prefix -- as above */
	-webkit-mask: var(--ftps-icon-chevron-down, var(--tc-icon-chevron-down)) center / contain no-repeat;
	mask: var(--ftps-icon-chevron-down, var(--tc-icon-chevron-down)) center / contain no-repeat;
}

.tc-shop-archive
	.wc-block-product-filter-checkbox-list__show-more-button:focus-visible {
	outline: 2px solid var(--wp--custom--color--secondary--600, #057db9);
	outline-offset: 2px;
}

/*
 * The chip sits left in its own row; Woo's wrapper is a block by default.
 * `:has()` rather than a flat `display: flex`, for the same reason as above —
 * the wrapper stays in the flow when its button is `hidden`, and an empty flex
 * row would still eat the fieldset's 16px gap after the list is expanded.
 */

.tc-shop-archive
	.wc-block-product-filter-checkbox-list__show-more:has(
		.wc-block-product-filter-checkbox-list__show-more-button:not([hidden])
	) {
	display: flex;
}

.tc-shop-archive
	.wc-block-product-filter-checkbox-list__show-more:not(
		:has(
			.wc-block-product-filter-checkbox-list__show-more-button:not(
				[hidden]
			)
		)
	) {
	display: none;
}

/* ── PRICE — woocommerce/product-filter-price-slider ─────────────────────────
 * ⚠ THE BOARD'S "$0 - $3,999" IS COMP FILLER. Measured on this catalogue
 * 2026-08-07, the range WooCommerce computes for `/shop/` is $0 - $21,999. The
 * slider reads its own bounds from the loop, so the label is data and the only
 * things this file can honour are the geometry and the type.
 *
 * `showInputFields: false` — the board draws a read-only label, not two text
 * boxes. Woo then emits `<span data-wp-text="state.formattedMinPrice">`, and the
 * Interactivity API resolves those SERVER-SIDE: verified in the rendered HTML,
 * the spans arrive carrying "$0" and "$21,999" rather than empty.
 *
 * ⚠ CORRECTED 2026-09-30 (I16): THE BOARD DOES DRAW A BORDER, AND A DOT. This
 * used to say the handles carry no border "which is the board", read from a
 * brief that bound only the 20px and the shadow. The handle itself (Frame 174 /
 * 175, 43324:19736 / :19739) exports as a 20px white disc with a 1px
 * `#DEE2E6` inside stroke (neutral-200) and a 6px `#123B9E` centre dot
 * (Ellipse 1, primary-600), carrying Shadow/Light/300. All three now ship.
 * Woo's 2px #1e1e1e ring is still dropped: it is not the board's line.
 * The borderless "thin" disc is off the designer list. DECISIONS.md 2026-09-30.
 *
 * The dot is a `radial-gradient` on the thumb's own background, because a
 * slider thumb takes no children and no pseudo-elements of its own.
 *
 * Shadow/Light/300 = `0 4px 8px rgb(48 49 51 / 10%), 0 0 1px rgb(48 49 51 / 5%)`
 * — the brief's #3031331A and #3031330D, written in the same form
 * product-detail.css:581 already uses for Shadow/Light/200.
 * ─────────────────────────────────────────────────────────────────────────── */

/*
 * ⚠ DECLARED ON THE THEME'S OWN GROUP, NOT ON `.wc-block-product-filter-price-
 * slider`. That class is not emitted either — the price slider builds its
 * wrapper class the same way the checkbox list does, and lands with
 * `wp-block-woocommerce-product-filter-price-slider` alone. Custom properties
 * inherit, so the group is a correct and stable place to set them; the Woo class
 * would have been a rule that silently did nothing.
 */

.tc-shop-archive .tc-shop-filter-group--price {
	--wc-product-filter-price-slider: var(--wp--custom--color--primary--600, #123b9e);
	--wc-product-filter-price-slider-handle: var(--wp--custom--color--base--white, #fff);
	--wc-product-filter-price-slider-handle-border: var(--wp--custom--color--neutral--200, #dee2e6);

	/* The board's handle face: primary-600 dot of r3 (6px) on white. Shared by
	   the rest, hover and focus states so no state loses the dot. */
	--tc-price-handle-face: radial-gradient(
		circle,
		var(--wp--custom--color--primary--600, #123b9e) 0 3px,
		var(--wp--custom--color--base--white, #fff) 3.5px
	);
}

/*
 * ⚠ THE GRID AREAS ARE REDEFINED, NOT NUDGED. Woo's own template is
 *     "price-slider price-slider price-slider" / "left-input middle-input right-input"
 * on `auto 1fr auto` — which pins the two numbers to opposite ENDS of the rail.
 * The board draws them as ONE group reading "Price: $0 - $21,999", so the
 * empty column moves and the two labels become adjacent.
 *
 * ⚠ LEFT-ALIGNED SINCE 2026-09-30 — this used to say the board draws the
 * group RIGHT-aligned, and put the empty column first. It does not: the label
 * (43324:19741) is a full-width 268 text box with default (left) alignment,
 * so the group starts on the track's left edge. The empty column is now the
 * LAST one. Carried over from PR #296's review.
 */

.tc-shop-archive .wc-block-product-filter-price-slider__content {
	grid-template-areas:
		"price-slider price-slider price-slider"
		"left-input right-input .";
	grid-template-columns: auto auto 1fr;
	column-gap: 4px;
	row-gap: var(--wp--preset--spacing--normal, 1rem); /* 16 under the track */
}

/*
 * Woo's `margin: 15px 0` is what the 16 row-gap replaces. The handles overhang
 * the 4px track by 8 either side and are allowed to sit in that gap rather than
 * being given a reserved band of their own — the board measures 16 from the
 * TRACK, not from the handle.
 */

.tc-shop-archive .wc-block-product-filter-price-slider__range {
	height: 4px;
	margin: 0;
}

.tc-shop-archive .wc-block-product-filter-price-slider__range .range-bar {
	height: 4px;
}

/*
 * 20px handles, stated as 20 rather than derived.
 *
 * ⚠ THE FIRST ATTEMPT LEANED ON WOO'S ARITHMETIC AND WAS WRONG. Woo declares
 * `box-sizing: content-box` with a 2px border, so 16 + 2 + 2 reads as 20 on
 * paper — but the computed `box-sizing` on `::-webkit-slider-thumb` measures
 * `border-box` in Chromium, which would have made that handle 16. Depending on
 * a third party's sizing mode to reach a board number is a calculation that can
 * be silently invalidated by an upgrade. `border-box` plus `border: 0` plus a
 * literal 20 cannot be.
 *
 * `margin-block-start` centres the handle on the 4px track: the range input's
 * runnable track sits at the top of the 4px rail, so the handle's own centre
 * moves up (20 - 4) / 2 = 8.
 *
 * ⚠ THE VENDOR PSEUDO-ELEMENTS CANNOT BE COMBINED IN ONE SELECTOR LIST. A
 * browser that does not know one of them drops the WHOLE rule, so they are
 * written out twice. That is not duplication to tidy up.
 */

.tc-shop-archive
	.wc-block-product-filter-price-slider__range
	input[type="range"]::-webkit-slider-thumb {
	box-sizing: border-box;
	width: 20px;
	height: 20px;
	margin: -8px 0 0;
	border: 1px solid var(--wc-product-filter-price-slider-handle-border);
	background: var(--tc-price-handle-face);
	box-shadow:
		0 4px 8px rgb(48 49 51 / 10%),
		0 0 1px rgb(48 49 51 / 5%);
}

.tc-shop-archive
	.wc-block-product-filter-price-slider__range
	input[type="range"]::-moz-range-thumb {
	box-sizing: border-box;
	width: 20px;
	height: 20px;
	border: 1px solid var(--wc-product-filter-price-slider-handle-border);
	background: var(--tc-price-handle-face);
	box-shadow:
		0 4px 8px rgb(48 49 51 / 10%),
		0 0 1px rgb(48 49 51 / 5%);
}

/*
 * ⚠ FOCUS HAS TO BE RE-BUILT, IT IS NOT MERELY RE-COLOURED. Woo marks hover and
 * focus by FLIPPING the handle — #1e1e1e fill, white border — which says nothing
 * at all on a handle that no longer has a border, and would also change the
 * board's white disc into a black one on plain hover. Both states are therefore
 * replaced rather than tweaked.
 *
 * The ring is a THIRD box-shadow layer rather than an `outline`, because
 * `outline` on a slider thumb is unreliable across engines and because the
 * board's Shadow/Light/300 has to survive alongside it — a shorthand
 * `box-shadow` replaces the whole stack, so both drop shadows are restated.
 *
 * Without this the control has NO visible focus state whatsoever: the handle is
 * a white disc on a white rail whose only edge is a 10%-alpha shadow.
 */

.tc-shop-archive
	.wc-block-product-filter-price-slider__range
	input[type="range"]:hover::-webkit-slider-thumb {
	background: var(--tc-price-handle-face);
}

.tc-shop-archive
	.wc-block-product-filter-price-slider__range
	input[type="range"]:focus::-webkit-slider-thumb {
	background: var(--tc-price-handle-face);
	box-shadow:
		0 0 0 2px var(--wp--custom--color--secondary--600, #057db9),
		0 4px 8px rgb(48 49 51 / 10%),
		0 0 1px rgb(48 49 51 / 5%);
}

.tc-shop-archive
	.wc-block-product-filter-price-slider__range
	input[type="range"]:hover::-moz-range-thumb {
	background: var(--tc-price-handle-face);
}

.tc-shop-archive
	.wc-block-product-filter-price-slider__range
	input[type="range"]:focus::-moz-range-thumb {
	background: var(--tc-price-handle-face);
	box-shadow:
		0 0 0 2px var(--wp--custom--color--secondary--600, #057db9),
		0 4px 8px rgb(48 49 51 / 10%),
		0 0 1px rgb(48 49 51 / 5%);
}

.tc-shop-archive .wc-block-product-filter-price-slider__left,
.tc-shop-archive .wc-block-product-filter-price-slider__right {
	font-family: var(--wp--preset--font-family--body);
	font-size: var(--wp--preset--font-size--label-small, 0.75rem); /* 12 */
	font-weight: 500;
	line-height: 1.3333; /* 16 */
	color: var(--wp--custom--color--base--black, #0a205b);
}

/*
 * ⚠ "Price:" AND THE DASH ARE GENERATED CONTENT, AND THAT COSTS TRANSLATABILITY.
 * The board's label is one sentence — "Price: $0 - $21,999" — and WooCommerce
 * emits it as two separate spans with nothing between them and no slot for a
 * prefix. Neither block has an attribute for it.
 *
 * The alternative is a second `render_block` filter doing string surgery on
 * Woo's markup, as includes/woocommerce.php already does for the Show All
 * label. It is NOT taken here, and the difference between the two cases is the
 * point: that one replaces a number that is WRONG, this one would add
 * decoration. These two words sit on top of controls that are already correctly
 * named — Woo gives both range inputs an `aria-label` — so generated content
 * costs nothing in the accessibility tree.
 *
 * What it does cost is i18n: a CSS string cannot be translated. Same class as
 * the "About" in blocks/term-heading/render.php, one notch worse because that
 * one is at least in a `__()`. Flagged in the report and in
 * docs/editing-guide.md rather than hidden.
 */

.tc-shop-archive .wc-block-product-filter-price-slider__left::before {
	content: "Price: ";
}

.tc-shop-archive .wc-block-product-filter-price-slider__right::before {
	content: "- ";
}

/* ══ Results count ══════════════════════════════════════════════════════════
   Inside the grid column and above the grid — which is why the block sits as
   the product-collection's FIRST inner block rather than beside it. It reads
   the loop's `query.inherit` from block context, and only a descendant gets
   that context at all.

   ⚠ DERIVED TYPE — the boards draw the string but bind no type role to it. It
   takes the sort control's role, which is the nearest labelled sibling on the
   same row of the page.
   ────────────────────────────────────────────────────────────────────────── */

.tc-shop-archive .tc-query-results-count {
	margin-block: 0 var(--wp--preset--spacing--normal, 1rem); /* 16 */
	font-family: var(--wp--preset--font-family--body);
	font-size: var(--wp--preset--font-size--label-large, 0.9375rem); /* 15 */
	font-weight: 500;
	line-height: 1.6; /* 24 */
	color: var(--wp--custom--color--base--black, #0a205b);
}

/* ══ The product track ══════════════════════════════════════════════════════
   ⚠ EXPLICIT COLUMN COUNTS PER BREAKPOINT, NEVER auto-fit. An `auto-fit`
   track with a sane minimum resolves to 3-up somewhere in the 1024-1279 band,
   and no board draws a 3-up shop grid at any width.

   ⚠ minmax(0, 1fr), NOT 1fr. A grid item's default `min-width: auto` refuses to
   shrink below its content, and this catalogue contains a card that blows out
   to roughly 3000px because of it. That defect lives in the CARD and is not
   fixed here — `minmax(0, …)` is the grid-side mitigation that stops one bad
   card from dragging the whole row. Recorded, live on this environment, and
   left to T-SA-6.

   The doubled class is not decoration: WooCommerce sizes its own `li` at
   (0,3,0) inside a `min-width: 600px` query, which product-grid.css:446 already
   records paying for.
   ────────────────────────────────────────────────────────────────────────── */

.tc-shop-archive .wc-block-product-template.wc-block-product-template {
	display: grid;
	grid-template-columns: minmax(0, 1fr);

	/* Row gap 0 at base: the mobile board separates rows with a rule and the
	   card's own 24 padding, not with a gap. */
	gap: 0 var(--wp--preset--spacing--large, 1.5rem); /* 24 column */
	margin: 0;
	padding: 0;
	list-style: none;
}

/*
 * ⚠ THE TRIPLED CLASS IS LOAD-BEARING, AND THE NUMBER IT FIXES WAS MEASURED.
 *
 * WooCommerce ships
 *   `.wc-block-product-template.is-flex-container.is-flex-container.columns-4 > li
 *       { width: calc(25% - 0.9375em) }`
 * inside a `min-width: 600px` query — FOUR classes plus an element, (0,4,1). It
 * is written for Woo's own flex track, where 25% is 25% of the row. Against a
 * CSS grid it is actively wrong: a percentage width on a grid item resolves
 * against its GRID AREA, not the row, so each card took a quarter of its own
 * 249px column.
 *
 * Measured before the fix, at 1920: cards 45.375px wide inside 249px tracks.
 * 25% x 249 = 62.25, minus 0.9375em at the li's 18px font = 16.875 -> 45.375.
 * The arithmetic matched to three decimal places, which is how the diagnosis was
 * confirmed rather than guessed.
 *
 * The rule below is (0,5,1) and beats it outright. product-detail.css:1823
 * solves the same collision at (0,4,1), a TIE with WooCommerce that it wins only
 * on stylesheet order; this file is enqueued on its own handle rather than
 * through the pattern glob, so its position relative to Woo's block styles is
 * less predictable and a tie is not good enough.
 *
 * `width`, not `inline-size`. The two set the same used value but cascade as
 * separate properties, and mixing them against a third party's `width` makes
 * the winner depend on logical/physical resolution order as well as on
 * specificity. Same property, same axis, one comparison.
 */

.tc-shop-archive
	.tc-shop-archive__collection
	.wc-block-product-template.wc-block-product-template.wc-block-product-template
	> li {
	margin-block: 0;
	width: auto;
	max-width: none;
}


/* ── The horizontal card, below 48rem ────────────────────────────────────────
 * ⚠ THE ONE PLACE THIS ARCHIVE'S CARD DIVERGES FROM THE SHARED ONE.
 *
 * Board 43324:17626: a 118px square at x=0 and the text column at x=130 — 118
 * plus 12 between them — with 24 above and below and a rule along the bottom.
 * The shared card is a vertical stack at every one of its own breakpoints, so
 * this is a genuine variant rather than a reflow of it.
 *
 * ⚠ THE MEDIA IS TAKEN OUT OF FLOW, AND THE REASON IS RECORDED RATHER THAN
 * REDISCOVERED. product-detail.css:1852 documents three failed attempts at the
 * same layout: with a flat DOM — five sibling blocks and no wrapper around the
 * text — a two-column grid must span the media across every row, a spanning
 * item sizes the rows it spans, and row 1 became as tall as the thumbnail with
 * the category eyebrow alone in it. `align-content: start`,
 * `grid-auto-rows: min-content` and `align-items: start` were each tried there
 * and each changed nothing, because all three govern how an item sits INSIDE a
 * row that was itself the wrong height. Absolute positioning removes the media
 * from row sizing entirely. This build reuses that conclusion and did not
 * re-run the experiment.
 *
 * `min-block-size` guarantees a short card still clears the thumbnail:
 * 118 + 24 + 24 = 166, and box-sizing is border-box theme-wide
 * (modern-normalize.css:15).
 * ─────────────────────────────────────────────────────────────────────────── */

.tc-shop-archive .wc-block-product {
	position: relative;
	min-block-size: 10.375rem; /* 166 = 118 media + 24 + 24 */
	border-block-end: 1px solid var(--wp--custom--color--neutral--200, #dee2e6);
	border-radius: 0;
	background-color: transparent;
	padding-block: var(--wp--preset--spacing--large, 1.5rem); /* 24 */
	padding-inline: 8.125rem 0; /* 130 = 118 media + 12 gap */
}

/* The shared card insets its non-media children by 8; this row's inset is the
   130px well instead, so that one is withdrawn rather than fought. */

.tc-shop-archive .wc-block-product > :not(.tc-product-card__media) {
	padding-inline: 0;
}

/* I17: the text column starts level with the thumbnail's top. On smartphone
   board 43324:17626 the text Frame 135 and the image share a y (0 on card 1,
   24 on the rest). The shared card's `> * + *` put the category 8 lower,
   counting the absolutely positioned media as a preceding sibling. md
   restates this as 16, where the image sits above instead. */
.tc-shop-archive .wc-block-product > .tc-product-card__media + * {
	margin-block-start: 0;
}

/*
 * ── THE STAR ROW (I18; rewritten I17 round 2, 2026-10-02) ───────────────────
 *
 * Board 43324:19044: title, then 8, then the 12px star row, then 12, then the
 * price ("Frame 134" gap 8, "Frame 158" gap 12, desktop and phone).
 *
 * ⚠ A WIDGET WITH NO STARS TAKES NO SPACE AND ADDS NO GAP. ftm-klaviyo-extended
 * prints an empty div and `klaviyo.js` fills it. For a product with no reviews
 * tonerdev ends up with `<div class="klaviyo-star-rating-widget" data-id="…">
 * <div></div></div>`: 0px tall, but NOT `:empty`, so #309's
 * `:not(:empty)` margin landed under every title (48 of 48 widgets on
 * /product-category/satellite-communications/, measured by the owner in
 * Chrome). Both empty shapes — the bare div here, where Klaviyo never loads,
 * and the one-empty-child div on tonerdev — are taken out of the flow, so the
 * price follows the title at the card's ordinary 8, the board's title-to-
 * price gap. Anything else Klaviyo puts in counts as stars, and the moment it
 * fills the inner div the widget stops matching and appears.
 *
 * ⚠ THE FILLED STATE IS VERIFIED AGAINST A FIXTURE, NOT KLAVIYO: a widget
 * stubbed to the board's 12px row (e2e/product-card-flow-verify.spec.ts).
 * There are no reviews on any environment yet.
 */
.tc-shop-archive .klaviyo-star-rating-widget:is(:empty, :has(> :only-child:empty)) {
	display: none;
}

.tc-shop-archive
	.klaviyo-star-rating-widget:not(:empty, :has(> :only-child:empty))
	+ .tc-product-card__price {
	margin-block-start: var(--wp--preset--spacing--small, 0.75rem); /* 12 */
}

.tc-shop-archive .tc-product-card__media {
	position: absolute;
	inset-block-start: var(--wp--preset--spacing--large, 1.5rem); /* 24, matching the card's own padding */
	inset-inline-start: 0;
	inline-size: 7.375rem; /* 118 */
}

/* 4, where the shared card's vertical variant draws 2. */

.tc-shop-archive .tc-product-card__media img {
	border-radius: 4px;
}

/*
 * ── THE FIRST ROW SITS 16 UNDER THE TOOLBAR RULE (QA batch D, 2026-09-29) ───
 *
 * The 2026-09-27 review saw ~70px of white between the Filters row's rule and
 * the first card on phones. Measured: 72 = the rail's own 24 below its rule +
 * the main column's 24 (WordPress's flow-layout `margin-block-start`; the body
 * is a grid, so the two do not collapse) + the first row's 24 of top padding.
 * The mobile board 43324:17626 draws 16: the toolbar row 43324:18291 ends at
 * y 370 and the list 43324:17636 starts at 386 with the first card's image at
 * its y 0 (card 1 has no top padding; cards 2+ have 24 above the image).
 * Below md only; 768 keeps its 48, which nothing reported.
 */
@media (max-width: 47.9375rem) {
	.tc-shop-archive .tc-shop-archive__body > .tc-shop-archive__rail {
		margin-block-end: var(--wp--preset--spacing--normal, 1rem); /* 16 */
	}

	.tc-shop-archive .tc-shop-archive__body > .tc-shop-archive__main {
		margin-block-start: 0;
	}

	.tc-shop-archive .wc-block-product-template > .wc-block-product:first-child {
		min-block-size: 8.875rem; /* 142 = 118 media + 24 below */
		padding-block-start: 0;
	}

	.tc-shop-archive .wc-block-product-template > .wc-block-product:first-child .tc-product-card__media {
		inset-block-start: 0;
	}
}

/*
 * ── THE CARD BUTTON MOVED OUT OF THIS FILE — 2026-09-01 ─────────────────────
 *
 * → Superseded by patterns/product-grid/product-grid.css, "THE LISTING
 *   VARIANT". Nothing about the archive button is declared here any more.
 *
 * This file used to carry an archive-only `width: fit-content` on the card
 * button (design review item 4e, 2026-08-31). It shipped a HALF FIX and the
 * comment above it said why it would be a whole one — incorrectly:
 *
 *   "`justify-content: center` inside it stays harmless once the box is the
 *    label's own width, and the wrapper's `align-items` never applied because
 *    the wrapper is not a flex container"
 *
 * ⚠ THE WRAPPER IS A FLEX CONTAINER. WooCommerce's own `product-button.css`
 * declares `.wc-block-components-product-button { display: flex; flex-direction:
 * column; align-items: center }`, so shrinking the box simply revealed the
 * centring instead of escaping it: measured on /shop/ at 1440, a 141px button
 * at dx 49 in a 239px card, under a price at dx 0.
 *
 * The whole fix is `align-items: flex-start` on the wrapper PLUS the
 * `fit-content` above, and the search results page needs the identical pair —
 * it never received 4e at all, because 4e was scoped `.tc-shop-archive`. Two
 * consumers wanting one decision is what moved it to the card kit's own file
 * rather than copying it into `search-results.css`, whose header forbids card
 * styling outright.
 *
 * 4e's reasoning that survives, and is restated there rather than lost here:
 * the kit's `width: 100%` is CORRECT for the product grid and the PDP related
 * rail — `docs/design/product-grid/desktop.png` draws a full-span button — so
 * this is still a variant and still not a correction to the kit.
 */

/* ══ Pagination ═════════════════════════════════════════════════════════════
   Grid-column width, centred. Deliberately the same chrome as
   blog-archive.css:215-340 — it is the same control on the same site, and the
   two were built from the same board family.
   ────────────────────────────────────────────────────────────────────────── */

.tc-shop-archive .tc-shop-archive__pagination {
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: var(--wp--preset--spacing--normal, 1rem); /* 16 */

	/* DERIVED — the boards draw the rule and everything below it, but not the
	   space above the rule. blog-archive uses 56 here; this takes the nearest
	   scale step rather than inventing a second off-scale value. */
	margin-block-start: var(--wp--preset--spacing--xxlarge, 4rem); /* 64 */
	padding-block-start: var(--wp--preset--spacing--large, 1.5rem); /* 24 */
	border-block-start: 1px solid var(--wp--custom--color--neutral--200, #dee2e6);
}

.tc-shop-archive .tc-shop-archive__pagination > * {
	margin-block: 0;
}

/* A ONE-PAGE ARCHIVE HAS NOTHING TO PAGINATE (QA batch D, C-5). The template
   prints this group unconditionally (archive-product.html), and with one page
   the count and both pagers render nothing, so it drew 64 + 24 and a rule
   around nothing: two rules ~65 apart under the last card on brand-aja, at
   every width. `:has(*)`, not `:empty`: the group still holds whitespace. */
.tc-shop-archive .tc-shop-archive__pagination:not(:has(*)) {
	display: none;
}

/* G01 (Gary 2026-09-14, corrected to 500 on 2026-09-15): every button at 500.
   `#wc-load-more` is injected by the woocommerce-smart-load-more plugin between
   the product list and this pagination, and its own stylesheet sets no type at
   all — so it inherited 400. Weight only: the control has no board and no other
   theme styling, and styling it further is not this change. Gary says this
   button should not exist at all — a plugin question, raised separately — and
   until that is settled it must not be the one odd-weight button on the site. */

.tc-shop-archive #wc-load-more {
	font-weight: 500;
}

.tc-shop-archive .tc-query-page-count {
	margin-block: 0;
	text-align: center;

	/* Literals: the frame binds Label/Small at weight 500, and `caption` — the
	   only role on label-small — is 400. Nothing in the system fits. Same
	   reasoning, same values, as blog-archive.css:227. */
	font-family: var(--wp--preset--font-family--body);
	font-size: var(--wp--preset--font-size--label-small, 0.75rem); /* 12, flat */
	font-weight: 500;
	line-height: 1.3333; /* 16 */
	letter-spacing: 0.002em;
	color: var(--wp--custom--color--base--black, #0a205b);
}

.tc-shop-archive .wp-block-query-pagination {
	gap: var(--wp--preset--spacing--xsmall, 0.5rem); /* 8 */
	margin: 0;
}

.tc-shop-archive .wp-block-query-pagination-numbers {
	display: flex;
	align-items: center;
	gap: var(--wp--preset--spacing--xsmall, 0.5rem); /* 8 */
	margin: 0;
}

/*
 * One box. The number cells, the ellipsis and the two arrows share a size so
 * they sit on one baseline; the ellipsis then drops its border and the arrows
 * swap their text for a chevron.
 *
 * 32 at base, 40 from 48rem — the mobile board shrinks the cell and nothing
 * else about the row.
 */

.tc-shop-archive .wp-block-query-pagination .page-numbers,
.tc-shop-archive .wp-block-query-pagination-previous,
.tc-shop-archive .wp-block-query-pagination-next {
	display: flex;
	align-items: center;
	justify-content: center;
	box-sizing: border-box;
	width: 32px;
	height: 32px;
	border: 1px solid var(--wp--custom--color--neutral--200, #dee2e6);
	border-radius: 2px;
	color: var(--wp--custom--color--base--black, #0a205b);
	text-decoration: none;

	/* Label/Extra Large, bold — the preset carries the size, the rest are
	   literals, exactly as blog-archive records. */
	font-family: var(--wp--preset--font-family--body);
	font-size: var(--wp--preset--font-size--label-x-large, 1rem); /* 16, flat */
	font-weight: 700;
	line-height: 1.5; /* 24 */
	letter-spacing: 0.002em;
}

/*
 * ⚠ secondary-600, AND THIS OVERRULES JUDGMENT CALL J-SA-1 ON EVIDENCE.
 *
 * J-SA-1 reads: "#057DB9 is NOT a ramp step — it sits between secondary-600
 * (#0284C7) and -700 (#0369A1). Ships as secondary-700. White on 600 is 4.10
 * and fails AA."
 *
 * Every clause of that was overtaken by commit b7eeb3d9, "secondary-600 is
 * #057DB9, and the F1 remap family is withdrawn", landed 2026-08-06 20:41 UTC
 * — the same day the plan was approved.
 *
 *   - theme.json:44 is `"600": "#057DB9"`. The frame is quoting the token.
 *   - #0284C7 is the OLD 600, and 4.10 is its number.
 *   - White on #057DB9 measures 4.53:1 and clears AA.
 *   - The three precedents J-SA-1 cites in support — the footer CTA F1, media
 *     family J2 and the secondary scheme bg remap — are the three b7eeb3d9
 *     names as WITHDRAWN.
 *
 * Two things then make 700 not merely wrong but unreachable. J-SA-1 also covers
 * the Add to Cart fill, which lives in the shared card scope at
 * product-grid.css:672, is already secondary-600, and is a file this task must
 * not edit. And blog-archive.css:286 — the shipped, frame-verified pagination
 * on this same site — already paints `.current` secondary-600. Shipping 700
 * here would make two pagination controls disagree in order to fix a contrast
 * failure that no longer exists.
 *
 * Full account and measurements in docs/reports/t-sa-1-shop-archive.md.
 */

.tc-shop-archive .wp-block-query-pagination .page-numbers.current {
	border: 2px solid var(--wp--custom--color--secondary--600, #057db9);
	color: var(--wp--custom--color--secondary--600, #057db9);
}

/* The only cell in the frame with no border. */

.tc-shop-archive .wp-block-query-pagination .page-numbers.dots {
	border-color: transparent;
}

/*
 * Icon-only arrows. `paginationArrow: "none"` removes core's glyph but keeps
 * the "Previous Page" / "Next Page" label, which is the link's accessible name
 * — so it is pushed out of the box rather than removed. `display: none` or
 * `font-size: 0` would leave the control unnamed.
 */

.tc-shop-archive .wp-block-query-pagination-previous,
.tc-shop-archive .wp-block-query-pagination-next {
	position: relative;
	overflow: hidden;
	text-indent: 110%;
	white-space: nowrap;
}

.tc-shop-archive .wp-block-query-pagination-previous::before,
.tc-shop-archive .wp-block-query-pagination-next::before {
	content: "";
	position: absolute;
	inset-block-start: 50%;
	inset-inline-start: 50%;
	width: 20px;
	height: 20px;
	transform: translate(-50%, -50%);
	background-color: var(--wp--custom--color--base--black, #0a205b);
}

.tc-shop-archive .wp-block-query-pagination-previous::before {
	/* stylelint-disable-next-line property-no-vendor-prefix -- mask needs the prefix pair; scoped exception per the header/media/blog-archive precedent */
	-webkit-mask: var(--tc-icon-chevron-left) center / contain no-repeat;
	mask: var(--tc-icon-chevron-left) center / contain no-repeat;
}

.tc-shop-archive .wp-block-query-pagination-next::before {
	/* stylelint-disable-next-line property-no-vendor-prefix -- as above */
	-webkit-mask: var(--tc-icon-chevron-right) center / contain no-repeat;
	mask: var(--tc-icon-chevron-right) center / contain no-repeat;
}

.tc-shop-archive .wp-block-query-pagination a:focus-visible {
	outline: 2px solid var(--wp--custom--color--secondary--600, #057db9);
	outline-offset: 2px;
}

/*
 * ── PHONE PAGINATION: a second, compact pager (QA batch A, 2026-09-27) ──────
 *
 * Measured at 360-430: page numbers 19×32 on later pages (the row does not
 * wrap and nothing stops a cell shrinking) and 32×32 at best, against the
 * 44×44 the phone brief requires. The repo owner approved a phone-only layout
 * change, fewer links if needed, desktop unchanged.
 *
 * WHY TWO BLOCKS. The full pager is `midSize: 3` — up to 13 cells (prev, 1, …,
 * seven numbers, …, last, next) — and 44-wide cells need 44+ pitch: that is
 * 570+px against 288 of row at 320. `midSize` is rendered server-side, so it
 * cannot follow the viewport. archive-product.html therefore carries the same
 * pager twice, `--full` (midSize 3, unchanged) and `--compact` (midSize 0),
 * and exactly one is displayed on each side of 48rem. display:none takes the
 * other out of the tab order and the accessibility tree, so there is only
 * ever one "Pagination" landmark.
 *
 * THE COMPACT ROW'S WORST CASE is prev · 1 · … · N · … · last · next: five
 * 44×44 links and two ellipses. 5 × 44 + 2 × 16 + 6 × 4 = 276, inside 288.
 * The cells are DRAWN at 44 rather than given an invisible hit box because
 * the arrows are `overflow: hidden` (that is how their text label is pushed
 * out), which would clip one. The ellipsis is not a control and narrows to 16.
 */
.tc-shop-archive .wp-block-query-pagination.tc-shop-archive__pager--compact {
	display: none;
}

@media (max-width: 47.9375rem) {
	.tc-shop-archive .wp-block-query-pagination.tc-shop-archive__pager--full {
		display: none;
	}

	.tc-shop-archive .wp-block-query-pagination.tc-shop-archive__pager--compact {
		display: flex;
		gap: 4px;
	}

	.tc-shop-archive .tc-shop-archive__pager--compact .wp-block-query-pagination-numbers {
		gap: 4px;
	}

	.tc-shop-archive .tc-shop-archive__pager--compact .page-numbers,
	.tc-shop-archive .tc-shop-archive__pager--compact.wp-block-query-pagination .wp-block-query-pagination-previous,
	.tc-shop-archive .tc-shop-archive__pager--compact.wp-block-query-pagination .wp-block-query-pagination-next {
		flex-shrink: 0;
		width: 44px;
		height: 44px;
	}

	.tc-shop-archive .tc-shop-archive__pager--compact .page-numbers.dots {
		width: 16px;
	}

	/*
	 * THE FILL RULE (QA batch A3, 2026-09-28): an ellipsis that would stand
	 * for exactly one page prints that page instead — includes/compact-pager.php,
	 * which emits the page (`--fill`) AND the ellipsis it replaces
	 * (`dots--fill`). From 360 the page shows. Below 360 the row is 288 and
	 * the extra 44 cell does not fit (304, or 332 on page 3 of 5), so the
	 * ellipsis shows and 320 draws what it always drew.
	 *
	 * Page 3 of 5 is the one seven-cell row, 7 × 44 + 6 × 4 = 332 against 324-328
	 * at 360, so the gaps go to 2 there (320; the search row is 324).
	 */
	.tc-shop-archive .tc-shop-archive__pager--compact .dots--fill {
		display: none;
	}

	.tc-shop-archive .tc-shop-archive__pager--compact:has(.page-numbers--fill ~ .page-numbers--fill),
	.tc-shop-archive .tc-shop-archive__pager--compact:has(.page-numbers--fill ~ .page-numbers--fill) .wp-block-query-pagination-numbers {
		gap: 2px;
	}
}

@media (max-width: 22.4375rem) {
	.tc-shop-archive .tc-shop-archive__pager--compact .page-numbers--fill {
		display: none;
	}

	.tc-shop-archive .tc-shop-archive__pager--compact .dots--fill {
		display: flex;
	}
}

.tc-shop-archive .tc-shop-archive__empty {
	margin-block: 0;
	color: var(--wp--custom--color--base--black, #0a205b);
}

/* ══ Category description ═══════════════════════════════════════════════════
   Grid-column width, BELOW the pagination — which is where the boards put it
   and is not where WooCommerce puts it by default.

   ⚠ THE PANEL PAINTS ITSELF — G23, 2026-09-25. The surface is
   `ftm-scheme-secondary-subtle`, set as a class on the block, and that class
   ONLY SETS CUSTOM PROPERTIES. Nothing global consumes them for a group, so
   until G23 the panel computed `rgba(0, 0, 0, 0)` at every width while this
   comment claimed "the scheme resolves the fill". It did not. The panel now
   reads `var(--ftm-scheme-bg)` itself, exactly as the category band below and
   heading-banner, the media family and testimonial-callout already do. Never
   the `scheme-bg` preset or a `has-*-background-color` class, which freeze the
   base scheme (build-knowledge §3).

   The frame's fill is secondary-100 #E0F2FE, which is what F5 re-pointed the
   scheme to. ⚠ The tablet and smartphone boards (`43324:18327`, `43324:17628`)
   bind neutral-100 #E9ECEF instead, against the designer's own "light blue
   background". Blue ships at every width by the repo owner's ruling; the grey
   frames are D-CD-1 on the designer list. If the ruling ever goes the other
   way, it is one `background-color` inside a query.

   ⚠ J-SA-4 — ONE clamp(), NOT three breakpoints, for the inline inset. The
   three board values are 16 at 320, 40 at 768 and 80 at 1024. Anchoring the
   ramp on the upper two and letting the floor catch the third hits ALL THREE
   EXACTLY rather than approximating them:
       slope   = (80 - 40) / (1024 - 768) = 0.15625 -> 15.625vw
       at 768  = 15.625% x 768  - 80 = 40 ✓
       at 1024 = 15.625% x 1024 - 80 = 80 ✓
       at 320  = 15.625% x 320  - 80 = -30 -> clamped to the 16 floor ✓
   The block inset is a flat 40 at all three, so it stays a token.
   ────────────────────────────────────────────────────────────────────────── */

.tc-shop-archive .tc-shop-archive__description {
	/* DERIVED — the boards place the panel below the pagination but bind no gap
	   to it. Same value as the pagination gap above, so the page keeps one
	   rhythm rather than two. */
	margin-block-start: var(--wp--preset--spacing--xxlarge, 4rem); /* 64 */
	border-radius: 2px;
	padding-block: var(--wp--preset--spacing--xlarge, 2.5rem); /* 40 */
	padding-inline: clamp(1rem, 15.625vw - 5rem, 5rem); /* 16 -> 40 -> 80 */
}

.tc-shop-archive .tc-shop-archive__description[class*="ftm-scheme-"] {
	background-color: var(--ftm-scheme-bg);
	color: var(--ftm-scheme-text);
}

/*
 * ⚠ SUPPRESSED WHEREVER THERE IS NO DESCRIPTION — G22, 2026-09-25.
 *
 * One template serves /shop/, /product-category/ and /product-tag/.
 * `core/term-description` renders NOTHING when the description is blank — not
 * even a wrapper — so the presence of the body IS the "there is something to
 * say" signal, and it is already computed server-side.
 *
 * One selector covers both cases:
 *   - a term with an empty description (116 of 118 categories, 1,176 of 1,180
 *     tags) — the heading renders, the body does not, the panel hides;
 *   - /shop/, where there is no term — neither renders, the panel hides.
 *
 * This was keyed on `.tc-term-heading` (i.e. "a term exists") until G22, which
 * answered the open question DECISIONS 2026-08-07 left for the repo owner.
 * ⚠ The category band's suppression below is NOT keyed the same way and must
 * not be: its h1 is the page title and has to render with or without a
 * description.
 *
 * Not guarded: a description of only whitespace or `&nbsp;` would render a
 * body and keep the panel. 0 such terms on 2026-09-25.
 */

.tc-shop-archive .tc-shop-archive__description:not(:has(.tc-shop-archive__description-body)) {
	display: none;
}

/*
 * Title/Large — G23, 2026-09-25. Boards `43324:19031` / `18329` / `17630`:
 * 27/32 on desktop and tablet, 19/24 on smartphone, −2%, and 16 to the body
 * (8 on smartphone). Was label-x-large/16/24 with 8, from the T-SA-1 brief,
 * never frame-verified.
 *
 * ⚠ SCOPED TO THE PANEL. `.tc-term-heading` is also the category band's h1,
 * and until G23 this rule was `.tc-shop-archive .tc-term-heading` and matched
 * both; the band beat it at (0,3,0). Scoping it means the band no longer has
 * anything to beat.
 *
 * ⚠ MD-CAPPED CLAMPS, NOT `--wp--preset--font-size--title-large`. That preset
 * is WP-fluid over 320→1600 and draws 22.33 at 768, 4.7 under the tablet board
 * (the ramp trap, tabbed-timeline.css). These three ramps share one slope,
 * 8px over 320→768, and hit all three boards exactly — the same size
 * expression as product-detail.css `.tc-pdp__title`.
 *
 * The line box is a LENGTH clamp on the size's ramp, not a ratio: 32/27 and
 * 24/19 differ, so no unitless value is right at both ends. It goes through a
 * custom property because stylelint restricts `line-height` to `px` — the
 * testimonial-callout / contact-form / footer idiom.
 *
 * `text-transform: capitalize` is bound on the boards and NOT applied: term
 * names are stored in title case, so it would change nothing today and would
 * rewrite any deliberately lower-case word the client types. Recorded in
 * DECISIONS 2026-09-25.
 */
.tc-shop-archive .tc-shop-archive__description .tc-term-heading {
	--tc-shop-desc-heading-lh: clamp(1.5rem, 1.1429rem + 1.7857vw, 2rem); /* 24 -> 32 */

	margin-block: 0 clamp(0.5rem, 0.1429rem + 1.7857vw, 1rem); /* 8 -> 16 */
	color: var(--ftm-scheme-heading);
	font-family: var(--wp--preset--font-family--display);
	font-size: clamp(1.1875rem, 0.8304rem + 1.7857vw, 1.6875rem); /* 19 -> 27 */
	font-weight: 700;
	line-height: var(--tc-shop-desc-heading-lh);
	letter-spacing: -0.02em;
}

.tc-shop-archive .tc-shop-archive__description-body {
	margin-block: 0;

	/*
	 * J-CD-1 (2026-09-25) — Body/Small 16/24 at every width, the boards'
	 * `43324:19032` / `18330` / `17631`. The body inherited the global fluid
	 * body, 18/27 at 1920. The designer did not name this: it is frame
	 * conformance on a panel G23 was already rewriting, not an invented
	 * value. Revert this pair of declarations to undo it.
	 */
	font-size: var(--wp--preset--font-size--body-small, 1rem); /* 16 */
	line-height: 1.5; /* 24 */
}

.tc-shop-archive .tc-shop-archive__description-body > :first-child {
	margin-block-start: 0;
}

.tc-shop-archive .tc-shop-archive__description-body > :last-child {
	margin-block-end: 0;
}

/* ══ md — 48rem / breakpoint/md (768) ═══════════════════════════════════════
   Two columns, the card returns to the shared vertical stack, still no rail.
   ────────────────────────────────────────────────────────────────────────── */

@media (min-width: 48rem) {
	.tc-shop-archive .wc-block-product-template.wc-block-product-template {
		grid-template-columns: repeat(2, minmax(0, 1fr));

		/* J-SA-3: the boards' 72 row pitch normalises to xxlarge. */
		gap: var(--wp--preset--spacing--xxlarge, 4rem) var(--wp--preset--spacing--large, 1.5rem); /* 64 / 24 */
	}

	/*
	 * Back to the shared vertical card.
	 *
	 * ⚠ THE OLD COMMENT HERE CLAIMED EVERYTHING WAS "UNSET RATHER THAN
	 * OVERRIDDEN, SO THE CARD FALLS BACK TO PRODUCT-GRID'S RULES INSTEAD OF TO A
	 * SECOND COPY OF THEM". Corrected in T-SA-7: only three of these seven
	 * declarations unset anything. `position`, `min-block-size` and
	 * `border-block-end` genuinely undo the horizontal variant above. The other
	 * four — `border-radius`, `background-color` and the two paddings — ARE second
	 * copies of the shared card's values, and have to be: the horizontal variant
	 * declares them unconditionally at the same (0,2,0), and CSS has no way to say
	 * "fall back to the other author rule" (`unset` goes to initial, not to it).
	 *
	 * ⚠ AND SINCE T-SA-7 THE PADDING IS A DELIBERATE OVERRIDE, NOT A RESTATEMENT.
	 *
	 * Until T-SA-7 that was harmless — product-grid.css's md inset was scoped to
	 * `.tc-product-grid`, so there was nothing here to override. T-SA-7 rescoped it
	 * to `.tc-product-cards`, which this collection carries
	 * (archive-product.html:125), so the shared `8px 8px 16px` now REACHES this
	 * card and these two lines are what keeps it off. The archive keeps its own
	 * flush treatment BY DECISION, not by accident.
	 *
	 * ⚠ AND IT HOLDS ONLY ON SOURCE ORDER. `.tc-shop-archive .wc-block-product`
	 * and `.tc-product-cards .wc-block-product` are BOTH (0,2,0), so the winner is
	 * whichever stylesheet loads last. shop-archive.css does, because
	 * ftm_enqueue_pattern_styles() registers on `enqueue_block_assets` before
	 * ftm_enqueue_shop_archive_styles() does (includes/enqueue.php:93 and :139),
	 * at the same priority. If that order ever changes the archive silently gains
	 * the 8px inset — nothing errors, the cards just move. Verified rendered
	 * output, not just the rule: `0 0 8px`, media flush, at md and lg — and `0`
	 * since I17 (2026-09-30), below.
	 */
	.tc-shop-archive .wc-block-product {
		position: static;
		min-block-size: 0;
		border-block-end: 0;
		border-radius: 4px;
		background-color: var(--wp--custom--color--base--white, #fff);

		/* I17, 2026-09-30: 0, where it was `0 0 8px`. Board card 43324:19039
		   is a 249-wide column with no padding on any side: text, price and
		   button all sit at x 0 and the button's bottom is the card's. */
		padding: 0;
	}

	/*
	 * ⚠ THE 8px TEXT INSET THAT USED TO BE RE-APPLIED HERE IS GONE (I17). The
	 * board draws category, title, price and button flush with the image edge
	 * (x 0 in 43324:19039). The base rule above already sets 0 for the
	 * horizontal row, and nothing re-insets at md or lg now.
	 */

	/* 16 between the image and the category — Frame 136's own gap — where
	   the shared card's `> * + *` gave 8. Less the 3.5 the board trims off
	   the top of the category's 12/16 line: recipe B (DECISIONS 2026-09-17
	   §4; I17 round 2, 2026-10-02). */
	.tc-shop-archive .wc-block-product > .tc-product-card__media + * {
		margin-block-start: calc(var(--wp--preset--spacing--normal, 1rem) - 3.5px); /* 12.5 */
	}

	/*
	 * ── LONG NAMES: A TWO-LINE CLAMP, ONE OR TWO LINES TALL (I17) ───────────
	 *
	 * Board 43324:19027 does not align its cards: each button follows its own
	 * content. From I17 round 2 (2026-10-02) neither did the build, until the
	 * client asked for the buttons aligned (2026-10-06): the button is pinned
	 * to the card's bottom again and the spare height sits above it
	 * (product-grid.css, "Add to Cart, and the card's flow"). The clamp stays
	 * because Gary is content with the truncation: a third line is clipped
	 * behind an ellipsis.
	 *
	 * ⚠ NO FIXED HEIGHT (since 2026-10-02). The 2026-09-30 version set the
	 * heading to `block-size: 2lh` so a one-line name reserved its second line.
	 * That left an empty line under every short name. The heading is as tall as
	 * its name.
	 *
	 * ⚠ VISUAL ONLY. The full name stays in the DOM, so screen readers, find in
	 * page and the link's accessible name all get all of it. Nothing is truncated
	 * server-side.
	 *
	 * md and up only, where cards share a row. Below 768 each card is its own
	 * row, so there is nothing to align, and the name wraps in full.
	 *
	 * ⚠ THE CLAMP IS ON THE LINK, NOT THE HEADING. The first version clamped the
	 * `h3` and rendered NO ellipsis. WordPress core makes the link
	 * `inline-block` (`.wp-block-post-title :where(a)`), a single atomic box that
	 * line-clamp cannot split into lines, so the name was only cut by overflow.
	 * A spec reading `-webkit-line-clamp: 2` passed anyway, which is why the spec
	 * now measures the link's box against its content. The link's own
	 * `overflow` does not clip its own outline, so focus needs no workaround.
	 *
	 * Chromium reports this clamped `-webkit-box` as computed `display:
	 * flow-root`. That is its line-clamp implementation, not a rule overriding
	 * this one (CDP matched styles, 2026-09-30).
	 */
	.tc-shop-archive .wc-block-product .tc-product-card__title a {
		display: -webkit-box;
		-webkit-box-orient: vertical;
		-webkit-line-clamp: 2;
		line-clamp: 2;
		overflow: hidden;
	}

	/*
	 * ⚠ `relative`, NOT `static` — AND THE DIFFERENCE BROKE THE BUY BOX ONCE.
	 * WooCommerce's product-image block absolutely positions
	 * `.wc-block-components-product-image__inner-container` and relies on this
	 * element being its containing block. product-detail.css:1918 records
	 * `static` here letting that container escape to the initial containing
	 * block, where it resolved to a transparent sheet over the whole page and
	 * silently ate every click. Nothing looked wrong; three assertions nine hundred
	 * pixels away started timing out.
	 */
	.tc-shop-archive .tc-product-card__media {
		position: relative;
		inset-block-start: auto;
		inset-inline-start: auto;
		inline-size: auto;
	}

	.tc-shop-archive .tc-product-card__media img {
		border-radius: 2px;
	}

	.tc-shop-archive .wp-block-query-pagination .page-numbers,
	.tc-shop-archive .wp-block-query-pagination-previous,
	.tc-shop-archive .wp-block-query-pagination-next {
		width: 40px;
		height: 40px;
	}
}

/* ══ lg — 64rem / breakpoint/lg (1024) ══════════════════════════════════════
   The rail appears and the grid goes 4-up.

   1400 content band = 268 rail + 64 gutter + 1068 grid,
   and 1068 = 4 x 249 + 3 x 24.
   ────────────────────────────────────────────────────────────────────────── */

@media (min-width: 64rem) {
	/*
	 * I14 (Gary, 2026-10-02): 56 above the "Filter by" toolbar, desktop only.
	 * Board 43324:19027: the band (Frame 155) ends at y 453 and the toolbar
	 * row (Frame 156) starts at 509. The gap was the layout's 24 block gap
	 * (`:where(.is-layout-constrained) > *`), and it is the toolbar's margin
	 * alone: the band's 40 bottom padding is inside its painted surface, and on
	 * /shop/ the band is hidden and this margin collapses out to the header.
	 * No token is 56, so it is two: xlarge 40 + normal 16. The toolbar only
	 * displays from lg, so below it nothing changes. "Filter by" is 16/24 and
	 * untrimmed on the board, so recipe B does not apply.
	 */
	.tc-shop-archive .tc-shop-archive__toolbar {
		margin-block-start: calc(var(--wp--preset--spacing--xlarge, 2.5rem) + var(--wp--preset--spacing--normal, 1rem)); /* 56 */
	}

	.tc-shop-archive .tc-shop-archive__body {
		grid-template-columns: 268px minmax(0, 1fr);
		column-gap: var(--wp--preset--spacing--xxlarge, 4rem); /* 64 */
	}

	.tc-shop-archive .tc-shop-archive__rail {
		display: block;
	}

	.tc-shop-archive .wc-block-product-template.wc-block-product-template {
		grid-template-columns: repeat(4, minmax(0, 1fr));
	}
}

/* ══════════════════════════════════════════════════════════════════════════
   T-SA-2 — THE CATEGORY BAND
   ══════════════════════════════════════════════════════════════════════════

   Boards 43324:19747 (desktop) · 43324:18997 (tablet) · 43324:18294 (mobile).
   ⚠ NO FRAME PNGs EXIST FOR THIS SURFACE in docs/design/. Every number below
   comes from the task brief's measurements of those boards, and nothing here
   was compared to a comp.

   A full-bleed band at the TOP of the archive holding the page h1 and a
   horizontal strip of the current category's children. It is the first thing
   in `.tc-shop-archive`, above the toolbar, and it sits entirely outside the
   two-column body — so nothing here interacts with the rail or the grid.

   ── THE GEOMETRY, AND WHY THE THREE SCROLLER WIDTHS ARE NOT ONE MEASUREMENT ─

   The band's surface bleeds edge to edge; its CONTENT is 1400 centred, which
   at 1920 leaves the board's 260 gutter exactly. Below the content width the
   inline padding is the gutter:

       1920:  1920 - 2 x 260 = 1400   inner hits its 87.5rem cap, gutters centre
        768:   768 - 2 x  40 =  688   inner is padding-bound
        320:   320 - 2 x  16 =  288   inner is padding-bound

   ⚠ THE BOARD'S THREE SCROLLER WIDTHS — 928 / 728 / 304 — ARE MEASUREMENTS OF
   TWO DIFFERENT THINGS, and building all three the same way gets two of them
   wrong.

       desktop 928 = 4 x 220 + 3 x 16   TILE MATH. It is what four tiles
                                        OCCUPY inside a 1400 scroller, not the
                                        scroller's own width.
       tablet  728 = 688 + 40           CONTAINER + ONE GUTTER.
       mobile  304 = 288 + 16           CONTAINER + ONE GUTTER.

   No tile count produces 728 or 304 (at 196 + 16 the run is 196 / 408 / 620 /
   832), so those two are not tile math and cannot be. They are the container
   plus the right-hand gutter — a GUTTER-BLEED scroller that runs past the text
   column into the margin, which is the standard "there is more, keep going"
   affordance. Built as a negative inline-end margin equal to the band's own
   padding, so the two can never drift apart.

   Desktop does NOT bleed: its scroller is content width.

   ── OVERFLOW-DRIVEN CHEVRONS, WHICH IS NOT A DEPARTURE FROM THE BOARD ──────

   The boards draw chevrons at tablet and mobile and none at desktop. That is
   the OUTPUT, not the rule. The rule is `scrollWidth > clientWidth`, applied
   at every breakpoint by assets/js/category-band-scroller.js, and it produces
   the board's output on the board's data:

       desktop 1400 fits 6 x 220 + 5 x 16 = 1400 EXACTLY -> no chevrons at 4,
                                                            5 or 6 tiles
       tablet   728 fits 3 tiles (620); 4 need 832     -> chevrons
       mobile   304 fits 1 tile (196);  2 need 408     -> chevrons

   ⚠ AND IT IS THE ONLY VERSION THAT SURVIVES THE REAL DATA. Hardcoding "no
   chevrons at desktop" draws the board correctly for the board's own category
   and silently clips any parent with seven or more children. Measured
   2026-08-07 on this environment, FOUR product_cat parents exceed six
   non-empty children — Accessories 8, Fiber Optics 9, Satellite
   Communications 10, Digital / IPTV 16 — so the seventh tile is live content,
   not a hypothetical.

   ── COLOUR: THE SCHEME PAINTS, WITH EXACTLY THREE DECLARED EXCEPTIONS ──────

   The band takes `ftm-scheme-secondary-subtle` and declares no colour of its
   own. In THIS theme that scheme is re-pointed (F5, page-sections-schemes.css)
   to bg secondary-100 #E0F2FE and text/heading primary-800 #0A205B, which is
   exactly what the board binds.

   Three colours are declared, and no fourth:

       1. tile fill    base-white   the scheme's bg is the TINT, not white
       2. h1 colour    base-black   pinned rather than inherited
       3. tile label   base-black   and this one is load-bearing — see the
                                    category-grid undo below, which paints it
                                    WHITE on a white tile

   ⚠ 2 AND 3 ARE BYTE-IDENTICAL TO WHAT THE SCHEME ALREADY SUPPLIES HERE:
   base-black and primary-800 are both #0A205B in theme.json. They are declared
   anyway because the BOARD binds base-black, and a future scheme re-point
   should move the band's chrome without moving its type. They are belt and
   braces, not live overrides — recorded so nobody later "simplifies" them away
   believing they are inert.

   ── RADIUS 4 AND 2 HAVE NO TOKEN ──────────────────────────────────────────

   The radius scale is sm 6 / md 16 / pill 1.5em. 4 and 2 ship as documented
   literals, the same call category-grid made for its own 4.
   ══════════════════════════════════════════════════════════════════════════ */

/* ── Surface ────────────────────────────────────────────────────────────────
   `.ftm-scheme-*` only sets the role custom properties; nothing global paints
   them, so the band paints itself — what every media-family member and
   heading-banner already do. */

.tc-shop-archive .tc-category-band[class*="ftm-scheme-"] {
	background-color: var(--ftm-scheme-bg);
	color: var(--ftm-scheme-text);
}

/*
 * ⚠ SUPPRESSED ON /shop/, WHERE THERE IS NO TERM. `term-heading` renders
 * nothing without a queried term and `term-cards` in `children` mode renders
 * nothing too, which would leave a painted, empty, 40px-tall blue band across
 * the top of the shop index.
 *
 * Keyed on the HEADING because the heading is the "there is a term here"
 * signal and it is already computed server-side. The strip can legitimately be
 * absent while the term exists — a category with no children — and that state
 * still wants its h1. ⚠ The description panel at the foot of the page was
 * keyed the same way until G22 (2026-09-25) and now keys on its BODY instead;
 * do not copy that here, or a category with no description loses its title.
 *
 * ⚠ CONSEQUENCE: /shop/ HAS NO PAGE TITLE AT ALL. No board draws /shop/.
 * Recorded as an open item for the designer rather than invented around —
 * docs/DECISIONS.md, 2026-08-07 (T-SA-2).
 */

.tc-shop-archive .tc-category-band:not(:has(.tc-term-heading)) {
	display: none;
}

.tc-shop-archive .tc-category-band {
	padding-block: var(--wp--preset--spacing--xlarge, 2.5rem); /* 40 */
	padding-inline: var(--wp--style--root--padding-left, 1rem); /* the page grid: 16 at 320, 19.8 at 390 (QA batch D, V-G1 / C-4) */
}

.tc-shop-archive .tc-category-band__inner {
	max-inline-size: 87.5rem; /* 1400 — the theme wide-size */
	margin-inline: auto;

	/* Title block -> strip. */
	display: flex;
	flex-direction: column;

	/* ⚠ 32 IS A DOCUMENTED LITERAL, NOT A PRESET. Nothing on the spacing scale
	   resolves to it — large is 1.5rem/24 and xlarge is 2.5rem/40 — so writing
	   `var(--wp--preset--spacing--xlarge, 2rem)` here would render 40 and only
	   look right in a browser with no theme.json. Same class of one-off as
	   category-grid's 48 chrome gap and the hero's 80. */
	gap: 2rem; /* 32 */
}

/*
 * G24 (Gary, 2026-09-14): the title-to-tiles gap MEASURED 56, not the 32 the
 * flex gap above declares. `__inner` is authored as a flow-layout group, so WP
 * emits `:root :where(.is-layout-flow) > * { margin-block-start: 24 }` against
 * its children and the scroller took it on top of the gap. The same collision
 * the footer records on `.site-footer__lower` and the newsletter form. (0,3,0)
 * clears WP's (0,1,0) on specificity rather than on load order. Measured after:
 * title box bottom to tile top 32 at 1280, 768 and 390.
 */
.tc-shop-archive .tc-category-band__inner > * {
	margin-block-start: 0;
}

/* ── Head row: h1 left, chevrons hard right ────────────────────────────────
   The boards place the chevron PAIR at x=632 of the 688 text container
   (tablet) and x=232 of 288 (mobile). 632 + 56 = 688 and 232 + 56 = 288, so in
   both cases the pair is flush with the container's right edge — which is what
   `justify-content: space-between` on this row produces, at any width, without
   pinning either number.

   ⚠ THE VERTICAL PLACEMENT IS DERIVED. The brief binds the pair's x and its
   56x24 size; no board number binds its y. Placed on the title's own row,
   which is the only row it can share, and centred against it. */

.tc-shop-archive .tc-category-band__head {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--wp--preset--spacing--normal, 1rem); /* 16 */
}

/*
 * Both `term-heading` instances — this h1 and the description panel's h2 —
 * are the same block and carry the same class. Until G23 (2026-09-25) the
 * panel's rule was the unscoped `.tc-shop-archive .tc-term-heading` at
 * (0,2,0), and this rule was written at (0,3,0) to beat it on specificity.
 * The panel's rule is now scoped under `.tc-shop-archive__description`, so it
 * no longer matches this h1 at all; the (0,3,0) stays as the band's own scope.
 *
 * DECLARED COLOUR 2 OF 3.
 */

.tc-shop-archive .tc-category-band .tc-term-heading {
	margin-block: 0;
	font-family: var(--wp--preset--font-family--display);

	/*
	 * ⚠ LITERAL, NOT `var(--wp--preset--font-size--heading-small)`. That preset
	 * IS defined (theme.json:236) but it is FLUID — 1.3125rem at 320 rising to
	 * 2.0625rem — so it would draw 21px where the brief binds the SAME ROLE at
	 * all three breakpoints. Identical trap to category-grid's title-small,
	 * recorded there for the same reason.
	 */
	font-size: 2.0625rem; /* 33 — heading-small at its cap */
	font-weight: 700;
	line-height: 1.2121; /* 40/33 */
	letter-spacing: -0.02em;
	text-transform: capitalize;
	color: var(--wp--custom--color--base--black, #0a205b);
}

/* ── Chevrons ──────────────────────────────────────────────────────────────
   56x24 pair = two 24x24 buttons with an 8 gap.

   ⚠ INJECTED BY JAVASCRIPT, and that is deliberate rather than a shortcut:
   they do nothing at all without it. A markup-shipped pair would be two dead
   buttons in the accessibility tree on a page with JS off, where the scroller
   still scrolls perfectly well by touch, trackpad and keyboard. They are real
   <button>s with real accessible names — see the script.

   Hidden until the script measures overflow, so they never flash in on a strip
   that does not scroll. */

.tc-shop-archive .tc-category-band__nav {
	display: flex;
	flex: 0 0 auto;
	gap: var(--wp--preset--spacing--xsmall, 0.5rem); /* 8 */
}

.tc-shop-archive .tc-category-band__nav[hidden] {
	display: none;
}

.tc-shop-archive .tc-category-band__arrow {
	display: grid;
	place-items: center;
	inline-size: 24px;
	block-size: 24px;
	padding: 0;
	border: 0;
	background: none;
	cursor: pointer;
	color: inherit; /* the scheme's text role — NOT a fourth declared colour */
}

.tc-shop-archive .tc-category-band__arrow[disabled] {
	opacity: 0.35;
	cursor: default;
}

/*
 * PHONE TAP TARGETS (QA batch A, 2026-09-27). The arrows measured 24×24 on
 * phones against the 44×44 the brief requires, and at the drawn 8 gap (a 32
 * pitch) two 44 targets would overlap by 12. The repo owner approved widening
 * the gap on phones only: 20 makes the pitch exactly 44, so each 24 glyph gets
 * a transparent 44×44 ::after (the glyph owns ::before) and the two targets
 * meet at the gap's midpoint without overlapping. The pair grows 56 → 68;
 * 48rem up is unchanged.
 */
@media (max-width: 47.9375rem) {
	.tc-shop-archive .tc-category-band__nav {
		gap: 20px;
		margin-inline-start: auto; /* flush right on either row — see below */
	}

	/* THE HEAD WRAPS, so a long term name puts the pair on its own row rather
	   than off the page. Measured: "Satellite Communications" cannot shrink
	   below its longest word (246.8 at 33px), so heading + 16 + pair did not
	   fit the 288 row at 320 even at the old 56px pair — main scrolled
	   sideways there (scrollWidth 335) — and the 68 pair also missed 328 at
	   360 by 2.8. `margin-inline-start: auto` keeps the pair flush right
	   whichever row it lands on. Short names stay on one row, unchanged. */
	.tc-shop-archive .tc-category-band__head {
		flex-wrap: wrap;
	}

	.tc-shop-archive .tc-category-band__arrow {
		position: relative;
	}

	.tc-shop-archive .tc-category-band__arrow::after {
		content: "";
		position: absolute;
		inset: -10px;
	}
}

.tc-shop-archive .tc-category-band__arrow::before {
	content: "";
	inline-size: 24px;
	block-size: 24px;
	background-color: currentcolor;
}

/*
 * ⚠ CONTRACT SLUG WITH A LOCAL FALLBACK, AND THE FALLBACK IS WHAT PAINTS.
 * `assets/css/theme-tokens.css` pins `--ftps-icon-*` on `html:root` as
 * `var(--tc-icon-*)`, which is a BLANKING and not a re-point — `--tc-icon-*`
 * is not defined on `:root` anywhere, so each pinned slug computes to the
 * EMPTY STRING on <html> and inherits nothing down. Written as a bare
 * `mask: var(--ftps-icon-chevron-left)` this declaration would be invalid and
 * the pseudo-element would render a solid currentColor SQUARE.
 *
 * ⚠ THE FALLBACK RESOLVES ONLY BECAUSE `.tc-shop-archive` DEFINES
 * `--tc-icon-chevron-left` AND `--tc-icon-chevron-right` (top of this file, for
 * the pagination arrows) AND CUSTOM PROPERTIES INHERIT. The band is inside that
 * element, so it gets both for free and does not redeclare them — a second copy
 * is a second thing to keep in sync. There is no global `--tc-icon-*` layer to
 * fall back to if that ever changes; move the band out of `.tc-shop-archive`
 * and these chevrons go blank.
 *
 * `--ftps-icon-chevron-left` was ADDED to the pin by this build. Verified
 * first that it had ZERO consumers across themes/ and plugins/, so the blast
 * radius is these two rules. `--ftps-icon-chevron-right` was already pinned;
 * its one other consumer, patterns/header/header.css:1736, is correctly written
 * with its own fallback and is unaffected.
 */

.tc-shop-archive .tc-category-band__arrow--prev::before {
	/* stylelint-disable-next-line property-no-vendor-prefix -- mask needs the prefix pair; scoped exception per the header/media precedent */
	-webkit-mask: var(--ftps-icon-chevron-left, var(--tc-icon-chevron-left)) center / contain no-repeat;
	mask: var(--ftps-icon-chevron-left, var(--tc-icon-chevron-left)) center / contain no-repeat;
}

.tc-shop-archive .tc-category-band__arrow--next::before {
	/* stylelint-disable-next-line property-no-vendor-prefix -- as above */
	-webkit-mask: var(--ftps-icon-chevron-right, var(--tc-icon-chevron-right)) center / contain no-repeat;
	mask: var(--ftps-icon-chevron-right, var(--tc-icon-chevron-right)) center / contain no-repeat;
}

.tc-shop-archive .tc-category-band__arrow:focus-visible {
	outline: 2px solid var(--wp--custom--color--secondary--600, #057db9);
	outline-offset: 2px;
}

/* ── Scroller ──────────────────────────────────────────────────────────────
   Mobile and tablet bleed into the right gutter; desktop does not. The
   negative margin equals the band's own inline padding, so the two cannot
   drift apart. */

.tc-shop-archive .tc-category-band__scroller {
	overflow-x: auto;
	overscroll-behavior-x: contain;

	/* Gutter-bleed: 288 + 16 = 304 at mobile. The padding puts the last tile's
	   trailing edge back where the container's edge is, so a fully-scrolled
	   strip does not end flush against the viewport. */
	margin-inline-end: calc(-1 * var(--wp--style--root--padding-left, 1rem)); /* the band's gutter */
	padding-inline-end: var(--wp--style--root--padding-left, 1rem);

	/* No visible scrollbar track — the chevrons and the bleed are the
	   affordance. Keyboard and touch scrolling are untouched. */
	scrollbar-width: none;
}

.tc-shop-archive .tc-category-band__scroller::-webkit-scrollbar {
	display: none;
}

/* ── The strip ─────────────────────────────────────────────────────────────
   ⚠ THE SELECTOR IS `ul.…`, NOT `.tc-category-band__strip ul`. `className` on a
   dynamic block lands on the block's OWN root element, and term-cards' root IS
   the <ul>. Both `is-style-strip` and `tc-category-band__strip` are on it. The
   descendant form matches NOTHING — the same trap brand-grid.css records. */

.tc-shop-archive .tc-category-band__strip {
	display: flex;
	gap: var(--wp--preset--spacing--normal, 1rem); /* 16, every breakpoint */
	margin-block: 0;
	margin-inline: 0;
	padding: 0;
	list-style: none;
}

.tc-shop-archive .tc-category-band__strip .tc-term-card {
	flex: 0 0 auto;
	margin: 0;

	/* Fixed, not fluid. A fluid tile would shrink to fit and the strip would
	   never overflow — which would silently delete the whole scroller.

	   GROW-ONLY-WHEN-NEEDED (QA batch A, 2026-09-27). The label used to break
	   INSIDE words — "Accessori / es", "Dishne / twork" at 768/834 — because
	   the tile was a hard 196 and the label `overflow-wrap: anywhere`. Measured
	   over all 118 product_cat names at the label's 14/500: 51 words are wider
	   than tablet's 68px text slot, 12 than phone's 84 and 5 than desktop's 92
	   ("Communications" 111). No fixed width fixes that.

	   So 196 (220 from 64rem) is now a FLOOR, and the tile is `min-content`
	   above it: a tile keeps the drawn width unless its longest word cannot
	   fit, and then grows by exactly the overflow ("Accessories" ~208 at
	   tablet). It is a horizontal scroller, so an uneven tile costs no layout.
	   Approved by the repo owner; the band has no frame (see §T-SA-2 above). */
	inline-size: min-content;
	min-inline-size: 196px; /* mobile and tablet */
}

/*
 * ⚠ UNDOING category-grid.css's CARD TREATMENT — load-bearing, and predicted.
 *
 * category-grid.css styles `.tc-term-card*` UNSCOPED and pattern CSS loads
 * globally (build-knowledge §1), so every rule it writes for a category card
 * also lands here. DECISIONS.md 2026-07-28 item 4 records this and names this
 * exact collision; brand-grid was the second consumer and this strip is the
 * THIRD. Left alone the tiles would render as navy panels with a bottom-up
 * scrim, a cover-fit image stretched edge to edge, an invisible white 20/700
 * title and a white arrow chip.
 *
 * NONE OF THAT IS IN THE BLOCK. Looking for it in blocks/term-cards/ wastes
 * the session.
 *
 * Undone SCOPED here rather than by rescoping category-grid.css, exactly as
 * brand-grid did and for the same reason: T34 is shipped and frame-verified and
 * this build must not move it. ⚠ RESCOPING category-grid.css REMAINS ITS OWN
 * OPEN TASK, with its own re-verification.
 *
 * Reaching (0,3,0)/(0,3,1) against its (0,1,0)/(0,1,1), so this wins on
 * specificity and not on which stylesheet the glob happens to emit first.
 */

.tc-shop-archive .tc-category-band__strip .tc-term-card__link {
	position: static;
	display: flex;
	flex-direction: row; /* image left, label right */
	align-items: center;
	justify-content: flex-start;
	gap: var(--wp--preset--spacing--normal, 1rem); /* 16 */
	aspect-ratio: auto; /* category-grid's 7/6 and its md 1/1 */
	padding-block: var(--wp--preset--spacing--xsmall, 0.5rem); /* 8 */
	padding-inline: var(--wp--preset--spacing--normal, 1rem); /* 16 */
	border-radius: 4px; /* documented literal — no token, see the header */
	overflow: visible;
	isolation: auto;

	/* DECLARED COLOUR 1 OF 3. The scheme's bg is the secondary-100 tint the
	   band is painted with; the board draws the tiles WHITE on top of it. */
	background-color: var(--wp--custom--color--base--white, #fff);
}

.tc-shop-archive .tc-category-band__strip .tc-term-card__media {
	position: static;
	inset: auto;
	z-index: auto;
	flex: 0 0 auto;
	display: block;
	inline-size: 64px; /* mobile */
	block-size: 64px;
	border-radius: 2px; /* documented literal — no token */
	overflow: hidden;
	background-color: transparent; /* category-grid's primary-800 panel */
}

.tc-shop-archive .tc-category-band__strip .tc-term-card__media::after {
	content: none; /* category-grid's bottom-up scrim */
}

.tc-shop-archive .tc-category-band__strip .tc-term-card__img,
.tc-shop-archive .tc-category-band__strip .tc-term-card__media img {
	position: static;
	inline-size: 100%;
	block-size: 100%;
	object-fit: cover; /* the board's fit — kept, not undone */
}

/*
 * THE NO-IMAGE STATE.
 *
 * ⚠ MEASURED 2026-08-07, AND IT SETTLES A DISPUTE IN THE BRIEF: 26 of 118
 * product_cat terms carry a `thumbnail_id` that passes
 * `wp_attachment_is_image()`. Not 0. The plan's "92 short" is the same number
 * from the other end (118 - 92 = 26) and is CONFIRMED.
 *
 * ⚠ BUT 0 OF THOSE 26 HAVE THE FILE ON DISK IN THIS CODESPACE, and
 * term-cards/render.php requires `file_exists( get_attached_file() )` before it
 * will emit an <img>. So the image state is real in the DATA and unreachable in
 * this ENVIRONMENT — which is why the fallback below is what renders on every
 * tile here, and why the image state is verified against a stubbed response
 * rather than claimed.
 *
 * Both states ship. The board draws 80x80 images, so images are the designed
 * state and the fallback is the edge case: an empty rounded well in
 * neutral-100, at the exact box the image would have occupied, so a strip of
 * mixed tiles keeps one baseline and one rhythm. Deliberately NOT
 * category-grid's navy-and-scrim large-card treatment, which is not this
 * build's to reuse and is wrong at 80px anyway.
 *
 * neutral-100 is not a fourth DECLARED colour in the sense the brief means: it
 * paints an empty box, not type or a surface the board binds. Called out here
 * so the count in the report is honest about what it does and does not include.
 */

/*
 * ⚠ `:not(:has(img))`, NOT `:empty`. render.php emits the span across three
 * lines with the <img> conditional inside it, so the element always contains a
 * whitespace text node and `:empty` never matches — measured, not assumed: with
 * `:empty` the well computed `rgba(0, 0, 0, 0)` on all five tiles of the board's
 * own category, i.e. it silently did nothing. `:has()` tests for the image
 * itself, which is the actual question, and this file already depends on `:has()`
 * in two other places.
 */
.tc-shop-archive .tc-category-band__strip .tc-term-card__media:not(:has(img)) {
	background-color: var(--wp--custom--color--neutral--100, #e9ecef);
}

/* The count is off by attribute on this instance; the rule is belt and braces
   for a template edit that turns it back on, where the absolutely-positioned
   badge category-grid draws would land on top of the label. */

.tc-shop-archive .tc-category-band__strip .tc-term-card__count {
	display: none;
}

.tc-shop-archive .tc-category-band__strip .tc-term-card__row {
	flex: 1 1 auto;
	min-inline-size: 0;
	justify-content: flex-start;
}

/*
 * DECLARED COLOUR 3 OF 3, and the one that is genuinely load-bearing:
 * category-grid paints this white at 20/700 for a photo card, which on a white
 * tile is invisible.
 *
 * ⚠ THE LABEL WRAPS, AND THAT IS A JUDGMENT CALL. The board's first tile
 * flexes; its other three are `whitespace: nowrap` and centred. The FIRST is
 * the one that ships, for all of them: "Conditional Access" and "Digital /
 * IPTV" are real child names of the board's own category and both overflow a
 * nowrap label in the 92px the desktop tile leaves for text
 * (220 - 16 - 80 - 16 - 16 = 92; tablet leaves 68). A nowrap label would clip
 * or blow the tile on real data the board simply did not draw.
 */

.tc-shop-archive .tc-category-band__strip .tc-term-card__title {
	font-size: var(--wp--preset--font-size--label-medium, 0.875rem); /* 14 */
	font-weight: 500;
	line-height: 1.4286; /* 20/14 */
	letter-spacing: normal;
	color: var(--wp--custom--color--base--black, #0a205b);
	text-align: start;

	/* `break-word`, not `anywhere`: `anywhere` lets min-content fall to one
	   character, which is what split words mid-way. `break-word` leaves
	   min-content at the longest word — so the tile above can size to it — and
	   still breaks a word only if something ever constrains the tile below it.
	   term-cards/render.php adds a <wbr> after every "/" so compound names
	   ("Cabinets/Enclosures", 138px unbroken) wrap at the slash instead. */
	overflow-wrap: break-word;
}

/*
 * category-grid's arrow chip. Decorative, carries no text, already
 * aria-hidden — so hiding it in CSS takes nothing out of the accessibility
 * tree. Same call and same reasoning as brand-grid. The boards draw no chip.
 */

.tc-shop-archive .tc-category-band__strip .tc-term-card__chip {
	display: none;
}

.tc-shop-archive .tc-category-band__strip .tc-term-card__link:focus-visible {
	outline: 2px solid var(--wp--custom--color--secondary--600, #057db9);
	outline-offset: 2px;
}

/* ══ md — 48rem / breakpoint/md (768) ═══════════════════════════════════════
   Gutter 40, so the inner is 688 and the bleed is 40 -> scroller 728.
   The image goes to its full 80; the tile stays at 196.
   ────────────────────────────────────────────────────────────────────────── */

@media (min-width: 48rem) {
	.tc-shop-archive .tc-category-band {
		padding-inline: var(--wp--preset--spacing--xlarge, 2.5rem); /* 40 */
	}

	.tc-shop-archive .tc-category-band__scroller {
		margin-inline-end: calc(-1 * var(--wp--preset--spacing--xlarge, 2.5rem)); /* -40 */
		padding-inline-end: var(--wp--preset--spacing--xlarge, 2.5rem); /* 40 */
	}

	.tc-shop-archive .tc-category-band__strip .tc-term-card__media {
		inline-size: 80px;
		block-size: 80px;
	}
}

/* ══ lg — 64rem / breakpoint/lg (1024) ══════════════════════════════════════
   The tile goes to 220 and the scroller stops bleeding: at desktop it is
   content width, and 6 x 220 + 5 x 16 = 1400 exactly.

   ⚠ THE BLEED IS REMOVED AT lg, NOT AT THE WIDTH THE INNER STOPS BEING
   PADDING-BOUND. Those are different widths (the inner caps at 1400 + 2 x 40 =
   1480), and lg is the one the boards change at. Between 1024 and 1480 the
   scroller is therefore content width with a 40 gutter, which is the desktop
   board's behaviour scaled down rather than a fourth layout.
   ────────────────────────────────────────────────────────────────────────── */

@media (min-width: 64rem) {
	.tc-shop-archive .tc-category-band__scroller {
		margin-inline-end: 0;
		padding-inline-end: 0;
	}

	.tc-shop-archive .tc-category-band__strip .tc-term-card {
		min-inline-size: 220px;
	}
}

/* ══ THE FILTERS SHEET — T-SA-4 ═════════════════════════════════════════════
   Boards 43869:24354 / 43869:24352 (tablet, closed and Brand-open),
   43869:24353 / 43869:24351 (smartphone), and the toolbar rows inside the
   Shop Archive Tablet and Smartphone boards, 43324:18994 / 43324:18291.

   ⚠ NO FRAME PNG EXISTS FOR ANY OF THESE IN docs/design/. Every number below
   came from `get_metadata` on the frames named above, which returns the real
   x/y/w/h of each layer, and from the task brief. No screenshot was compared to
   a comp — the same standing as T-SA-1 and T-SA-3.

   ── BRANCH A: THE 601px BOUNDARY IS A MEDIA QUERY, SO IT CAN BE MOVED ───────

   `product-filters-style.css` switches WooCommerce's overlay from a fixed
   bottom sheet to a static column at `@media (min-width: 601px)`. That is a
   plain media query, not Interactivity API state, so this build moves the
   boundary to lg by restating the sheet for the whole below-lg range.

   ⚠ EVERY RULE IN WOO'S BLOCK IS WRAPPED IN `:where()`, so the whole desktop
   mode weighs (0,1,0), or (0,2,0) where `.is-overlay-opened` is compounded in.
   Everything below reaches at least (0,3,0) through `.tc-shop-archive` and
   `.tc-shop-archive__rail`, so nothing here depends on load order. That matters
   because Woo's stylesheet is a block style and the print order of block styles
   against theme styles is not something this file should be betting on.

   ── WHAT THIS SECTION RESTATES THAT WOO ALREADY DOES BELOW 601 ─────────────

   Below 601px Woo's base rules already produce the fixed sheet, so about half
   of the mechanics block below is a restatement rather than an override there.
   That is deliberate and it is ONE query rather than two: a
   `(min-width: 601px) and (max-width: 63.9375rem)` window would put the tablet
   sheet and the phone sheet in different places in this file and invite them to
   drift. Read it as "below lg, this is what a sheet is", and note that between
   601 and 1023 every line of it is load-bearing.

   ── PANEL HEIGHT IS DERIVED, AND SO IS ONE HORIZONTAL NUMBER ──────────────

   ⚠ THE COMP FIXES THE PANEL AT 996px AND THAT IS AN ARTIFACT. Measured on
   43869:24352: the group list ends at panel y=480 and the footer starts at
   y=868, so the comp is 388px of empty space produced by drawing a short list
   inside a fixed-height frame. Shipped viewport-bound instead — a flex column
   whose header and footer are pinned and whose group list is the only scroll
   region. A later board that binds a height overrides `block-size` on
   `__overlay-dialog` and nothing else.

   ⚠ THE PANEL IS 752 OF 768 AND 304 OF 320, AND THE BOARDS DO NOT SAY WHICH
   SIDE THE 16 FALLS ON. Split evenly, 8 each side, which is what makes the
   radius-4 corners visible at all. The 16 is the board's; the split is derived.

   ── RADIUS 2 AND 4 ARE DOCUMENTED LITERALS ────────────────────────────────

   The scale is sm 6 / md 16 / pill 1.5em (build-knowledge §2). Nothing resolves
   to 2 or 4. Same call T-SA-2 §3 and category-grid both made.
   ────────────────────────────────────────────────────────────────────────── */

/* ── Below lg — the toolbar row, and the sheet ─────────────────────────────── */

@media (max-width: 63.9375rem) {
	/*
	 * ⚠ THE REAL TOOLBAR IS HIDDEN AND THE SORT CONTROL GOES WITH IT. The
	 * tablet and mobile boards draw the trigger and the count on this row and
	 * NO sort control — and the trigger they draw is labelled "Filter & Sort",
	 * which is the contradiction. This build ships the trigger and does not
	 * ship sorting below lg. Open item 1 in the report; it is a follow-up task
	 * and not a thing to invent here.
	 */
	.tc-shop-archive .tc-shop-archive__toolbar {
		display: none;
	}

	/*
	 * The rail IS the toolbar row here. 43324:18994: trigger 133x40 at x=0, the
	 * count right-aligned to the container's own right edge, and the row 56 tall
	 * — 40 of trigger plus 16 of padding above the rule. 43324:18291 is the same
	 * row at 36 and 52.
	 */
	.tc-shop-archive .tc-shop-archive__rail {
		display: flex;
		align-items: center;
		justify-content: space-between;
		gap: var(--wp--preset--spacing--large, 1.5rem); /* 24 — the board's */
		padding-block-end: var(--wp--preset--spacing--normal, 1rem); /* 16 */
		border-block-end: 1px solid var(--wp--custom--color--neutral--200, #dee2e6);

		/* Same derived 24 the real toolbar uses above the grid, and for the same
		   reason: the boards draw the rule but not the space under it. */
		margin-block-end: var(--wp--preset--spacing--large, 1.5rem); /* 24 */
	}

	/*
	 * ⚠ THE ARCHIVE'S COUNT MOVES, IT IS NOT DUPLICATED. The desktop board
	 * (43324:19027) puts "N results" above the grid, inside the grid column;
	 * both small boards put it on the trigger's row and draw nothing above the
	 * grid. Two elements exist in the DOM below lg and exactly one is displayed.
	 * The one inside the collection is a block; the one here is appended by
	 * includes/shop-filters-sheet.php, because the toolbar has no query context
	 * and firsttracks-theme/query-results-count correctly renders nothing
	 * without one.
	 */
	.tc-shop-archive .tc-shop-archive__main .tc-query-results-count {
		display: none;
	}

	/* `flex: 1 1 auto` so the text box runs from the trigger's gap to the
	   container's right edge and `text-align: end` has something to align in —
	   43324:18994 draws the count as a 531-wide box ending at x=688, not as a
	   shrink-wrapped label. */
	.tc-shop-archive .tc-shop-archive__toolbar-count {
		flex: 1 1 auto;
		margin-block: 0;
		font-family: var(--wp--preset--font-family--body);
		font-size: var(--wp--preset--font-size--label-medium, 0.875rem); /* 14 */
		font-weight: 500;
		line-height: 1.4286; /* 20 */
		text-align: end;
		color: var(--wp--custom--color--base--black, #0a205b);
	}

	/* ── The trigger ────────────────────────────────────────────────────────
	 * 43324:18995: 133x40, icon 16 at x=12, label at x=36, right padding 12.
	 * So padding 12 inline / 8 block, gap 8, icon 16 — and the 133 is what
	 * "Filter & Sort" measures — the label shipped since 2026-10-02 (it was
	 * "Filters" while the sheet had no sort). The generative numbers are the
	 * padding, the gap and the type; the width is their consequence, and with
	 * the board's label it lands on the board's 133.
	 *
	 * Woo styles this button at (0,1,0) — transparent, borderless, `.875em`,
	 * zero padding — and its span at (0,2,0). Both are overridden here.
	 */
	.tc-shop-archive .tc-shop-archive__rail .wc-block-product-filters__open-overlay {
		display: flex;
		flex: 0 0 auto;
		align-items: center;
		gap: var(--wp--preset--spacing--xsmall, 0.5rem); /* 8 */

		/* G25 (Gary, 2026-09-14): base/black, was neutral-200. Gary states there
		   is no colour mockup for this control; the value is his ruling, and
		   getting it into the frame is on his list (review doc §6). */
		border: 1px solid var(--wp--custom--color--base--black, #0a205b);
		border-radius: 2px;

		/* Outline only (Gary, 2026-10-01): no fill, so it does not compete with
		   the active-filter chips under it. Border, label, icon and height kept. */
		background-color: transparent;
		padding-block: 0;

		/* 11 + the 1px border = the board's 12 from the edge. Figma's stroke is
		   INSIDE the frame, so 43324:18995's "icon at x=12" includes it — the
		   same inside-stroke arithmetic as `block-size` below. At 12 the box
		   measured 135 against the board's 133 with "Filter & Sort". No token:
		   8 and 16 are the neighbours. */
		padding-inline: 11px;
		font-family: var(--wp--preset--font-family--body);
		font-size: var(--wp--preset--font-size--label-large, 0.9375rem); /* 15 */
		font-weight: 500;
		line-height: 1.3333; /* 20 — the mobile board's label box */
		color: var(--wp--custom--color--base--black, #0a205b);
		cursor: pointer;

		/*
		 * ⚠ THE HEIGHT IS STATED, NOT BUILT FROM THE BOARD'S 8 OF BLOCK PADDING,
		 * AND THE 2px DIFFERENCE IS THE WHOLE REASON. Figma draws a border
		 * inside the frame, so 43324:18292's 8 + 20 + 8 closes at its stated 36
		 * WITH the 1px stroke included. In CSS at `height: auto` the border is
		 * added outside the padding box and the same three numbers compute to
		 * 38 — measured, before this was written. 36 and 40 are the board's own
		 * figures; deriving them from the padding would have missed both.
		 */
		block-size: 36px;
	}

	.tc-shop-archive
		.tc-shop-archive__rail
		.wc-block-product-filters__open-overlay
		span {
		padding: 0; /* Woo ships `0 5px`, which would widen the gap to 13 */
	}

	/*
	 * The glyph. `::before` rather than Woo's own `<svg>` because the filter in
	 * includes/shop-filters-sheet.php replaces the button's whole content —
	 * Woo's icon is a three-bar list glyph and the board draws a two-track
	 * slider. See the `--tc-icon-filters` note at the head of this file for why
	 * the contract slug had to be pinned before this could paint.
	 */
	.tc-shop-archive
		.tc-shop-archive__rail
		.wc-block-product-filters__open-overlay::before {
		content: "";
		flex: 0 0 auto;
		width: 16px;
		height: 16px;
		background-color: currentcolor;

		/* stylelint-disable-next-line property-no-vendor-prefix -- mask needs the prefix pair; scoped exception per the header/media/blog-archive precedent */
		-webkit-mask: var(--ftps-icon-filters, var(--tc-icon-filters)) center / contain no-repeat;
		mask: var(--ftps-icon-filters, var(--tc-icon-filters)) center / contain no-repeat;
	}

	/* build-knowledge §25 — this theme gives <button> no focus ring, so every
	   pattern declares its own. Four controls in this build need one. */
	.tc-shop-archive
		.tc-shop-archive__rail
		.wc-block-product-filters__open-overlay:focus-visible {
		outline: 2px solid var(--wp--custom--color--secondary--600, #057db9);
		outline-offset: 2px;
	}

	/* ── The sheet's mechanics ──────────────────────────────────────────────
	 * Undoing Woo's `min-width: 601px` desktop mode. Measured at 768 before
	 * this existed: trigger `display: none`, overlay `position: relative`,
	 * header and footer `display: none`, content `overflow-y: visible`.
	 *
	 * ⚠ `visibility` IS THIS BUILD'S ADDITION AND IT IS NOT DECORATION. Woo
	 * parks the closed dialog at `translateY(100vh)` — off screen, but still
	 * rendered and still in the accessibility tree. Without this a screen
	 * reader at tablet reads three filter groups that a sighted user cannot
	 * see and cannot reach. The delayed transition is what lets the slide-out
	 * finish before the sheet is hidden; on open the delay is removed so it
	 * becomes visible before it moves.
	 */

	/*
	 * ⚠ `inline-size: auto` IS THE LOAD-BEARING DECLARATION HERE AND IT UNDOES
	 * A T-SA-3 RULE 1,800 LINES UP. That rule sets `inline-size: 100%` so the
	 * block fills the 268px rail track. On this row the block must shrink-wrap
	 * its trigger instead, or it takes the whole row and pushes the count off
	 * the end — which is exactly what it did when measured.
	 *
	 * ⚠ AND `display: inline-flex` WOULD BE A NO-OP, WHICH IS WHY IT IS NOT
	 * WRITTEN. A flex item's display is blockified, so `inline-flex` computes to
	 * `flex` — measured. The shrink-wrap comes from the two properties below and
	 * from nothing else.
	 */
	.tc-shop-archive .tc-shop-archive__rail .wc-block-product-filters {
		flex: 0 0 auto;
		inline-size: auto;
	}

	.tc-shop-archive .tc-shop-archive__rail .wc-block-product-filters__overlay {
		position: fixed;
		inset: 0;
		inset-block-start: var(--top-padding, 0);
		flex-grow: 0;
		visibility: hidden;
		background-color: transparent;
		pointer-events: none;
		transition:
			background-color 0.5s,
			visibility 0s linear 0.5s;
	}

	.tc-shop-archive
		.tc-shop-archive__rail
		.wc-block-product-filters.is-overlay-opened
		.wc-block-product-filters__overlay {
		visibility: visible;
		z-index: 9999;
		background-color: rgb(95 95 95 / 35%);
		pointer-events: auto;
		transition-delay: 0s;
	}

	.tc-shop-archive
		.tc-shop-archive__rail
		.wc-block-product-filters__overlay-wrapper {
		position: relative;
		block-size: 100%;
		inline-size: 100%;
	}

	/*
	 * The panel. 752 of 768 and 304 of 320 — 8 each side, so the radius shows.
	 * Full height by design, NOT the comp's 996 (see the section head).
	 *
	 * G26 (Gary, 2026-09-14): 8 top and bottom as well, so the panel is a
	 * rounded card inset on all four sides rather than a sheet running edge to
	 * edge vertically. Was `0 8px`. The height is still viewport-bound — it is
	 * now the viewport less 16, so 884 in a 900 window.
	 */
	.tc-shop-archive
		.tc-shop-archive__rail
		.wc-block-product-filters__overlay-dialog {
		position: absolute;
		inset: 8px;
		display: flex;
		flex-direction: column;
		max-block-size: 100%;
		border-radius: 4px;
		background-color: var(--wp--custom--color--base--white, #fff);
		transform: translateY(100vh);
		transition: none;
	}

	.tc-shop-archive
		.tc-shop-archive__rail
		.wc-block-product-filters.is-overlay-opened
		.wc-block-product-filters__overlay-dialog {
		transform: translateY(0);
		transition: transform 0.5s;
	}

	/* ── The sheet's header ─────────────────────────────────────────────────
	 * 43869:24352, Frame 374: "All Filters" 70x24 at x=0, "Close" 41x24 right
	 * aligned to 672, the row 40 tall — i.e. 24 of text and 16 below it — and
	 * the whole block inset 40 from each edge and 24 from the top.
	 *
	 * ⚠ WOO SETS `flex-flow: row-reverse` HERE, which with a second child would
	 * put Close on the LEFT. Reset to `row`.
	 */
	.tc-shop-archive
		.tc-shop-archive__rail
		.wc-block-product-filters__overlay-header {
		display: flex;
		flex-flow: row nowrap;
		align-items: center;
		justify-content: space-between;
		gap: var(--wp--preset--spacing--normal, 1rem);
		border-block-end: 1px solid var(--wp--custom--color--neutral--200, #dee2e6);
		padding-block: var(--wp--preset--spacing--large, 1.5rem) var(--wp--preset--spacing--normal, 1rem); /* 24 / 16 */
		padding-inline: var(--tc-sheet-pad-inline);
	}

	.tc-shop-archive .tc-shop-sheet__title {
		margin-block: 0;
		font-family: var(--wp--preset--font-family--body);
		font-size: var(--wp--preset--font-size--label-large, 0.9375rem); /* 15 */
		font-weight: 700;
		line-height: 1.6; /* 24 */
		color: var(--wp--custom--color--base--black, #0a205b);
	}

	.tc-shop-archive
		.tc-shop-archive__rail
		.wc-block-product-filters__close-overlay {
		flex: 0 0 auto;
		gap: 0;
		border: 0;
		background: none;
		padding: 0;
		font-family: var(--wp--preset--font-family--body);
		font-size: var(--wp--preset--font-size--label-large, 0.9375rem); /* 15 */
		font-weight: 500;
		line-height: 1.6; /* 24 */
		color: var(--wp--custom--color--base--black, #0a205b);
		cursor: pointer;
	}

	/* The boards draw the word alone. Woo ships a close glyph beside it. */
	.tc-shop-archive
		.tc-shop-archive__rail
		.wc-block-product-filters__close-overlay
		svg {
		display: none;
	}

	.tc-shop-archive
		.tc-shop-archive__rail
		.wc-block-product-filters__close-overlay
		span {
		padding: 0;
	}

	.tc-shop-archive
		.tc-shop-archive__rail
		.wc-block-product-filters__close-overlay:focus-visible {
		outline: 2px solid var(--wp--custom--color--secondary--600, #057db9);
		outline-offset: 2px;
	}

	/* ── The scrolling group list ───────────────────────────────────────────
	 * Frame 125 starts 16 below the header's rule and is the only thing that
	 * scrolls. `overflow-y: auto` rather than Woo's `scroll`: a scrollbar
	 * gutter drawn on a list that fits is a line the boards do not draw.
	 */
	.tc-shop-archive
		.tc-shop-archive__rail
		.wc-block-product-filters__overlay-content {
		display: flex;
		flex: 1 1 auto;
		flex-direction: column;
		gap: 0;
		overflow-y: auto;
		padding-block: 0;
		padding-inline: var(--tc-sheet-pad-inline);
	}

	/*
	 * ⚠ THE GROUP RHYTHM IS NOT THE DESKTOP RAIL'S AND THE DIVIDER RULE IS
	 * DIFFERENT TOO. T-SA-3 draws 24 of padding per group and a divider
	 * BETWEEN groups, never above the first. Measured on 43869:24352 the sheet
	 * draws a divider at the BOTTOM OF EVERY GROUP, the last one included, and
	 * the first group carries no top padding — Frame 117 is 40 tall with its
	 * text at y=0, while Frames 118 and 119 are 56 with text at y=16.
	 *
	 * So T-SA-3's three head-suppression rules are not wanted here. They are
	 * neutralised by resetting padding and border on the group itself and
	 * hanging the divider off every group instead.
	 *
	 * ⚠ THE COMP'S FIRST GROUP HAS NO TOP PADDING AND THIS SHIPS 16 ON ALL
	 * THREE, WHICH DRAWS THE SAME PIXELS AND SURVIVES A CASE THE COMP DOES NOT
	 * CONTAIN. The comp puts 16 above the first heading as the scroll region's
	 * own top padding and 16 on groups two and three. That needs a
	 * `:first-child` rule — and on `/shop/` the first group is Category, which
	 * T-SA-3 correctly renders as nothing and hides, while `:first-child` still
	 * matches it. The first VISIBLE group would then have lost its 16. Moving
	 * the whole 16 onto every group and zeroing the container's padding makes
	 * the head case disappear rather than be special-cased, which is the same
	 * defect T-SA-3 §10.8 had to fix in its divider rules by hand.
	 *
	 * ⚠ `gap: 0` UNDOES T-SA-3's 16 BETWEEN THE HEADING AND THE BODY. Here the
	 * space above the first checkbox row is the heading's own padding-bottom;
	 * leaving the gap in place would add a second 16 to a spec that names one.
	 */
	.tc-shop-archive
		.wc-block-product-filters__overlay-content
		.tc-shop-filter-group {
		gap: 0;
		border-block: 0;
		border-block-end: 1px solid var(--wp--custom--color--neutral--200, #dee2e6);
		padding-block: var(--wp--preset--spacing--normal, 1rem) 0; /* 16 / 0 */
	}

	/* 15 in the sheet, against the desktop rail's 16. Only the size moves. */
	.tc-shop-archive
		.wc-block-product-filters__overlay-content
		.tc-shop-filter-group__title {
		font-size: var(--wp--preset--font-size--label-large, 0.9375rem); /* 15 */
		padding-block-end: var(--wp--preset--spacing--normal, 1rem); /* 16 */
	}

	/*
	 * The expanded body. Frame 90: six rows at a 40 pitch — 24 of row and 16 of
	 * gap — then 24 of padding before the divider. No top padding; the heading's
	 * own 16 is the whole of the space above the first row.
	 */

	/*
	 * ⚠ `margin-block-start: 0` IS NOT TIDYING. Measured: the taxonomy and price
	 * blocks carry a 24px top margin of their own, which put the first checkbox
	 * row 24 below the heading where the board draws 16. The heading's own
	 * padding-bottom is the whole of that space and this is what lets it be.
	 */
	.tc-shop-archive
		.wc-block-product-filters__overlay-content
		.tc-shop-filter-group__title
		+ * {
		margin-block-start: 0;
		padding-block-end: var(--wp--preset--spacing--large, 1.5rem); /* 24 */
	}

	/*
	 * ⚠ THE ROW IS MADE A FLEX CONTAINER AND THE REASON IS SUBTLE ENOUGH THAT IT
	 * COST A MEASUREMENT. Woo's `__item` is a block wrapping an `inline-flex`
	 * label. An inline-level child sits in a LINE BOX, and the line box takes
	 * its height from the parent's inherited strut — 25.24px here, from Woo's
	 * compounding `.875em` — plus half-leading. So a 24px label measured a
	 * 31.23px row and the 40 pitch the board draws came out at 47.23. Making the
	 * row a flex container removes the line box, and the row is then exactly its
	 * label: 24, plus `__items`' own 16 gap, which is the board's 40.
	 *
	 * ⚠ SCOPED TO THE SHEET BY THE MEDIA QUERY THIS SITS IN, NOT BY THE
	 * SELECTOR. `__overlay-content` is the same element at every width — at lg
	 * WooCommerce merely makes it static — so this selector would reach the
	 * desktop rail if it were not inside the below-lg query. T-SA-3's rail is
	 * shipped and asserted at 18/14/20 and this build does not move it.
	 */
	.tc-shop-archive
		.wc-block-product-filters__overlay-content
		.wc-block-product-filter-checkbox-list__item {
		display: flex;
	}

	/* 24 box and 15/24 label, over the desktop rail's 18 and 14/20. */
	.tc-shop-archive
		.wc-block-product-filters__overlay-content
		.wc-block-product-filter-checkbox-list__input-wrapper,
	.tc-shop-archive
		.wc-block-product-filters__overlay-content
		input[type="checkbox"].wc-block-product-filter-checkbox-list__input,
	.tc-shop-archive
		.wc-block-product-filters__overlay-content
		svg.wc-block-product-filter-checkbox-list__mark {
		width: 24px;
		height: 24px;
	}

	/*
	 * ⚠ THE 24px BACKPLATE OVERRIDE THAT USED TO SIT HERE IS GONE, NOT MOVED.
	 * The base rule now sets `content: none` on that pseudo-element, so there
	 * is no box left to size — see the long note beside it. Sizing a
	 * pseudo-element that is never generated is the kind of rule that reads as
	 * load-bearing to the next person.
	 *
	 * ── THE BOX'S BORDER, READ OFF THE COMPONENT'S OWN VECTOR ───────────────
	 *
	 * `get_design_context` on 43869:24214 (" Checkbox / Normal", the instance
	 * the sheet draws) returns an SVG, not declared CSS, so the border is in
	 * the path: an outer rounded rect 24x24 r4 with an inner cutout at
	 * 2.667..21.333 r1.333, filled `#CED4DA`. That is a 2.667px ring, radius 4,
	 * neutral-300, over an unfilled (white) centre.
	 *
	 * ⚠ 2.667 IS AN ARTIFACT OF A SCALED INSTANCE, AND SHIPS AS 2. The master
	 * component (`0:1202`) is **18x18** — the size the desktop rail board
	 * 43324:19694 draws it at — and 18 -> 24 is x1.3333, which takes a 2px ring
	 * to 2.667 and a 3px radius to 4. So the sheet's stroke is the rail's 2px
	 * scaled up, not an independent value, and a fractional border-width would
	 * be reproducing the scale factor rather than the design. The RADIUS is
	 * kept at the drawn 4, because a radius genuinely does scale with the box
	 * — the same split review-batch-4 §5 made between the kit's 2px and the
	 * related rail's 4px.
	 *
	 * ⚠ AND THE RAIL'S OWN 18px CONTROL IS 2px/3px ON ITS BOARD, AGAINST THE
	 * 1px/2px IT SHIPS. Measured, deliberately NOT changed here: the rail is
	 * outside this brief, and its 1px/2px is the contact form's frame-sampled
	 * contract (contact-form.css:560, F16), so moving it is a two-surface
	 * decision rather than a drawer fix. Flagged in
	 * `docs/reports/review-batch-5.md` §2.
	 */
	.tc-shop-archive
		.wc-block-product-filters__overlay-content
		input[type="checkbox"].wc-block-product-filter-checkbox-list__input {
		border-width: 2px;
		border-radius: 4px;
	}

	.tc-shop-archive
		.wc-block-product-filters__overlay-content
		.wc-block-product-filter-checkbox-list__text-wrapper {
		font-size: var(--wp--preset--font-size--label-large, 0.9375rem); /* 15 */
		line-height: 1.6; /* 24 */
	}

	/* ── The pinned footer ──────────────────────────────────────────────────
	 * Frame 392: rule, 24, the count at 20 tall and CENTRED, 16, then two 44
	 * buttons in a 16 gap. Woo's footer holds only its Apply button, so the
	 * count and Clear All are injected before it (includes/shop-filters-sheet.php)
	 * and the three are laid out as a two-column grid with the count spanning —
	 * which is one rule fewer than wrapping the buttons in a flex row that does
	 * not exist in anybody's markup.
	 *
	 * ⚠ THE COMP'S FOOTER IS 688 WIDE AGAINST THE HEADER'S 672, i.e. its right
	 * inset is 24 where every other inset on the panel is 40. The mobile board
	 * has no such discrepancy — its buttons are 128 each in a 272 track, exactly
	 * consistent with 16 both sides. Read as a comp slip and normalised to 40;
	 * the buttons therefore measure 328, not the comp's 336. Recorded.
	 *
	 * Woo's own `box-shadow` above the footer is dropped: the boards draw a 1px
	 * rule there and a shadow as well would be two separators.
	 */
	.tc-shop-archive
		.tc-shop-archive__rail
		.wc-block-product-filters__overlay-footer {
		display: grid;
		flex: 0 0 auto;
		grid-template-columns: 1fr 1fr;
		gap: var(--wp--preset--spacing--normal, 1rem); /* 16 */
		box-shadow: none;
		border-block-start: 1px solid var(--wp--custom--color--neutral--200, #dee2e6);
		padding-block: var(--wp--preset--spacing--large, 1.5rem); /* 24 */
		padding-inline: var(--tc-sheet-pad-inline);
	}

	.tc-shop-archive .tc-shop-sheet__count {
		grid-column: 1 / -1;
		margin-block: 0;
		font-family: var(--wp--preset--font-family--body);
		font-size: var(--wp--preset--font-size--label-medium, 0.875rem); /* 14 */
		font-weight: 500;
		line-height: 1.4286; /* 20 */
		text-align: center;
		color: var(--wp--custom--color--base--black, #0a205b);
	}

	.tc-shop-archive .tc-shop-sheet__clear,
	.tc-shop-archive
		.tc-shop-archive__rail
		.wc-block-product-filters__apply {
		display: flex;
		align-items: center;
		justify-content: center;
		inline-size: 100%;
		border: 0;
		border-radius: 2px;
		padding-block: 12px; /* no token — 8 and 16 are the neighbours */
		padding-inline: var(--wp--preset--spacing--normal, 1rem); /* 16 */
		font-family: var(--wp--preset--font-family--body);
		font-size: var(--wp--preset--font-size--label-large, 0.9375rem); /* 15 */
		font-weight: 500;
		line-height: 1.3333; /* 20 -> 44 tall */
		text-decoration: none;
		cursor: pointer;
	}

	.tc-shop-archive .tc-shop-sheet__clear {
		background-color: var(--wp--custom--color--neutral--100, #e9ecef);
		color: var(--wp--custom--color--base--black, #0a205b);
	}

	.tc-shop-archive
		.tc-shop-archive__rail
		.wc-block-product-filters__apply {
		background-color: var(--wp--custom--color--secondary--600, #057db9);
		color: var(--wp--custom--color--base--white, #fff);
	}

	.tc-shop-archive .tc-shop-sheet__clear:focus-visible,
	.tc-shop-archive
		.tc-shop-archive__rail
		.wc-block-product-filters__apply:focus-visible {
		outline: 2px solid var(--wp--custom--color--secondary--600, #057db9);
		outline-offset: 2px;
	}
}

/* ── md — the sheet's inline padding, and two heights ───────────────────────
   The sheet's inline padding is 16 at 320 (43869:24353) and 40 from md
   (43869:24352). It is one custom property — declared with the icons at the
   head of this file — so the header, the group list and the footer cannot drift
   apart.
   ────────────────────────────────────────────────────────────────────────── */

@media (min-width: 48rem) {
	.tc-shop-archive {
		--tc-sheet-pad-inline: var(--wp--preset--spacing--xlarge, 2.5rem); /* 40 */
	}

	/* 43324:18994 draws the trigger 40 tall, and its label box 24, against the
	   mobile board's 36 and 20. */
	.tc-shop-archive .tc-shop-archive__rail .wc-block-product-filters__open-overlay {
		block-size: 40px;
		line-height: 1.6; /* 24 */
	}

	.tc-shop-archive .tc-shop-sheet__clear,
	.tc-shop-archive .tc-shop-archive__rail .wc-block-product-filters__apply {
		padding-block: 12px; /* 44 tall, the tablet board's */
	}
}

/* The tablet trigger's tap target: 40 on its board (43324:18995), 44 here.
   Invisible, the phone R2 pattern below at 2 rather than 4: an absolute box is
   inset from the PADDING box, so 1px border + 2 = 3. Nothing within 3 above or
   below it — the rail's 16 padding or the chip row's 16 gap. */
@media (min-width: 48rem) and (max-width: 63.9375rem) {
	.tc-shop-archive .tc-shop-archive__rail .wc-block-product-filters__open-overlay {
		position: relative;
	}

	.tc-shop-archive .tc-shop-archive__rail .wc-block-product-filters__open-overlay::after {
		content: "";
		position: absolute;
		inset-block: -3px;
		inset-inline: 0;
	}
}

/* ⚠ 8 BLOCK PADDING ON THE SHEET BUTTONS AT MOBILE, NOT 12. 43869:24353 draws
   them 36 tall against the tablet board's 44, and the type does not change. */

@media (max-width: 47.9375rem) {
	.tc-shop-archive .tc-shop-sheet__clear,
	.tc-shop-archive .tc-shop-archive__rail .wc-block-product-filters__apply {
		padding-block: var(--wp--preset--spacing--xsmall, 0.5rem); /* 8 -> 36 */
	}
}

/* ── Phone tap targets: the Filter & Sort button and the open sheet (QA batch A3) ──
   The 2026-09-27 re-review hit-tested the sheet open and expanded at 360 and
   390 and found every control under 44 (R1, R2): Filters 95×36, Close 41×24,
   group toggles 24 tall, Clear All / See Results 36, category links 20 at a
   36 pitch, brand rows 24 at a 40 pitch, Show All 30, and a price slider whose
   20px handles were the only thing that took a press.

   INVISIBLE WHERE THE GEOMETRY ALLOWS IT. Two stacked targets need a 44 pitch,
   so the two lists pitched under 44 cannot be fixed without moving pixels:
     - category links: 20 + 16 gap = 36. The gap becomes the links' own
       padding, 12 above and below: pitch 44, +8 per link.
     - brand rows: 24 + 16 gap = 40. The gap goes to 20: pitch 44, +4 per gap.
     - an expanded Price heading gains 4 below it (see the slider note).
   Those three are the visible changes, phones only, on D-QA-1 (DECISIONS.md
   Standing list 2). Everything else draws nothing.

   Every split below was measured against its neighbours, so no two targets
   overlap: a toggle takes 14 above its 24 and 6 below, because the first
   brand label reaches 10 above its row and the heading's padding is 16.

   PHONES ONLY, like the rest of the phone brief: the tablet sheet is out of
   its scope and unchanged. Checked on /shop/, a category and a brand archive
   (one template). e2e/touch-targets-verify.spec.ts pins every one of them.
   ────────────────────────────────────────────────────────────────────────── */

@media (max-width: 47.9375rem) {
	/* R2 — Filter & Sort, 36 tall on its board (43324:18292): 4 above and below.
	   Written as 5: an absolute box is inset from the PADDING box, and the
	   button has a 1px border. */
	.tc-shop-archive .tc-shop-archive__rail .wc-block-product-filters__open-overlay {
		position: relative;
	}

	.tc-shop-archive .tc-shop-archive__rail .wc-block-product-filters__open-overlay::after {
		content: "";
		position: absolute;
		inset-block: -5px;
		inset-inline: 0;
	}

	/* Close: padding and equal negative margin, so the word does not move.
	   24 of header padding above, 16 below, 16 to the panel's edge. */
	.tc-shop-archive .tc-shop-archive__rail .wc-block-product-filters__close-overlay {
		padding: 10px 2px;
		margin: -10px -2px;
	}

	/* Group toggles: 14 above, 6 below — see the section note. */
	.tc-shop-archive
		.wc-block-product-filters__overlay-content
		.tc-shop-filter-group__toggle {
		padding-block: 14px 6px;
		margin-block: -14px -6px;
	}

	/* Category links: pitch 44 (visible, D-QA-1). */
	.tc-shop-archive .wc-block-product-filters__overlay-content .tc-category-filter__list {
		gap: 0;
	}

	.tc-shop-archive .wc-block-product-filters__overlay-content .tc-category-filter__link {
		padding-block: 12px;
	}

	/* Brand rows: pitch 44 (visible, D-QA-1); each label reaches 10 above and
	   below its 24 row, so neighbours abut. It also fills the row, so a short
	   brand name still gives a wide target. The <label> is what the hit test
	   and a finger meet: a tap on it toggles its checkbox. */
	.tc-shop-archive
		.wc-block-product-filters__overlay-content
		.wc-block-product-filter-checkbox-list__items {
		gap: 20px;
	}

	.tc-shop-archive
		.wc-block-product-filters__overlay-content
		.wc-block-product-filter-checkbox-list__label {
		flex: 1 1 auto;
		padding-block: 10px;
		margin-block: -10px;
	}

	/* Show All, 30: 6 above (the last row's target ends 10 into the 16 gap)
	   and 8 below. Written 7 / 9: the inset is from the padding box and
	   the chip has a 1px border. */
	.tc-shop-archive
		.wc-block-product-filters__overlay-content
		.wc-block-product-filter-checkbox-list__show-more-button {
		position: relative;
	}

	.tc-shop-archive
		.wc-block-product-filters__overlay-content
		.wc-block-product-filter-checkbox-list__show-more-button::before {
		content: "";
		position: absolute;
		inset-block: -7px -9px;
		inset-inline: 0;
	}

	/* Clear All / See Results, 36 on the board (43869:24353): 4 above (16 of
	   gap to the count) and 4 below (24 of footer padding). */
	.tc-shop-archive .tc-shop-sheet__clear,
	.tc-shop-archive .tc-shop-archive__rail .wc-block-product-filters__apply {
		position: relative;
	}

	.tc-shop-archive .tc-shop-sheet__clear::after,
	.tc-shop-archive .tc-shop-archive__rail .wc-block-product-filters__apply::after {
		content: "";
		position: absolute;
		inset-block: -4px;
		inset-inline: 0;
	}

	/*
	 * ── THE PRICE SLIDER: A 44px GRAB, AND THE NEAREST HANDLE WINS ──────────
	 *
	 * WooCommerce's slider is two stacked `<input type="range">`, each 0px tall
	 * and `pointer-events: none`, whose 20px thumbs alone take a press. Measured
	 * at 360: a press 21px from a handle's centre grabbed nothing. And where two
	 * handles' targets overlap, the later input always wins whichever is
	 * nearer — which CSS cannot fix, because the handles' pixel distance is not
	 * available to a selector.
	 *
	 * So assets/js/shop-filters-sheet.js takes the press (section 3 there):
	 * it marks the sheet `has-tc-price-grab`, and only then does this apply.
	 *   - a transparent layer on the 4px rail: 20 above and below it (22 from
	 *     the handles' centre line) and 12 past each end (a handle's centre
	 *     sits 10 in from the end at its extreme);
	 *   - the thumbs stop taking the press themselves, so every press in the
	 *     band reaches the script, which picks the nearest handle within 22px
	 *     and drags it. Keyboard use is untouched: the inputs are still the
	 *     focusable, named controls.
	 *   - `pan-y`: a vertical swipe that starts on the slider still scrolls
	 *     the sheet; a horizontal one drags.
	 *
	 * ⚠ THE LAYER REACHES 20 ABOVE THE RAIL, AND THE RAIL SITS ONLY 16 BELOW
	 * THE PRICE HEADING. So while Price is expanded its heading gets 20 of
	 * padding below, not 16 (visible, +4, D-QA-1), and its toggle takes all
	 * of its extra 20 above the word and none below.
	 */
	.tc-shop-archive.has-tc-price-grab
		.wc-block-product-filters__overlay-content
		.wc-block-product-filter-price-slider__range {
		touch-action: pan-y;
	}

	/*
	 * ⚠ THIS RULE IS WHAT MAKES THE GRAB WORK UNDER TOUCH (QA batch F, N7).
	 *
	 * The round-three review drove the slider with Chromium touch for the
	 * first time: a press 14 or 21px ABOVE a handle went to the Price
	 * toggle, whose box ends 9px higher, although `elementFromPoint` there
	 * returns this rail. Cause: Chromium's TOUCH ADJUSTMENT. It retargets a
	 * touch to a nearby node that "responds to taps" (a button, a node with
	 * click listeners or with `:hover` / `:active` styles), and the rail was
	 * none of those — the grab listener is delegated from the document. So
	 * the toggle won. Measured on main at 360 and 390: 74 of 98 touch presses
	 * grabbed their handle, every miss above the handle.
	 *
	 * An `:active` style marks the rail as tap-responsive, and it then wins
	 * on its own pixels: 98 / 98. The cursor is a real affordance for the
	 * mouse as well. Keep a `:hover` or `:active` rule here whatever else
	 * changes — deleting it looks harmless and silently brings the bug back
	 * on phones only (touch-targets-verify, "price slider under touch").
	 */
	.tc-shop-archive.has-tc-price-grab
		.wc-block-product-filters__overlay-content
		.wc-block-product-filter-price-slider__range:active {
		cursor: grabbing;
	}

	.tc-shop-archive.has-tc-price-grab
		.wc-block-product-filters__overlay-content
		.wc-block-product-filter-price-slider__range::after {
		content: "";
		position: absolute;
		inset-block: -20px;
		inset-inline: -12px;
	}

	.tc-shop-archive.has-tc-price-grab
		.wc-block-product-filters__overlay-content
		.wc-block-product-filter-price-slider__range
		input[type="range"]::-webkit-slider-thumb {
		pointer-events: none;
	}

	.tc-shop-archive.has-tc-price-grab
		.wc-block-product-filters__overlay-content
		.wc-block-product-filter-price-slider__range
		input[type="range"]::-moz-range-thumb {
		pointer-events: none;
	}

	.tc-shop-archive.has-tc-price-grab
		.wc-block-product-filters__overlay-content
		.tc-shop-filter-group--price
		.tc-shop-filter-group__title:has(> .tc-shop-filter-group__toggle[aria-expanded="true"]) {
		padding-block-end: 20px;
	}

	.tc-shop-archive.has-tc-price-grab
		.wc-block-product-filters__overlay-content
		.tc-shop-filter-group--price
		.tc-shop-filter-group__toggle[aria-expanded="true"] {
		padding-block: 20px 0;
		margin-block: -20px 0;
	}
}

/* ── lg and up — the injected toolbar count is not drawn ────────────────────
   The desktop board keeps the count above the grid, which is the block inside
   the collection. This one stays in the DOM and is hidden, rather than being
   emitted conditionally in PHP, because `is_shop()`-style breakpoint logic does
   not exist on the server and a CSS breakpoint is where every other breakpoint
   decision on this archive already lives.
   ────────────────────────────────────────────────────────────────────────── */

@media (min-width: 64rem) {
	.tc-shop-archive .tc-shop-archive__toolbar-count {
		display: none;
	}
}

/* ══ Sort by, in the sheet — I14 follow-up (Gary, 2026-10-02) ═══════════════
   Injected first into `__overlay-content` by includes/shop-filters-sheet.php
   (edit 7). It is WooCommerce's catalog-sorting block a second time, so the
   options and the change handler are the toolbar's own.

   ⚠ NO BOARD DRAWS IT. Every number below is the sheet's own rhythm reused:
   the group's 16 above, the title's 15/24 bold and 16 below it, the body's 24
   below that, and the divider at the bottom — exactly what a Brand or Price
   group draws, so it reads as one more group that does not fold. The control
   is a bordered 44 box, NOT the toolbar's bare label: in a column of rows a
   label with a chevron reads as another collapsible heading, and 44 is the
   touch floor (touch-targets.spec). Border neutral-300 and radius 4 are the
   sheet checkbox's ring (43869:24214), the only bordered control it draws.

   ⚠ `__overlay-content` IS ALSO THE DESKTOP RAIL. At lg Woo makes the overlay
   a static column, so without the `display: none` below this control would
   appear in the rail beside the toolbar's — two sorts on one page. Hidden, not
   just moved, so it is out of the tab order and the accessibility tree too.
   ────────────────────────────────────────────────────────────────────────── */

@media (max-width: 63.9375rem) { /* below breakpoint/lg */
	.tc-shop-archive .tc-shop-sheet-sort {
		padding-block: var(--wp--preset--spacing--normal, 1rem) var(--wp--preset--spacing--large, 1.5rem); /* 16 / 24 */
		border-block-end: 1px solid var(--wp--custom--color--neutral--200, #dee2e6);
	}

	.tc-shop-archive .tc-shop-sheet-sort__label {
		display: block;
		padding-block-end: var(--wp--preset--spacing--normal, 1rem); /* 16 */
		font-family: var(--wp--preset--font-family--body);
		font-size: var(--wp--preset--font-size--label-large, 0.9375rem); /* 15 */
		font-weight: 700;
		line-height: 1.6; /* 24 */
		letter-spacing: 0.002em;
		color: var(--wp--custom--color--base--black, #0a205b);
	}

	/* 12 + 20 chevron + 12, centred in the 44. */
	.tc-shop-archive .tc-shop-sheet-sort .woocommerce-ordering::after {
		inset-block-start: 12px;
		inset-inline-end: 12px;
	}

	.tc-shop-archive .tc-shop-sheet-sort .woocommerce-ordering select {
		field-sizing: fixed;
		inline-size: 100%;
		block-size: 44px;
		border: 1px solid var(--wp--custom--color--neutral--300, #ced4da);
		border-radius: 4px;
		background-color: var(--wp--custom--color--base--white, #fff);
		padding: 0 44px 0 12px;
		font-weight: 500;
		text-align: start;
		text-align-last: start;
	}
}

@media (min-width: 64rem) {
	.tc-shop-archive .tc-shop-sheet-sort {
		display: none;
	}
}

/* ── Reduced motion ─────────────────────────────────────────────────────────
   The sheet still opens, closes and expands; only the animation goes. Same
   reading T-SA-2's scroller applies — reduced motion means no animation, not no
   behaviour. ⚠ The visibility transition's DELAY has to go with it, or the
   closed sheet would stay in the accessibility tree for half a second after a
   close that appeared instant.
   ────────────────────────────────────────────────────────────────────────── */

@media (prefers-reduced-motion: reduce) {
	.tc-shop-archive .tc-shop-archive__rail .wc-block-product-filters__overlay,
	.tc-shop-archive
		.tc-shop-archive__rail
		.wc-block-product-filters.is-overlay-opened
		.wc-block-product-filters__overlay,
	.tc-shop-archive
		.tc-shop-archive__rail
		.wc-block-product-filters__overlay-dialog,
	.tc-shop-archive
		.tc-shop-archive__rail
		.wc-block-product-filters.is-overlay-opened
		.wc-block-product-filters__overlay-dialog,
	.tc-shop-archive .tc-shop-filter-group__toggle::after {
		transition: none;
	}
}

/* ══ Active filters row — Gary, TC-DEV-I2 follow-up, 2026-10-01 ══════════════
   `includes/shop-active-filters.php` prints `ul.tc-active-filters` INSIDE
   woocommerce/product-filters, straight after the Filters trigger: one chip
   per active filter (the archive's own term, brand, price, `?categories=`) and
   "Clear all" at two or more. Supersedes #293's brand-only `.tc-brand-chip`.

   Gary's values: neutral-100 fill, 1px neutral-200 stroke, 14px text that does
   not change with the viewport (label-medium is `fluid: false`), and an × the
   size of the Category group's chevron: the same 24 box
   (`.tc-shop-filter-group__toggle::after` above) with the glyph drawn 14 wide,
   which is how wide the chevron's own path is inside its 24 (x 5 -> 19), so
   the two read the same size. The chip is 36 tall: 5 + 24 + 5 + 2.

   ⚠ THE OLD CHIP'S TABLET BUG, AND WHY THIS ONE CANNOT HAVE IT. #293's chip
   broke its line with `margin-inline-end: 100%` on a SHRINKABLE flex item: the
   margin took the whole line and the chip shrank to 60 × 276, one letter per
   line, at 390 and 768 (measured 2026-10-01). Here the row is a full-width
   wrapping flex line and every chip is `flex: none`, so a chip is always its
   content's size and only ever wraps WHOLE.

   ⚠ THE × IS A 24 BOX, 14 DRAWN AND 44 HIT: the glyph on `::before`, a
   transparent 44 × 44 `::after` hit box, so the box is not masked with it.
   ────────────────────────────────────────────────────────────────────────── */

.tc-shop-archive .tc-active-filters {
	display: flex;
	flex-wrap: wrap;
	align-self: stretch; /* the full line, in the block's column flex */
	align-items: center;
	gap: var(--wp--preset--spacing--xsmall, 0.5rem); /* 8 */
	margin: 0;
	padding: 0;
	list-style: none;
}

.tc-shop-archive .tc-active-filters > li {
	margin: 0;
}

.tc-shop-archive .tc-filter-chip {
	--tc-icon-close: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20' fill='%23000'%3E%3Cpath d='M20 1.17833L18.8217 0L10 8.82167L1.17833 0L0 1.17833L8.82167 10L0 18.8217L1.17833 20L10 11.1783L18.8217 20L20 18.8217L11.1783 10L20 1.17833Z'/%3E%3C/svg%3E");

	display: inline-flex;
	flex: none;
	align-items: center;
	gap: var(--wp--preset--spacing--xsmall, 0.5rem); /* 8 */
	box-sizing: border-box;
	max-inline-size: 100%;
	min-block-size: 36px; /* the trigger's */
	padding-block: 5px; /* 5 + 24 (the ×) + 5 + 2 of border = 36 */
	padding-inline: 12px; /* the trigger's — no token, 8 and 16 are the neighbours */
	border: 1px solid var(--wp--custom--color--neutral--200, #dee2e6);
	border-radius: 2px;
	background-color: var(--wp--custom--color--neutral--100, #e9ecef);
	font-family: var(--wp--preset--font-family--body);
	font-size: var(--wp--preset--font-size--label-medium, 0.875rem); /* 14, not fluid */
	font-weight: 500;
	line-height: 1.4286; /* 20 */
	color: var(--wp--custom--color--base--black, #0a205b);
}

/* A long brand name wraps inside the chip only when it is wider than the row. */
.tc-shop-archive .tc-filter-chip .tc-filter-chip__label {
	overflow-wrap: anywhere;
}

.tc-shop-archive .tc-filter-chip .tc-filter-chip__remove {
	position: relative;
	flex: 0 0 auto;
	inline-size: 24px; /* = the Category group chevron */
	block-size: 24px;
	color: inherit;
}

.tc-shop-archive .tc-filter-chip .tc-filter-chip__remove::before {
	content: "";
	position: absolute;
	inset: 0;
	background-color: currentcolor;

	/* stylelint-disable-next-line property-no-vendor-prefix -- mask needs the prefix pair; scoped exception per the header/media/blog-archive precedent */
	-webkit-mask: var(--ftps-icon-close, var(--tc-icon-close)) center / 14px 14px no-repeat;
	mask: var(--ftps-icon-close, var(--tc-icon-close)) center / 14px 14px no-repeat;
}

.tc-shop-archive .tc-filter-chip .tc-filter-chip__remove::after {
	content: "";
	position: absolute;
	inset-block-start: 50%;
	inset-inline-start: 50%;
	inline-size: 44px;
	block-size: 44px;
	transform: translate(-50%, -50%);
}

.tc-shop-archive .tc-filter-chip .tc-filter-chip__remove:hover {
	color: var(--wp--custom--color--primary--600, #123b9e);
}

.tc-shop-archive .tc-active-filters__clear {
	display: inline-flex;
	align-items: center;
	min-block-size: 36px;
	font-family: var(--wp--preset--font-family--body);
	font-size: var(--wp--preset--font-size--label-medium, 0.875rem); /* 14 */
	font-weight: 500;
	line-height: 1.4286;
	color: var(--wp--custom--color--base--black, #0a205b);
	text-decoration: underline;
	text-underline-offset: 0.15em;
}

.tc-shop-archive .tc-active-filters__clear:hover {
	color: var(--wp--custom--color--primary--600, #123b9e);
}

/* build-knowledge §25 — the theme draws no focus ring for us. */
.tc-shop-archive .tc-filter-chip .tc-filter-chip__remove:focus-visible,
.tc-shop-archive .tc-active-filters__clear:focus-visible {
	outline: 2px solid var(--wp--custom--color--secondary--600, #057db9);
	outline-offset: 2px;
}

/*
 * Below lg the rail is the trigger + count ROW (T-SA-4) and the chips take a
 * line of their own under the trigger, outside the overlay. DOM order is
 * trigger, chips, (overlay), count — the owner's ruling is that the focusable
 * order matches what is seen, trigger then chips. The count is plain text and
 * is pinned to the trigger's line on the right, where it already sat, so the
 * product-filters block can take the full width and wrap. Only when chips are
 * present: /shop/ with nothing active keeps T-SA-4's row exactly as it was.
 * 16 between the trigger and the chips keeps the × hit box's overhang clear of
 * the trigger.
 */
@media (max-width: 63.9375rem) { /* below breakpoint/lg */
	.tc-shop-archive .tc-shop-archive__rail:has(.tc-active-filters) {
		position: relative;
	}

	.tc-shop-archive .tc-shop-archive__rail:has(.tc-active-filters) > .wc-block-product-filters {
		flex: 1 1 100%;
		flex-wrap: wrap;
		row-gap: var(--wp--preset--spacing--normal, 1rem); /* 16 */
	}

	/* The block is a column flex, which stretches the trigger to the full line. */
	.tc-shop-archive .tc-shop-archive__rail:has(.tc-active-filters) .wc-block-product-filters__open-overlay {
		align-self: flex-start;
	}

	.tc-shop-archive .tc-shop-archive__rail:has(.tc-active-filters) > .tc-shop-archive__toolbar-count {
		position: absolute;
		inset-block-start: 0;
		inset-inline-end: 0;
		display: flex;
		align-items: center;
		block-size: 36px; /* the trigger's line */
	}
}

@media (min-width: 48rem) and (max-width: 63.9375rem) {
	.tc-shop-archive .tc-shop-archive__rail:has(.tc-active-filters) > .tc-shop-archive__toolbar-count {
		block-size: 40px; /* the trigger is 40 from md */
	}
}
