.pqf-control.pqf-sort-apply[hidden] { display: none; }

/**
 * Plain Query Filters — defensive invariants. DELIBERATELY UNLAYERED.
 *
 * DO NOT ADD AN `@layer` WRAPPER TO THIS FILE. scripts/wrap-css-layer.js lists
 * `defense.css` in UNLAYERED_SHEETS and will step over it; wrapping it by hand
 * would silently undo every rule below without changing a single byte of the
 * rules themselves — the exact failure mode this file exists to end.
 *
 * THE CONTRACT
 * ------------
 * Since 0.32 every other shipped stylesheet is wrapped in `@layer pqf-neutral`,
 * so a consuming theme's unlayered CSS beats ours by ORIGIN: at any specificity,
 * in any load order, with neither side having to reason about selector weights
 * again. That is the correct default for a plugin that ships neutral structure
 * and expects the theme to own the paint, and it is a large part of why this
 * plugin can be sold onto sites it has never seen.
 *
 * It is the wrong default for one narrow class of rule, and the original
 * justification for applying it everywhere was subtly the wrong shape. It read:
 * layering is safe because "no shipped rule targets a bare element". The premise
 * is true; the conclusion does not follow. The collision was never about what OUR
 * selectors target — it is about the bare elements this plugin RENDERS. A
 * <select>, a <label>, <option> rows, three kinds of <input>, three <button>s and
 * about a dozen <a>s go out inside our markup, and every generic `label {}` /
 * `input {}` / `a {}` rule already sitting on the consuming page reaches straight
 * into them. Class-scoping our own selectors does nothing about that. Layered, we
 * lose to all of it, at every specificity, and nothing anywhere warns you.
 *
 * Three confirmed defects came out of that gap:
 *   1. a WooCommerce store was measured lifting the sort <label> 5.3px off its
 *      own control, via nothing more exotic than
 *      `.woocommerce-page label { margin-bottom: .7em }` (0,1,1);
 *   2. the <select> font defect had to be worked around in a consuming THEME,
 *      because the plugin was not able to defend itself;
 *   3. filter-popover's own comment records every popover on every site
 *      reopening with the wrong row gap, continuously, from 0.32 onward.
 *
 * So the split is BY CONTRACT — not by block, not by file size, not by taste:
 *
 *   layered   (assets/css/controls.css, src/<block>/style.css) = DEFAULTS.
 *             "A theme MAY repaint this." Paint, sizing, decoration, preference.
 *   unlayered (this file)                                      = INVARIANTS.
 *             "This MUST survive foreign CSS on elements we render."
 *
 * Keep this file small and keep the bar high. Every declaration added here is a
 * right taken away from the consuming site, so it belongs here only if a generic
 * element rule on somebody else's stylesheet would otherwise BREAK the control —
 * not merely restyle it in a way we did not choose. Restyling is the theme's
 * prerogative and the layer exists to grant it.
 *
 * NO `!important` IN THIS FILE. EVER.
 * ------------------------------------
 * Specificity is sufficient here, and being sufficient is the entire point. Our
 * (0,2,0) already beats `.woocommerce-page label` at (0,1,1) and a bare `label`
 * at (0,0,1) — the two selector shapes behind the incidents above — while still
 * leaving a consuming theme a clean, ordinary escape one step up at (0,2,1) or
 * (0,3,0). `!important` would take that escape away entirely: an `!important`
 * author declaration also beats the theme's OWN `!important`, and for a plugin
 * sold onto other people's sites that is a right we do not own and cannot ask
 * for. If a rule here genuinely cannot win on specificity, that is a signal the
 * rule is in the wrong file or aimed at the wrong element — raise it, do not
 * reach for `!important`.
 *
 * THE DOUBLED CLASS
 * -----------------
 * `.pqf-control.pqf-control` is not a typo or a paste error. It is the
 * specificity-only idiom: identical matching, (0,2,0) instead of (0,1,0), no
 * `!important`, and no dependence on markup structure we might later change.
 * (0,1,0) would beat a bare `select {}` but LOSE to `.woocommerce-page select` —
 * which is exactly the rule shape that broke us. Anything added to this file
 * should carry the same weight for the same reason.
 *
 * Load order is irrelevant here. Unlayered author CSS beats layered author CSS by
 * origin, so `pqf-defense` wins against `pqf-controls` whichever order WordPress
 * happens to print the two <link> tags in — this file must never be made to
 * depend on being enqueued last.
 */

/**
 * The defensive half of `.pqf-control` — the shared recipe stamped onto FIVE
 * bare form controls (the sort <select>, the popover trigger <button>, the
 * map-toggle <button>, <input type=search>, <input type=number>). The paint half
 * — min-height, background, border, radius, padding, cursor — stays layered in
 * assets/css/controls.css, where a theme can still take it.
 *
 * All four declarations below are neutralisers: they say "whatever the page has
 * already done to this element, undo it", which is the one thing a theme cannot
 * be allowed to win by accident.
 */
