/**
 * Search Results UI (2026-09-29) — grouped result-type sections rendered by
 * inc/search/results-ui.php, only on is_search() (see inc/assets.php).
 *
 * Product/repair/part cards are Flatsome's own [ux_products style="default"]
 * markup (content-product.php) — this file only styles the wrapper/heading
 * layer around those, never the card internals.
 *
 * The "Bài viết liên quan" article cards (2026-09-29 fix) are DIFFERENT:
 * that markup is this theme's own (vnt39_search_render_article_cards() in
 * inc/search/results-ui.php), not a Flatsome shortcode, specifically
 * because Flatsome's blog_posts/ux_pages shortcode CSS collided badly
 * inside this wrapper (images/titles/excerpts overlapping). Every rule for
 * it below is scoped under .vnt39-search-results so it can never leak onto
 * Blog/News/Homepage, which have their own separate card styles
 * (assets/css/blog-home.css) this file must not touch or depend on.
 */

.vnt39-search-results {
	max-width: 1280px;
	margin: 0 auto;
	padding: var( --vnt39-container-gap, clamp(16px, 2.4vw, 32px) ) 0 48px;
	display: flex;
	flex-direction: column;
	gap: 40px;
}
/* COMPACT ABOVE-THE-FOLD (2026-09-30) — desktop only: less dead space
   before the title row. Tablet/mobile keep the existing clamp() as-is
   (task section F: "preserve current approved tablet/mobile behavior"). */
@media ( min-width: 1200px ) {
	.vnt39-search-results {
		padding-top: 14px;
	}
}

/* ---- Related series ("Danh mục liên quan") + mini promo banners ----
   Both are optional, presence-gated rows ABOVE the title row (task
   section G order) — the tight negative-margin pairs below only ever
   apply when the row they target actually exists in the DOM (an
   adjacent-sibling selector matches nothing otherwise), so a query with
   neither renders exactly as before this task, gap:40px untouched. */
