/*!
 * QuantumSet Advanced Search — Frontend Stylesheet
 *
 * Intentionally minimal. Every rule inherits font, color, and spacing
 * from the active theme wherever possible (font-family: inherit, color
 * via currentColor, em-based spacing). Class names are namespaced with
 * `qsas-` so they will never collide with theme styles.
 *
 * Override or extend by enqueueing a higher-priority stylesheet in your
 * theme.
 *
 * Version: 1.0.0
 */

/* -------------------------------------------------------------------------
 * Search form
 * --------------------------------------------------------------------- */
.qsas-search-form {
	position: relative;
	display: block;
	width: 100%;
	margin: 0 0 1.25em;
	font-family: inherit;
	color: inherit;

	/* ----- Search-button design tokens (overridden by ButtonLibrary::inline_css
	 *       when the user changes them on the "Search Button" admin tab). -----
	 * These defaults reproduce the original green-gradient lift button exactly,
	 * so admins who never visit the new tab see no visual change. */
	--qsas-btn-size:          3em;
	--qsas-btn-radius:        8px;
	--qsas-btn-rest-bg:       #28a745;
	--qsas-btn-rest-bg2:      #1f8a3a;
	--qsas-btn-rest-fg:       #ffffff;
	--qsas-btn-rest-border:   transparent;
	--qsas-btn-hover-bg:      #2ebd4f;
	--qsas-btn-hover-bg2:     #1f8a3a;
	--qsas-btn-hover-fg:      #ffffff;
	--qsas-btn-hover-border:  transparent;
	--qsas-btn-active-bg:     #1f8a3a;
	--qsas-btn-active-fg:     #ffffff;
	--qsas-btn-focus-outline: #1d2327;
}

.qsas-search-form__field-row {
	/* v1.9.74 — Unified capsule via container-owned perimeter.
	 *
	 * Previous approach (v1.9.72/73) had the border on the input
	 * (left/top/bottom) and the button (top/right/bottom), with
	 * no border between them. Two problems:
	 *
	 * 1. THEME OVERRIDES — many themes set
	 *      input[type="search"] { border-radius: ... }
	 *    Selector specificity 0-1-1 (1 element + 1 attr) beats my
	 *    .qsas-search-form__input (0-1-0, single class), so the
	 *    input's border-radius would silently revert to whatever
	 *    the theme dictates while the button (a real <button>)
	 *    kept the right corners rounded — the exact asymmetric
	 *    look the user reported.
	 *
	 * 2. SEAM THICKNESS — the input and button each owned half
	 *    the perimeter. If their border colors differed (input
	 *    inherits currentColor, button uses --qsas-btn-rest-border),
	 *    the resulting line wasn't visually uniform top-to-bottom.
	 *
	 * Container-owned border + overflow:hidden fixes both:
	 *   • The capsule has ONE perimeter, drawn on the field-row.
	 *   • The radius is on the container; children get clipped to
	 *     the rounded shape regardless of their own border-radius
	 *     (so theme overrides on the input become moot).
	 *   • Input and button have NO borders of their own — no seam,
	 *     no double-line, no color mismatch. The visual transition
	 *     between them is just where the input's transparent bg
	 *     ends and the button's colored bg begins. */
	display: flex;
	gap: 0;
	align-items: stretch;
	width: 100%;
	border: 1px solid currentColor;
	border-radius: var( --qsas-btn-radius, 8px );
	overflow: hidden;
}

.qsas-search-form__field-row:focus-within {
	/* Focus indicator wraps the whole capsule. Outline draws outside
	 * the element and isn't subject to overflow:hidden clipping. */
	outline: 2px solid currentColor;
	outline-offset: 2px;
}

.qsas-search-form__field-row .qsas-search-form__input {
	flex: 1 1 auto;
	min-width: 0;
	height: var( --qsas-btn-size, 3em );
	padding: 0 0.7em;
	font: inherit;
	color: inherit;
	box-sizing: border-box;
	opacity: 0.9;
	/* v1.9.75 — Force-strip the input's perimeter. Theme CSS like
	 *   input[type="search"] { border: 1px solid #ccc; border-radius: 4px; }
	 * has specificity (0,0,1,1) which beats my plain class selector.
	 * The container (.qsas-search-form__field-row) now owns the
	 * visible shape — the input must be completely neutral. Bump
	 * the selector to two classes AND use !important on the
	 * properties themes most commonly set on search inputs, so the
	 * input can't show its own border, radius, background, or focus
	 * shadow that would visually compete with the capsule outline.
	 * Without these the user sees a "double line" (inner theme
	 * border + outer container border) and the input keeps its
	 * theme-rounded corners. */
	background: transparent !important;
	border: 0 !important;
	border-radius: 0 !important;
	box-shadow: none !important;
	/* v1.9.73 — Disable native search-input styling so the container's
	 * clip can take effect regardless of browser UA stylesheet. */
	-webkit-appearance: none;
	appearance: none;
}
.qsas-search-form__field-row .qsas-search-form__input::-webkit-search-cancel-button,
.qsas-search-form__field-row .qsas-search-form__input::-webkit-search-decoration {
	-webkit-appearance: none;
	appearance: none;
	display: none;
}

.qsas-search-form__field-row .qsas-search-form__input:focus {
	outline: 0;
	opacity: 1;
	/* Defeat theme :focus ring on the input — focus visual lives on
	 * the field-row container. */
	box-shadow: none !important;
	border-color: transparent !important;
}

/* ===================================================================
 * SEARCH BUTTON — base
 *
 * All shared structural rules. Effect-specific cosmetic rules (gradient,
 * shadow, transforms, etc.) live under .qsas-btn-effect--<id> below.
 * =================================================================== */
.qsas-search-form__field-row .qsas-search-form__submit {
	flex: 0 0 auto;
	/* v1.9.77 — `font: inherit` is the KEY fix here. Without it, the
	 * button uses the browser/theme default font-size. Many themes set
	 *   button { font-size: 1rem | 1.1em | 18px | ... }
	 * which makes the button's `em` context DIFFERENT from the input
	 * (which uses font:inherit) and the container. Result:
	 *   • input height: 3 × form-font-size
	 *   • button height: 3 × theme-button-font-size  ← MISMATCH
	 * The button ends up taller (or shorter) than the input, the
	 * capsule looks broken, the icon gets sized off too. Forcing
	 * font:inherit makes the button's em match everywhere else, so
	 * `height: var(--qsas-btn-size, 3em)` resolves to the same
	 * pixel value as on the input. */
	font: inherit;
	/* v1.9.77b — box-sizing: content-box (not border-box) so the
	 * declared width/height are the CONTENT box. With border-box,
	 *   width: 3em + padding: 0 0.6em → content area = 1.8em
	 * which is smaller than the icon (2.4em = 80% of 3em). The
	 * overflow:hidden would then clip the icon's left/right edges,
	 * making it look thicker/bigger than configured. Many modern
	 * themes set `* { box-sizing: border-box }` globally — this
	 * explicit content-box defends against that. */
	box-sizing: content-box;
	/* In icon mode the button is a square sized by --qsas-btn-size. In
	 * text mode (data-mode="text" below) width becomes auto so the
	 * button hugs its label. Height stays --qsas-btn-size in both modes
	 * so it lines up with the input. */
	width: var( --qsas-btn-size, 3em );
	height: var( --qsas-btn-size, 3em );
	min-width: var( --qsas-btn-size, 3em );
	padding: 0;
	background: var( --qsas-btn-rest-bg );
	color: var( --qsas-btn-rest-fg );
	cursor: pointer;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	line-height: 1;
	position: relative;
	overflow: hidden; /* required for the slide effect's ::before */
	transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease, transform 0.1s ease;
	/* v1.9.76 — Defeat ONLY theme decoration on the perimeter
	 * (border, radius). The 2-class selector specificity (0,0,2,0)
	 * already beats typical theme rules on button[type="submit"]
	 * (0,0,1,1) — no !important needed here, and using !important
	 * would clobber the .qsas-btn-effect--* classes that depend
	 * on box-shadow and transform for the visible lift/glow/slide
	 * treatments. */
	border: 0;
	border-radius: 0;
}

/* v1.9.77 — Icon stroke width override. The static SVG icons in
 * ButtonLibrary::idle_icons() have hardcoded `stroke-width="2.2"`
 * (or 2/2.4 depending on the icon). CSS rules win over SVG
 * presentation attributes per spec, BUT some browsers and themes
 * still manage to lock the visible stroke at the hardcoded value:
 *
 *   • Themes that inline-style SVG inside admin pages
 *   • Themes with `svg [stroke]` rules at equal specificity
 *   • UA stylesheet edge cases on SVG content
 *
 * Two defenses combined:
 *   1. Specificity bump — 3 classes + 2 elements = (0,0,3,2),
 *      well above any plausible theme selector.
 *   2. !important — this property is ONLY set here (no hover,
 *      no active, no effect class touches stroke-width), so
 *      forcing it is safe and has no side effects.
 *
 * The default `2.2` matches the original magnifier weight. Solid-
 * fill icons (magnifier_solid) ignore stroke entirely, so this
 * is a no-op for them. */
.qsas-search-form .qsas-search-form__field-row .qsas-search-form__submit svg path,
.qsas-search-form .qsas-search-form__field-row .qsas-search-form__submit svg circle,
.qsas-search-form .qsas-search-form__field-row .qsas-search-form__submit svg g {
	stroke-width: var( --qsas-btn-icon-stroke, 2.2 ) !important;
}

/* When the button is showing a text label (mode=text), let it grow horizontally
 * to fit the text instead of being a square. Height (and therefore vertical
 * alignment with the input) is preserved via --qsas-btn-size. */
.qsas-search-form__submit[data-mode="text"] {
	width: auto;
	min-width: 0;
	padding: 0 1.25em;
}

.qsas-search-form__submit:focus { outline: 0; }
.qsas-search-form__submit:focus-visible {
	outline: 2px solid var(--qsas-btn-focus-outline);
	outline-offset: 2px;
}

.qsas-search-form__submit:active {
	background: var(--qsas-btn-active-bg);
	color: var(--qsas-btn-active-fg);
}

/* Text label inside the button (mode=text) */
.qsas-search-form__label {
	font: inherit;
	white-space: nowrap;
}

/* Icon glyph — sized as a FIXED proportion of the button height (not %
 * of width), so the rendered glyph stays the same physical size whether
 * the button is a 3em square (icon mode) or an auto-width text button
 * (text mode), and whether it's showing the idle icon or any of the
 * loading spinners. Calc against --qsas-btn-size means changing the
 * size setting rescales the glyph proportionally too. */
.qsas-search-form__icon {
	display: block;
	width:  calc(var(--qsas-btn-size, 3em) * 0.8);
	height: calc(var(--qsas-btn-size, 3em) * 0.8);
	flex: 0 0 auto;
}