.pqf-control.pqf-control {
	/* Controls own their box math: border-box is what makes the shared
	   min-height floor (layered, in controls.css) mean the same thing on a
	   native <select> and on an icon-bearing button, whose natural heights
	   differ. A `* { box-sizing: content-box }` or a framework reset on the
	   consuming site silently changes what every padding and min-height in this
	   plugin computes to. */
	box-sizing: border-box;

	/* THE HOOK IS NOT DECORATION — same contract as --pqf-control-line-height
	   below, and it exists for the same reason that one does.
	   `color: inherit` is what stops a consuming site's `select { color: … }` or
	   `input { color: … }` from tinting a control. But unlayered at (0,2,0) it
	   ALSO outranks a theme's own considered `color` on its own control at
	   (0,1,0) — and until this hook existed a theme had no way to win except
	   `!important` or a specificity arms race, which is precisely the taking of
	   control the layer split exists to give back. Found in the wild: a consuming
	   theme setting `color` on this plugin's search input and popover trigger,
	   silently overruled. `inherit` remains the default, so nothing changes for
	   an install that does not set the property. */
	color: var(--pqf-control-color, inherit);

	/* `font` STAYS A SHORTHAND, and stays first. It is the only declaration that
	   can defeat a consuming site's `select { font: … }` wholesale, and it resets
	   line-height to the inherited value — which is why the line-height below
	   must come after it. The two longhands that follow re-open the parts a
	   theme legitimately owns WITHOUT weakening that reset: neither
	   `font-family` nor `font-size` resets line-height, so the ordering
	   requirement is unaffected, and both default to `inherit` — exactly what
	   the shorthand alone produced. An install that sets neither property
	   renders identically to before these hooks existed.

	   WHY font-size NEEDED A HOOK. A theme's form-field scale is not decoration:
	   a consuming theme was found setting its field size to `max(1rem, 16px)`
	   deliberately, because iOS Safari zooms the viewport when a focused input
	   is under 16px. Inheriting a 14px body size silently reintroduced that zoom
	   on every filter search box on the site. A plugin has no business making
	   that call for a theme. */
	font: inherit;
	font-family: var(--pqf-control-font-family, inherit);
	font-size: var(--pqf-control-font-size, inherit);

	/* MUST stay AFTER `font`, and MUST stay in this file with it. (It used to say
	   "immediately after"; the `font-family` / `font-size` hooks above now sit
	   between the two. That is safe — neither longhand resets line-height, which
	   is the only thing the ordering protects against — but nothing else may be
	   inserted here without checking that same property.)
	   `font` is a shorthand: it resets line-height to the inherited value, so a
	   line-height declared before it is discarded. Splitting the pair across the
	   layer boundary is worse still — `font: inherit` unlayered with
	   `line-height: 1.2` left layered would let any unlayered `select {
	   line-height: 2 }` on the consuming site inflate every control on the page.
	   The value itself: controls use compact, predictable leading — article
	   leading (often 1.6+) inflates the control box and skews icon/text optical
	   alignment.

	   THE HOOK IS NOT DECORATION. Everything else in this file is an invariant no
	   theme has a legitimate reason to change; leading is the exception, because
	   control density is a real design decision and themes DO set it deliberately
	   (a consuming theme was found setting 1.4 on this very select). Unlayered at
	   (0,2,0) this rule outranks that theme's (0,1,0) declaration, so without a
	   channel we would be silently overruling considered design — the same taking
	   of control this whole split exists to give back, wearing a different hat.
	   The custom property is that channel: a theme sets
	   `--pqf-control-line-height` and wins without having to out-specify a plugin,
	   which is the contract the rest of the plugin already offers via
	   --pqf-control-padding, --pqf-control-radius and friends. */
	line-height: var(--pqf-control-line-height, 1.2);
}

/* A toolbar instance renders both its desktop toggle and its mobile segmented
   control so it can cross the 781px breakpoint without rebuilding markup.
   Hiding the desktop action on mobile is behavioural, not paint: a generic
   theme `button { display: inline-flex }` must not expose both controls. */
@media (max-width: 781px) {
	/* Target visibility is behavior, including native block-anchor targets whose
	   core wrapper rules are emitted outside the neutral paint layer. */
	.pqf-map-panel.pqf-map-panel:not(.pqf-map-overlay):not(.pqf-map-region-open),
	.pqf-map-target.pqf-map-target:not(.pqf-map-overlay):not(.pqf-map-region-open) {
		display: none;
	}

	.pqf-map-toggle--toolbar .wp-block-plainplugins-map-toggle__button--toggle {
		display: none;
	}
}