.vnt39-search-related-series {
	padding: 4px 0;
}
.vnt39-search-related-series__track {
	display: flex;
	gap: 14px;
	overflow-x: auto;
	-webkit-overflow-scrolling: touch;
	scrollbar-width: none;
	/**
	 * Hover-clip fix (2026-09-30): `overflow-x: auto` with no explicit
	 * `overflow-y` makes this axis compute to `auto` too (a CSS spec rule
	 * — "visible" paired with a non-visible value on the other axis isn't
	 * allowed), so this track clips vertically even though nothing here
	 * intentionally scrolls up/down. The hover state below moves the
	 * thumb `translateY(-2px)` and the active state adds a `box-shadow: 0
	 * 0 0 2px` ring — neither affects layout height (both are paint-only),
	 * so with zero top padding the track's own top edge clipped off the
	 * top ~2-4px of that ring at the highest hover position. 6px covers
	 * the worst case (2px translate + 2px ring + a little slack) with room
	 * to spare. This does NOT add visible space before the banner below —
	 * `.vnt39-search-related-series + .vnt39-search-results__banners`'s
	 * negative margin is computed off this row's own actual bottom edge,
	 * which is unaffected by padding added at the TOP of this track.
	 */
	padding-top: 6px;
}
.vnt39-search-related-series__track::-webkit-scrollbar {
	display: none;
}
.vnt39-search-related-series__item {
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: 6px;
	width: 72px;
	flex: 0 0 auto;
	text-decoration: none;
	color: var( --vnt39-text, #17191f );
}
.vnt39-search-related-series__thumb {
	display: flex;
	align-items: center;
	justify-content: center;
	width: 42px;
	height: 42px;
	border-radius: 50%;
	overflow: hidden;
	background: var( --vnt39-surface-soft, #f8f9fb );
	border: 1.5px solid var( --vnt39-border, #e5e7eb );
	color: var( --vnt39-muted, #667085 );
	transition: border-color 0.15s ease, box-shadow 0.15s ease;
}
.vnt39-search-related-series__thumb--all {
	background: rgba( 143, 11, 24, 0.05 );
	color: var( --vnt39-brand, #8f0b18 );
}
.vnt39-search-related-series__img {
	display: block;
	width: 100%;
	height: 100%;
	object-fit: cover;
}
.vnt39-search-related-series__thumb svg {
	width: 18px;
	height: 18px;
}
.vnt39-search-related-series__label {
	font-size: 11.5px;
	font-weight: 600;
	line-height: 1.25;
	text-align: center;
	max-width: 100%;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}
.vnt39-search-related-series__count {
	font-weight: 400;
	color: var( --vnt39-muted, #667085 );
}
.vnt39-search-related-series__item.is-active .vnt39-search-related-series__thumb {
	border-color: var( --vnt39-brand, #8f0b18 );
	box-shadow: 0 0 0 2px rgba( 143, 11, 24, 0.12 );
}
.vnt39-search-related-series__item.is-active .vnt39-search-related-series__label {
	color: var( --vnt39-brand, #8f0b18 );
}
@media ( hover: hover ) and ( pointer: fine ) {
	.vnt39-search-related-series__item {
		transition: transform 0.15s ease;
	}
	.vnt39-search-related-series__item:hover .vnt39-search-related-series__thumb {
		border-color: var( --vnt39-brand, #8f0b18 );
		transform: translateY( -2px );
	}
}

/* Order (2026-09-30 correction): related series -> banners -> title row.
   Each pair only ever matches when the row it targets actually exists in
   the DOM (an optional row that didn't render leaves nothing to be an
   adjacent sibling of), so a query with neither renders exactly as before
   this feature existed. Targets: related-series->banners 10-14px net,
   banners->title-row 14-18px net, related-series->title-row (no banners
   configured) ~12-14px net. */
.vnt39-search-related-series + .vnt39-search-results__banners {
	margin-top: -28px; /* 40px - 28px = 12px */
}
.vnt39-search-results__banners + .vnt39-search-results__title-row {
	margin-top: -34px; /* 40px - 34px = 6px net gap dots-bottom -> H1-top —
		tightened 2026-09-30 (real-screenshot rework): target is banner
		height + only ~12-18px total for dots+gap combined (dots block
		itself is ~9px: 4px margin-top + ~5px row height). */
}
.vnt39-search-related-series + .vnt39-search-results__title-row {
	margin-top: -27px; /* 40px - 27px = 13px */
}

/**
 * MINI PROMO BANNERS (redesigned 2026-09-30) — modern, compact,
 * image-focused ecommerce promo slider. Audit before this rewrite found
 * the previous Flickity-based tablet/mobile "slider" never actually
 * initialized on this template (window.Flickity undefined, no
 * flickity-enabled class — confirmed live) — it was silently a static
 * stacked block. Replaced with the same native scroll-snap + interval-
 * autoplay architecture already proven by the "Bài viết liên quan" article
 * carousel just below in this file (see that block's own docblock) — one
 * track/item markup, two looks:
 *   - >=1024px: track becomes a static, non-scrolling flex row (both
 *     banners already fit — nothing to slide, dots hidden).
 *   - <1024px: track is a horizontally-scrolling, scroll-snap "1 primary
 *     banner + peek of the next" slider (assets/js/search-results.js's
 *     initBannerSlider() only ever runs autoplay/dots when the track
 *     actually overflows, so this falls out naturally with zero
 *     duplicated breakpoint logic in JS).
 * Image stays the dominant visual element throughout — no text card, no
 * heavy chrome; object-fit:cover on the <img> keeps every ratio below
 * exact regardless of the real uploaded file's own dimensions.
 */
.vnt39-search-results__banners {
	position: relative;
}
.vnt39-search-results__banners-track {
	display: flex;
	gap: 10px;
	direction: ltr; /* defensive — an inherited RTL context would otherwise
		render the peek slide on the LEFT at rest instead of the right. */
	scroll-padding-left: 0;
	overflow-anchor: none; /* defensive — never let scroll-anchoring nudge
		the resting scroll position once images finish loading in. */
}
.vnt39-search-results__banner-item {
	display: block;
	flex: 1 1 0;
	min-width: 0;
	box-sizing: border-box; /* the placeholder variant below adds padding — without
		border-box that padding inflates the rendered box past this flex-basis
		instead of fitting inside it (found via live measurement, 2026-09-30). */
	aspect-ratio: 4.2 / 1;
	max-height: 150px;
	border-radius: 12px;
	overflow: hidden;
	background: var( --vnt39-surface-soft, #f8f9fb );
	box-shadow: 0 1px 4px rgba( 17, 24, 39, 0.05 ); /* extremely subtle at
		rest, per final visual pass — artwork itself carries the weight. */
}
.vnt39-search-results__banner-img {
	display: block;
	width: 100%;
	height: 100%;
	object-fit: cover;
	object-position: center;
}

/**
 * TEMP REVIEW FALLBACK placeholder box — styles ONLY the `--placeholder`
 * item created by results-ui.php's vnt39_search_render_mini_banners() when
 * no real banner is configured. Delete this rule block together with that
 * PHP branch once real banners are uploaded; nothing else references
 * `--placeholder`. Deliberately stays an obvious "under construction"
 * diagonal-stripe pattern (never a fake photo) so it's never mistaken for
 * real content, while still living inside the exact same sized/rounded
 * box real banners use, so the redesigned layout can be judged correctly
 * before any image is uploaded.
 */
.vnt39-search-results__banner-item--placeholder {
	display: flex;
	align-items: center;
	justify-content: center;
	padding: 0 16px;
	text-align: center;
	background: repeating-linear-gradient(
		135deg,
		var( --vnt39-surface-soft, #f8f9fb ),
		var( --vnt39-surface-soft, #f8f9fb ) 12px,
		#eef0f3 12px,
		#eef0f3 24px
	);
	border: 1px dashed var( --vnt39-border, #d7dae0 );
	box-shadow: none;
}
.vnt39-search-results__banner-placeholder-label {
	font-size: 12.5px;
	font-weight: 600;
	color: var( --vnt39-muted, #667085 );
	letter-spacing: 0.01em;
}
@media ( hover: hover ) and ( pointer: fine ) {
	.vnt39-search-results__banner-item {
		transition: box-shadow 0.2s ease, transform 0.2s ease;
	}
	.vnt39-search-results__banner-item:hover {
		box-shadow: 0 6px 16px rgba( 17, 24, 39, 0.1 );
		transform: translateY( -2px );
	}
}

/**
 * PAGINATION DOTS — only rendered by PHP when >1 card exists, only ever
 * shown by CSS below 1024px, and additionally set `[hidden]` by JS when
 * the track doesn't actually overflow (e.g. a real single-banner setup on
 * tablet where the lone banner already fills the view).
 */
.vnt39-search-results__banner-dots {
	display: none;
	justify-content: center;
	align-items: center;
	gap: 5px;
	height: 5px;
	margin: 5px 0 0;
	padding: 0;
	min-height: 0;
}
.vnt39-search-results__banner-dots[ hidden ] {
	display: none;
}
/**
 * Root cause of the "huge grey/red pill" bug (found 2026-09-30 by reading
 * Flatsome's actual assets/css/flatsome.css, which this dev pane's browser
 * tooling cannot load to test against directly): Flatsome ships a global
 * bare-tag rule — `.button,button,input[type=button],...{ min-height:2.5em;
 * min-width:24px; padding:0 1.2em; line-height:2.4em; ... }` — plus a
 * separate `.button,button,fieldset,input,select,textarea{margin-bottom:1em}`.
 * This dot is a real <button>, and CSS cascades PER PROPERTY, not per rule:
 * our higher-specificity class selector already won padding/border (we
 * declared them), but min-height/min-width/line-height/margin were never
 * declared here before, so Flatsome's un-contested tag-selector values
 * applied directly — a ~38-40px min-height floor is exactly what forced a
 * 5px-tall dot to render as a huge pill regardless of the explicit
 * `height: 5px` below. Every property Flatsome's button rule sets is
 * neutralized explicitly here, scoped only to this one class — no broad
 * `button` override anywhere else.
 */
.vnt39-search-results__banner-dot {
	display: block;
	width: 5px;
	height: 5px;
	min-width: 0;
	min-height: 0;
	max-width: none;
	padding: 0;
	margin: 0;
	border: 0;
	box-shadow: none;
	background-color: var( --vnt39-border, #e5e7eb );
	background: var( --vnt39-border, #e5e7eb );
	border-radius: 999px;
	line-height: 0;
	font-size: 0;
	letter-spacing: normal;
	text-transform: none;
	cursor: pointer;
	transition: background-color 0.15s ease, width 0.15s ease, border-radius 0.15s ease;
}
.vnt39-search-results__banner-dot.is-active {
	width: 14px;
	border-radius: 999px;
	background-color: var( --vnt39-brand, #8f0b18 );
	background: var( --vnt39-brand, #8f0b18 );
}

/* ===================== <1024px: peek slider ===================== */
@media ( max-width: 1023px ) {
	.vnt39-search-results__banners-track {
		overflow-x: auto;
		scroll-snap-type: x mandatory;
		scroll-behavior: smooth;
		-webkit-overflow-scrolling: touch;
		scrollbar-width: none;
		-ms-overflow-style: none;
	}
	.vnt39-search-results__banners-track::-webkit-scrollbar {
		display: none;
	}
	/**
	 * NO PEEK (2026-09-30, real-screenshot rework) — a real 390px
	 * screenshot showed the previous "92%-width + right peek" treatment
	 * reading as a broken/chopped fragment, not an intentional carousel
	 * hint. Full-width items mean exactly one banner is visible at rest —
	 * no previous/next fragment on either side — and discovery happens
	 * purely through autoplay/swipe/dots, per this task's explicit
	 * instruction. 3/1 matches the real uploaded banner artwork's own
	 * ratio (2172x724, attachments 11914/11915) exactly, so nothing is
	 * cropped that doesn't need to be; at ~358px real content width this
	 * computes to ~119px tall, matching the task's own target number.
	 * max-height is a wide-tablet safety net only — it never engages at
	 * real phone widths (aspect-ratio alone already lands well under it).
	 */
	.vnt39-search-results__banner-item {
		flex: 0 0 100%;
		aspect-ratio: 3 / 1;
		max-height: 220px;
		scroll-snap-align: start;
		border-radius: 10px;
	}
	.vnt39-search-results__banner-dots {
		display: flex;
	}
}

/* Desktop (>=1024px): static 2-up row, never a slider — belt-and-braces
   alongside the JS canScroll() gate, matching the mobile sort popover
   fix's convention of never trusting one single mechanism. */
@media ( min-width: 1024px ) {
	.vnt39-search-results__banner-dots {
		display: none !important;
	}
}

/**
 * TITLE ROW (2026-09-29 visual rebuild) — H1 + result count + sort now
 * share one row (task section G), replacing the earlier H1-alone line.
 * flex-wrap so it degrades to two lines gracefully on narrow widths
 * without a separate breakpoint rule; .vnt39-search-filters__sort--desktop
 * is the copy that lives here (see assets/js/search-filters.js /
 * results-ui.php's vnt39_search_render_sort_form() docblock for why the
 * sort form is deliberately duplicated once, not moved between rows).
 *
 * ONE-ROW ENFORCEMENT AT >=1200px (2026-09-29 mockup-alignment pass) — this
 * flex-wrap fallback is exactly why a real (long) search query's H1 pushed
 * the result-count+sort block onto its own full-width line, LOOKING like a
 * separate row even though the markup already always put them side by
 * side. Real screenshots confirmed the wrap was actually happening at
 * normal desktop widths. Root cause: neither flex child declared a
 * `min-width`, so the default flex-item `min-width:auto` let the H1 refuse
 * to shrink below its own text width, forcing a wrap once H1 + meta
 * together exceeded the row's width. The @media(min-width:1200px) block
 * below (this task's own explicit desktop threshold) forces `nowrap` and
 * lets the H1 truncate with an ellipsis instead — the meta block (count +
 * sort) is given `flex:0 0 auto` so it's never the one that shrinks.
 * Below 1200px (tablet, this task's own explicit allowance) the original
 * wrap-to-two-lines fallback above is left completely untouched.
 */
.vnt39-search-results__title-row {
	display: flex;
	align-items: baseline;
	justify-content: space-between;
	flex-wrap: wrap;
	gap: 8px 16px;
}

.vnt39-search-results__title {
	margin: 0;
	font-size: 26px;
	font-weight: 700;
	color: var( --vnt39-text, #17191f );
	letter-spacing: -0.01em;
}

.vnt39-search-results__meta {
	display: flex;
	align-items: center;
	gap: 10px;
	flex-wrap: wrap;
}

.vnt39-search-results__count {
	font-size: 11.5px;
	color: var( --vnt39-muted, #98a1b3 );
	white-space: nowrap;
}

/* >=1200px (this task's own desktop threshold): force the title row to
   stay on one line — H1 left, result-count+sort right — instead of the
   wrap-to-two-lines fallback above, which is kept exactly as-is for
   768-1199px (task section 4's explicit tablet allowance). */
@media ( min-width: 1200px ) {
	.vnt39-search-results__title-row {
		flex-wrap: nowrap;
	}
	.vnt39-search-results__title-row .vnt39-search-results__title {
		flex: 1 1 auto;
		min-width: 0;
		overflow: hidden;
		text-overflow: ellipsis;
		white-space: nowrap;
	}
	.vnt39-search-results__title-row .vnt39-search-results__meta {
		flex: 0 0 auto;
	}
}

/* Tighten the title-row -> filter-bar and filter-block -> results gaps
   (task section F/G target density) — same negative-margin technique as
   before: the parent's gap:40px is right for space BETWEEN result
   sections, not for the tighter UI chrome above them. Only these two
   specific adjacent pairs are touched; every other section-to-section gap
   keeps the original 40px untouched. Tightened further (2026-09-30,
   compact-header pass, task section C: "giảm mạnh" title->toolbar and
   toolbar->first-section spacing) from the previous -20px/-24px. */
.vnt39-search-results__title-row + .vnt39-search-filters {
	margin-top: -26px; /* 40px - 26px = 14px */
}
.vnt39-search-filters + .vnt39-search-results__section {
	margin-top: -24px; /* 40px - 24px = 16px — task C target 14-18px */
}

.vnt39-search-results__section {
	display: flex;
	flex-direction: column;
	gap: 16px;
}

/* GLOBALIZE PRODUCT FILTER — Shop/category archive wrapper (2026-10-01).
   inc/woo-archive-filters.php renders the same .vnt39-search-filters
   component here, but OUTSIDE the .vnt39-search-results flex-gap:40px
   parent the two negative-margin rules above depend on (there is no
   .vnt39-search-results__title-row sibling in this context — see
   vnt39_search_render_filters_toolbar()'s own $context='archive' branch),
   so this wrapper supplies its own, independent spacing instead: breathing
   room above (clear of Flatsome's own category title/breadcrumb row) and
   below (clear of the product grid that follows it via
   woocommerce_before_shop_loop). */
.vnt39-shop-filters-wrap {
	margin: 18px 0 28px;
}

/* Archive mode's own sort control (2026-10-01, UNIFY-SORT-UI pass —
   replaces the earlier version of this rule, which styled a wrapped
   native .woocommerce-ordering form: that was never actually the same
   component as Search's compact sort, only relocated — confirmed by a
   real side-by-side screenshot). vnt39_search_render_filters_toolbar()'s
   archive branch now calls the exact same canonical vnt39_search_render_
   sort_form() Search's own two copies use, carrying class
   ".vnt39-search-filters__sort.vnt39-search-filters__sort--archive" — ALL
   visual styling (height/padding/border/radius/font/background) comes
   from the shared ".vnt39-search-filters__sort select" rule further down
   this file, nothing duplicated here. This wrapper only positions it:
   pushed to the far right of the flex .vnt39-search-filters__bar row,
   after the "Bộ lọc"/group dropdowns, matching the task's own target
   layout "[BỘ LỌC][DANH MỤC]...[GIÁ]    [SẮP XẾP ▼]". flex-shrink:0 keeps
   it from being squeezed by the bar's own overflow-x:auto scroll on
   narrow widths — it scrolls into view like any other bar item instead,
   same as the group dropdown chips already do; no separate mobile-width
   override needed (the shared select is already the compact 120-130px
   size everywhere, unlike the old native-Woo-select version of this rule,
   which needed its own mobile max-width/ellipsis fix precisely because it
   WASN'T that shared, already-compact style). */
.vnt39-search-filters__sort-archive {
	margin-left: auto;
	flex-shrink: 0;
	display: flex;
	align-items: center;
}
/* --archive has no display:none default (unlike --mobile below, which is
   one of two DUPLICATE copies Search renders and shows/hides by
   breakpoint) — this is the only copy archive renders, visible at every
   width. */

.vnt39-search-results__heading {
	margin: 0;
	display: flex;
	align-items: center;
	gap: 10px;
	font-size: 19px;
	font-weight: 700;
	color: var( --vnt39-text, #17191f );
	padding-bottom: 12px;
	border-bottom: 2px solid var( --vnt39-border, #e5e7eb );
}

.vnt39-search-results__icon {
	flex: 0 0 auto;
	color: var( --vnt39-brand, #8f0b18 );
}

.vnt39-search-results__section--repair_service .vnt39-search-results__heading,
.vnt39-search-results__section--part_accessory .vnt39-search-results__heading,
.vnt39-search-results__section--article .vnt39-search-results__heading {
	border-bottom-color: var( --vnt39-border, #e5e7eb );
}

.vnt39-search-results__empty {
	padding: 40px 0;
	text-align: center;
	color: var( --vnt39-muted, #667085 );
}

/**
 * PRODUCT / REPAIR / PART GRID — 5 columns desktop (2026-09-29 update).
 *
 * Cards themselves are untouched Flatsome markup: [ux_products ids="..."
 * style="default" type="row" columns="5" columns__md="3" columns__sm="2"]
 * (inc/search/results-ui.php) outputs Flatsome's own
 * <div class="row large-columns-5 medium-columns-3 small-columns-2"> with
 * each card as <div class="product-small col ...">, and Flatsome's own CSS
 * already defines .large-columns-5>.col{flex-basis:20%} etc — so the
 * `columns="5"` attribute alone is the real, correct fix for the old
 * 4-column lock (this file previously hardcoded columns="4").
 *
 * The override below exists ONLY because Flatsome's own large/medium/small
 * breakpoints (850px / 550px / 0px) don't line up with this task's exact
 * spec (1200 / 768 / 380 — same tablet/mobile breakpoints the article grid
 * above already uses, for consistency). Without it, Flatsome's native
 * .large-columns-5 would already switch to 5 columns at 850px, and
 * .small-columns-2 wouldn't kick in until below 550px, which is not what
 * was asked. Each rule here targets the exact same .col elements Flatsome
 * already renders; !important is used because Flatsome's own row carries
 * large-columns-5/medium-columns-3/small-columns-2 simultaneously (only
 * their own media queries decide which wins), so forcing the outcome here
 * is more reliable than out-specificity-ing three overlapping upstream
 * rules. Scoped under .vnt39-search-results so Shop/category/homepage grids
 * (which never carry this wrapper class) are untouched.
 *
 * box-sizing: Flatsome's own .product-small.col keeps content-box with its
 * own horizontal padding (its normal gutter mechanism, meant to be offset
 * by a negative margin on an ANCESTOR .row/.container this shortcode's
 * output here doesn't have — this file renders it straight inside
 * .vnt39-search-results__section, not inside a page-builder row). Left as
 * content-box, N columns' padding adds ON TOP of their N × (100/N)%
 * flex-basis, so the row's total requested width exceeds 100% and
 * flex-wrap silently drops to fewer per row than intended — confirmed live
 * this is what broke the 2-column mobile case. border-box makes the
 * padding come out of the declared width instead of adding to it, so
 * N × (100/N)% always sums to exactly 100% regardless of the padding
 * value, at every breakpoint below — no need to reverse-engineer Flatsome's
 * own gutter/margin values.
 */
.vnt39-search-results .row > .col {
	box-sizing: border-box;
}

/**
 * EQUAL-HEIGHT CARDS (2026-09-29 fix) — a row with a mixed discount/
 * countdown/student-price badge stack next to plainer cards left the
 * shorter cards' visible white box ending well above the taller ones', at
 * every column count (confirmed live: "Linh kiện & phụ kiện" 17 PRM screens
 * vs 15/13 Pro Max batteries).
 *
 * ROOT CAUSE (real DOM, inc/search/results-ui.php doesn't render these
 * cards — [ux_products] does, via the parent theme's own
 * woocommerce/content-product.php, confirmed by reading that file):
 *   .row                                    <- already display:flex by
 *                                               default (Flatsome), and
 *                                               align-items defaults to
 *                                               its own CSS initial value,
 *                                               stretch — so each .col
 *                                               already SILENTLY stretches
 *                                               to the row's tallest card.
 *     .product-small.col                    <- the flex item that stretch
 *                                               actually reaches — but it
 *                                               is plain display:block, so
 *                                               that extra height never
 *                                               reaches its own child.
 *       .col-inner                          <- Flatsome's own CSS already
 *                                               gives this flex:1 0 auto,
 *                                               but that's inert: it only
 *                                               works if ITS parent (.col,
 *                                               above) is display:flex,
 *                                               which it isn't by default.
 *         .product-small.box                <- THE visible card: image +
 *                                               text sit here, this is
 *                                               what actually shows the
 *                                               white background/border/
 *                                               shadow a shopper sees.
 *           .box-image                      <- product image, natural
 *                                               aspect-ratio height.
 *           .box-text                       <- title/price/discount/
 *                                               countdown/badges — THIS is
 *                                               the part whose content
 *                                               length varies card to
 *                                               card, so it's the one that
 *                                               needs to grow and absorb
 *                                               the height difference.
 *
 * Fix: turn every link in that chain into an actual flex column (.col ->
 * .col-inner -> .product-small.box), each stretching to fill its own
 * parent's already-stretched height, so the VISIBLE box (.product-small
 * .box, not just the invisible outer .col) reaches all the way down —
 * then let .box-text alone flex-grow so a short card's title/price finish
 * exactly where they always did (no gap forced between image and title)
 * and only the true empty space, if any, lands at the very bottom of the
 * card. No fixed/min height anywhere; every card still sizes off its own
 * tallest-in-row sibling, per row, via stretch alone.
 */
.vnt39-search-results .row {
	align-items: stretch;
}

.vnt39-search-results .row > .col {
	display: flex;
	flex-direction: column;
}

.vnt39-search-results .row > .col > .col-inner {
	display: flex;
	flex-direction: column;
	flex: 1 1 auto;
	width: 100%;
}





@media ( max-width: 1199px ) {
	.vnt39-search-results .row > .col {
		flex-basis: 33.3333% !important;
		max-width: 33.3333% !important;
	}
}

@media ( max-width: 767px ) {
	.vnt39-search-results .row > .col {
		flex-basis: 50% !important;
		max-width: 50% !important;
	}
}

@media ( max-width: 380px ) {
	.vnt39-search-results .row > .col {
		flex-basis: 100% !important;
		max-width: 100% !important;
	}
}

/**
 * ARTICLE CARDS — own carousel + card component, independent of the
 * [ux_products] cards above and of blog-home.css (see file docblock).
 *
 * MOBILE/TABLET/DESKTOP SLIDER (2026-09-29): .vnt39-search-results__
 * articles-grid is a horizontal-scroll flex track (native momentum
 * scroll + scroll-snap gives real finger-swipe for free — no JS needed
 * for dragging), auto-advanced by assets/js/search-results.js. Visible
 * card count is controlled purely by each card's flex-basis below:
 * >=1200px 5 visible, 768-1199px 3 visible, <768px 2 visible — using
 * calc() against the existing gap value (not a fixed px card width) so
 * the fit is always exact and never overflows regardless of viewport
 * width. Only 5 articles are ever queried
 * (vnt39_search_render_related_articles(), posts_per_page=5), so at
 * >=1200px all of them already fit and the track simply doesn't scroll.
 */
.vnt39-search-results__articles-carousel {
	position: relative;
}

.vnt39-search-results__articles-grid {
	display: flex;
	gap: 18px;
	overflow-x: auto;
	scroll-snap-type: x proximity;
	scroll-behavior: smooth;
	-webkit-overflow-scrolling: touch;
	scrollbar-width: none;
	-ms-overflow-style: none;
}

.vnt39-search-results__articles-grid::-webkit-scrollbar {
	display: none;
}

.vnt39-search-results__article-card {
	display: flex;
	flex-direction: column;
	flex: 0 0 calc( ( 100% - 4 * 18px ) / 5 );
	min-width: 0;
	box-sizing: border-box;
	scroll-snap-align: start;
	background: var( --vnt39-surface, #ffffff );
	border: 1px solid var( --vnt39-border, #e5e7eb );
	border-radius: var( --vnt39-radius-md, 10px );
	box-shadow: var( --vnt39-shadow-sm, 0 6px 24px rgba( 17, 24, 39, 0.07 ) );
	overflow: hidden;
	color: inherit;
	text-decoration: none;
	transition: box-shadow 0.15s ease, transform 0.15s ease;
}

.vnt39-search-results__article-card:hover,
.vnt39-search-results__article-card:focus-visible {
	box-shadow: 0 10px 28px rgba( 17, 24, 39, 0.12 );
	transform: translateY( -2px );
	color: inherit;
}

.vnt39-search-results__article-image {
	display: block;
	width: 100%;
	aspect-ratio: 4 / 3;
	background: var( --vnt39-surface-soft, #f8f9fb );
	overflow: hidden;
}

.vnt39-search-results__article-image img {
	display: block;
	width: 100%;
	height: 100%;
	object-fit: cover;
}

.vnt39-search-results__article-image--empty {
	display: flex;
	align-items: center;
	justify-content: center;
}

.vnt39-search-results__article-image--empty .vnt39-search-results__icon {
	width: 32px;
	height: 32px;
	color: var( --vnt39-muted, #667085 );
}

.vnt39-search-results__article-body {
	display: flex;
	flex-direction: column;
	flex: 1 1 auto;
	min-width: 0;
	padding: 14px;
	gap: 6px;
}

.vnt39-search-results__article-cat {
	font-size: 11px;
	font-weight: 700;
	letter-spacing: 0.04em;
	text-transform: uppercase;
	color: var( --vnt39-brand, #8f0b18 );
}

.vnt39-search-results__article-title {
	font-size: 15px;
	font-weight: 700;
	line-height: 1.4;
	color: var( --vnt39-text, #17191f );
	display: -webkit-box;
	-webkit-box-orient: vertical;
	-webkit-line-clamp: 3;
	overflow: hidden;
}

.vnt39-search-results__article-excerpt {
	font-size: 13px;
	line-height: 1.55;
	color: var( --vnt39-muted, #667085 );
	display: -webkit-box;
	-webkit-box-orient: vertical;
	-webkit-line-clamp: 2;
	overflow: hidden;
}

.vnt39-search-results__article-cta {
	margin-top: auto;
	padding-top: 6px;
	font-size: 13px;
	font-weight: 700;
	color: var( --vnt39-brand, #8f0b18 );
}

@media ( max-width: 1199px ) {
	.vnt39-search-results__article-card {
		flex-basis: calc( ( 100% - 2 * 18px ) / 3 );
	}
}

@media ( max-width: 767px ) {
	.vnt39-search-results__articles-grid {
		gap: 14px;
	}
	.vnt39-search-results__article-card {
		flex-basis: calc( ( 100% - 14px ) / 2 );
	}
}

@media ( max-width: 549px ) {
	.vnt39-search-results {
		gap: 32px;
		padding-top: 16px;
	}
	.vnt39-search-results__title {
		font-size: 21px;
	}
	.vnt39-search-results__heading {
		font-size: 17px;
	}

	/**
	 * MOBILE RESPONSIVE COMPLETION PASS (2026-09-30) — real measurement at
	 * 390px (empty-result query "iphonne 17"): breadcrumb's own bottom edge
	 * to the search results container's top was ~52px — Flatsome's stock
	 * `.page-title-inner` (min-height:60px + ~15px/20px top/bottom padding,
	 * confirmed by reading flatsome.css directly) is sized for a real page
	 * title/hero bar, not a compact search breadcrumb row. `body.search`
	 * scopes this to ONLY the search results page — Shop/category archives
	 * share the exact same Flatsome markup/class and are explicitly required
	 * to stay untouched, so nothing here can affect them (a Shop/category
	 * page is never `body.search`). H1 also further reduced from 21px (the
	 * pre-existing >=550px-down value) since the task flagged it as still
	 * "visually dominant" at phone widths specifically.
	 */
	body.search .page-title-inner {
		min-height: 0;
		padding-top: 10px;
		padding-bottom: 10px;
	}
	body.search .woocommerce-breadcrumb {
		font-size: 11px;
		line-height: 1.4;
		margin: 0;
	}
	.vnt39-search-results__title {
		font-size: 18px;
		line-height: 1.3;
	}

	/* EMPTY STATE (2026-09-30) — 40px top+bottom (task section F's own
	   desktop/tablet value) reads as a large dead block on a short phone
	   viewport; the message should sit close under the toolbar, not be
	   vertically centered in generous whitespace. Search behaviour/copy
	   itself is untouched — spacing only. */
	.vnt39-search-results__empty {
		padding: 20px 0 12px;
	}
}

/**
 * PRODUCT FILTER — VISUAL REBUILD (2026-09-29). Scoped entirely under
 * .vnt39-search-filters (inc/search/results-ui.php's
 * vnt39_search_render_filters_toolbar()).
 *
 * BREAKPOINT: single boundary at 768px, matching this file's own existing
 * convention elsewhere. Below it: the familiar full-height bottom sheet,
 * one "Bộ lọc (n)" trigger, no per-group dropdowns (task section I). At/
 * above it: a compact horizontal bar with "Bộ lọc" + one small dropdown
 * trigger per group (task sections B/C/H) — the SAME row serves both
 * tablet (horizontally scrollable, `overflow-x:auto`) and desktop (fits
 * without scrolling), so tablet never needs its own separate CSS block.
 *
 * POSITIONING: every popover (the 5 small per-group ones AND the "Bộ lọc"
 * mega-popover, both only at >=768px) is `position:fixed` with `top`/
 * `left` set inline by assets/js/search-filters.js from the trigger's own
 * getBoundingClientRect() at open time, flipped to right-aligned if it
 * would overflow the viewport's right edge. `position:fixed` was chosen
 * over anchoring via a `position:relative` ancestor specifically because
 * the mega-popover's trigger ("Bộ lọc") and the chips row can sit between
 * the bar and the panel in the DOM — an ancestor-relative `top:100%` would
 * then anchor under the chips row instead of under the bar itself
 * whenever chips are showing. JS-computed fixed coordinates avoid that
 * entirely and match "anchored directly below trigger" exactly regardless
 * of what else is on the page. A popover closes on outside click, Escape,
 * or page scroll (simpler and more robust than live-repositioning on
 * scroll) — see the JS file's own docblock.
 */

/* ---- Compact filter bar ---- */
/* TOOLBAR COMPACTION (2026-09-30, task section D) — gap widened slightly
   (8px -> 11px, the 10-12px target) so tighter buttons don't read as
   crowded. */
/* TOOLBAR COMPACTION PASS 2 (2026-09-30, same day) — gap 11px->8px
   (7-9px target). */
.vnt39-search-filters__bar {
	display: flex;
	align-items: stretch;
	gap: 6px;
	overflow-x: auto;
	-webkit-overflow-scrolling: touch;
	scrollbar-width: none;
	padding-bottom: 1px; /* keeps a focus outline on the last item from being clipped by overflow */
}
.vnt39-search-filters__bar::-webkit-scrollbar {
	display: none;
}
/* HOVER CLIPPING FIX (2026-10-03, ONE shared fix for Shop / category / nested
   category / Search — every context renders this same bar). Cause: the bar's
   `overflow-x:auto` makes overflow-y compute to `auto` too, so its padding box
   is a hard clip; a hovered control lifts 2px (translateY -2px) and casts
   `0 5px 14px` of shadow, which has no room above (padding-top was 0) and only
   1px below, so the top border/shadow was sliced off. Room is given by the
   WRAPPER (this bar), never per button: 5px above (also lowers the whole row
   5px), 8px below. Only on hover-capable pointers — the hover lift is gated to
   them, touch devices never clip, so real phones/tablets keep today's spacing.
   The surrounding margins give back exactly the added space so nothing else
   (toolbar → product grid) moves; the row itself is the only thing lowered. */
@media ( hover: hover ) and ( pointer: fine ) {
	.vnt39-search-filters__bar {
		padding-top: 5px;
		padding-bottom: 8px;
	}
	.vnt39-shop-filters-wrap {
		margin-bottom: 21px; /* 28px - 7px (the bar's extra bottom room) */
	}
	.vnt39-search-filters + .vnt39-search-results__section {
		margin-top: -31px; /* -24px - 7px */
	}
	/* Narrow window + mouse (no touch): hand the 5px above back too, so the
	   mobile spacing tuned earlier is unchanged (shop-page.css does the same
	   for the Shop/category wrapper). */
	@media ( max-width: 767px ) {
		.vnt39-search-results__title-row + .vnt39-search-filters {
			margin-top: -31px; /* -26px - 5px */
		}
		.vnt39-shop-filters-wrap {
			margin-top: 13px; /* 18px - 5px (category archives; the Shop page has its own rule) */
		}
	}
}

.vnt39-search-filters__dropdown {
	position: relative;
	flex: 0 0 auto;
}

/* TOOLBAR COMPACTION PASS 2 (2026-09-30, same day, real screenshot still
   too big) — height 44px->35px, horizontal padding 18px->13px, font-size
   14px->13px, icon 17px->16px. "Bộ lọc" still deliberately shares this
   exact same rule (no separate bigger size) — task: "'BỘ LỌC' không cần
   to hơn các control khác". */
.vnt39-search-filters__toggle,
.vnt39-search-filters__dd-trigger {
	display: inline-flex;
	align-items: center;
	gap: 5px;
	height: 31px;
	padding: 0 10px;
	background: var( --vnt39-surface, #ffffff );
	border: 1px solid var( --vnt39-border, #e5e7eb );
	border-radius: 6px;
	font: inherit;
	font-size: 11.5px;
	font-weight: 600;
	color: var( --vnt39-text, #17191f );
	cursor: pointer;
	white-space: nowrap;
	transition: border-color 0.15s ease, color 0.15s ease, background 0.15s ease;
}
.vnt39-search-filters__toggle svg {
	width: 13px;
	height: 13px;
	flex: 0 0 auto;
}

/* "Bộ lọc" stands out — pale red bg + red border/text, always (not only
   when active), per task section B. */
.vnt39-search-filters__toggle {
	background: rgba( 143, 11, 24, 0.05 );
	border-color: rgba( 143, 11, 24, 0.35 );
	color: var( --vnt39-brand, #8f0b18 );
	font-weight: 700;
}
.vnt39-search-filters__toggle:hover,
.vnt39-search-filters__toggle[ aria-expanded="true" ] {
	background: rgba( 143, 11, 24, 0.1 );
	border-color: var( --vnt39-brand, #8f0b18 );
}

.vnt39-search-filters__dd-trigger-text {
	overflow: hidden;
	text-overflow: ellipsis;
	max-width: 160px;
}
/* SVG markup ships as width="10" height="6" (results-ui.php); scaled up
   slightly here to the 12-14px target width (proportional height via
   height:auto keeps the original 10:6 aspect). */
.vnt39-search-filters__dd-chevron {
	flex: 0 0 auto;
	width: 11px;
	height: auto;
	color: var( --vnt39-muted, #667085 );
	transition: transform 0.15s ease;
}

/* A group with an active value, OR whose popover is currently open, reads
   the same way "Bộ lọc" does — red border + pale-red bg (task section B:
   "Active individual filter: red border or pale-red active state"). */
.vnt39-search-filters__dropdown.is-active .vnt39-search-filters__dd-trigger,
.vnt39-search-filters__dd-trigger[ aria-expanded="true" ] {
	background: rgba( 143, 11, 24, 0.05 );
	border-color: var( --vnt39-brand, #8f0b18 );
	color: var( --vnt39-brand, #8f0b18 );
}
.vnt39-search-filters__dd-trigger[ aria-expanded="true" ] .vnt39-search-filters__dd-chevron {
	transform: rotate( 180deg );
	color: inherit;
}

.vnt39-search-filters__badge {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	min-width: 16px;
	height: 16px;
	padding: 0 4px;
	border-radius: 999px;
	background: var( --vnt39-brand, #8f0b18 );
	color: #ffffff;
	font-size: 9.5px;
	font-weight: 700;
	line-height: 1;
}
.vnt39-search-filters__badge[ hidden ] {
	display: none;
}

/* ---- Sort (2 copies — desktop in the title row, mobile in the bar) ---- */
.vnt39-search-filters__sort {
	display: flex;
	align-items: center;
	gap: 8px;
	margin: 0;
}
/**
 * "Sắp xếp:" LABEL — REMOVED VISUALLY, KEPT ACCESSIBLE (2026-09-29
 * mockup-alignment pass) — the approved mockup's sort control is just
 * "[Độ liên quan ▼]", no separate label text, on both the desktop
 * title-row copy and the mobile bar copy. `display:none` would also strip
 * this <label>'s association from screen-reader users (the <select> has no
 * aria-label of its own — the `for`/`id` pair on this label IS its
 * accessible name), so it's visually hidden with the same off-screen
 * clip-rect technique this file already uses for
 * .vnt39-search-filters__chip's own hidden checkbox/radio inputs, not
 * display:none.
 */
.vnt39-search-filters__sort-label {
	position: absolute;
	width: 1px;
	height: 1px;
	padding: 0;
	margin: -1px;
	overflow: hidden;
	clip: rect( 0, 0, 0, 0 );
	white-space: nowrap;
	border: 0;
}
/* COMPACT HEADER PASS 2 (2026-09-30, same day) — 38px -> 35px, width
   constrained to the 135-155px target, applies to the desktop bar copy
   and the mobile bar copy alike (same class, same shrink — still
   comfortably tappable) — and to the archive context's own single copy
   (.vnt39-search-filters__sort--archive), all three sharing this one
   rule, which is the whole point of the canonical-component pass. */
.vnt39-search-filters__sort select {
	height: 31px;
	min-width: 120px;
	max-width: 130px;
	padding: 0 10px;
	border: 1px solid var( --vnt39-border, #e5e7eb );
	border-radius: 7px;
	background: var( --vnt39-surface, #ffffff );
	color: var( --vnt39-text, #17191f );
	font-size: 11.5px;
	font-weight: 600;
}
/* SORT-POSITION FIX (2026-10-01) — both copies now live inside
   .vnt39-search-filters__bar as flex siblings (desktop's copy used to sit
   in .vnt39-search-results__meta instead, a real screenshot showed that
   reading as a whole separate row above the filter toolbar instead of the
   same row as "Bộ lọc"/the group dropdowns — see inc/search/results-ui.php's
   own docblock on the relocated echo call). margin-left:auto pushes
   whichever copy is visible to the end of that row, right-aligned, same
   as Category/Shop's own .vnt39-search-filters__sort-archive wrapper —
   same positioning technique, now literally shared between all 3 variants
   instead of defined once per context. flex-shrink:0 keeps it from being
   squeezed by the bar's own overflow-x:auto on narrow widths (irrelevant
   for --desktop in practice, since it's display:none below 768px anyway,
   but kept symmetric with --mobile rather than a special case). */
.vnt39-search-filters__sort--desktop {
	flex: 0 0 auto;
	margin-left: auto;
}
.vnt39-search-filters__sort--mobile {
	display: none;
	flex: 0 0 auto;
	margin-left: auto;
}

/**
 * MOBILE SORT DROPDOWN — custom trigger/popover (2026-09-30, root-cause
 * fix — see assets/js/search-filters.js's enhanceMobileSort() docblock for
 * WHY: a native <select>'s own popup can't be positioned via CSS at all,
 * which is what let it open upward over Related Series/the mini banners).
 * `.vnt39-sort-enhanced select` is hidden ONLY once JS has successfully
 * built the replacement trigger — if JS fails, the plain native <select>
 * stays fully visible/usable (progressive enhancement, no dead end).
 * Trigger is styled to look IDENTICAL to the select it replaces (same
 * height/border/radius/font as `.vnt39-search-filters__sort select`
 * above) so nothing about the approved compact UI visibly changes.
 */
.vnt39-search-filters__sort--mobile.vnt39-sort-enhanced select {
	display: none;
}
.vnt39-search-sort-trigger {
	display: inline-flex;
	align-items: center;
	justify-content: space-between;
	gap: 6px;
	height: 31px;
	min-width: 120px;
	max-width: 130px;
	padding: 0 10px;
	border: 1px solid var( --vnt39-border, #e5e7eb );
	border-radius: 7px;
	background: var( --vnt39-surface, #ffffff );
	color: var( --vnt39-text, #17191f );
	font: inherit;
	font-size: 11.5px;
	font-weight: 600;
	cursor: pointer;
}
.vnt39-search-sort-trigger span {
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}
.vnt39-search-sort-trigger__chev {
	flex: 0 0 auto;
	width: 10px;
	height: auto;
	color: var( --vnt39-muted, #667085 );
	transition: transform 0.15s ease;
}
.vnt39-search-sort-trigger[ aria-expanded="true" ] .vnt39-search-sort-trigger__chev {
	transform: rotate( 180deg );
}
/* position:fixed, top/right set by JS from the trigger's own live rect
   (positionPopover()) — anchored below, right-edge-aligned, ~5px gap, per
   spec. z-index matches the desktop per-group dropdowns' own convention
   (assets/css/search-results.css's existing `z-index:400` on
   .vnt39-search-filters__dd-popover) — comfortably above product cards
   without needing the mobile filter sheet's much higher 90010 (the two
   are never open at the same time: opening "Bộ lọc" already closes any
   other open control via this same file's closeAll()/outside-click
   handling, and vice versa in practice since this is a separate control
   with its own independent open/close). */
.vnt39-search-sort-popover {
	position: fixed;
	z-index: 400;
	margin: 0;
	padding: 6px;
	list-style: none;
	min-width: 150px;
	max-width: min( 220px, calc( 100vw - 16px ) );
	background: var( --vnt39-surface, #ffffff );
	border: 1px solid var( --vnt39-border, #e5e7eb );
	border-radius: 10px;
	box-shadow: 0 12px 32px rgba( 17, 24, 39, 0.14 );
}
.vnt39-search-sort-popover[ hidden ] {
	display: none;
}
.vnt39-search-sort-popover__item {
	display: block;
	width: 100%;
	box-sizing: border-box;
	padding: 8px 10px;
	border: 0;
	border-radius: 6px;
	background: none;
	font: inherit;
	font-size: 12px;
	font-weight: 600;
	color: var( --vnt39-text, #17191f );
	text-align: left;
	cursor: pointer;
}
.vnt39-search-sort-popover__item:hover {
	background: var( --vnt39-surface-soft, #f8f9fb );
}
.vnt39-search-sort-popover__item.is-active {
	color: var( --vnt39-brand, #8f0b18 );
	background: rgba( 143, 11, 24, 0.06 );
}
@media ( min-width: 768px ) {
	/* Desktop never shows this trigger/popover at all (the mobile sort
	   form itself is already display:none there — see the existing
	   >=768px block further down); belt-and-braces so nothing can ever
	   render off-screen if a resize listener fires mid-transition. */
	.vnt39-search-sort-trigger,
	.vnt39-search-sort-popover {
		display: none !important;
	}
}

/* ---- Applied filters ("Đang lọc theo") ---- */
/* .vnt39-search-filters is a bare block container (no gap of its own), so
   .bar and .applied would otherwise touch directly — small explicit
   top margin here only (task section E: compact, not zero). */
.vnt39-search-filters__applied {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: 6px;
	margin-top: 12px; /* task section E: toolbar -> applied-filters target ~12px */
}
.vnt39-search-filters__applied-label {
	font-size: 11px;
	color: var( --vnt39-muted, #667085 );
	font-weight: 600;
}
.vnt39-search-filters__applied-chips {
	display: flex;
	flex-wrap: wrap;
	gap: 6px;
}
/* COMPACT CHIPS PASS 3 (2026-09-30, task section D) — height 29px->26px,
   font 12px->11px, padding 0 9px->0 8px. */
.vnt39-search-filters__applied-chip {
	display: inline-flex;
	align-items: center;
	gap: 4px;
	height: 26px;
	padding: 0 8px;
	box-sizing: border-box;
	background: rgba( 143, 11, 24, 0.06 );
	border: 1px solid rgba( 143, 11, 24, 0.25 );
	border-radius: 999px;
	color: var( --vnt39-brand, #8f0b18 );
	font-size: 11px;
	font-weight: 600;
	line-height: 1;
	text-decoration: none;
	transition: background 0.15s ease;
}
.vnt39-search-filters__applied-chip:hover {
	background: rgba( 143, 11, 24, 0.12 );
	color: var( --vnt39-brand, #8f0b18 );
}
.vnt39-search-filters__applied-chip span {
	font-weight: 700;
	font-size: 10.5px;
}
.vnt39-search-filters__clear-all {
	font-size: 11px;
	font-weight: 700;
	color: var( --vnt39-muted, #667085 );
	text-decoration: underline;
	margin-left: 4px;
}
.vnt39-search-filters__clear-all:hover {
	color: var( --vnt39-brand, #8f0b18 );
}

/* ---- Popovers: small per-group ones + the "Bộ lọc" mega-popover ---- */
/* Base (mobile, <768px): both kinds render as the SAME full-height bottom
   sheet — small dropdowns are never triggered on mobile at all (their
   trigger buttons are display:none below), so only .panel's mobile rules
   matter here. */
.vnt39-search-filters__dd-popover[ hidden ],
.vnt39-search-filters__panel[ hidden ] {
	display: none;
}
.vnt39-search-filters__dd-popover {
	display: none; /* only ever shown at >=768px — see media query below */
}

.vnt39-search-filters__panel-overlay {
	display: none;
}
.vnt39-search-filters__panel-header-clear {
	display: none; /* mobile-only "Xóa tất cả" shortcut in the sheet header, task section I */
}

.vnt39-search-filters__panel-sheet {
	background: var( --vnt39-surface, #ffffff );
	border: 1px solid var( --vnt39-border, #e5e7eb );
	display: flex;
	flex-direction: column;
	/**
	 * SQUARE BOTTOM CORNERS FIX (2026-09-29) — real screenshot: top-left/
	 * top-right rounded, bottom-left/bottom-right look square. Root cause,
	 * confirmed via computed styles: .panel-sheet itself already carries a
	 * uniform border-radius (10px desktop / 16px 16px 0 0 mobile — all 4
	 * desktop corners, same value, nothing asymmetric there), but
	 * .panel-footer paints its OWN opaque `background` right at the sheet's
	 * bottom edge, and .panel-sheet had no `overflow`, so that footer's
	 * plain rectangular box was never clipped to the sheet's rounded shape
	 * — it simply painted a square white block over where the rounded
	 * corners should show. .panel-sheet is the single owner of background/
	 * border/radius/shadow (header and body have no background of their
	 * own; only footer does), so `overflow:hidden` here clips every child's
	 * painting to the sheet's own rounded border-box — verified this does
	 * NOT clip the sheet's own box-shadow (a box's own box-shadow is never
	 * affected by that same box's overflow, only descendant painting is),
	 * doesn't touch .panel-body's independent `overflow-y:auto` (nested
	 * overflow contexts don't interfere with each other), and doesn't
	 * affect the standalone .dd-popover/mega-.panel positioning (both are
	 * separate elements, never descendants of .panel-sheet).
	 */
	overflow: hidden;
}
/**
 * HEADER DENSITY FIX (2026-09-29) — real screenshot showed the header area
 * absurdly tall despite these padding/height numbers already looking
 * reasonable on paper. Root cause, confirmed by reading Flatsome core's
 * OWN compiled CSS directly (wp-content/themes/flatsome/assets/css/
 * flatsome.css), not guessed:
 *   h1,h2,h3,h4,h5,h6 { margin-bottom:.5em; margin-top:0; width:100% }
 * `width:100%` on the <h2> forces it to claim the FULL width of this flex
 * ROW (.panel-header), pushing/squeezing its flex siblings (the "Xóa tất
 * cả" link and the × close button) and, combined with the `.5em` bottom
 * margin Flatsome also sets election, produces exactly the "small text,
 * huge surrounding box" the screenshot shows. My own `.panel-header h2`
 * selector (specificity class+element) already outranks Flatsome's bare
 * `h2` there for the properties I redeclare — but I was never redeclaring
 * `width`, so Flatsome's `width:100%` had nothing to lose to and applied
 * uncontested. Explicit `width:auto` + `line-height` below closes that
 * gap; nothing here needed `!important` once the actual missing property
 * was found.
 */
.vnt39-search-filters__panel-header {
	display: flex;
	align-items: center;
	gap: 10px;
	min-height: 0;
	padding: 13px 18px;
	border-bottom: 1px solid var( --vnt39-border, #e5e7eb );
	flex: 0 0 auto;
	box-sizing: border-box;
}
.vnt39-search-filters__panel-header h2 {
	width: auto;
	margin: 0;
	font-size: 19px;
	line-height: 1.3;
	font-weight: 700;
	color: var( --vnt39-text, #17191f );
	flex: 1 1 auto;
}
.vnt39-search-filters__panel-close {
	background: none;
	border: 0;
	font-size: 20px;
	line-height: 1;
	color: var( --vnt39-muted, #667085 );
	cursor: pointer;
	padding: 4px 6px;
	flex: 0 0 auto;
}
.vnt39-search-filters__panel-body {
	display: flex;
	flex-direction: column;
	gap: 14px;
	margin: 0;
	padding: 16px 18px;
	overflow-y: auto;
	flex: 1 1 auto;
	min-height: 0; /* the classic column-flex gotcha: without this, a flex:1 1 auto item defaults to min-height:auto (= its content's own height) and never actually shrinks to scroll within the parent's max-height, letting content spill past the sheet instead of scrolling inside .panel-body. */
}
.vnt39-search-filters__panel-grid {
	display: flex;
	flex-direction: column;
	gap: 18px;
}
.vnt39-search-filters__panel-footer {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 12px;
	padding: 9px 18px;
	border-top: 1px solid var( --vnt39-border, #e5e7eb );
	background: var( --vnt39-surface, #ffffff );
	flex: 0 0 auto;
	box-sizing: border-box;
}
.vnt39-search-filters__clear-btn {
	font-size: 12px;
	font-weight: 700;
	color: var( --vnt39-muted, #667085 );
	text-decoration: underline;
	background: none;
	border: 0;
	cursor: pointer;
	padding: 0;
}
.vnt39-search-filters__clear-btn:hover {
	color: var( --vnt39-brand, #8f0b18 );
}
.vnt39-search-filters__apply-btn {
	flex: 1 1 auto;
	height: 39px;
	padding: 0 22px;
	background: var( --vnt39-brand, #8f0b18 );
	border: 0;
	border-radius: 7px;
	color: #ffffff;
	font-size: 13px;
	font-weight: 700;
	cursor: pointer;
	transition: opacity 0.15s ease;
}
.vnt39-search-filters__apply-btn:hover {
	opacity: 0.9;
}

/* Mobile (<768px) — full-height bottom sheet + dimmed overlay. */
.vnt39-search-filters__panel:not( [ hidden ] ) {
	position: fixed;
	inset: 0;
	z-index: 90010; /* clears the header's own is-live-search-open fix (#header:has(...){z-index:90003}, assets/css/header-search.css) — the two never appear together, but the sheet must always win if they somehow did. */
	display: flex;
	align-items: flex-end;
}
.vnt39-search-filters__panel-overlay {
	display: block;
	position: absolute;
	inset: 0;
	background: rgba( 17, 24, 39, 0.5 );
}
.vnt39-search-filters__panel-sheet {
	position: relative;
	width: 100%;
	max-height: 85vh;
	border-radius: 16px 16px 0 0;
}
.vnt39-search-filters__panel-header-clear {
	display: inline;
	font-size: 12px;
	font-weight: 700;
	color: var( --vnt39-muted, #667085 );
	text-decoration: underline;
	white-space: nowrap;
}

body.vnt39-filters-open {
	overflow: hidden;
}

/* Groups + chips — shared shape at every width. */
.vnt39-search-filters__group {
	border: 0;
	margin: 0;
	padding: 0;
	min-width: 0; /* legend's own display:table default (see below) can otherwise force this fieldset wider than its grid column. */
	display: flex;
	flex-direction: column;
	gap: 10px;
}
/**
 * LEGEND -> CONTROL SPACING (2026-09-29) — real screenshot: chips/inputs
 * sit too close under each group's title/divider (256 GB, color chips,
 * Từ/Đến inputs worst; Loại sản phẩm/Danh mục slightly). Tried raising
 * `.vnt39-search-filters__group`'s own flex `gap` first (10px -> 16px),
 * but LIVE measurement (getBoundingClientRect on legend vs the chips/price
 * div right after it) proved that gap computes to 16px yet contributes
 * ZERO actual space between legend and the next flex item — a real,
 * confirmed browser quirk where <legend> keeps its HTML-spec "rendered
 * legend" box behavior and doesn't participate in flex `gap` like a normal
 * item, even with `display:flex` on the fieldset. The group's `gap` (kept
 * at its original 10px) still does its job for OTHER sibling pairs inside
 * a group (e.g. chips-row -> group-footer inside the small standalone
 * popovers), so lowering it back avoids touching that already-accepted
 * spacing. The legend->control gap is instead created directly on the
 * control container via the adjacent-sibling combinator below — this is
 * the one and only source of that spacing, so there is no risk of it
 * stacking with the (non-functional, for this pair) flex gap.
 *
 * Value is 10px, not the ~16px that would centre the 14-18px target: the
 * desktop mega-panel's 2-col grid pairs vtype+vcat in row 1 and
 * vstorage+vcolor in row 2 (vprice alone spans row 3), so this margin adds
 * to EVERY row's height once per row (measured live: delta is exactly
 * 3 x the margin value, e.g. 16px -> +48px total). The task's own ceiling
 * ("không tăng quá khoảng 20-30px") is the harder constraint, so 10px was
 * chosen as the largest value that stays at that ceiling (measured +30px
 * total) rather than the softer 14-18px distance target.
 */
.vnt39-search-filters__group legend + .vnt39-search-filters__chips,
.vnt39-search-filters__group legend + .vnt39-search-filters__price {
	margin-top: 10px;
}
/* LEGEND CLIPPING FIX (2026-09-29) — same root-cause category as the h2
   fix above: Flatsome bundles a normalize.css reset that gives every
   <legend> `display:table` unconditionally. A table-display legend inside
   a flex/column fieldset is a well-documented cross-browser rendering
   quirk (legends historically ignore normal box flow and can render
   overlapping/clipped against the fieldset's own edge) — exactly
   "headings appear cramped/cut at the content boundary". Forcing
   `display:block` here (a real property this selector was never
   previously setting at all, not just a value tweak) removes the
   special-cased legend layout entirely. */
.vnt39-search-filters__group legend {
	display: block;
	width: 100%;
	padding: 0;
	margin: 0;
	font-size: 13px; /* task section F: group title target 13px */
	line-height: 1.3;
	font-weight: 700;
	color: var( --vnt39-text, #17191f );
	/* .vnt39-search-filters__group's own flex `gap: 10px` already provides
	   the legend -> chips spacing (task target 10-12px) — no separate
	   margin needed here, which would otherwise double up with that gap. */
}
.vnt39-search-filters__chips {
	display: flex;
	flex-wrap: wrap;
	gap: 8px;
}
/**
 * CHIP SIZE FIX (2026-09-29 UI polish) — real screenshot showed chips
 * ("128 GB", "Đen", ...) rendering as huge circular pills. Root cause:
 * `min-height` is a FLOOR, not a cap, and generous vertical padding
 * (7px top+bottom) stacked on top of it with no `box-sizing` or
 * `line-height` control — for short labels the box grew well past its
 * intended size instead of shrink-wrapping to the text. Fixed height +
 * box-sizing:border-box + line-height:1 + flex:0 0 auto (never stretched
 * by the flex `.chips` row) removes every one of those inflation paths.
 */
/* CHIP COMPACTION PASS 2 (2026-09-30, same day, task section E) — switched
   from a fixed `height` to `min-height` (task's own explicit term), still
   safe against the original "giant circular chip" bug (2026-09-29) since
   padding/line-height/box-sizing all stay fixed/controlled — nothing here
   can grow past the 33px floor uncontrolled, it just guarantees AT LEAST
   33px so short labels ("SSD", "Đỏ"...) don't collapse thinner than that. */
.vnt39-search-filters__chip {
	position: relative;
	display: inline-flex;
	align-items: center;
	flex: 0 0 auto;
	box-sizing: border-box;
	min-height: 30px; /* task section F: option chip 29-31px */
	padding: 5px 11px;
	background: var( --vnt39-surface-soft, #f8f9fb );
	border: 1px solid var( --vnt39-border, #e5e7eb );
	border-radius: 16px;
	font-size: 11.5px; /* task section F: option font 11.5-12px */
	line-height: 1;
	font-weight: 600;
	color: var( --vnt39-text, #17191f );
	cursor: pointer;
	user-select: none;
	white-space: nowrap;
	transition: border-color 0.15s ease, background 0.15s ease, color 0.15s ease;
}
.vnt39-search-filters__chip input {
	position: absolute;
	width: 1px;
	height: 1px;
	margin: -1px;
	overflow: hidden;
	clip: rect( 0, 0, 0, 0 );
	white-space: nowrap;
	border: 0;
	padding: 0;
}
.vnt39-search-filters__chip:has( input:checked ) {
	background: rgba( 143, 11, 24, 0.08 );
	border-color: var( --vnt39-brand, #8f0b18 );
	color: var( --vnt39-brand, #8f0b18 );
}
.vnt39-search-filters__chip:has( input:focus-visible ) {
	outline: 2px solid var( --vnt39-brand, #8f0b18 );
	outline-offset: 2px;
}

/**
 * HOVER LIFT (2026-09-29) — toolbar triggers ("Bộ lọc", per-group dd-
 * triggers) and chips read as flat; task wants a light "lift" on hover.
 * Scoped to `.toggle`/`.dd-trigger`/`.chip` only (not Xem kết quả/Áp dụng/
 * Xóa — those weren't in the task's list and already have their own hover
 * treatment). The same 3 selectors are reused as-is inside standalone
 * popovers (task section C "Standalone group popovers: cùng behavior") —
 * popovers share this exact markup/classes via the fieldset-reparenting
 * architecture, so no extra selector is needed for that case.
 * `transform`/`box-shadow` only — never width/height/scale, so nothing
 * shifts layout — and gated to real hover-capable pointers so touch
 * devices never get a stuck hover state. `.chip`'s own :focus-visible
 * already exists via `:has(input:focus-visible)` above (the input, not
 * the label, is what's actually focusable) — only toggle/dd-trigger are
 * real <button> elements needing their own :focus-visible rule here.
 */
@media ( hover: hover ) and ( pointer: fine ) {
	.vnt39-search-filters__toggle,
	.vnt39-search-filters__dd-trigger,
	.vnt39-search-filters__chip {
		transition: transform 160ms ease, box-shadow 160ms ease, border-color 160ms ease, background-color 160ms ease, color 160ms ease;
	}
	.vnt39-search-filters__toggle:hover,
	.vnt39-search-filters__dd-trigger:hover,
	.vnt39-search-filters__chip:hover {
		transform: translateY( -2px );
		box-shadow: 0 5px 14px rgba( 0, 0, 0, 0.08 );
	}
}
.vnt39-search-filters__toggle:focus-visible,
.vnt39-search-filters__dd-trigger:focus-visible {
	outline: 2px solid var( --vnt39-brand, #8f0b18 );
	outline-offset: 2px;
}
@media ( prefers-reduced-motion: reduce ) {
	.vnt39-search-filters__toggle,
	.vnt39-search-filters__dd-trigger,
	.vnt39-search-filters__chip {
		transition: none;
	}
	.vnt39-search-filters__toggle:hover,
	.vnt39-search-filters__dd-trigger:hover,
	.vnt39-search-filters__chip:hover {
		transform: none;
	}
}

/* Color swatch dot (task section 3/6) — one shared style used in color
   chips, the toolbar trigger, and applied-filter chips. White always gets
   a visible border regardless of the mapped hex (so it never disappears
   against a light chip background); unresolved/unknown colors fall back
   to a plain neutral ring via the --unknown modifier, per
   vnt39_search_filters_color_swatch_hex()'s own "null = no guess" rule. */
.vnt39-search-filters__swatch {
	display: inline-block;
	flex: 0 0 auto;
	width: 19px;
	height: 19px;
	border-radius: 50%;
	border: 1px solid rgba( 0, 0, 0, 0.18 );
	margin-right: 6px;
}
.vnt39-search-filters__swatch--unknown {
	background: repeating-conic-gradient( #d8dbe0 0% 25%, #eef0f3 0% 50% ) 0 0 / 8px 8px;
}
.vnt39-search-filters__applied-chip .vnt39-search-filters__swatch,
.vnt39-search-filters__dd-trigger .vnt39-search-filters__swatch {
	width: 12px;
	height: 12px;
	margin-right: 5px;
}

/**
 * PRICE ROW — SINGLE LINE FIX (2026-09-30) — real screenshot showed "Từ
 * [input] ¥" and "Đến [input] ¥" stacked as 2 full rows despite this
 * container already being `display:flex` with room to spare (measured live
 * at the narrowest required width, 820px tablet: the price group's own grid
 * column is 353px wide, while the two label+input+¥ clusters only need
 * ~300px total un-wrapped) — so the wrap was never a real space shortage,
 * it's an unknown upstream rule (Flatsome/WooCommerce's own global
 * `input[type=number]`/form-row styling, not identifiable without live
 * wp-admin/devtools access) winning the cascade against this file's own
 * `.vnt39-search-filters__price input` selector and forcing the input to
 * some wider/block value, which is what pushed the second cluster onto its
 * own line. `flex: 0 0 auto` + explicit `width`/`box-sizing` are repeated
 * with `!important` below for exactly this reason — same "must reliably
 * beat unknown, possibly-multiple upstream rules" situation already
 * documented and !important-guarded elsewhere in this file (see the product
 * grid column overrides above). `flex-wrap: nowrap` only kicks in >=768px
 * (desktop/tablet, this task's own bucket) — under that, the two clusters
 * are free to wrap onto their own line each (mobile bucket, task's own
 * explicit allowance), which is what the bare `flex-wrap: wrap` base value
 * already gives for free with no extra rule needed.
 */
.vnt39-search-filters__price {
	display: flex;
	align-items: center;
	gap: 9px;
	flex-wrap: wrap;
	width: 100%;
}
.vnt39-search-filters__price label {
	display: inline-flex;
	align-items: center;
	gap: 6px;
	flex: 0 0 auto !important;
	font-size: 12px;
	white-space: nowrap;
	color: var( --vnt39-muted, #667085 );
}
.vnt39-search-filters__price input {
	flex: 0 0 auto !important;
	width: 128px !important;
	max-width: 128px !important;
	height: 31px;
	padding: 0 10px;
	border: 1px solid var( --vnt39-border, #e5e7eb );
	border-radius: 7px;
	font-size: 12px;
	box-sizing: border-box !important;
}
.vnt39-search-filters__price-sep {
	flex: 0 0 auto;
	color: var( --vnt39-muted, #667085 );
}
@media ( min-width: 768px ) {
	.vnt39-search-filters__price {
		flex-wrap: nowrap;
	}
}

/* Per-group footer — exists in every multi-select/price fieldset's
   markup, but stays invisible unless that fieldset is CURRENTLY sitting
   inside a small dropdown popover (JS reparents it there and back — see
   results-ui.php's vnt39_search_render_group_footer() docblock). Inside
   the mega-panel it's always hidden; that panel has its own single shared
   footer already.
   Two variants (task section 4/5 — same shared vcolor fieldset needs
   different behavior depending on which popover currently holds it):
   --standard (Xóa/Áp dụng) shows in any popover EXCEPT vcolor's own;
   --color-quick ("Tất cả màu") shows ONLY in vcolor's own popover, since
   color auto-applies per click there and never needs an explicit Áp dụng
   step. Both stay hidden inside the mega-panel via the base rule below. */
.vnt39-search-filters__group-footer {
	display: none;
}
.vnt39-search-filters__dd-popover .vnt39-search-filters__group-footer--standard {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 10px;
	margin-top: 2px;
	padding-top: 10px;
	border-top: 1px solid var( --vnt39-border, #e5e7eb );
}
.vnt39-search-filters__dd-popover[ data-vnt39-dd-popover="vcolor" ] .vnt39-search-filters__group-footer--standard {
	display: none;
}
.vnt39-search-filters__dd-popover[ data-vnt39-dd-popover="vcolor" ] .vnt39-search-filters__group-footer--color-quick {
	display: flex;
	margin-top: 2px;
	padding-top: 10px;
	border-top: 1px solid var( --vnt39-border, #e5e7eb );
}
.vnt39-search-filters__group-clear {
	font-size: 12px;
	font-weight: 700;
	color: var( --vnt39-muted, #667085 );
	text-decoration: underline;
	background: none;
	border: 0;
	cursor: pointer;
	padding: 0;
}
.vnt39-search-filters__group-clear:hover {
	color: var( --vnt39-brand, #8f0b18 );
}
/* Áp dụng — task section 2: compact, 36-40px tall, ~90-120px wide, never
   the near-full-width button the mega-panel's own "Xem kết quả" is. */
.vnt39-search-filters__group-apply {
	box-sizing: border-box;
	height: 38px;
	min-width: 90px;
	max-width: 120px;
	padding: 0 16px;
	background: var( --vnt39-brand, #8f0b18 );
	border: 0;
	border-radius: 6px;
	color: #ffffff;
	font-size: 13px;
	font-weight: 700;
	cursor: pointer;
}
.vnt39-search-filters__color-clear-apply {
	font-size: 12px;
	font-weight: 700;
	color: var( --vnt39-brand, #8f0b18 );
	background: none;
	border: 0;
	text-decoration: underline;
	cursor: pointer;
	padding: 0;
}
.vnt39-search-filters__color-clear-apply:hover {
	color: var( --vnt39-text, #17191f );
}

/* ================= >= 768px: tablet + desktop ================= */
@media ( min-width: 768px ) {
	.vnt39-search-filters__sort--mobile {
		display: none !important;
	}

	/* Per-group triggers only ever exist in the bar at this width — the
	   mobile-only "Bộ lọc (n)" styling below never applies here since the
	   bar itself isn't display:none (that only happens <768px, see the
	   max-width block). */

	.vnt39-search-filters__dd-popover:not( [ hidden ] ) {
		display: block;
		position: fixed; /* top/left set inline by JS from the trigger's own rect */
		z-index: 400;
		background: var( --vnt39-surface, #ffffff );
		border: 1px solid var( --vnt39-border, #e5e7eb );
		border-radius: 10px;
		box-shadow: 0 12px 32px rgba( 17, 24, 39, 0.14 );
		padding: 18px;
		min-width: 220px;
		max-width: min( 340px, calc( 100vw - 24px ) );
		max-height: min( 420px, calc( 100vh - 24px ) );
		overflow-y: auto;
	}

	/* MODAL SIZE PASS 2 (2026-09-30, same day, task section E) — width
	   formula switched to the task's own exact spec:
	   min(calc(100vw - 48px), 960px) (was min(800px, calc(100vw-24px)) —
	   960px max is the task's explicit "900-980px" target). */
	.vnt39-search-filters__panel:not( [ hidden ] ) {
		position: fixed; /* top/left set inline by JS, same mechanism as the small popovers */
		inset: auto;
		z-index: 400;
		display: block;
		width: min( calc( 100vw - 48px ), 900px ); /* task section F: 880-900px, target 900px */
	}
	.vnt39-search-filters__panel-overlay {
		display: none; /* task section D/E: no giant all-page overlay for floating popovers */
	}
	/* max-height switched from a fixed 560px to the task's explicit
	   78-82vh target (80vh), still capped against very short viewports. */
	.vnt39-search-filters__panel-sheet {
		width: 100%;
		max-height: min( 80vh, calc( 100vh - 24px ) );
		border-radius: 10px;
		box-shadow: 0 16px 40px rgba( 17, 24, 39, 0.16 );
	}
	.vnt39-search-filters__panel-header-clear {
		display: none;
	}
	/* HEADER COMPACTION PASS 3 (2026-09-30, task section F) — padding 18px
	   24px -> 16px 22px, h2 font 21px -> 19-20px, close X 19px -> 18px. */
	.vnt39-search-filters__panel-header {
		padding: 16px 22px;
	}
	.vnt39-search-filters__panel-header h2 {
		font-size: 20px;
	}
	.vnt39-search-filters__panel-close {
		font-size: 18px;
	}
	.vnt39-search-filters__panel-grid {
		display: grid;
		grid-template-columns: 1fr 1fr;
		gap: 18px 30px; /* row-gap 20px->18px, column-gap 32px->30px, task's proportional reduction */
	}
	/**
	 * TÌNH TRẠNG ADDED (2026-09-29) — .group--price's own `grid-column:1/-1`
	 * (full-width) is REMOVED here, not just left in place: with 5 groups
	 * before it (vtype, vcat, vstorage, vcondition, vcolor), a full-width
	 * price row would leave vcolor stranded alone in its own row with an
	 * empty gap next to it (2-col auto-flow can't backfill past an explicit
	 * full-span item). Letting Giá pair normally with Màu sắc as the grid's
	 * 6th, EVEN item removes that gap entirely — verified live the Từ/Đến
	 * inputs (120px each + separator, ~280px total) fit comfortably inside
	 * a half-width column at every grid width this panel actually renders.
	 */

	/* Desktop mega-panel footer (task section 8) — the mobile sheet keeps
	   its full-width Xem kết quả (task explicitly allows it "may remain
	   wider" there); on desktop it must NOT stretch across the panel. */
	.vnt39-search-filters__panel-footer {
		justify-content: space-between;
	}
	.vnt39-search-filters__apply-btn {
		display: inline-flex;
		align-items: center;
		justify-content: center;
		flex: 0 0 auto;
		width: auto;
		min-width: 170px;
		max-width: 200px;
		height: 39px; /* 42px -> 38-40px target, task section F */
		padding: 0 20px;
		box-sizing: border-box;
	}
}

/* ===================== < 768px: mobile ===================== */
@media ( max-width: 767px ) {
	.vnt39-search-filters__bar {
		gap: 8px;
	}
	/* Only "Bộ lọc" (+ the mobile sort copy) shows in the bar — every
	   per-group quick-access dropdown is desktop/tablet-only (task
	   section I: closed mobile state is just "[Bộ lọc (n)] [Sắp xếp ▾]"). */
	.vnt39-search-filters__dropdown:not( .vnt39-search-filters__dropdown--main ) {
		display: none;
	}
	.vnt39-search-filters__sort--mobile {
		display: flex;
	}
	/* The title-row's sort copy is desktop/tablet-only — mobile shows sort
	   exactly once, next to "Bộ lọc" (task section I), not also up in the
	   title row. */
	.vnt39-search-filters__sort--desktop {
		display: none;
	}
}

/* Widened from 380px -> 430px (2026-09-30, price single-line fix): live
   measurement showed the un-shrunk 128px-input row (365px content) just
   barely exceeds the ~352-360px sheet width in the 381-430px range (e.g.
   390px, one of this task's own required test widths), tipping it into an
   avoidable 2-line wrap. Shrinking here too keeps 390px (and 375px) on one
   line; 500px+ already fits the 128px version with room to spare, so it's
   untouched. !important needed to beat the base rule's own !important (see
   that rule's docblock) — a media query alone doesn't outrank !important
   regardless of source order. */
@media ( max-width: 430px ) {
	.vnt39-search-filters__price input {
		width: 100px !important;
		max-width: 100px !important;
	}
}

/* QUERY RESOLVER messages (inc/search/resolver.php) */
.vnt39-search-notice { margin: 0 0 14px; padding: 10px 14px; border-radius: 10px; background: #fff7e6; border: 1px solid #f5d9a8; color: #5c3b00; font-size: 14px; line-height: 1.45; }
.vnt39-search-notice a { color: #a30d1b; font-weight: 700; text-decoration: underline; }
.vnt39-search-empty-suggest { margin-top: 18px; }
.vnt39-search-empty-suggest p { margin: 0 0 10px; color: #475467; }
.vnt39-search-empty-suggest__chips { display: flex; flex-wrap: wrap; gap: 8px; }
.vnt39-search-empty-suggest__chip { display: inline-block; padding: 6px 14px; border: 1px solid #d9dde3; border-radius: 999px; background: #fff; color: #1f2430; font-size: 13.5px; font-weight: 600; text-decoration: none; }
.vnt39-search-empty-suggest__chip:hover { border-color: #a30d1b; color: #a30d1b; }