/* Search hint — rendered above the field row when settings['search_hint']
 * is non-empty. Plain text, theme-coloured, low emphasis. */
.qsas-search-form__hint {
	display: block;
	margin: 0 0 0.5em;
	font: inherit;
	font-size: 0.9em;
	color: inherit;
	opacity: 0.75;
}

/* ===================================================================
 * EFFECTS — additive cosmetics on top of the base rules above.
 * Each effect is keyed by .qsas-btn-effect--<id>.
 * =================================================================== */

/* lift: gradient + soft drop shadow that lifts on hover, depresses on active */
.qsas-btn-effect--lift {
	background: linear-gradient( 180deg, var(--qsas-btn-rest-bg) 0%, var(--qsas-btn-rest-bg2) 100% );
	box-shadow: 0 2px 5px rgba( 0, 0, 0, 0.18 ), 0 1px 2px rgba( 0, 0, 0, 0.12 );
}
.qsas-btn-effect--lift:hover,
.qsas-btn-effect--lift:focus-visible {
	background: linear-gradient( 180deg, var(--qsas-btn-hover-bg) 0%, var(--qsas-btn-hover-bg2) 100% );
	color: var(--qsas-btn-hover-fg);
	border-color: var(--qsas-btn-hover-border, transparent);
	box-shadow: 0 4px 10px rgba( 0, 0, 0, 0.22 ), 0 2px 4px rgba( 0, 0, 0, 0.16 );
}
.qsas-btn-effect--lift:active {
	transform: translateY( 1px );
	box-shadow: 0 1px 3px rgba( 0, 0, 0, 0.18 );
}

/* flat: solid bg, gentle hover swap, no shadow */
.qsas-btn-effect--flat {
	background: var(--qsas-btn-rest-bg);
	box-shadow: none;
}
.qsas-btn-effect--flat:hover,
.qsas-btn-effect--flat:focus-visible {
	background: var(--qsas-btn-hover-bg);
	color: var(--qsas-btn-hover-fg);
	border-color: var(--qsas-btn-hover-border, transparent);
}

/* outline: transparent face with a colored border, fills on hover */
.qsas-btn-effect--outline {
	background: transparent;
	color: var(--qsas-btn-rest-bg);
	border: 2px solid var(--qsas-btn-rest-bg);
}
.qsas-btn-effect--outline:hover,
.qsas-btn-effect--outline:focus-visible {
	background: var(--qsas-btn-hover-bg);
	color: var(--qsas-btn-hover-fg);
	border-color: var(--qsas-btn-hover-bg);
}

/* glow: solid + soft radiating glow on hover */
.qsas-btn-effect--glow {
	background: var(--qsas-btn-rest-bg);
	box-shadow: 0 0 0 0 rgba( 0, 0, 0, 0 );
}
.qsas-btn-effect--glow:hover,
.qsas-btn-effect--glow:focus-visible {
	background: var(--qsas-btn-hover-bg);
	color: var(--qsas-btn-hover-fg);
	box-shadow: 0 0 0 4px rgba( 255, 255, 255, 0.0 ),
				0 0 22px var(--qsas-btn-hover-bg);
}

/* slide: a second color slides in from the left */
.qsas-btn-effect--slide {
	background: var(--qsas-btn-rest-bg);
	z-index: 0;
}
.qsas-btn-effect--slide::before {
	content: "";
	position: absolute;
	inset: 0;
	background: var(--qsas-btn-hover-bg);
	transform: translateX( -101% );
	transition: transform 0.3s cubic-bezier( 0.4, 0.0, 0.2, 1 );
	z-index: -1;
}
.qsas-btn-effect--slide:hover::before,
.qsas-btn-effect--slide:focus-visible::before {
	transform: translateX( 0 );
}
.qsas-btn-effect--slide:hover,
.qsas-btn-effect--slide:focus-visible {
	color: var(--qsas-btn-hover-fg);
}

/* scale: gentle zoom on hover, shrink on click */
.qsas-btn-effect--scale {
	background: var(--qsas-btn-rest-bg);
}
.qsas-btn-effect--scale:hover,
.qsas-btn-effect--scale:focus-visible {
	background: var(--qsas-btn-hover-bg);
	color: var(--qsas-btn-hover-fg);
	transform: scale( 1.08 );
}
.qsas-btn-effect--scale:active {
	transform: scale( 0.94 );
}

/* pulse: pulsing ring of color radiates outward on hover */
.qsas-btn-effect--pulse {
	background: var(--qsas-btn-rest-bg);
}
.qsas-btn-effect--pulse:hover,
.qsas-btn-effect--pulse:focus-visible {
	background: var(--qsas-btn-hover-bg);
	color: var(--qsas-btn-hover-fg);
	animation: qsas-btn-pulse 1.4s ease-out infinite;
}
@keyframes qsas-btn-pulse {
	0%   { box-shadow: 0 0 0 0   var(--qsas-btn-hover-bg); }
	70%  { box-shadow: 0 0 0 12px rgba( 0, 0, 0, 0 ); }
	100% { box-shadow: 0 0 0 0   rgba( 0, 0, 0, 0 ); }
}

/* invert: bg and fg swap on hover */
.qsas-btn-effect--invert {
	background: var(--qsas-btn-rest-bg);
	color: var(--qsas-btn-rest-fg);
}
.qsas-btn-effect--invert:hover,
.qsas-btn-effect--invert:focus-visible {
	background: var(--qsas-btn-rest-fg);
	color: var(--qsas-btn-rest-bg);
	border-color: var(--qsas-btn-rest-bg);
}

@media ( prefers-reduced-motion: reduce ) {
	.qsas-search-form__submit { transition: none; }
	.qsas-search-form__submit:active { transform: none; }
	.qsas-btn-effect--scale:hover,
	.qsas-btn-effect--scale:focus-visible { transform: none; }
	.qsas-btn-effect--pulse:hover,
	.qsas-btn-effect--pulse:focus-visible { animation: none; }
	.qsas-btn-effect--slide::before { transition: none; }
}

/* ===================================================================
 * SPINNER ICONS — visible only while .qsas-is-loading is on the form.
 * Each spinner variant has its own animation hook.
 * =================================================================== */
.qsas-search-form__icon--spinner { display: none; }
.qsas-search-form.qsas-is-loading .qsas-search-form__icon--idle,
.qsas-search-form.qsas-is-loading .qsas-search-form__label { display: none; }
.qsas-search-form.qsas-is-loading .qsas-search-form__icon--spinner { display: block; }

/* arrows + ring: rotate the whole SVG */
.qsas-search-form.qsas-is-loading .qsas-spinner--arrows,
.qsas-search-form.qsas-is-loading .qsas-spinner--ring {
	animation: qsas-spin 0.9s linear infinite;
	transform-origin: 50% 50%;
}
@keyframes qsas-spin {
	from { transform: rotate( 0deg ); }
	to   { transform: rotate( 360deg ); }
}

/* dots: three dots pulse with staggered phases */
.qsas-search-form.qsas-is-loading .qsas-spinner--dots .qsas-spinner__dot {
	transform-origin: center;
	animation: qsas-dot-pulse 1.2s ease-in-out infinite;
}
.qsas-search-form.qsas-is-loading .qsas-spinner--dots .qsas-spinner__dot--1 { animation-delay: 0s; }
.qsas-search-form.qsas-is-loading .qsas-spinner--dots .qsas-spinner__dot--2 { animation-delay: 0.18s; }
.qsas-search-form.qsas-is-loading .qsas-spinner--dots .qsas-spinner__dot--3 { animation-delay: 0.36s; }
@keyframes qsas-dot-pulse {
	0%, 100% { opacity: 0.3; transform: scale( 0.7 ); }
	50%      { opacity: 1;   transform: scale( 1 );   }
}

/* bars: three vertical bars scale with staggered phases */
.qsas-search-form.qsas-is-loading .qsas-spinner--bars .qsas-spinner__bar {
	transform-origin: center;
	animation: qsas-bar-scale 0.95s ease-in-out infinite;
}
.qsas-search-form.qsas-is-loading .qsas-spinner--bars .qsas-spinner__bar--1 { animation-delay: 0s; }
.qsas-search-form.qsas-is-loading .qsas-spinner--bars .qsas-spinner__bar--2 { animation-delay: 0.15s; }
.qsas-search-form.qsas-is-loading .qsas-spinner--bars .qsas-spinner__bar--3 { animation-delay: 0.3s; }
@keyframes qsas-bar-scale {
	0%, 100% { transform: scaleY( 0.4 ); }
	50%      { transform: scaleY( 1 );   }
}

@media ( prefers-reduced-motion: reduce ) {
	.qsas-search-form.qsas-is-loading .qsas-spinner--arrows,
	.qsas-search-form.qsas-is-loading .qsas-spinner--ring,
	.qsas-search-form.qsas-is-loading .qsas-spinner--dots .qsas-spinner__dot,
	.qsas-search-form.qsas-is-loading .qsas-spinner--bars .qsas-spinner__bar {
		animation: none;
	}
}

/* Screen-reader-only label (kept so AT users still hear "Search"). */
.qsas-search-form__submit .screen-reader-text {
	position: absolute;
	width: 1px;
	height: 1px;
	margin: -1px;
	padding: 0;
	overflow: hidden;
	clip: rect( 0 0 0 0 );
	white-space: nowrap;
	border: 0;
}

/* Hide spinner by default; only show while .qsas-is-loading is present on
 * the form (set by frontend.js around the AJAX live-search call). */
.qsas-search-form__icon--spinner {
	display: none;
}

.qsas-search-form.qsas-is-loading .qsas-search-form__icon--glass {
	display: none;
}

.qsas-search-form.qsas-is-loading .qsas-search-form__icon--spinner {
	display: block;
	animation: qsas-spin 0.9s linear infinite;
	transform-origin: 50% 50%;
}

@keyframes qsas-spin {
	from { transform: rotate(0deg); }
	to   { transform: rotate(360deg); }
}

/* Respect users who prefer reduced motion — show the spinner but freeze it. */
@media ( prefers-reduced-motion: reduce ) {
	.qsas-search-form.qsas-is-loading .qsas-search-form__icon--spinner {
		animation: none;
	}
}

/* Screen-reader-only label (kept so AT users still hear "Search"). Mirrors
 * the WordPress core `.screen-reader-text` utility in case the theme has
 * not defined it. */
.qsas-search-form__submit .screen-reader-text {
	position: absolute;
	width: 1px;
	height: 1px;
	margin: -1px;
	padding: 0;
	overflow: hidden;
	clip: rect(0 0 0 0);
	white-space: nowrap;
	border: 0;
}

/* -------------------------------------------------------------------------
 * Live search dropdown
 * --------------------------------------------------------------------- */