/**
 * The defensive half of `.pqf-link` — the shared role class stamped onto every
 * <a> this plugin emits. There are eleven: the taxonomy-, meta- and
 * post-type-filter rows, the meta-toggle switch, active-filters' chip "x" and
 * its "Clear all", the two query-views tabs, no-results' clear link,
 * range-filter's reset, the shared reset link in inc/chips.php, and the
 * filter-popover trigger in its link mode. Same idiom as `.pqf-control` above,
 * for the same reason: a role class on a bare element, defended unlayered.
 *
 * WHY IT EXISTS
 * -------------
 * Those eleven anchors are the plugin's controls — filter rows, tabs, chips,
 * resets. They are not prose. But an anchor is an anchor to a consuming site's
 * stylesheet, and every page this plugin is sold onto already carries some form
 * of `a { color: var(--link); text-decoration: underline }`. Layered, we lose to
 * it at any specificity, and a filter list arrives underlined in link blue —
 * reading as a paragraph of links rather than as a row of controls, and
 * mismatching the sort dropdown's <option> rows beside it, which inherit.
 *
 * It also ends a duplication: the SAME two declarations were copy-pasted across
 * seven block stylesheets (taxonomy-filter, meta-filter, post-type-filter,
 * meta-toggle, active-filters, query-views, plus the chip-variant restatements
 * in assets/css/controls.css), where — being layered — none of them had won
 * anything since 0.32. One rule here replaces all seven, and four anchors that
 * never had the reset at all (`pqf-reset-link`, `no-results__clear`,
 * `range-filter__reset`, and the link-mode `filter-popover__trigger`) gain it:
 * this closes an inconsistency as well as a defect.
 *
 * SPECIFICITY — ours (0,2,0) via the doubled class. Beats a bare `a` (0,0,1) and
 * the scoped element rules that actually ship on consuming sites,
 * `.woocommerce-page a` and `.entry-content a`, both (0,1,1). WINS.
 *
 * THE HOOKS ARE MANDATORY, NOT DECORATION
 * ---------------------------------------
 * Exactly as `--pqf-control-line-height` does for control leading, and for the
 * same reason. "Should this control look like a link?" is a real design
 * decision, and themes make it deliberately: one consuming theme
 * sets `active-filters__clear { text-decoration: underline }` at (0,1,0) — a
 * "clear all filters" affordance a theme quite reasonably wants to read as a
 * link, because that is what it is. Unlayered at (0,2,0) we would outrank that
 * declaration and silently strip it: considered design overruled by a plugin,
 * with nothing anywhere to warn either party. That is the same taking of control
 * this whole split exists to give back, wearing a different hat.
 *
 * `--pqf-link-decoration` and `--pqf-link-color` hand the decision back without a
 * specificity fight — the theme sets a custom property and wins, rather than
 * having to out-specify a plugin it did not write. `inherit` / `none` remain the
 * defaults, so an unthemed install still gets a control that reads as a control.
 */
.pqf-link.pqf-link {
	color: var(--pqf-link-color, inherit);
	text-decoration: var(--pqf-link-decoration, none);
}

/* ===========================================================================
 * The defensive half of six block stylesheets, moved out of
 * `@layer pqf-neutral` (src/sort-dropdown, src/filter-popover,
 * src/taxonomy-filter, src/meta-filter, src/post-type-filter,
 * src/range-filter).
 *
 * Every rule below used to carry `!important` purely to survive the layer, or
 * carried a comment recording that it had silently stopped winning since 0.32.
 * Unlayered, none of them needs it: each states the specificity it wins on and
 * the foreign selector it beats, and each deliberately leaves a consuming theme
 * an escape one class above ours.
 * ======================================================================== */

/* ── Sort Dropdown: label margin reset ─────────────────────────────────────
 * The label is a real <label> (accessible-name association, 0.22.3), so it
 * matches themes' GENERIC form-label rules written for STACKED form fields —
 * `label { margin-bottom: .375rem }`, WooCommerce's
 * `.woocommerce-page label { margin-bottom: .7em }`. In this inline control row
 * that margin knocks the label off the select's centerline. Measured on a live
 * WooCommerce store: the label carried `margin-bottom: 9.8px` (WooCommerce's
 * .7em at the inherited 14px) and lifted "Sort by:" 5.3px above the text inside
 * its own <select>.
 *
 * SPECIFICITY — ours (0,2,0), two classes (the :is() contributes its most
 * specific branch, one class). Beats `.woocommerce-page label` (0,1,1) and a
 * bare `label` (0,0,1). WINS. A theme that genuinely wants a margin here has an
 * escape at (0,2,1) or by restyling the wrapper / retuning `--pqf-label-gap`. */
.wp-block-plainplugins-sort-dropdown
	:is(.wp-block-plainplugins-sort-dropdown__label) {
	margin: 0;
}

/* ── Filter Popover: panel row gap ─────────────────────────────────────────
 * A popover panel is a listbox, not article flow: a theme's GLOBAL blockGap
 * (`:root :where(.is-layout-flex) { gap: 16px }`, printed by WordPress from
 * theme.json) otherwise leaks into the option list and makes the dropdown
 * leggy. Rows are contiguous by default (gap 0) because inside a panel each row
 * carries its own padding — see the `--pqf-panel-item-*` defaults, which stay
 * LAYERED in src/filter-popover/style.css because they are paint. Themes and
 * authors retune the gap via `--pqf-panel-item-gap`.
 *
 * This is the rule whose own comment recorded that "from 0.32 until now this
 * rule lost outright and every popover dropdown on every consuming site
 * reopened with 16px between its rows".
 *
 * SPECIFICITY — ours (0,2,0): `.…__panel` plus the :is() branch (one class).
 * Beats `:root :where(.is-layout-flex)` at (0,1,0) — `:root` is a pseudo-class,
 * `:where()` contributes nothing. WINS. */
.wp-block-plainplugins-filter-popover__panel
	:is(
		.wp-block-plainplugins-taxonomy-filter,
		.wp-block-plainplugins-meta-filter,
		.wp-block-plainplugins-post-type-filter
	) {
	gap: var(--pqf-panel-item-gap, 0);

	/* Menu rows span the panel. WordPress prints the block's vertical flex layout
	   as an UNLAYERED `.wp-container-… { align-items: flex-start }` rule, under
	   which every row shrink-wraps its text (measured live: 58px rows in a 224px
	   panel) and the hover wash hugs the label instead of painting edge to edge
	   like every native menu. A layered override would lose to that generated rule
	   by ORIGIN at any specificity — same story as the blockGap leak above, so it
	   lives here with it. The rows' inline inset comes from
	   `--pqf-panel-item-padding` (see filter-popover/style.css).
	 *
	 * SPECIFICITY — ours (0,2,0): `.…__panel` plus the :is() branch. Beats the
	 * generated `.wp-container-…` at (0,1,0). WINS. */
	align-items: stretch;
}

/* ── Standalone filter lists: wrap gap ─────────────────────────────────────
 * CANONICAL NOTE for the three twins below (src/taxonomy-filter,
 * src/meta-filter, src/post-type-filter — and the citation target for the
 * chip-variant rule in assets/css/controls.css).
 *
 * A filter list is a control, not article flow: it wants a compact wrap gap,
 * and the same unlayered WordPress global as above
 * (`:root :where(.is-layout-flex){gap:16px}`) otherwise leaks in as 16px+ row
 * gaps. These guards were written as an `:is()` specificity bump, went dead the
 * day `@layer pqf-neutral` shipped (unlayered author CSS beats ALL layered
 * author CSS at any specificity), and were then escalated to `!important` —
 * which also beat a consuming theme's own `!important`, a right this plugin
 * does not own. Unlayered, plain specificity is enough.
 *
 * The `:where(:not(...))` keeps these out of popover panels, where the panel
 * gap rule above (`--pqf-panel-item-gap`) stays in charge. The theme/author
 * override channel is unchanged: `--pqf-list-gap`. Authored gaps opt out via
 * `pqf-has-block-gap`, leaving preset, axial and responsive CSS to WordPress.
 *
 * SPECIFICITY — ours (0,2,0): the block class plus the `:is(.is-layout-flex)`
 * branch; `:where()` contributes 0. Beats `:root :where(.is-layout-flex)` at
 * (0,1,0). WINS. Theme escape: any (0,3,0) rule, or the var. */
.wp-block-plainplugins-taxonomy-filter:is(.is-layout-flex):not(
		.pqf-has-block-gap
	):where(:not(.wp-block-plainplugins-filter-popover__panel *)) {
	gap: var(--pqf-list-gap, 0.4em 1em);
}

.wp-block-plainplugins-meta-filter:is(.is-layout-flex):not(
		.pqf-has-block-gap
	):where(:not(.wp-block-plainplugins-filter-popover__panel *)) {
	gap: var(--pqf-list-gap, 0.4em 1em);
}

.wp-block-plainplugins-post-type-filter:is(.is-layout-flex):not(
		.pqf-has-block-gap
	):where(:not(.wp-block-plainplugins-filter-popover__panel *)) {
	gap: var(--pqf-list-gap, 0.4em 1em);
}

/* ── Segmented list style: one row, never wrapped ─────────────────────────
 * The `is-style-segmented` variant (block.json `styles`) draws the options as
 * one joined control. Its row has to stay ONE row: a segmented control that
 * wraps is two half-controls. The block's default layout is a WRAPPING flex row
 * and WordPress prints that as an unlayered `.wp-container-… { flex-wrap: wrap }`
 * rule, which a layered rule cannot beat — same fight as the wrap gap above, so
 * the structural half lives here; the shape lives in assets/css/controls.css.
 *
 * `inline-flex` so the control is as wide as its options by default. A theme
 * that wants equal segments across a full width (a phone) sets `width: 100%` on
 * the block and `flex: 1 1 0` on the items; nothing here fights that.
 *
 * SPECIFICITY — ours (0,2,0). Beats the generated `.wp-container-…` (0,1,0) and
 * `body .is-layout-flex` (0,1,1). WINS. */
.wp-block-plainplugins-taxonomy-filter.is-style-segmented,
.wp-block-plainplugins-meta-filter.is-style-segmented,
.wp-block-plainplugins-post-type-filter.is-style-segmented {
	display: inline-flex;
	flex-wrap: nowrap;
	max-width: 100%;
	gap: var(--pqf-segment-gap, 2px);
}

/* ── Typeahead: a hidden row must actually collapse ────────────────────────
 * FUNCTIONAL, not cosmetic: the opt-in typeahead (`searchable`) filters by
 * setting the bare `hidden` attribute on rows. `hidden` is only a UA-stylesheet
 * `display:none`, so ANY author `display` on the row defeats it — the plugin's
 * own `.…__item { display: inline-flex }` (post-type-filter), the chip/pill
 * variants in assets/css/controls.css, or a consuming theme setting its list
 * rows to flex/grid. If this rule loses, typing in the search box hides nothing
 * and the feature looks broken.
 *
 * SPECIFICITY — ours (0,2,0): one class + one attribute selector. Beats a
 * theme's `.…__item { display: flex }` at (0,1,0) and every element-level rule.
 * WINS. (Kept at the original selector — no bump — so a theme retains a (0,3,0)
 * escape, though there is no legitimate reason to take it.) */
.wp-block-plainplugins-taxonomy-filter__item[hidden] {
	display: none;
}