.qsas-live-results {
	position: absolute;
	left: 0;
	right: 0;
	top: 100%;
	margin-top: 4px;
	max-height: 70vh;
	overflow-y: auto;
	z-index: 9999;
	background: #fff;
	color: #1d2327;
	border: 1px solid rgba(0, 0, 0, 0.12);
	border-radius: 4px;
	box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
}

.qsas-live-results[hidden] {
	display: none;
}

.qsas-live-results__list {
	list-style: none;
	margin: 0;
	padding: 0;
}

.qsas-live-results__item {
	border-bottom: 1px solid rgba(0, 0, 0, 0.06);
}

.qsas-live-results__item:last-child {
	border-bottom: 0;
}

.qsas-live-results__link {
	display: flex;
	gap: 0.75em;
	align-items: center;
	padding: 0.6em 0.75em;
	text-decoration: none;
	color: inherit;
}

.qsas-live-results__link:hover,
.qsas-live-results__link:focus,
.qsas-live-results__item--active .qsas-live-results__link {
	background: rgba(0, 0, 0, 0.04);
	outline: 0;
}

.qsas-live-results__thumb {
	flex: 0 0 auto;
	width: 40px;
	height: 40px;
	object-fit: cover;
	border-radius: 3px;
}

.qsas-live-results__title {
	font-weight: 600;
	margin: 0;
	line-height: 1.3;
}

.qsas-live-results__excerpt {
	font-size: 0.85em;
	opacity: 0.75;
	margin: 0.15em 0 0;
	line-height: 1.3;
}

.qsas-live-results__meta {
	display: block;
	margin: 0.2em 0 0;
	font-size: 0.8em;
	opacity: 0.7;
	line-height: 1.3;
}

.qsas-live-results__author,
.qsas-live-results__date {
	display: inline;
}

.qsas-live-results__tax {
	display: block;
	margin: 0.2em 0 0;
	font-size: 0.8em;
	opacity: 0.7;
	font-style: italic;
	line-height: 1.3;
}

/* Live Search custom-field values: a compact value list (no labels — per
 * requirement). The dropdown is small and the row is already wrapped in
 * <a>, so we keep this to plain values only. */
.qsas-live-results__cf {
	list-style: none;
	margin: 0.3em 0 0;
	padding: 0;
	font-size: 0.82em;
	display: flex;
	flex-wrap: wrap;
	gap: 0.1em 0.6em;
}

.qsas-live-results__cf li {
	opacity: 0.85;
	margin: 0;
}

.qsas-live-results__cf li:not(:last-child)::after {
	content: ' ·';
	opacity: 0.55;
	margin-left: 0.2em;
}

.qsas-live-results__view-all {
	display: block;
	padding: 0.6em 0.75em;
	text-align: center;
	font-size: 0.9em;
	font-weight: 600;
	background: rgba(0, 0, 0, 0.025);
	color: inherit;
	text-decoration: none;
	border-top: 1px solid rgba(0, 0, 0, 0.06);
}

.qsas-live-results__view-all:hover,
.qsas-live-results__view-all:focus {
	background: rgba(0, 0, 0, 0.05);
}

.qsas-live-results__empty,
.qsas-live-results__loading {
	padding: 0.75em;
	font-size: 0.9em;
	opacity: 0.7;
	text-align: center;
}

/* -------------------------------------------------------------------------
 * Results list
 * --------------------------------------------------------------------- */
.qsas-results {
	display: block;
	width: 100%;
	font-family: inherit;
	color: inherit;
}

.qsas-results__header {
	margin: 0 0 1em;
	padding: 0 0 0.5em;
	border-bottom: 1px solid currentColor;
	opacity: 0.95;
}

/* v1.9.13 — [details] DSL widget. Minimalist collapsible disclosure
 * region for the Custom Card Layout. Built on native <details>/<summary>
 * so the browser handles keyboard activation, ARIA semantics, and the
 * open/close state for free. We just style the bar and panel here.
 *
 * Two trigger modes (set by the PHP renderer):
 *   data-trigger="bar"  → only the bar (the <summary>) opens it.
 *   data-trigger="card" → also clicking any non-interactive data
 *                         inside the same card opens it. The card-click
 *                         behavior is wired up in JS — CSS-side this
 *                         mode is otherwise identical.
 *
 * .qsas-layout-details--bar-hidden hides the bar entirely (for use
 * cases where the visitor should expand by clicking card data only). */
.qsas-layout-details {
	/* Compact pill design: rounded box with subtle gray background
	 * and border. The pill itself lives on the summary — NOT on
	 * the details element — so we force border:0 here with
	 * !important to defeat themes that add a border or top line
	 * to <details> elements by default. All sizing units are
	 * absolute (px) — em compounds with the parent card body's
	 * font-size which is often 22-28px in modern themes. */
	margin: 4px 0 0;
	border: 0 !important;
	border-top: 0 !important;
	background: transparent;
}
.qsas-layout-details > summary.qsas-layout-details__bar {
	list-style: none !important;
	list-style-type: none !important;
	list-style-image: none !important;
	display: flex;
	align-items: center;
	justify-content: flex-end;
	gap: 4px;
	padding: 2px 7px;
	cursor: pointer;
	/* Absolute size. Previously this was 0.7em which compounded
	 * with the parent card body's font-size (often 22-28px in
	 * modern themes), making the bar render at 18-20px — too
	 * large. Hard-coding 11px guarantees the same compact size
	 * across every theme and parent context. !important defeats
	 * theme rules that set font-size on summary elements. */
	font-size: 11px !important;
	font-weight: 500;
	line-height: 1.2;
	color: inherit;
	background: rgba(0, 0, 0, 0.035);
	border: 1px solid rgba(0, 0, 0, 0.1);
	border-radius: 5px;
	user-select: none;
	transition: background-color 0.15s ease, border-color 0.15s ease;
	min-height: 22px;
}
/* Nuclear nuke for every kind of marker/pseudo-element a theme
 * (or browser default) might inject onto the summary. We saw a
 * stray chevron on the LEFT of the bar that wasn't in our HTML —
 * tracing back to a theme's `summary::before { content: '▾' }`
 * rule. These four overrides cover every variant: Safari's
 * legacy ::-webkit-details-marker, modern ::marker (Firefox +
 * Chromium), and any ::before or ::after pseudos the theme
 * might add. content:none + display:none + !important so no
 * combination of cascade can revive them. */
.qsas-layout-details > summary.qsas-layout-details__bar::-webkit-details-marker {
	display: none !important;
	content: none !important;
}
.qsas-layout-details > summary.qsas-layout-details__bar::marker {
	display: none !important;
	content: none !important;
}
.qsas-layout-details > summary.qsas-layout-details__bar::before {
	display: none !important;
	content: none !important;
}
.qsas-layout-details > summary.qsas-layout-details__bar:hover,
.qsas-layout-details > summary.qsas-layout-details__bar:focus-visible {
	background: rgba(0, 0, 0, 0.06);
	border-color: rgba(0, 0, 0, 0.15);
}
.qsas-layout-details__bar-text {
	flex: 0 0 auto;
	opacity: 0.72;
}
/* Unicode chevron — same font-size as the surrounding text, so
 * both visually render at the same scale (resolving the "text
 * bigger than arrow" mismatch in the rotated-square version).
 * The container <span> stays in the HTML and we inject the
 * character via ::before so we don't have to change PHP/JS
 * markup. Rotation on open transforms the whole span. */
.qsas-layout-details__chevron {
	flex: 0 0 auto;
	display: inline-block;
	width: auto;
	height: auto;
	border: 0;
	transform: none;
	line-height: 1;
	opacity: 0.6;
	transition: transform 0.18s ease;
}
.qsas-layout-details__chevron::before {
	content: '\25BE';            /* ▾ — small down-pointing triangle */
}
.qsas-layout-details[open] > summary > .qsas-layout-details__chevron {
	transform: rotate(180deg);
}
.qsas-layout-details__panel {
	padding: 6px 8px 7px;
	margin-top: 4px;
}
/* Variant: bar hidden. The summary still needs to exist for native
 * <details> semantics; we collapse it to zero visible space.
 *
 * Specificity note: the default visible rule for the bar is
 * `.qsas-layout-details > summary.qsas-layout-details__bar`
 * (specificity 0,2,1). To win against that we match the same way
 * here — using `summary.qsas-layout-details__bar` — so this rule
 * sits at 0,2,1 too and the later source-order wins. Without the
 * class qualifier this rule was 0,1,1 and `display: flex` was
 * winning, leaving the bar visible even when the admin had
 * "Expand on card click" toggled on. */
.qsas-layout-details--bar-hidden > summary.qsas-layout-details__bar {
	display: none;
}
.qsas-layout-details--bar-hidden {
	border: 0;
	margin: 0;
}
.qsas-layout-details--bar-hidden > .qsas-layout-details__panel {
	padding: 0;
	margin-top: 0;
}
/* When the visitor is hovering over a card that has card-trigger
 * mode, hint subtly that the card is clickable. Limited to non-touch
 * pointers to avoid changing cursor behavior on mobile.
 *
 * v1.9.86 — Applies in BOTH the collapsed and the open state (the
 * `:not([open])` qualifier was removed). In click-card mode the
 * entire card — including the revealed panel area — toggles the
 * disclosure, so the pointer affordance should persist while open
 * to signal that clicking the expanded content collapses it.
 * Interactive descendants (links, buttons, form controls) keep
 * their own cursors via their UA / component styles. */
@media (hover: hover) {
	.qsas-result:has(.qsas-layout-details--card-trigger) {
		cursor: pointer;
	}
}

/* v1.9.10 — Default view: no keyword + no active filter. Hide the
 * counter entirely so it doesn't show "X result for ''" — which would
 * misleadingly suggest a search has been run when the visitor has
 * only just landed on the page. The element itself stays in the DOM
 * so the sticky-header JS continues to find it; once the visitor
 * searches or filters and the AJAX response swaps in a non-silent
 * header, the syncStickyHeaderCounter helper moves it into the
 * existing sticky wrap and the counter becomes visible. */
.qsas-results__header--silent {
	display: none;
}

.qsas-results__count {
	margin: 0;
	font-size: 0.95em;
	opacity: 0.8;
}

.qsas-results__count--empty {
	opacity: 0.6;
	font-style: italic;
}

/* v1.9.71 — Results list as CSS Grid. The default behavior uses
 * `repeat(auto-fill, minmax(280px, 1fr))` which adapts the column
 * count to whatever fits the container — 1 column on phones, 2-3
 * on tablets, 3-4+ on desktop, automatically. No media queries,
 * no JS, no layout shift.
 *
 * Admins who want a SPECIFIC column count per device set the
 * `--qsas-cols-{device}` variables via the Grid Layout settings
 * (emitted by Frontend\Frontend::build_grid_css). The fallback
 * chain is mobile → tablet → desktop → auto, matching the per-
 * device layout cascade used elsewhere — empty levels inherit
 * the next-larger device.
 *
 * `--qsas-min-card-width` is also configurable; it controls the
 * minimum card width the auto-fill mode respects (default 280px). */