.wp-block-plainplugins-meta-filter__item[hidden] {
	display: none;
}

.wp-block-plainplugins-post-type-filter__item[hidden] {
	display: none;
}

/* ── Range Filter: the reset link must actually collapse ───────────────────
 * IDENTICAL IN KIND to the three typeahead rules above, and here for the same
 * reason. The store binds `hidden` on the reset <a> whenever the range sits at
 * its full extent, and `hidden` is only a UA-stylesheet `display: none` — any
 * author `display` on that element defeats it. The reset now also carries
 * `.pqf-link`, and a theme styling its links as `inline-flex` (an icon + label
 * row, which is exactly what this element is) would leave a "Reset" control
 * permanently on screen offering to reset nothing.
 *
 * SPECIFICITY — ours (0,2,0): one class plus one attribute selector. Beats a
 * theme's `.…__reset { display: inline-flex }` at (0,1,0) and every
 * element-level rule. WINS. Kept at the original selector — no bump — so a
 * theme retains a (0,3,0) escape, as with the typeahead rules. */
.wp-block-plainplugins-range-filter__reset[hidden] {
	display: none;
}

/* The fieldset must shrink inside core layouts, including narrow popovers. */
fieldset.wp-block-plainplugins-range-filter {
	min-inline-size: 0;
}

/* One native form shares the fieldset's authored block layout. Enhancement
   changes behavior, not geometry. Its Apply target keeps its natural space,
   including translated text, while leaving focus and the accessibility tree. */
.wp-block-plainplugins-range-filter__form.wp-block-plainplugins-range-filter__form {
	display: contents;
}
.wp-block-plainplugins-range-filter__apply--enhanced.wp-block-plainplugins-range-filter__apply--enhanced {
	visibility: hidden;
}

/* Map sheet chrome is absent until the mobile presentation opens. This is a
   visibility invariant, not theme paint: generic button/div display rules must
   not reveal the server-rendered heading or expansion action with JS off or on
   desktop. Keep it unlayered like the other functional hidden-state resets. */
.pqf-map-sheet-chrome[hidden],
.pqf-map-sheet-expand[hidden] {
	display: none;
}

/* ── Range Filter: the two range <input>s stay unstyled by the UA ──────────
 * FUNCTIONAL, and the bluntest case in this file. The dual-thumb control is two
 * native `<input type=range>`s stacked over one shared `__rail` element, and the
 * whole mechanism rests on the natives being stripped: lose `appearance: none`
 * and the browser restores its own track and thumb outright, so the rail paints
 * behind a second, native track and the block reads as broken rather than
 * restyled. These are bare <input>s inside our markup, so the generic rules
 * every consuming site already ships reach straight into them —
 * `input { margin: 0 0 1em }` (a stacked-form default, which here pushes the
 * upper slider off its own rail) and `.woocommerce-page input { background: #fff }`
 * (which paints an opaque band over the rail and fill beneath).
 *
 * The stacked inputs must share the rail's box. Generic input padding, borders
 * or heights move their native thumbs off that rail, so the complete mechanism
 * lives here. The shared --pqf-range-thumb-size still sizes its area and thumbs
 * together; their paint remains layered and authorable in the component sheet.
 *
 * SPECIFICITY — ours (0,2,0) via the doubled class; the source rule was a single
 * class (0,1,0), which loses to `.woocommerce-page input` (0,1,1). Doubled it
 * beats that and a bare `input` (0,0,1). WINS, with a (0,2,1) escape left open
 * for a theme that genuinely wants the native widget back. */
.wp-block-plainplugins-range-filter__slider.wp-block-plainplugins-range-filter__slider {
	position: absolute;
	top: 50%;
	transform: translateY(-50%);
	left: 0;
	width: 100%;
	height: var(--pqf-range-thumb-size, 1rem);
	min-width: 0;
	max-width: none;
	min-height: 0;
	max-height: none;
	box-sizing: border-box;
	margin: 0;
	padding: 0;
	border: 0;
	background: none;
	-webkit-appearance: none;
	appearance: none;
	pointer-events: none;
}

/* This field's width is a budget for digits and the native stepper. Keep that
   content budget when a theme adds padding/borders, rather than consuming it
   inside a fixed border-box and leaving no visible text area. Paint stays
   authorable; the readout's existing flex wrapping handles the resulting box. */
.wp-block-plainplugins-range-filter__input.pqf-control {
	box-sizing: content-box;
}

/* ── Item template blocks: zero trapped block-axis margins ─────────────────
 * The item template's blocks (a Paragraph holding {{label}}, a Heading, …)
 * render INSIDE the <a> control row. A theme's article-flow spacing — most
 * often theme.json's `:root :where(p){margin-block-end:…}` — lands on that
 * inner element, and because the anchor is a flex item (its own formatting
 * context) the margin cannot collapse out: it becomes dead space below every
 * row's text, which in wrapped lists reads as bloated leading. Control rows
 * aren't article flow. Inline-axis margins stay authorable.
 *
 * These three were the rules `scripts/validate-contracts.js` allowlisted with
 * "dead since 0.32, needs the same !important fix the matching gap rule already
 * got" — going unlayered is that fix, without the `!important`.
 *
 * SPECIFICITY — ours (0,1,1): one class plus the :is() branch (one element).
 * Beats `:root :where(p)` at (0,1,0) and a bare `p { margin: 0 0 1em }` at
 * (0,0,1). WINS. A theme scoping to our item (e.g. `.…__item p`, also (0,1,1))
 * ties and wins on order — an intentional escape, not a defeat. */
.wp-block-plainplugins-taxonomy-filter__item > :is(p, h1, h2, h3, h4, h5, h6) {
	margin-block: 0;
}

.wp-block-plainplugins-meta-filter__item > :is(p, h1, h2, h3, h4, h5, h6) {
	margin-block: 0;
}

.wp-block-plainplugins-post-type-filter__item > :is(p, h1, h2, h3, h4, h5, h6) {
	margin-block: 0;
}

/* ── Empty region placeholders must not cost a flex gap ───────────────────
 * FUNCTIONAL, and the one rule in this file whose adversary is the plugin's OWN
 * layered CSS rather than a consuming theme's.
 *
 * WHAT THE PLACEHOLDER IS. REGION CONTRACT v1 rule 5: the Interactivity Router
 * only ever UPDATES a region id that existed when the page was hydrated, so a
 * control that renders nothing at first paint could never appear later, however
 * many times the server sent it. inc/views.php `region_placeholder()` therefore
 * emits an empty element carrying that control's id and wrapper classes, so the
 * id is registered and the swap has somewhere to land.
 *
 * WHAT THAT COST. A suppressed control used to be ABSENT from the DOM; it is now
 * PRESENT. A filter bar is a `core/group` with flex layout, and CSS `gap` is
 * charged per flex ITEM, not per painted box — an item of zero size still gets a
 * full gap on each side. `src/filter-popover/style.css` and
 * `src/post-type-filter/style.css` set `display: inline-flex` / `flex` on exactly
 * the wrapper classes the placeholder reproduces, so every dead control opened a
 * hole in the bar, and only in the state where it went dead. The layout moved
 * with the filter — the most expensive kind of layout bug to see, because both
 * screenshots look deliberate.
 *
 * `display: contents` is the precise answer: the element generates NO BOX at all,
 * so it is not a flex item and no gap is charged, while it stays in the DOM with
 * its id where the router finds it. `display: none` would not do — the router
 * renders the fetched content into this element and has no reason to clear a
 * `display` it never set.
 *
 * WHY IT IS SAFE TO STYLE A CLASS THAT SURVIVES THE SWAP, twice over. The router
 * replaces the region root's ATTRIBUTES from the fetched document, so after one
 * navigation `pqf-region-placeholder` is gone and the real block classes are
 * there instead (verified in a live browser). And `:empty` pins the rule to the
 * state that justifies it regardless: the instant the element holds a child, the
 * selector stops matching and the box comes back. Either half alone would be
 * enough; together the rule cannot suppress content that exists. The placeholder
 * is emitted with nothing whatsoever between its tags, so `:empty` matches it
 * without depending on the Selectors-4 whitespace relaxation.
 *
 * IT LIVES HERE BECAUSE IT HAS TO WIN AGAINST OUR OWN LAYERED SHEETS. Unlayered
 * author CSS beats ALL layered author CSS by ORIGIN, so a rule left in
 * `@layer pqf-neutral` could not beat `.wp-block-plainplugins-post-type-filter {
 * display: flex }` in the same layer at any specificity worth writing, and a
 * theme's own `display` on that wrapper would beat it outright.
 *
 * SPECIFICITY — ours (0,2,0): one class plus `:empty`, which is a pseudo-class
 * and counts as one. Beats a theme's `.wp-block-plainplugins-… { display: flex }`
 * at (0,1,0) and every element-level rule, and leaves the usual (0,3,0) escape —
 * though a `display` on an empty stand-in is never something a theme meant. */
.pqf-region-placeholder:empty {
	display: contents;
}

/* ── Chip variants: tighter wrap gap ──────────────────────────────────────
 * Re-declares the base list gap above with a style-variant class added, so the
 * pills/buttons lists sit tighter (0.5em/0.5em) than the default row.
 *
 * IT LIVES HERE BECAUSE ITS BASE RULE DOES. This was the last `!important` in
 * controls.css, carried to beat the theme blockGap global. Unlayered it does not
 * need one — but it also could not have STAYED layered once the base rule moved:
 * layered, it would lose to its own unlayered base by ORIGIN, and the extra
 * class would buy nothing. Two rules that override each other by specificity
 * must sit on the same side of the layer boundary; splitting them silently
 * inverts which one wins.
 *
 * SPECIFICITY — ours (0,3,0): block class + the :is() variant branch + the
 * :is(.is-layout-flex) branch (:where() contributes nothing). Beats the base
 * rule at (0,2,0) and the theme global `:root :where(.is-layout-flex)` at
 * (0,1,0). WINS. Override channel is unchanged: `--pqf-list-gap`. */
.wp-block-plainplugins-taxonomy-filter:is(
		.is-style-pills,
		.is-style-buttons
	):is(.is-layout-flex):where(
		:not(.wp-block-plainplugins-filter-popover__panel *)
	),
.wp-block-plainplugins-meta-filter:is(.is-style-pills, .is-style-buttons):is(
		.is-layout-flex
	):where(:not(.wp-block-plainplugins-filter-popover__panel *)),