.qsas-results__list {
	list-style: none;
	margin: 0;
	padding: 0;
	display: grid;
	grid-template-columns: var(
		--qsas-cols-desktop,
		repeat( auto-fill, minmax( var( --qsas-min-card-width, 280px ), 1fr ) )
	);
	gap: 1em;
}
@media ( max-width: 1024px ) {
	.qsas-results__list {
		grid-template-columns: var(
			--qsas-cols-tablet,
			var(
				--qsas-cols-desktop,
				repeat( auto-fill, minmax( var( --qsas-min-card-width, 280px ), 1fr ) )
			)
		);
	}
}
@media ( max-width: 600px ) {
	.qsas-results__list {
		grid-template-columns: var(
			--qsas-cols-mobile,
			var(
				--qsas-cols-tablet,
				var(
					--qsas-cols-desktop,
					repeat( auto-fill, minmax( var( --qsas-min-card-width, 280px ), 1fr ) )
				)
			)
		);
	}
}

/* Each item is the card surface. Slight shadow + rounded corners give the
 * elegant feel; the hover lift signals interactivity without being loud. */
.qsas-results__item {
	display: block;
	background: #fff;
	color: #1d2327;
	border: 1px solid rgba(0, 0, 0, 0.06);
	border-radius: 10px;
	padding: 1em 1.1em;
	box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04), 0 1px 3px rgba(0, 0, 0, 0.06);
	transition: box-shadow 0.18s ease, transform 0.18s ease;
	/* Containment — anything inside that tries to grow wider than
	 * the card (long URLs, wide images, unbreakable strings, etc.)
	 * gets clipped instead of pushing the card off the page. */
	overflow: hidden;
	max-width: 100%;
}

/* v1.9.39 — When the admin set a [card pad:X] override in the
 * builder, the inner <article> carries an inline padding via the
 * `qsas-result--custom-pad` marker class. In that case the outer
 * <li>'s own 1em 1.1em padding would STACK on top of the chosen
 * value, defeating the override. Zeroing it here lets the
 * admin's choice be the sole source of card padding. Layouts
 * WITHOUT the override (no marker class) keep the default 1em 1.1em.
 *
 * :has() is supported in all evergreen browsers since 2023; older
 * browsers fall through to the default padding which is a safe
 * graceful degradation. */
.qsas-results__item:has(> .qsas-result--custom-pad) {
	padding: 0;
}

.qsas-results__item:hover {
	box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08), 0 2px 4px rgba(0, 0, 0, 0.06);
	transform: translateY(-1px);
}

@media (prefers-reduced-motion: reduce) {
	.qsas-results__item { transition: none; }
	.qsas-results__item:hover { transform: none; }
}

/* -------------------------------------------------------------------------
 * Result card
 * --------------------------------------------------------------------- */
.qsas-result {
	display: flex;
	gap: 1em;
	align-items: flex-start;
	min-width: 0;
	max-width: 100%;
	/* Break long unbreakable strings (URLs, IDs, long meta values
	 * without spaces) instead of forcing the card to grow wider.
	 * Cascades to all text descendants. */
	overflow-wrap: anywhere;
	word-break: break-word;
	/* v1.9.54 — Container query context. Distinct name from the
	 * custom-layout card (qsas-card) so the two render paths can
	 * have independent breakpoints without leaking rules into
	 * each other. Used by the fields-grid 480-cqi rule below. */
	container-type: inline-size;
	container-name: qsas-card-default;
}

/* Defensive image bound — anywhere inside the card, an <img> can't
 * grow wider than its column. Prevents featured images and inline
 * images in excerpts from busting the layout. */
.qsas-result img,
.qsas-result svg {
	max-width: 100%;
	height: auto;
}

/* Thumbnail: hard-capped at 120×120 on every viewport, with object-fit
 * cover so non-square source images don't stretch. */
.qsas-result__thumb {
	flex: 0 0 120px;
	width: 120px;
	height: 120px;
	display: block;
	overflow: hidden;
	border-radius: 8px;
	background: #f6f7f7;
}

.qsas-result__thumb-img {
	display: block;
	width: 120px;
	height: 120px;
	max-width: 120px;
	max-height: 120px;
	object-fit: cover;
}

/* v1.9.41 — When the admin set @size:N on a featured_image leaf
 * in the builder, the renderer adds the `--sized` modifier class
 * and an inline `style="flex: 0 0 N%; width: N%; max-width: N%;"`
 * on the wrapper. These rules drop the fixed 120-px height so the
 * image scales by aspect ratio, plus rewrite the inner <img>
 * so it fills the resized wrapper and uses `contain` (don't crop
 * what's now visible after the rescale).
 *
 * A 400-px safety cap on max-height stops a tall image (e.g. a
 * portrait logo) from blowing out the card vertically when the
 * admin picks a large size; the user can override that on their
 * own theme if they want unconstrained height. */
/* v1.9.56 — Per-device image sizing via CSS custom properties.
 *
 * The image_size_style() helper emits one to three inline CSS
 * variables on the <img> element:
 *
 *   --qsas-sz       → desktop (card > 768cqi)
 *   --qsas-sz-tab   → tablet  (481-768cqi)
 *   --qsas-sz-mob   → mobile  (≤ 480cqi)
 *
 * Each rule's `var()` fallback chain handles missing values: if
 * tablet isn't specified, tablet uses desktop; if mobile isn't
 * specified, mobile cascades through tablet to desktop. This is
 * what makes the old `@size:N` (single value) keep working
 * unchanged — without --sz-tab or --sz-mob set, all three
 * breakpoints resolve to --sz.
 *
 * NO floor / NO clamp — the admin's percentage is rendered
 * exactly as authored. The earlier 40-px floor was removed at
 * the admin's explicit request: if you ask for 5% on a 360-cqi
 * mobile card, you get 18px. */
.qsas-result__thumb--sized {
	flex-basis: var(--qsas-sz);
	width: var(--qsas-sz);
	height: auto;
	max-height: 400px;
	max-width: 100%;
}
.qsas-result__thumb--sized .qsas-result__thumb-img {
	width: 100%;
	height: auto;
	max-width: 100%;
	max-height: none;
	object-fit: contain;
}

@container qsas-card (max-width: 768px) {
	.qsas-result__thumb--sized {
		flex-basis: var(--qsas-sz-tab, var(--qsas-sz));
		width: var(--qsas-sz-tab, var(--qsas-sz));
	}
}

@container qsas-card (max-width: 480px) {
	.qsas-result__thumb--sized {
		flex-basis: var(--qsas-sz-mob, var(--qsas-sz-tab, var(--qsas-sz)));
		width: var(--qsas-sz-mob, var(--qsas-sz-tab, var(--qsas-sz)));
	}
}

.qsas-result__body {
	min-width: 0;
}

.qsas-result__title {
	margin: 0 0 0.35em;
	font-size: 1.25em;
	line-height: 1.3;
}

.qsas-result__title a {
	color: inherit;
	text-decoration: none !important;
	transition: opacity 0.15s ease;
}

/* No underline on hover/focus — many themes inject one via
 * `a:hover { text-decoration: underline }` globally, so we use
 * !important to defeat that. Visual feedback via opacity dim
 * instead, which is subtler and doesn't disrupt the layout. */
.qsas-result__title a:hover,
.qsas-result__title a:focus {
	text-decoration: none !important;
	opacity: 0.75;
}

.qsas-result__meta {
	display: flex;
	flex-wrap: wrap;
	gap: 0.75em;
	font-size: 0.85em;
	opacity: 0.75;
	margin: 0 0 0.5em;
}

.qsas-result__excerpt {
	margin: 0 0 0.6em;
	line-height: 1.55;
}

.qsas-result__excerpt p:last-child {
	margin-bottom: 0;
}

.qsas-result__taxonomies {
	font-size: 0.85em;
	opacity: 0.85;
	margin: 0 0 0.6em;
}

/* Custom fields layout — a 2-column grid where each cell holds one field
 * (inline bold label + value). Items flow row-by-row (2-by-2). The Call Now
 * block is a sibling that naturally sits below this grid on its own row.
 *
 * On narrow viewports the grid collapses to a single column so very small
 * phone screens don't get cramped two-line wraps in each cell. */
.qsas-result__fields-grid {
	margin: 0 0 0.8em;
	font-size: 0.92em;
	display: grid;
	grid-template-columns: 1fr 1fr;
	column-gap: 1.5em;
	row-gap: 0.5em;
}

.qsas-result__field {
	min-width: 0; /* allow long values to truncate / wrap inside the cell */
	line-height: 1.45;
}

.qsas-result__cf-label {
	font-weight: 700;
	margin-right: 0.3em;
	/* inline-flow with the value so the colon-and-value follow on the same
	 * line, only wrapping when the value is genuinely too long for the cell. */
}

.qsas-result__cf-value {
	/* inherits inline flow */
}

@container qsas-card-default (max-width: 480px) {
	.qsas-result__fields-grid {
		grid-template-columns: 1fr;
		row-gap: 0.35em;
	}
}

/* Theme-override fallback — older copies of result-item.php in custom
 * themes still emit the old <dl class="qsas-result__custom-fields"> markup
 * (with <dt> and <dd> pairs). Keep these rules so those overrides don't
 * suddenly render unstyled when the plugin is upgraded. The current
 * built-in template uses the new .qsas-result__fields-grid structure
 * above instead. */
.qsas-result__custom-fields {
	margin: 0 0 0.6em;
	font-size: 0.92em;
	display: grid;
	grid-template-columns: max-content 1fr;
	gap: 0.2em 0.6em;
}

.qsas-result__custom-fields--no-labels {
	grid-template-columns: 1fr;
}

/* ==========================================================================
 * Custom Card Layout (DSL output)
 *
 * Emitted by Frontend\CardLayout::render() when the user has authored a
 * layout in the "Custom Card Layout" textarea on the Result Display tab.
 *
 * Two container primitives:
 *   .qsas-layout-row — horizontal flex (children side by side, wrap on small)
 *   .qsas-layout-col — vertical flex (children stacked top to bottom)
 *
 * Modifier classes the parser emits:
 *   .qsas-layout-col--fixed — column hugs its content (used for featured_image)
 *   .qsas-layout-col--1 / --2 / --3 — proportional weights (flex-grow values)
 *
 * Themes can override gap/padding/etc on these classes without touching the
 * layout logic — these rules are intentionally minimal so they don't fight
 * theme styles.
 * ========================================================================== */

.qsas-result--custom-layout {
	/* Custom layouts manage their own internal flow — drop the article's
	 * default flex (which was logo-on-left, body-on-right for the built-in
	 * card) so the user's DSL is in charge. */
	display: block;
	/* v1.9.43 — Containment context so descendants can size with
	 * `cqw` units (1cqw = 1% of this card's inline size). The
	 * @size:N feature on featured_image uses this so the image is
	 * always sized relative to the CARD width, not the immediate
	 * flex column — which fails to resolve percentages when the
	 * column is `Hug content` (flex: 0 0 auto), causing the column
	 * to balloon to fit the full-size image's intrinsic width. */
	container-type: inline-size;
	container-name: qsas-card;
}

/* v1.9.59 — Per-device slots. The custom-layout article emits one
 * to three .qsas-card-slot wrappers, each carrying classes for the
 * device(s) it serves. CSS @container queries on the card's own
 * inline-size show/hide each slot so only one is visible at a time.
 *
 * Default: all slots hidden. Each device's @container rule reveals
 * the matching slot. Slots can be members of multiple devices (when
 * the cascade resolved them to the same DSL) — the matching rule
 * for the active card-width range wins.
 *
 * Breakpoints aligned with the existing row-stack thresholds:
 *   mobile  → card ≤ 480cqi
 *   tablet  → card 481–768cqi
 *   desktop → card > 768cqi
 */
.qsas-card-slot {
	display: none;
}
@container qsas-card (max-width: 480px) {
	.qsas-card-slot--mobile {
		display: block;
	}
}
@container qsas-card (min-width: 481px) and (max-width: 768px) {
	.qsas-card-slot--tablet {
		display: block;
	}
}
@container qsas-card (min-width: 769px) {
	.qsas-card-slot--desktop {
		display: block;
	}
}

/* v1.9.62 — @inline merge. The `•` separator and the inline-leaf
 * content are spliced INSIDE the previous sibling's wrapper at
 * render time (see CardLayout::render_children). The classes here
 * just style the separator and give the appended content a
 * normal whitespace boundary. No display:block worries — the
 * spans flow inline with the host wrapper's existing content. */
.qsas-inline-sep {
	margin: 0 0.5em;
	color: #9ca3af;
	font-weight: 400;
}
.qsas-inline-content {
	display: inline;
}
/* v1.9.64 — Fallback wrapper used when the previous sibling has
 * no `</div>` to splice into (e.g. a <h2> title widget). Without
 * this, the bullet + content spans would float as siblings in the
 * parent flex column and each become its own line. The host keeps
 * them grouped as a single block under the previous element. */
.qsas-inline-host {
	display: block;
}

.qsas-layout-row {
	display: flex;
	flex-direction: row;
	flex-wrap: wrap;
	gap: 1em;
	align-items: flex-start;
	margin: 0 0 0.6em;
}

.qsas-layout-row:last-child {
	margin-bottom: 0;
}

.qsas-layout-col {
	flex: 1 1 0;
	min-width: 0; /* allow long content to wrap inside */
	display: flex;
	flex-direction: column;
	gap: 0.5em;
}

/* "Fixed" column hugs its content rather than expanding — typically used
 * for the featured-image column so the image's intrinsic 120 × 120 size is
 * respected and the column doesn't waste space. */
.qsas-layout-col--fixed {
	flex: 0 0 auto;
}

/* Numeric weight modifiers — useful for asymmetric layouts ([col 2] is
 * twice as wide as a default [col 1]). */
.qsas-layout-col--1 { flex: 1 1 0; }
.qsas-layout-col--2 { flex: 2 1 0; }
.qsas-layout-col--3 { flex: 3 1 0; }
.qsas-layout-col--4 { flex: 4 1 0; }

/* v1.9.54 — Container queries replace the viewport media query that
 * used to collapse rows on small SCREENS regardless of how wide the
 * actual card was. The card itself is the container now
 * (.qsas-result--custom-layout has `container-type: inline-size` and
 * `container-name: qsas-card`, set in v1.9.43), so descendants react
 * to the CARD's own inline-size:
 *
 *   - Search results page on mobile (viewport 390 px, card 360 px):
 *     card < 600 cqi → collapse. Same as before.
 *   - Search results page on desktop (viewport 1400 px, card 800 px):
 *     card > 600 cqi → horizontal. Same as before.
 *   - NEW — card placed in a sidebar widget on a 1400-px desktop
 *     (card ~280 px wide): used to stay horizontal because viewport
 *     was big; now collapses correctly because the CARD is narrow.
 *   - NEW — three cards in a CSS grid on a wide desktop (each card
 *     ~380 px): each card decides independently based on its slot
 *     width, not the viewport.
 *
 * Browser support: Chrome 105+ (Aug 2022), Safari 16+ (Sep 2022),
 * Firefox 110+ (Feb 2023) — universally available in 2026. */
@container qsas-card (max-width: 480px) {
	.qsas-layout-row {
		flex-direction: column;
		align-items: stretch;
	}
	.qsas-layout-col--fixed {
		/* The "fixed" column (typically featured_image) stops
		 * being fixed-width on narrow cards and just sits above
		 * the body content. */
		align-self: flex-start;
	}
}

/* v1.9.55 — Per-row stack threshold overrides.
 *
 * The default (above) collapses every .qsas-layout-row at
 * card ≤ 480cqi. The classes below let the DSL author opt out
 * or shift that threshold per individual row:
 *
 *   [row stack:never]   → never collapses (always horizontal)
 *   [row stack:wide]    → collapses earlier, at card ≤ 768cqi
 *   [row stack:normal]  → same as default (≤ 480cqi)
 *   [row stack:narrow]  → only on very narrow cards (≤ 320cqi)
 *   [row stack:always]  → always vertical regardless of card width
 *
 * `stack:never` and `stack:always` use `!important` to win
 * against the default threshold rule above; the breakpoint
 * variants use higher-specificity selectors (.row.row--stack-X)
 * so a parent @container query can re-trigger them. */
.qsas-layout-row--stack-never {
	flex-direction: row !important;
	align-items: flex-start !important;
}
.qsas-layout-row--stack-always {
	flex-direction: column !important;
	align-items: stretch !important;
}
.qsas-layout-row--stack-always > .qsas-layout-col--fixed {
	align-self: flex-start;
}
/* Keep "stack-wide" rows horizontal between 481cqi and 768cqi,
 * collapse them at ≤ 768cqi. Also keep "stack-narrow" rows
 * horizontal between 321cqi and 480cqi, collapse only at ≤ 320cqi.
 * "stack-normal" inherits the default behavior — no override. */
@container qsas-card (min-width: 481px) {
	.qsas-layout-row.qsas-layout-row--stack-narrow,
	.qsas-layout-row.qsas-layout-row--stack-wide {
		flex-direction: row;
		align-items: flex-start;
	}
	.qsas-layout-row.qsas-layout-row--stack-narrow > .qsas-layout-col--fixed,
	.qsas-layout-row.qsas-layout-row--stack-wide > .qsas-layout-col--fixed {
		align-self: auto;
	}
}
@container qsas-card (max-width: 768px) {
	.qsas-layout-row--stack-wide {
		flex-direction: column;
		align-items: stretch;
	}
	.qsas-layout-row--stack-wide > .qsas-layout-col--fixed {
		align-self: flex-start;
	}
}
@container qsas-card (max-width: 320px) {
	.qsas-layout-row--stack-narrow {
		flex-direction: column;
		align-items: stretch;
	}
	.qsas-layout-row--stack-narrow > .qsas-layout-col--fixed {
		align-self: flex-start;
	}
}

/* v1.9.55 — Visibility flags.
 *
 *   .qsas-hide--mobile  → hidden when card ≤ 480cqi
 *   .qsas-hide--desktop → hidden when card > 480cqi
 *
 * Same breakpoint as the default row-collapse, so "mobile" and
 * "desktop" mean a consistent thing across the responsive system.
 * The class is emitted by the renderer on the leaf's wrapper
 * div or on the container element itself. */
@container qsas-card (max-width: 480px) {
	.qsas-hide--mobile {
		display: none !important;
	}
}
@container qsas-card (min-width: 481px) {
	.qsas-hide--desktop {
		display: none !important;
	}
}

/* Call Now button — a pill-shaped, green CTA. The number is intentionally
 * NOT on the button face (per requirement) — the icon + "Call Now" label
 * carries the affordance, and the tel: href + aria-label carry the actual
 * number for the dialer and assistive tech. */
.qsas-result__calls {
	display: flex;
	flex-wrap: wrap;
	gap: 0.5em;
	margin: 0.4em 0 0.6em;
}