.wp-block-plainplugins-post-type-filter:is(
		.is-style-pills,
		.is-style-buttons
	):is(.is-layout-flex):where(
		:not(.wp-block-plainplugins-filter-popover__panel *)
	) {
	gap: var(--pqf-list-gap, 0.5em 0.5em);
}

/* ── Filter drawer + popover heading: display invariants ──────────────────
 * plainplugins/filter-drawer shows ONE set of filter controls two ways: inline
 * above 781px, as a bottom sheet below it. Which of its parts exist at which
 * width is structure, not paint, and every one of them is a <button> or a <p>
 * this plugin renders — exactly what a theme's bare `button {}` / `p {}` rule
 * would otherwise reach from outside the layer. So the switches live here,
 * unlayered, at plain specificity.
 *
 * The popover's `__heading` (its label repeated inside the panel) is hidden in
 * a normal dropdown and shown in the two presentations that have no trigger to
 * carry the label: the drawer sheet (below) and no-JS (the popover's own
 * <noscript> style, which is keyed by id and outranks both rules here).
 *
 * The two `html:has()` rules key off the document root because that is where
 * their effect lands: the page stops scrolling under an open sheet, and
 * map-toggle's floating pill — which lives in the TOP LAYER, where no z-index
 * can cover it — steps aside instead of sitting on the sheet's own "Show"
 * button. `visibility` rather than `display` so the pill keeps its box and its
 * top-layer state. */
.wp-block-plainplugins-filter-popover__heading {
	display: none;
}

.wp-block-plainplugins-filter-drawer__title {
	margin: 0;
}

.wp-block-plainplugins-filter-drawer__close {
	padding: 0;
}

@media (min-width: 782px) {
	.wp-block-plainplugins-filter-drawer__trigger,
	.wp-block-plainplugins-filter-drawer__header,
	.wp-block-plainplugins-filter-drawer__footer {
		display: none;
	}
}

/* A closed native dialog must stay absent even if a theme styles dialogs. */
dialog.wp-block-plainplugins-filter-drawer__dialog:not([open]) {
	display: none;
}

@media (max-width: 781px) {
	.wp-block-plainplugins-filter-drawer
		> .wp-block-plainplugins-filter-drawer__panel:not(:popover-open) {
		display: none;
	}
	.wp-block-plainplugins-filter-drawer
		> .wp-block-plainplugins-filter-drawer__panel:popover-open {
		display: flex;
	}

	.wp-block-plainplugins-filter-drawer__body
		.wp-block-plainplugins-filter-popover
		.wp-block-plainplugins-filter-popover__trigger {
		display: none;
	}

	.wp-block-plainplugins-filter-drawer__body
		.wp-block-plainplugins-filter-popover
		.wp-block-plainplugins-filter-popover__heading {
		display: block;
	}

	html:has(dialog.wp-block-plainplugins-filter-drawer__dialog[open]) {
		overflow: hidden;
	}

	html:has(dialog.wp-block-plainplugins-filter-drawer__dialog[open])
		.wp-block-plainplugins-map-toggle {
		visibility: hidden;
	}
}

/* ── Sort dropdown: the chevron fallback's select half ────────────────────
 * Browsers without Customizable Select (`appearance: base-select`) draw their
 * own arrow on the sort <select>. src/sort-dropdown/style.css hides nothing
 * itself: it has the WRAPPER draw the plugin's glyph as an ::after over the
 * select's end padding. This is the other half — the native arrow off and the
 * end padding grown by the glyph and its inner gap — and it is a reset on a
 * bare element we render, so it lives here: a layered copy loses to any
 * `select { appearance: auto; padding: … }` a theme or WooCommerce ships, and
 * the result is two arrows, or our glyph printed over the selected label.
 * Inert where base-select is supported, and for `--chevron-none`.
 *
 * SPECIFICITY — ours (0,3,0) (the :where() adds nothing). Beats `select`
 * (0,0,1) and a WooCommerce-style `.woocommerce-page select` (0,1,1). WINS.
 * A theme retunes the room via `--pqf-select-end-inset` (its control's inline
 * padding) and `--pqf-chevron-size`, not by out-specifying this. */
@supports not (appearance: base-select) {
	.wp-block-plainplugins-sort-dropdown:where(
			[class*="-dropdown--chevron-"]
		):not(.wp-block-plainplugins-sort-dropdown--chevron-none)
		.wp-block-plainplugins-sort-dropdown__select {
		appearance: none;
		padding-inline-end: calc(var(--pqf-select-end-inset, 0.75em) + var(--pqf-chevron-size, 1em) + 0.4em);
	}
}

/* A form hidden by option search must stay hidden even with display: contents. */
.pqf-control-form.pqf-control-form[hidden] {
	display: none;
}
.pqf-option.pqf-option {
	color: inherit;
	font: inherit;
	line-height: inherit;
	margin: 0;
}
.pqf-state-control:where(button) {
	text-decoration: none;
}

.pqf-option__checkbox.pqf-option__checkbox {
	margin: 0;
}

/* Converted core template containers retain paint without paragraph margins. */
.pqf-template-container.pqf-template-container {
	margin-block: 0;
}