.qsas-result__call-button {
	display: inline-flex;
	align-items: center;
	gap: 0.45em;
	padding: 0.55em 1.15em;
	background: linear-gradient(180deg, #28a745 0%, #1f8a3a 100%);
	color: #fff;
	border: 0;
	border-radius: 999px; /* full pill */
	font-weight: 600;
	font-size: 0.92em;
	line-height: 1;
	text-decoration: none;
	box-shadow: 0 2px 5px rgba(31, 138, 58, 0.28), 0 1px 2px rgba(31, 138, 58, 0.18);
	transition: background 0.15s ease, box-shadow 0.15s ease, transform 0.1s ease;
}

.qsas-result__call-button:hover,
.qsas-result__call-button:focus {
	background: linear-gradient(180deg, #2ebd4f 0%, #1f8a3a 100%);
	box-shadow: 0 4px 10px rgba(31, 138, 58, 0.34), 0 2px 4px rgba(31, 138, 58, 0.22);
	color: #fff;
	text-decoration: none;
	outline: 0;
}

.qsas-result__call-button:focus-visible {
	outline: 2px solid #1d2327;
	outline-offset: 2px;
}

.qsas-result__call-button:active {
	transform: translateY(1px);
	box-shadow: 0 1px 3px rgba(31, 138, 58, 0.28);
}

.qsas-result__call-icon {
	flex: 0 0 16px;
	width: 16px;
	height: 16px;
}

/* v1.9.80 — Toggle variant of the Call Now button. Visually
 * IDENTICAL to the regular Call Now button — same gradient, hover,
 * lift, focus ring, icon. Only the JS click behavior differs (see
 * frontend.js: clicks toggle the visible text between "Call Now"
 * and the actual phone number instead of dialing a tel: link). The
 * dedicated modifier class is here to give us a hook in case we
 * later want to add a subtle visual cue (e.g. a tooltip arrow or
 * a small "tap to reveal" affordance). For now it's a no-op style. */
.qsas-result__call-button--toggle {
	/* Intentionally empty — inherits everything from the base
	 * .qsas-result__call-button rule above. */
}

@media (prefers-reduced-motion: reduce) {
	.qsas-result__call-button { transition: none; }
	.qsas-result__call-button:active { transform: none; }
}

.qsas-result__more {
	margin: 0.5em 0 0;
}

.qsas-result__more-link {
	display: inline-block;
	font-weight: 600;
	text-decoration: none;
	color: inherit;
	border-bottom: 1px solid currentColor;
	padding-bottom: 1px;
}

.qsas-result__more-link:hover,
.qsas-result__more-link:focus {
	opacity: 0.8;
}

/* -------------------------------------------------------------------------
 * Pagination
 * --------------------------------------------------------------------- */
.qsas-results__pagination {
	display: flex;
	flex-wrap: wrap;
	gap: 0.35em;
	justify-content: center;
	margin: 1.5em 0 0;
	font-size: 0.95em;
}

.qsas-results__pagination .page-numbers {
	display: inline-flex;
	min-width: 2em;
	padding: 0.35em 0.6em;
	justify-content: center;
	align-items: center;
	border: 1px solid currentColor;
	border-radius: 3px;
	text-decoration: none;
	color: inherit;
	opacity: 0.85;
}

.qsas-results__pagination .page-numbers.current {
	font-weight: 700;
	opacity: 1;
}

.qsas-results__pagination .page-numbers:hover,
.qsas-results__pagination .page-numbers:focus {
	opacity: 1;
}

.qsas-results__pagination .page-numbers.dots {
	border-color: transparent;
	cursor: default;
}

/* -------------------------------------------------------------------------
 * Loading + empty states
 * --------------------------------------------------------------------- */
.qsas-loading {
	display: inline-flex;
	align-items: center;
	gap: 0.5em;
	font-size: 0.9em;
	opacity: 0.8;
}

.qsas-loading::before {
	content: '';
	display: inline-block;
	width: 14px;
	height: 14px;
	border: 2px solid currentColor;
	border-right-color: transparent;
	border-radius: 50%;
	animation: qsas-spin 0.8s linear infinite;
}

@keyframes qsas-spin {
	to { transform: rotate(360deg); }
}

@media (prefers-reduced-motion: reduce) {
	.qsas-loading::before { animation: none; }
}

/* Screen-reader-only utility (in case theme doesn't define one). */
.qsas-search-form .screen-reader-text {
	border: 0;
	clip: rect(1px, 1px, 1px, 1px);
	clip-path: inset(50%);
	height: 1px;
	margin: -1px;
	overflow: hidden;
	padding: 0;
	position: absolute;
	width: 1px;
	word-wrap: normal !important;
}

/* =========================================================================
 * Bidirectional Infinite Scroll — UI affordances
 *
 * Three pieces of UI added by the JS-side InfiniteScroll module:
 *
 *   1. `.qsas-pagination--hidden-by-scroll` — applied to the inline
 *      <nav> when the JS takes over pagination. The nav stays in the DOM
 *      (the AJAX swap needs to know max-pages from data-attrs that
 *      hydrate on every response), it just doesn't render.
 *
 *   2. `.qsas-results__end` — the "End of Results" sticker appended
 *      below the results list. Hidden by default, made visible only
 *      when currentPage === maxPages so the user knows there's nothing
 *      more to load downward.
 *
 *   3. `.qsas-back-to-top` — the thin floating bar pinned to the
 *      bottom of the viewport with a single button. Only visible
 *      when content overflows the viewport AND the user has scrolled
 *      away from the top (or is past page 1). Click → smooth-scroll
 *      to top, or jump back to page 1 first if they're past it.
 *
 * The bar is `position: fixed` and lives at <body> level (the JS
 * appends it there) so it isn't affected by transformed ancestors
 * anywhere in the page's DOM tree.
 * ========================================================================= */

.qsas-pagination--hidden-by-scroll {
	display: none;
}

.qsas-results__end {
	display: none;
	margin: 24px 0 8px;
	padding: 12px 16px;
	text-align: center;
	color: #646970;
	font-size: 13px;
	font-style: italic;
	border-top: 1px dashed #dcdcde;
}
.qsas-results__end--visible {
	display: block;
}

/* v1.9.104 — "Upgrade membership" upsell at the end of the results.
 * Hidden until the infinite-scroll JS reveals it at the end (or rendered
 * visible immediately for single-page results). Subtle one-line message
 * with the upgrade button centered beneath it. The button inherits the
 * plugin's default button color (--qsas-btn-rest-*) with a safe fallback. */
.qsas-results__upsell {
	display: none;
	margin: 20px auto 8px;
	padding: 18px 16px;
	max-width: 640px;
	text-align: center;
	border-top: 1px solid #ececec;
}
.qsas-results__upsell--visible {
	display: block;
}
.qsas-results__upsell-text {
	margin: 0 0 12px;
	font-size: 15px;
	line-height: 1.4;
	color: #3c434a;
}
/* v1.9.106 — configurable message font size. Medium matches the
 * historical default. */
.qsas-results__upsell-text--small { font-size: 13px; }
.qsas-results__upsell-text--medium { font-size: 15px; }
.qsas-results__upsell-text--large { font-size: 18px; }
.qsas-results__upsell-text--xlarge { font-size: 22.5px; }
.qsas-results__upsell-btn {
	display: inline-block;
	padding: 10px 22px;
	border: none;
	border-radius: 6px;
	background: var( --qsas-btn-rest-bg, #2271b1 );
	color: var( --qsas-btn-rest-fg, #ffffff );
	font-size: 14px;
	font-weight: 500;
	line-height: 1.2;
	text-decoration: none;
	cursor: pointer;
	transition: opacity .15s ease, background-color .15s ease;
}
.qsas-results__upsell-btn:hover,
.qsas-results__upsell-btn:focus {
	background: var( --qsas-btn-hover-bg, var( --qsas-btn-rest-bg, #135e96 ) );
	color: var( --qsas-btn-hover-fg, var( --qsas-btn-rest-fg, #ffffff ) );
	opacity: .92;
	text-decoration: none;
}
/* v1.9.105 — configurable button width as a % of the results row.
 * A sized button becomes a centered block of the chosen width. */
.qsas-results__upsell-btn--w25,
.qsas-results__upsell-btn--w50,
.qsas-results__upsell-btn--w75,
.qsas-results__upsell-btn--w100 {
	display: block;
	margin-left: auto;
	margin-right: auto;
	box-sizing: border-box;
}
.qsas-results__upsell-btn--w25 { width: 25%; }
.qsas-results__upsell-btn--w50 { width: 50%; }
.qsas-results__upsell-btn--w75 { width: 75%; }
.qsas-results__upsell-btn--w100 { width: 100%; }
/* v1.9.107 — configurable button label font size. */
.qsas-results__upsell-btn--text-small { font-size: 13px; }
.qsas-results__upsell-btn--text-medium { font-size: 14px; }
.qsas-results__upsell-btn--text-large { font-size: 16px; }
.qsas-results__upsell-btn--text-xlarge { font-size: 20px; }
@media ( max-width: 600px ) {
	.qsas-results__upsell {
		padding: 16px 12px;
	}
	.qsas-results__upsell-text {
		font-size: 14px;
	}
	.qsas-results__upsell-text--small { font-size: 12px; }
	.qsas-results__upsell-text--medium { font-size: 14px; }
	.qsas-results__upsell-text--large { font-size: 16px; }
	.qsas-results__upsell-text--xlarge { font-size: 20px; }
	.qsas-results__upsell-btn,
	.qsas-results__upsell-btn--w25,
	.qsas-results__upsell-btn--w50,
	.qsas-results__upsell-btn--w75,
	.qsas-results__upsell-btn--w100 {
		display: block;
		width: 100%;
		box-sizing: border-box;
	}
}

.qsas-back-to-top {
	position: fixed;
	left: 0;
	right: 0;
	bottom: 0;
	z-index: 9990;
	padding: 6px 14px;          /* thin — keeps it out of the way */
	background: rgba( 255, 255, 255, 0.96 );
	border-top: 1px solid #dcdcde;
	box-shadow: 0 -2px 6px rgba( 0, 0, 0, 0.05 );
	display: flex;
	justify-content: flex-end;  /* button on the right */
	align-items: center;
	pointer-events: none;
	opacity: 0;
	transform: translateY( 100% );
	transition: opacity 180ms ease, transform 220ms ease;
}
.qsas-back-to-top--visible {
	opacity: 1;
	transform: translateY( 0 );
	pointer-events: auto;
}
.qsas-back-to-top__btn {
	appearance: none;
	cursor: pointer;
	border: 1px solid #c3c4c7;
	background: #fff;
	color: #1d2327;
	padding: 4px 12px;
	font-size: 13px;
	line-height: 1.4;
	border-radius: 3px;
}
.qsas-back-to-top__btn:hover,
.qsas-back-to-top__btn:focus {
	background: #f0f0f1;
	border-color: #8c8f94;
}

/* Featured-image thumbnail when link gating is off — render as a
 * plain block (no anchor) so the click-affordance and pointer cursor
 * are gone but the image keeps its dimensions and lazy-loading. */
.qsas-result__thumb--nolink {
	display: inline-block;
	cursor: default;
}
.qsas-result__thumb--nolink .qsas-result__thumb-img {
	pointer-events: none;
}

/* Title when link gating is off — same h3 wrapper, same class as the
 * linked variant; no special selector needed. The bare text just
 * inherits the qsas-result__title font-size/weight automatically. */

/* Compensate for the thin Back-to-Top bar: when it's visible we add
 * a touch of bottom padding to the document so the last result isn't
 * trapped underneath. Only kicks in when the bar is actually visible
 * (the JS toggles the --visible class). */
body:has( .qsas-back-to-top--visible ) {
	padding-bottom: 48px;
}

/* =========================================================================
 * Link gating — non-clickable variants of dropdown rows
 *
 * When the admin has gated links off for the current visitor (see
 * ResultCap::should_show_link), live search rows render as <div>
 * instead of <a>. CSS keeps everything else identical — same padding,
 * same row layout, same hover-background — only the click cursor and
 * the underline-like affordance go away.
 * ========================================================================= */
.qsas-live-results__link--nolink {
	cursor: default;
	text-decoration: none;
}
.qsas-live-results__link--nolink:hover,
.qsas-live-results__link--nolink:focus {
	/* Same hover bg as the linked version so the layout doesn't shift,
	 * but no underline / text-color change since the row isn't actionable. */
	cursor: default;
}

/* =========================================================================
 * Loading indicators for the bidirectional infinite scroll. Two stable
 * elements (top + bottom of the results container) toggled via the
 * `--visible` modifier class on each fetch. The JS guarantees at least
 * 300ms of visibility per fetch so even fast servers / cached responses
 * give the user perceptible feedback that something happened.
 * ========================================================================= */
.qsas-results__loading {
	display: none;
	padding: 14px 16px;
	color: #646970;
	font-size: 13px;
	gap: 10px;
	align-items: center;
	justify-content: center;
}
.qsas-results__loading--visible {
	display: flex;
}
.qsas-results__loading--top {
	border-bottom: 1px solid rgba( 0, 0, 0, 0.06 );
	margin-bottom: 8px;
}
.qsas-results__loading--bottom {
	border-top: 1px solid rgba( 0, 0, 0, 0.06 );
	margin-top: 8px;
}
.qsas-results__spinner {
	width: 16px;
	height: 16px;
	display: inline-block;
	border: 2px solid #c3c4c7;
	border-top-color: #2271b1;
	border-radius: 50%;
	animation: qsas-spin 0.8s linear infinite;
	flex: 0 0 auto;
}
.qsas-results__loading-text {
	font-style: italic;
}

@keyframes qsas-spin {
	to { transform: rotate( 360deg ); }
}

/* Respect users who've opted out of motion. Static dashes instead of
 * a spinning ring — still visually conveys "in progress" but without
 * the rotation. */
@media (prefers-reduced-motion: reduce) {
	.qsas-results__spinner {
		animation: none;
		border-style: dashed;
		border-color: #8c8f94;
		border-top-color: #2271b1;
	}
}

/* =========================================================================
 * Sticky header strip — search form + result counter pinned to the top
 * of the viewport while the visitor scrolls through results.
 *
 * The JS-side init builds this wrapper on every results page that has
 * both a `.qsas-search-form` and a `.qsas-results__header` available
 * (see initStickySearchHeader in frontend.js). Form and header are
 * moved INSIDE this wrapper at DOM-ready, so the two scroll together
 * as a single sticky unit. The wrapper is position:sticky from the
 * first paint (no JS transition, no fade-in — the admin asked for
 * "fija desde que carga la página, sin transición").
 *
 * The background colour is themable via `--qsas-results-header-bg`
 * (defaults baked into class-options-manager.php at #f5f5f5). The
 * counter text colour is hardcoded black for legibility — see the
 * `.qsas-sticky-search-header .qsas-results__count` rule below.
 *
 * Z-index 50 sits above ordinary content but below admin bar overlays
 * (typically z-index 99999) so logged-in administrators still see the
 * WP admin bar above the sticky strip. Box-shadow uses the same colour
 * formula as the Back-to-Top button so the two areas relate visually.
 * ========================================================================= */
.qsas-sticky-search-header {
	position: sticky;
	top: 0;
	z-index: 50;
	background: var( --qsas-results-header-bg, #f5f5f5 );
	padding: 10px 16px;
	margin: 0 0 16px 0;
	box-shadow: 0 2px 4px var( --qsas-back-to-top-shadow, rgba( 0, 0, 0, 0.06 ) );

	/* Match the content width exactly. Without box-sizing:border-box,
	 * the wrapper's 16px horizontal padding gets ADDED to the parent's
	 * available width, which pushes the bar's right edge past the
	 * result cards below. With border-box, padding is included in the
	 * computed width and the bar lines up with the cards. */
	box-sizing: border-box;
	max-width: 100%;
}

/* Document-level scroll padding: tells the browser to treat the top
 * `--qsas-sticky-bottom` pixels as reserved space for any programmatic
 * scrolling. This covers three real-world cases that previously left
 * the first result card partially hidden behind the pinned sticky
 * strip on certain screen sizes:
 *
 *   1. Browser scroll restoration: on reload or back/forward
 *      navigation, the browser tries to restore the previous scroll
 *      position. Without scroll-padding-top, that restore can place
 *      the viewport such that the first card's top edge lands behind
 *      the sticky.
 *
 *   2. Anchor-link navigation (URL with #fragment): the browser
 *      scrolls so the target element sits at top:0 of the viewport.
 *      With scroll-padding-top, it lands at the configured offset
 *      below the sticky instead.
 *
 *   3. Any scrollIntoView() call elsewhere in the codebase (e.g., a
 *      future feature, a theme integration, an accessibility tool):
 *      this rule provides a sensible default landing zone.
 *
 * Fallback 80px keeps the rule defined even in the brief window
 * between page paint and the JS that publishes --qsas-sticky-bottom. */
html {
	scroll-padding-top: var( --qsas-sticky-bottom, 80px );
}

/* Tighten internal spacing when form + header live inside the wrapper —
 * the wrapper already provides padding so the children shouldn't add
 * their own. Also strip the global `.qsas-results__header` border-bottom
 * + opacity (defined far above this rule for standalone headers) so the
 * counter line inside the sticky strip renders clean against the
 * configured background — no dividing line, full opacity. */
.qsas-sticky-search-header .qsas-search-form {
	margin: 0;
}
.qsas-sticky-search-header .qsas-results__header {
	margin: 0;
	padding: 0;
	border-bottom: 0;
	opacity: 1;
}

/* Counter text is always solid black against the configurable
 * background. The admin chose this explicitly: configuring the counter
 * colour separately would make it too easy to land on a hard-to-read
 * combination, so we lock it. Override opacity from the global rule
 * so the black reads as solid (not 0.8 faded). */
.qsas-sticky-search-header .qsas-results__count {
	color: #000;
	font-weight: 500;
	opacity: 1;
}

/* Note: the WP admin bar offset and the theme-header offset are both
 * computed and applied via inline style by the JS at init time (see
 * resolveStickyOffset in frontend.js). We don't set them in CSS so we
 * don't conflict with the dynamic value. The `top: 0` baked into
 * `.qsas-sticky-search-header` above acts as a fallback for the brief
 * window between page paint and JS execution. */

/* Apply themable colours to the existing Back-to-Top floating bar.
 * (The base layout — fixed/bottom/right, padding, visibility states —
 * was already established by an earlier rule block above.) Now that
 * the var pipeline is in place, the bar's background, the inner
 * button's bg/text, and the shadow all become admin-configurable. */
.qsas-back-to-top {
	background: var( --qsas-back-to-top-bg, #ffffff );
	box-shadow: 0 -2px 6px var( --qsas-back-to-top-shadow, rgba( 0, 0, 0, 0.05 ) );
}
.qsas-back-to-top__btn {
	background: var( --qsas-back-to-top-bg, #ffffff );
	color: var( --qsas-back-to-top-text, #1d2327 );
	border-color: var( --qsas-back-to-top-shadow, #c3c4c7 );
}
.qsas-back-to-top__btn:hover,
.qsas-back-to-top__btn:focus {
	/* On hover, mix the bg toward the shadow tone slightly. With
	 * color-mix() supported in all evergreen browsers, this is the
	 * cleanest way to derive a hover surface from the live var. */
	background: color-mix( in srgb, var( --qsas-back-to-top-bg, #ffffff ) 90%, var( --qsas-back-to-top-shadow, #000 ) 10% );
}

/* =========================================================================
 * Debug index badge — admin tool for validating the bidirectional
 * infinite scroll output. Renders only when the admin enables
 * "Show result index numbers" in Settings → Advanced → Debug Tools.
 *
 * The badge sits in the .qsas-results__item LI wrapper (not inside the
 * article) so it overlays the entire card regardless of whether the
 * card uses the built-in default layout or a Custom Card Layout DSL.
 * The LI gets position:relative so the badge can absolute-position
 * itself in the top-right corner.
 *
 * Pure visual marker — pointer-events: none so it never intercepts
 * clicks on the card content beneath it. aria-hidden="true" in the
 * PHP also keeps it out of the accessibility tree, since it's debug
 * info for the admin, not content for screen readers.
 * ========================================================================= */
.qsas-results__item {
	position: relative;

	/* Scroll-margin lets scrollIntoView from the bidirectional
	 * infinite-scroll's applySwap land each boundary item with the
	 * right clearance:
	 *
	 *   - top: the value of --qsas-sticky-bottom, kept in sync by
	 *     resolveStickyOffset in frontend.js. Equals the total
	 *     height of the pinned chrome (theme header + admin bar +
	 *     sticky search strip). When the JS calls
	 *     firstItem.scrollIntoView({block:'start'}) for a downward
	 *     swap, the item's top lands at exactly that margin from
	 *     the viewport top — just BELOW the sticky chrome, never
	 *     behind it. Fallback 80px if the var isn't set yet
	 *     (handles the brief window between page paint and JS
	 *     execution).
	 *
	 *   - bottom: a small 16px gap so the upward-swap case (last
	 *     item scrolled to block:'end') sits a few pixels above
	 *     the viewport bottom, not flush against it.
	 */
	scroll-margin-top: var( --qsas-sticky-bottom, 80px );
	scroll-margin-bottom: 16px;
}
.qsas-result__debug-index {
	position: absolute;
	top: 8px;
	right: 8px;
	z-index: 5;
	background: #dc2626;
	color: #ffffff;
	font-size: 12px;
	font-weight: 700;
	line-height: 1;
	padding: 4px 8px;
	border-radius: 10px;
	min-width: 18px;
	text-align: center;
	font-variant-numeric: tabular-nums;
	box-shadow: 0 1px 3px rgba( 0, 0, 0, 0.25 );
	pointer-events: none;
}

/* ===================================================================
 * Filter shortcodes ([qs_filter], [qs_filter_bar],
 * [qs_search_filter_interface])
 * ===================================================================
 * Three layouts share the same field markup — only the wrapper class
 * differs:
 *   .qsas-filter--vertical    Sidebar style (fields stacked, full width)
 *   .qsas-filter--horizontal  Bar style    (fields side-by-side, compact)
 *   inside .qsas-interface--with-filter  All-in-one combined layout
 */

/* Base form wrapper — common to all layouts. */
.qsas-filter {
	font-size: 14px;
}
.qsas-filter * { box-sizing: border-box; }

/* Individual field — label + input(s). */
/* v1.9.135 — Frontend filter fields use a fieldset + legend so each field's
 * label sits on its top border (notched outline), independent of the theme
 * background color. The fieldset is the visible outline; inner controls are
 * borderless (rule further below) so there is a single border per field. */
.qsas-filter__field {
	position: relative;
	margin: 0 0 14px;
	padding: 4px 10px 8px;
	border: 1px solid #c3c4c7;
	border-radius: 8px;
	min-width: 0;
	min-inline-size: 0;
	background: transparent;
}
.qsas-filter__label {
	width: auto;
	max-width: 100%;
	padding: 0 6px;
	margin: 0 0 0 2px;
	font-weight: 600;
	font-size: 12px;
	line-height: 1.2;
	color: #1d2327;
}
/* Inputs/selects fill the available width by default. Horizontal layout
 * overrides this below. */
.qsas-filter select,
.qsas-filter input[type="text"],
.qsas-filter input[type="number"],
.qsas-filter input[type="date"] {
	width: 100%;
	padding: 6px 8px;
	border: 1px solid #c3c4c7;
	border-radius: 3px;
	font-size: 14px;
}

/* v1.9.135 — Inside the notched fieldset the controls drop their own border /
 * background so the fieldset is the single field outline (matches the design
 * reference). The whole field highlights on focus. */
.qsas-filter__field select,
.qsas-filter__field input[type="text"],
.qsas-filter__field input[type="number"],
.qsas-filter__field input[type="date"] {
	border: 0;
	border-radius: 0;
	padding: 4px 0;
	background: transparent;
	box-shadow: none;
	font-size: 14px;
}
.qsas-filter__field select:focus,
.qsas-filter__field input:focus {
	outline: none;
	box-shadow: none;
}
.qsas-filter__field:focus-within {
	border-color: #2271b1;
	box-shadow: 0 0 0 1px #2271b1;
}

/* Checkbox / radio option lists. */
.qsas-filter__options {
	display: flex;
	flex-direction: column;
	gap: 4px;
}
.qsas-filter__option {
	display: flex;
	align-items: center;
	gap: 6px;
	font-weight: normal;
	font-size: 14px;
	cursor: pointer;
}
.qsas-filter__option input[type="checkbox"],
.qsas-filter__option input[type="radio"] {
	margin: 0;
}

/* Range + date_range — two inputs separated by an em-dash. */
.qsas-filter__range,
.qsas-filter__date-range {
	display: flex;
	align-items: center;
	gap: 8px;
}
.qsas-filter__range input,
.qsas-filter__date-range input {
	flex: 1;
	min-width: 0;
}
.qsas-filter__range-sep {
	color: #888;
	flex: 0 0 auto;
}

/* Toggle — single checkbox + label. */
.qsas-filter__toggle {
	display: inline-flex;
	align-items: center;
	gap: 6px;
	font-weight: normal;
	cursor: pointer;
}

/* Apply + reset actions. */
.qsas-filter__actions {
	display: flex;
	align-items: center;
	gap: 8px;
	margin-top: 12px;
	padding-top: 12px;
	border-top: 1px solid #e0e0e0;
}
/* v1.9.133 — Apply & Reset are fully styled by an inline rule generated
 * from each button's saved config (colors, hover/effect, radius, padding,
 * font size — see ButtonLibrary::filter_button_inline_css). These static
 * rules are just a sane fallback for when that inline CSS is unavailable,
 * plus the shared icon/label layout. */
.qsas-filter__apply,
.qsas-filter__reset {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	gap: 6px;
	background: #2271b1;
	color: #fff;
	border: 1px solid #2271b1;
	border-radius: 3px;
	padding: 8px 16px;
	font-size: 14px;
	line-height: 1.2;
	text-decoration: none;
	cursor: pointer;
}
.qsas-fbtn__icon {
	display: inline-flex;
	align-items: center;
}
.qsas-fbtn__icon svg {
	width: 1.1em;
	height: 1.1em;
	display: block;
}

/* ---- Vertical layout (sidebar) ---- */
.qsas-filter--vertical {
	display: flex;
	flex-direction: column;
	padding: 16px;
	background: #f9f9f9;
	border: 1px solid #e0e0e0;
	border-radius: 4px;
	max-width: 320px;
}

/* ---- Horizontal layout (bar) ---- */
.qsas-filter--horizontal {
	display: flex;
	flex-wrap: wrap;
	align-items: flex-end;
	gap: 12px;
	padding: 12px 16px;
	background: #f9f9f9;
	border: 1px solid #e0e0e0;
	border-radius: 4px;
}
.qsas-filter--horizontal .qsas-filter__field {
	margin-bottom: 0;
	flex: 1 1 180px;
	min-width: 0;
}
.qsas-filter--horizontal .qsas-filter__actions {
	margin-top: 0;
	padding-top: 0;
	border-top: none;
	flex: 0 0 auto;
}
/* Checkbox/radio groups inside the horizontal bar lay out inline. */
.qsas-filter--horizontal .qsas-filter__options {
	flex-direction: row;
	flex-wrap: wrap;
	gap: 8px;
}

/* ---- All-in-one interface ---- */
.qsas-interface--with-filter .qsas-interface__body {
	display: flex;
	gap: 24px;
	margin-top: 16px;
}
.qsas-interface--with-filter .qsas-interface__filter {
	flex: 0 0 280px;
}
.qsas-interface--with-filter .qsas-interface__filter .qsas-filter--vertical {
	max-width: none;
}
.qsas-interface--with-filter .qsas-interface__results {
	flex: 1;
	min-width: 0;
}

/* v1.9.125 — Self-contained [qs_filter_bar]: the horizontal filter bar sits
 * on top, the results list below. Brand-new class, scoped here only, so it
 * cannot affect any existing layout. */
.qsas-interface--filter-bar .qsas-interface__filter-bar {
	margin-bottom: 1.25rem;
}

/* v1.9.129 — [qs_search_filter_interface_any]: ONE unified piece, fixed
 * three rows that never reflow — search on top, filter bar below, results
 * (with the total) last. Scoped entirely to the new wrapper, so no other
 * layout is affected. */
.qsas-interface--stacked {
	display: flex;
	flex-direction: column;
	gap: 6px;
}
.qsas-interface--stacked .qsas-interface__search,
.qsas-interface--stacked .qsas-interface__filter-bar {
	margin-bottom: 0;
}
.qsas-interface--stacked .qsas-search-form {
	margin: 0;
}
/* The filter bar blends into the piece — drop its standalone gray card so
 * it doesn't read as a separate box wasting vertical space. */
.qsas-interface--stacked .qsas-filter--horizontal {
	flex-direction: row;
	flex-wrap: nowrap;
	align-items: flex-end;
	gap: 8px;
	padding: 0;
	background: transparent;
	border: 0;
	border-radius: 0;
}
/* Every filter shrinks to share a SINGLE row, on ANY device (overrides the
 * generic ≤782px column-stacking for this interface only). */
.qsas-interface--stacked .qsas-filter--horizontal .qsas-filter__field {
	flex: 1 1 0;
	min-width: 0;
}
.qsas-interface--stacked .qsas-filter--horizontal .qsas-filter__field select,
.qsas-interface--stacked .qsas-filter--horizontal .qsas-filter__field input {
	width: 100%;
	min-width: 0;
	box-sizing: border-box;
}
/* v1.9.149 — Give the control content horizontal breathing room so the
 * selected value / placeholder isn't flush against the field edges.
 * v1.9.155 — Also trim the vertical padding so the filter row reads more
 * compact (less tall), closer in height to the search input above it. */
.qsas-interface--stacked .qsas-filter__field select,
.qsas-interface--stacked .qsas-filter__field input[type="text"],
.qsas-interface--stacked .qsas-filter__field input[type="number"],
.qsas-interface--stacked .qsas-filter__field input[type="date"] {
	padding: 2px 8px;
}
/* v1.9.155 — Tighter fieldset padding so the notched filter boxes aren't
 * noticeably taller than the search field. */
.qsas-interface--stacked .qsas-filter__field {
	padding-top: 1px;
	padding-bottom: 4px;
}
.qsas-interface--stacked .qsas-filter__label {
	font-size: 11px;
}
/* Keep labels on one line so the columns stay tidy when narrow. */
.qsas-interface--stacked .qsas-filter--horizontal .qsas-filter__label {
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
}
/* Reset/Apply stay compact and don't steal row space from the fields. */
.qsas-interface--stacked .qsas-filter--horizontal .qsas-filter__actions {
	flex: 0 0 auto;
}
/* v1.9.136 — Drop the divider line under the result total ("1 Agency") in
 * this interface only. The global .qsas-results__header border-bottom stays
 * for every other layout. */
.qsas-interface--stacked .qsas-results__header {
	border-bottom: 0;
	padding-bottom: 0;
}

/* v1.9.146 — The Sticky wrapper (`.qsas-iface-bar .qsas-sticky-search-header`)
 * holds the search + the movable blocks (filters, total); results render
 * OUTSIDE it. `.qsas-iface-bar` only stacks those rows vertically — the gray
 * band + pinned positioning come from the reused `.qsas-sticky-search-header`
 * class, whose definition is left untouched. */
.qsas-interface--stacked .qsas-iface-bar {
	display: flex;
	flex-direction: column;
	gap: 6px;
}
.qsas-interface--stacked .qsas-interface__total {
	min-width: 0;
}
.qsas-interface--stacked .qsas-interface__total:empty {
	display: none;
}
/* v1.9.148 — In this interface the focused filter field no longer draws the
 * bright blue ring around the fieldset (which also wrapped its <legend>
 * label). Keep the neutral border; no glowing outline. Scoped here so other
 * shortcodes keep their existing focus styling. */
.qsas-interface--stacked .qsas-filter__field:focus-within {
	border-color: #c3c4c7;
	box-shadow: none;
}

/* ---- Mobile: stack filter above results, full-width controls ---- */
@media (max-width: 782px) {
	/* v1.9.152 — On mobile, every search shortcode spans the FULL screen
	 * width (breaks out of the theme's content padding) to use all the
	 * available horizontal space. */
	.qsas-interface,
	.qsas-search-form {
		width: 100vw;
		max-width: 100vw;
		margin-left: calc( 50% - 50vw );
		margin-right: calc( 50% - 50vw );
		box-sizing: border-box;
	}
	/* The interface already bleeds full-width; its inner search form must
	 * NOT bleed again (that would overflow). Keep it normal inside. */
	.qsas-interface .qsas-search-form {
		width: auto;
		max-width: 100%;
		margin-left: 0;
		margin-right: 0;
	}
	.qsas-filter--vertical {
		max-width: none;
	}
	/* v1.9.153 — With the interface bled full-width, give the result cards a
	 * little side breathing room so they don't sit flush against the screen
	 * edges. */
	.qsas-interface__results {
		padding-left: 5px;
		padding-right: 5px;
		box-sizing: border-box;
	}
	.qsas-filter--horizontal {
		flex-direction: column;
		align-items: stretch;
	}
	.qsas-filter--horizontal .qsas-filter__field {
		flex: 1 1 auto;
	}
	.qsas-interface--with-filter .qsas-interface__body {
		flex-direction: column;
	}
	.qsas-interface--with-filter .qsas-interface__filter {
		flex: 1 1 auto;
	}
}

/* ===================================================================
 * Filter form AJAX states (v1.9.0)
 * =================================================================== */

/* In LIVE mode, the Apply button is redundant — every input change
 * triggers an AJAX update — so hide it. We use the JS-controlled
 * class so the button is still visible on no-JS fallback (where the
 * form will do its native method=GET reload). */
.qsas-filter--js-controlled[data-qsas-filter-apply="live"] .qsas-filter__apply {
	display: none;
}

/* Loading state on the results container during a filter AJAX
 * request. Dims slightly so the visitor can see something's happening
 * without making the content unreadable. */
.qsas-results--filter-loading {
	opacity: 0.55;
	pointer-events: none;
	transition: opacity 180ms ease-out;
}