/* Focus geometry is one structural contract. Generic :focus rules in Global
   Styles must not override width independently of the clipping-safe offset.
   Paint stays layered in controls.css; targeted component rules remain an
   explicit theme override. Core-block content is outside this contract. */
.wp-block-plainplugins-filter-popover__trigger[hidden] {
	display: none;
}

.pqf-control:focus-visible,
.pqf-state-control:focus-visible,
.pqf-option:has(> .pqf-option__checkbox:focus-visible),
.wp-block-plainplugins-taxonomy-filter__item:focus-visible,
.wp-block-plainplugins-meta-filter__item:focus-visible,
.wp-block-plainplugins-post-type-filter__item:focus-visible,
.wp-block-plainplugins-meta-toggle__item:focus-visible,
.wp-block-plainplugins-query-views__tab:focus-visible {
	outline-width: var(--pqf-focus-width, 2px);
	outline-style: solid;
	/* Evaluate both lengths on this target. An inherited precomputed length
	   would use the container's width instead of a control's own override. */
	--pqf-internal-focus-outside: calc(
		(1 - var(--pqf-internal-focus-inset, 0)) * var(--pqf-focus-offset, 2px)
	);
	--pqf-internal-focus-inside: calc(
		var(--pqf-internal-focus-inset, 0) * var(--pqf-focus-width, 2px)
	);

	outline-offset: calc(
		var(--pqf-internal-focus-outside) - var(--pqf-internal-focus-inside)
	);
}

/* Keep full-width menu rows and joined cells inside their scrollport edges. */
.wp-block-plainplugins-filter-popover__panel,
.wp-block-plainplugins-query-views__toggle,
.wp-block-plainplugins-range-filter__readout--editable,
.wp-block-plainplugins-taxonomy-filter.is-style-tabs,
.wp-block-plainplugins-meta-filter.is-style-tabs,
.wp-block-plainplugins-post-type-filter.is-style-tabs {
	--pqf-internal-focus-inset: 1;
}

@media (max-width: 781px) {
	.wp-block-plainplugins-filter-drawer__body {
		--pqf-internal-focus-inset: 1;
	}
}

@supports (appearance: base-select) {
	.wp-block-plainplugins-sort-dropdown__select option:focus {
		outline-width: var(--pqf-focus-width, 2px);
		outline-style: solid;
		outline-offset: calc(0px - var(--pqf-focus-width, 2px));
	}
}

/* The chip's existing pseudo-element owns its full 24px interactive target. */
.wp-block-plainplugins-active-filters__remove:focus-visible {
	outline: none;
}

.wp-block-plainplugins-active-filters__remove:focus-visible::before {
	outline-width: var(--pqf-focus-width, 2px);
	outline-style: solid;
	outline-offset: calc(
		0px - var(--pqf-internal-focus-inset, 0) * var(--pqf-focus-width, 2px)
	);
}

/* ── Tabs variant: one line, no gap, no centred overflow ───────────────────
 * The three declarations a segmented tab strip cannot be talked out of. Its
 * SHAPE is layered in assets/css/controls.css; these three are here because
 * each of them has to beat unlayered CSS that is already on the page:
 *
 *   flex-wrap      the base list wraps by design, and both the block's own
 *                  block.json layout default and WordPress's flex-layout output
 *                  say `wrap`. A tab strip that wraps is not a tab strip — it is
 *                  a two-row grid of white boxes with a stray divider.
 *   gap            re-declares the base list gap above (and the chip-variant gap
 *                  beside it) with the variant class added. Same reasoning as
 *                  that rule, verbatim: two rules that override each other by
 *                  specificity must sit on the same side of the layer boundary.
 *                  Tabs butt together, so the default is 0, not a tighter wrap.
 *   justify-content  the one that is a BUG guard rather than a style. WordPress
 *                  prints an author's flex justification as
 *                  `.wp-container-<block>-is-layout-<hash>{justify-content:center}`
 *                  — unlayered, so nothing in controls.css can reach it. Left in
 *                  place on an overflowing scroller it centres the negative free
 *                  space: the first tab sits at a NEGATIVE offset, outside the
 *                  scrollable range, and is unreachable at every viewport too
 *                  narrow for the strip. Measured on a 390px viewport with four
 *                  tabs totalling 669px. Centring is not lost — controls.css
 *                  centres the ROW instead (`width: fit-content` +
 *                  `margin-inline: auto`), which is the same result at every
 *                  width where the tabs fit and a working scroller at every
 *                  width where they do not.
 *
 * SPECIFICITY — ours (0,3,0): block class + `.is-style-tabs` + the
 * `:is(.is-layout-flex)` branch. Beats the base gap rule at (0,2,0), the
 * layout-class justification at (0,1,0) and the theme global
 * `:root :where(.is-layout-flex)` at (0,1,0). WINS. Override channels:
 * `--pqf-tabs-gap`, or any (0,4,0) rule for the other two. */
.wp-block-plainplugins-taxonomy-filter.is-style-tabs:is(.is-layout-flex),
.wp-block-plainplugins-meta-filter.is-style-tabs:is(.is-layout-flex),
.wp-block-plainplugins-post-type-filter.is-style-tabs:is(.is-layout-flex) {
	flex-wrap: nowrap;
	gap: var(--pqf-tabs-gap, 0);
	justify-content: flex-start;
}
