/**
 * Shop page (/cua-hang/) redesign — Hero + "Mua sắm theo danh mục" +
 * "Thương hiệu nổi bật" + two-column Shop area (sidebar: Trade-in/Repair/
 * Trust, main: heading+filter+grid+pagination) + mobile Trust slider.
 * Above the already-LOCKED filter/sort toolbar + product grid (untouched,
 * this file never targets their selectors). Enqueued only on the real
 * Shop page (inc/shop-page.php), never is_front_page().
 */

/* ------------------------------------------------------------------ */
/* Hero — #fptLiteBlock/.fpt-slide/.fpt-nav are already fully styled   */
/* sitewide in components.css; this only adds compact Shop spacing +   */
/* the dot styling for the new #fptDots element.                       */
/* ------------------------------------------------------------------ */
.vnt39-shop-hero {
	margin: 0 0 20px;
}
/* WHITE SPACE PASS (2026-10-02) — "khoảng trắng dưới dots" (task rule).
   Each top-block section (Hero, Category, Brand, mobile Trade-in) is
   independently wrapped in its own real [row][col span="12"] shortcode
   (same mechanism every homepage section already uses, for the real
   Flatsome container gutter). Flatsome's own `.col` carries a real,
   sitewide 30px padding-bottom gutter — confirmed live this (not this
   file) was the dominant source of the 50px hero→category gap, not the
   dots sizing. Targeting only the ONE `.col` that wraps the Hero
   (via :has(), scoped so Category/Brand/Trade-in's own [col] wrappers,
   and every other page's [row][col] elsewhere on the site, are
   untouched) brings the gap down to exactly this rule's own 20px
   margin-bottom above — within the 18–24px target. */
.col:has( > .col-inner > .vnt39-shop-hero ) {
	padding-bottom: 0;
}
/* MASTER SIZE PASS (2026-10-02) — target asset is 1620×600 (2.7:1), but
   the 4 real uploaded images are confirmed 1200×200 (6:1 — verified via
   `sips -g pixelWidth -g pixelHeight`, all 4 identical) and their real
   content (headline text left, price box right) runs edge-to-edge.
   Tested live: forcing aspect-ratio:2.7/1 with object-fit:cover (the
   MASTER CSS concept) crops ~55% of the image's width and DOES cut off
   the headline text and the price box — confirmed via screenshot, not
   assumed. object-fit:contain was also tried and avoids the crop but
   replaces the outer whitespace this task asked to reduce with large
   internal letterbox bars instead, which does not match MASTER either.
   Per this task's own instruction ("nếu crop 2.7:1 làm mất nội dung thì
   report rằng asset cần thay bằng bản 1620×600"): NOT forcing 2.7/1
   here — the Hero keeps auto-sizing to the real image's own 6:1 ratio
   (components.css's existing #fptLiteBlock .fpt-slide img{aspect-ratio:
   6/1}, untouched) until real 1620×600 assets exist. Swapping this one
   line to `aspect-ratio: 2.7 / 1` (desktop only, >=850px) is all that's
   needed the moment those real assets are uploaded. */
/* PRODUCT GRID RESPONSIVE FIXES (2026-10-03) — scoped to the Shop's own
   main column; found by measuring real card geometry, not by taste.
   1) Sale ribbon (left:8px, ~86px) and warranty ribbon (right:8px, ~98px)
      are both absolutely positioned in the card image's top corners. In
      the 5-per-row desktop grid the image box is only 167px wide at 1366,
      so on any card that has BOTH they overlapped by 33px (measured:
      "Giảm 7%" sitting on top of "BH 3 tháng"). Below 850px the grid is 3
      or 2 per row with wider cards (and components.css already shrinks the
      pair under 550px), so this only applies >=850px: when a card carries a
      sale ribbon, the warranty ribbon stacks under it on the same left edge
      instead of fighting for the same row.
   2) At 1024–1279px the main column is only ~770–1000px wide, and Flatsome's
      5-per-row made cards 132–152px: "Thanh toán trước Free ship" wrapped to
      3 lines and the product name was ellipsised after one word. 4 per row
      there (cards ~190px) is the same card width the 1366 desktop already
      has and was already visually approved. */
/* (the >=850px "stack the warranty badge under the sale badge" rule that stood here is gone: both badges now share ONE row
   everywhere — shared rule in components.css.) */
@media ( min-width: 1024px ) and ( max-width: 1279px ) {
	.vnt39-shop-main-col .products.large-columns-5 > .col {
		flex-basis: 25%;
		max-width: 25%;
	}
	/* 3) The filter/sort bar is `flex-wrap:nowrap; overflow-x:auto`
	      (built for phones). At 1024px with the sidebar, its 7 controls need
	      771px but the main column gives it 738px, so the sort select ("Mới
	      nhất") ended up 33px past the edge, reachable only by a hidden
	      horizontal scroll. Let it wrap onto a second row here instead. */
	.vnt39-shop-main-col .vnt39-search-filters__bar {
		flex-wrap: wrap;
		row-gap: 8px;
	}
}

/* SHOP GRID DENSITY (2026-10-04) — desktop >=1280px: 4 product cards per row (was Flatsome's 5), the same density as the
   1024–1279px rule above. Only the Shop archive's main column (.vnt39-shop-main-col, sidebar present) and only its product
   grid (.products — the Hot Sale slider above it is a `.row`, not `.products`, so it keeps its own count). <1024px is untouched. */
@media ( min-width: 1280px ) {
	.vnt39-shop-main-col .products.large-columns-5 > .col {
		flex-basis: 25%;
		max-width: 25%;
	}
}

/* SHOP SIDEBAR WIDTH (2026-10-04) — desktop >=1280px: the sidebar column is 250px (was Flatsome's large-3 = 25% = ~318px at the
   1270px row) and the main column takes the rest (row − 250px), so the product grid gets the width the sidebar gave up while
   keeping 4 cards per row. Column paddings/gutter are untouched. <1280px keeps the existing 25% / 75% split. */
@media ( min-width: 1280px ) {
	.vnt39-shop-page-row > .vnt39-shop-sidebar {
		flex: 0 0 250px;
		max-width: 250px;
	}
	.vnt39-shop-page-row > .vnt39-shop-main-col {
		flex: 1 1 0;
		min-width: 0;
		max-width: calc( 100% - 250px );
	}
}

/* SHOP CARD FOOT ALIGNMENT (2026-10-04) — the "Thêm vào so sánh" button used to sit right after the last promo row, so a card
   without sale/countdown rows put it ~48px higher than its row-mates and left blank space UNDER it. Flatsome's equalize-box
   script already gives every `.price-wrapper` in a row the same (tallest) height; making that wrapper a column flex box and the
   footer row `margin-top:auto` pins the Compare / Like row to the wrapper's — i.e. the card's — bottom, so any spare room sits ABOVE it, inside
   the price/info area. Nothing is added or faked: only where the leftover space goes changes. Scoped to the Shop archive's
   product grid. */


/* keeps an 8px gap above the footer row even when a card has no spare room (the row's own top margin is now `auto`) */


/* SHOP TOTAL — "522 sản phẩm" at the right of the title row (MASTER). Plain gray text, aligned with the title's baseline area. */
.vnt39-shop-total {
	margin: 0;
	color: #667085;
	font-size: 15px;
	font-weight: 600;
	line-height: 1.3;
	letter-spacing: 0;
	text-align: right;
	text-transform: none;
	white-space: nowrap;
}
/* the title row's right column is vertically centred by Flatsome; MASTER puts the count level with the "CỬA HÀNG" line */
.shop-page-title .page-title-inner > .flex-col:has( > .vnt39-shop-total ) {
	align-self: flex-start;
	padding-top: 10px;
}
@media ( max-width: 849px ) {
	.vnt39-shop-total { font-size: 13px; }
}

/* HERO MOBILE CROP FIX (2026-10-03) — the shared `#fptLiteBlock` rule
   forces `aspect-ratio:16/7` + `object-fit:cover` at <=767px (components.css,
   built for the old sitewide banner). On the Shop hero's real 1200×200 (6:1)
   banners that crops a 390px phone to the image's middle ~36%: measured
   live, the headline and price box were cut in half. At <=849px the Shop
   hero now shows the image at its own natural ratio, never cropped (the
   breakpoint is components.css's own 849px, so 768–849px tablets, which
   measured a 323px-tall cover-crop at 768, are fixed too) — an
   admin-uploaded mobile image (VNT39 → Shop Hero) renders full-bleed at its
   own proportions, and a desktop-only image shrinks to fit the width. */
/* SHOP HERO STANDARD (2026-10-03): desktop/tablet (>=768px) = 1200x400, 3:1 — the same standard as the
   Category banner. Every slide box is exactly 3:1 (all slides share one height, no CLS), an on-standard
   image fills it, a legacy image of another ratio is shown whole and centred (contain) — never stretched
   or cropped. Phones (<=767px): no forced ratio — each slide takes its own image's ratio (the mobile
   image if there is one, else the desktop image), shown whole. Ratios come from the attachments
   (--ar / --arm set inline in inc/shop-page.php). */
.vnt39-shop-hero#fptLiteBlock .fpt-slide .vnt39-shop-hero__link,
.vnt39-shop-hero#fptLiteBlock .fpt-slide picture {
	display: block;
	width: 100%;
	height: 100%;
}
.vnt39-shop-hero#fptLiteBlock .fpt-slide img {
	width: 100%;
	height: 100% !important;
	aspect-ratio: auto;
	object-fit: contain;
	object-position: center;
}
.vnt39-shop-hero#fptLiteBlock .fpt-slide {
	background: #f3f4f6;
	aspect-ratio: 3 / 1;
}
@media ( max-width: 767px ) {
	.vnt39-shop-hero#fptLiteBlock .fpt-slide {
		aspect-ratio: var( --arm, var( --ar, 3 / 1 ) );
	}
}
.vnt39-shop-hero#fptLiteBlock .fpt-slider {
	touch-action: pan-y;
}
.vnt39-shop-hero__link {
	display: block;
}
/* HERO EDGE ALIGNMENT (2026-10-03) — measured at 1366px: Hero 1200px wide
   (left edge 83) vs Category/Brand/Hot Sale 1240px (left edge 63) vs the
   sidebar+grid row 1270px (left edge 48): the banner sat visibly inset
   from every section under it. Two inherited sitewide rules cause it —
   `#fptLiteBlock{margin:20px}` (an ID selector, so it beats this file's
   own `.vnt39-shop-hero{margin:0 0 20px}`) and `.fpt-slider{max-width:
   1200px}`. Above 849px (below it components.css already uses 12px 0 and
   the hero is already full width) the side margin and the max-width are
   dropped so the banner shares Category/Brand/Hot Sale's exact edges; the
   top/bottom 20px gaps are unchanged. */
@media ( min-width: 850px ) {
	.vnt39-shop-hero#fptLiteBlock {
		margin-left: 0;
		margin-right: 0;
	}
	.vnt39-shop-hero#fptLiteBlock .fpt-slider {
		max-width: none;
	}
}
/* HERO DOTS ON THE BANNER (2026-10-03) — the dots used to sit in their own
   row under the banner (looked detached, +23px of height). Now overlaid at
   the bottom-centre INSIDE the banner: the dots leave the flow (absolute),
   so the block is exactly the slider's height and the gap to Category is
   unchanged. White dots + soft shadow read on both dark and light banners;
   the active one is a longer white pill. */
.vnt39-shop-hero#fptLiteBlock {
	position: relative;
}
#fptDots.vnt39-shop-hero__dots {
	position: absolute;
	left: 50%;
	bottom: 5px;
	z-index: 3;
	transform: translateX( -50% );
	display: flex;
	align-items: center;
	justify-content: center;
	gap: 7px;
	margin: 0;
	padding: 0;
	background: none;
}
@media ( max-width: 849px ) {
	#fptDots.vnt39-shop-hero__dots {
		bottom: 3px;
		gap: 5px;
	}
}
#fptDots.vnt39-shop-hero__dots button {
	/* min-height:2.5em / min-width:24px — Flatsome's own sitewide generic
	   `.button, button, ...` reset (unrelated to this slider) was winning
	   unopposed on these two properties since this rule never declared
	   them, inflating every dot into a ~39px-tall pill (confirmed live via
	   computed style) instead of the small circle the explicit 7px width/
	   height below was supposed to produce. Zeroing both here is what
	   actually lets width/height take effect. */
	min-height: 0;
	min-width: 0;
	width: 8px;
	height: 8px;
	border: 0;
	border-radius: 999px;
	padding: 0;
	background: rgba( 255, 255, 255, 0.75 );
	box-shadow: 0 0 3px rgba( 0, 0, 0, 0.45 );
	cursor: pointer;
	transition: background-color 0.2s ease, width 0.2s ease;
}
#fptDots.vnt39-shop-hero__dots button.active {
	width: 20px;
	background: #fff;
}
@media ( max-width: 849px ) {
	#fptDots.vnt39-shop-hero__dots button {
		width: 6px;
		height: 6px;
	}
	#fptDots.vnt39-shop-hero__dots button.active {
		width: 14px;
	}
}

/* ------------------------------------------------------------------ */
/* Shared section heading                                              */
/* ------------------------------------------------------------------ */
.vnt39-shop-categories__heading,
.vnt39-shop-brands__heading {
	margin: 0 0 14px;
	padding-left: 10px;
	border-left: 4px solid var( --vnt39-brand, #8f0b18 );
	font-size: 19px;
	font-weight: 800;
	color: #252a31;
	line-height: 1.2;
}

/* ------------------------------------------------------------------ */
/* "Mua sắm theo danh mục" — real CARDS, not an icon strip.             */
/* ------------------------------------------------------------------ */
.vnt39-shop-categories {
	margin: 0 0 24px;
}
/* REMOVE DEAD SPACE PASS (2026-10-02) — same real cause/fix already used
   for the Hero→Category gap: Flatsome's own `.col{padding-bottom:30px}`
   grid gutter on the one [col] this section's own [row][col span="12"]
   wrap renders into (inc/shop-page.php) was stacking with the 24px
   margin-bottom above to produce ~50px before Brand's heading. Scoped to
   only the Category-containing [col] via :has(), so Brand/Hero/mobile
   Trade-in's own [col] wrappers are untouched — leaves exactly this
   rule's own 24px, inside the 24–30px target. */
.col:has( > .col-inner > .vnt39-shop-categories ) {
	padding-bottom: 0;
}

/* REUSABLE CARD SHELL PASS (2026-10-02) — this is a SHELL, not a design
   built around today's temporary icon artwork. Every card (desktop/
   tablet row cell AND mobile 2x2-grid cell alike) shares this one
   fixed-dimension definition, so a 1-line vs. 2-line title can never
   make one card taller than its neighbor, and an admin swapping an
   icon/image later (any real PNG/WebP/JPG, transparent or not) never
   changes card size — only the image's OWN box (below) ever responds to
   what's actually uploaded, via object-fit:contain, never a crop. Width
   stays 100% of whatever container gives the card (the desktop carousel
   cell fixes that to 158px below; the mobile 2x2 grid cell sizes it
   instead) — only height and the two internal areas are fixed
   everywhere. CellphoneS-reference proportions: light neutral card, no
   border, generous radius, no "selected" look on a normal card. */
.vnt39-shop-categories__item {
	display: flex;
	flex-direction: column;
	width: 100%;
	height: 135px;
	border-radius: 19px;
	background: #f6f6f6;
	text-decoration: none;
	color: #252a31;
	overflow: hidden;
	transition: box-shadow 0.2s ease, transform 0.2s ease;
}
div.vnt39-shop-categories__item {
	cursor: default;
}
@media ( hover: hover ) and ( pointer: fine ) {
	a.vnt39-shop-categories__item:hover {
		box-shadow: 0 8px 18px rgba( 0, 0, 0, 0.1 );
		transform: translateY( -2px );
	}
}
a.vnt39-shop-categories__item:focus-visible {
	outline: 2px solid var( --vnt39-brand, #8f0b18 );
	outline-offset: 2px;
}
/* Image container — a neutral, un-colored box (no pink/brand-tinted
   background) so it reads correctly under ANY future upload, not just
   today's red circular placeholder artwork. The fallback SVG icon
   component itself (vnt39_home_category_icon(), PHP) is left exactly as
   it already is — this task does not redraw/recolor/resize it, only the
   container it sits inside. */
.vnt39-shop-categories__icon {
	flex: 0 0 80px;
	display: flex;
	align-items: center;
	justify-content: center;
	width: 100%;
	height: 80px;
}
/* A REAL uploaded image (wp_get_attachment_image()) — never the fallback
   SVG, which carries no <img> tag. max-width/max-height bound it inside
   the reserved area without ever cropping or distorting it, regardless
   of the uploaded asset's own dimensions; width/height:auto keeps its
   real aspect ratio. */
.vnt39-shop-categories__icon img {
	display: block;
	width: auto;
	height: auto;
	max-width: 85px;
	max-height: 70px;
	object-fit: contain;
	object-position: center;
}
.vnt39-shop-categories__icon .vnt39-icon {
	width: 34px;
	height: 34px;
	color: var( --vnt39-brand, #8f0b18 );
}
/* Fixed title area, up to 2 lines, vertically AND horizontally centered,
   3rd+ line (a title longer than 2 lines) ellipsized instead of growing
   the card — the specific mechanism that guarantees equal height
   regardless of title length or future content changes. */
.vnt39-shop-categories__title {
	box-sizing: border-box;
	flex: 1 1 auto;
	display: -webkit-box;
	-webkit-box-orient: vertical;
	-webkit-box-pack: center;
	-webkit-line-clamp: 2;
	overflow: hidden;
	/* 135px card − 80px fixed image area = 55px remaining; border-box
	   keeps the padding inside that 55px instead of adding to it, so the
	   card's own fixed height is never exceeded regardless of title
	   length (1 line, 2 lines, or a clamped 3rd). */
	min-height: 55px;
	padding: 2px 8px 6px;
	font-size: 14px;
	font-weight: 700;
	color: #252a31;
	text-align: center;
	line-height: 1.25;
}

/* ------------------------------------------------------------------ */
/* Desktop/tablet (>=768px) — ONE real Flickity row, every category its */
/* own cell, never grouped/paged, never wraps to a second row. Hidden   */
/* below 768px (mobile keeps the separate 2x2-page slider instead).    */
/* ------------------------------------------------------------------ */
.vnt39-shop-categories__row {
	display: none;
}
@media ( min-width: 768px ) {
	.vnt39-shop-categories {
		position: relative;
	}
	.vnt39-shop-categories__row {
		display: block;
		position: relative;
		/* FULL SECTION WIDTH PASS (2026-10-02) — no reserved side padding;
		   the arrows now overlay ON TOP of the first/last card (absolute,
		   inset — see below), exactly like the Hero banner's own arrows,
		   instead of living in a dedicated gutter that narrowed the
		   visible track and read as two separate square "ears". */
		padding: 0;
	}
	/* CONTAIN/REACHABILITY FIX (2026-10-02) — the 18px gap lives INSIDE the
	   cell's own border-box (padding-right), not as a CSS margin outside
	   it. Confirmed live via Flickity's own `inst.cells[i].size.outerWidth`
	   that its gap-aware layout math reads a plain `margin-right` as 0
	   (every cell measured exactly its bare width) — Flickity's
	   `contain:true` then under-counts the real track width and stops
	   clamping before the last card is actually fully in view. Folding the
	   gap into the cell's own border-box width makes Flickity's own
	   measurement already correct, with no further gap to miscount. The
	   inner card (.vnt39-shop-categories__item, width:100% from its shared
	   rule above) ends up exactly 140px either way. */
	/* !important — same specificity fight already found/fixed on the Brand
	   mobile slider: Flatsome's own `.flickity-slider>div:not(.col)` rule
	   (specificity 0,2,1) also sets `padding:0`, which otherwise beats
	   this single-class selector (0,1,0) and silently zeroes the gap
	   (confirmed live: computed padding-right read 0px without this). */
	.vnt39-shop-categories__row-cell {
		box-sizing: border-box !important;
		padding-right: 18px !important;
	}
	/* !important + 3-class selector — same fix as the Brand mobile slider:
	   Flatsome's own `.flickity-slider>div:not(.col){width:100%!important}`
	   (specificity 0,2,1) otherwise overrides this cell's own fixed width
	   (confirmed live on Brand; applied pre-emptively here). */
	.vnt39-shop-categories__row.flickity-enabled .vnt39-shop-categories__row-cell,
	.vnt39-shop-categories__row .vnt39-shop-categories__row-cell.vnt39-shop-categories__row-cell {
		width: 158px !important;
	}
	/* The spacer (see inc/shop-page.php) needs its own, wider width —
	   higher specificity (4 classes) to win over the real-card rule just
	   above. 220px was tuned against the live-measured ~203px shortfall
	   with headroom; never visible, never a real category, purely so
	   `contain:true` has enough extra track to scroll into that the real
	   last card (iPhone...Tin công nghệ) ends up fully in view. */
	.vnt39-shop-categories__row .vnt39-shop-categories__row-cell.vnt39-shop-categories__row-cell--spacer.vnt39-shop-categories__row-cell--spacer {
		width: 220px !important;
		padding-right: 0 !important;
		pointer-events: none;
	}
	/* Dots — same lazyFlickity two-phase-init quirk already found/fixed on
	   the Brand mobile slider: the loader's own construction pass creates
	   the page-dots element from the bare `data-flickity-options="{}"`
	   attribute before this file's `pageDots:false` option merge applies,
	   so the dots DOM exists even though pageDots:false is correct on the
	   live instance (confirmed). Not wanted here at all (task spec has no
	   dots for the desktop/tablet row), so hidden outright. */
	.vnt39-shop-categories__row .flickity-page-dots {
		display: none !important;
	}
	/* ARROW — COPIED FROM THE HERO BANNER'S OWN ARROWS (components.css,
	   #fptLiteBlock .fpt-nav), not a new style: round, white, translucent,
	   absolutely positioned INSIDE the track (inset from the edge, no
	   dedicated gutter/square "ear" reserving layout width — the previous
	   bordered/boxed version did exactly that, confirmed live). Only
	   difference from Hero's own arrows: this one rests at opacity .25
	   instead of 0 (task's own explicit number — "vẫn discoverable", not
	   fully invisible at rest), everything else (size, shape, inset,
	   hover-reveal mechanism) matches. */
	/* VERTICAL-CENTER FIX (2026-10-02) — Flickity's own PrevNextButton CSS
	   (compiled into Flatsome's bundle) already claims the `transform`
	   property on `.flickity-prev-next-button` for its own purpose (a
	   horizontal `translateX()` nudge that centers the arrow SVG inside
	   the button), confirmed live via getComputedStyle: the applied
	   transform was `matrix(1,0,0,1,-7.2,0)` — a pure X-shift, never the
	   `translateY(-50%)` this rule asked for, because that other rule won
	   the cascade on the `transform` property specifically. Centering via
	   `margin-top` instead avoids fighting over the same property —
	   `top:50%` + `margin-top: -18px` (half of the 36px height below)
	   centers the button with no `transform` involved on our side at all,
	   leaving Flickity's own icon-nudge transform completely intact.
	   min-height/min-width lock the button at exactly 36px — Flatsome's
	   generic `button{min-height:2.5em}` reset otherwise wins (min-height
	   always overrides a smaller `height`, independent of specificity),
	   which was inflating the real height to ~38.8px and throwing off the
	   -18px math. */
	.vnt39-shop-categories__row .flickity-prev-next-button {
		position: absolute;
		top: 50%;
		z-index: 2;
		display: inline-flex;
		align-items: center;
		justify-content: center;
		width: 36px;
		height: 36px;
		min-width: 36px !important;
		min-height: 36px !important;
		margin: 0;
		margin-top: -18px !important;
		border: 0;
		border-radius: 999px;
		background: rgba( 255, 255, 255, 0.9 );
		opacity: 0.25 !important;
		transition: opacity 0.2s ease, background-color 0.2s ease;
	}
	.vnt39-shop-categories__row:hover .flickity-prev-next-button {
		opacity: 1 !important;
	}
	.vnt39-shop-categories__row .flickity-prev-next-button.previous {
		left: 12px;
	}
	.vnt39-shop-categories__row .flickity-prev-next-button.next {
		right: 12px;
	}
	.vnt39-shop-categories__row .flickity-prev-next-button .arrow {
		fill: var( --vnt39-brand, #8f0b18 );
	}
	.vnt39-shop-categories__row .flickity-prev-next-button:disabled {
		opacity: 0.12 !important;
		cursor: default;
	}
}
/* ARROW VISIBILITY (2026-10-03, per task): on devices that really hover
   (mouse), the arrows are hidden at rest and fade in when the pointer is
   anywhere over the WHOLE "Mua sắm theo danh mục" section (heading
   included — previously only the card row itself revealed them, and they
   rested at 25%); leaving hides them again; a disabled end arrow stays
   dimmed. Touch devices (no hover) keep the existing always-visible resting
   state, so nothing depends on hover to use the carousel. */
@media ( min-width: 768px ) and ( hover: hover ) and ( pointer: fine ) {
	.vnt39-shop-categories .vnt39-shop-categories__row .flickity-prev-next-button:not( :disabled ) {
		opacity: 0 !important;
	}
	.vnt39-shop-categories:hover .vnt39-shop-categories__row .flickity-prev-next-button:not( :disabled ) {
		opacity: 1 !important;
	}
}

/* Mobile slider — hidden until the <=767px tier turns it on. */
.vnt39-shop-categories__slider {
	display: none;
}
.vnt39-shop-categories__page-grid {
	display: grid;
	grid-template-columns: repeat( 2, 1fr );
	grid-template-rows: repeat( 2, 1fr );
	gap: 16px;
}

/* ------------------------------------------------------------------ */
/* "Thương hiệu nổi bật" — real product_brand terms. CellphoneS-reference  */
/* compact one-row carousel (2026-10-02 pass) — same desktop-row +         */
/* mobile-slider Flickity architecture as the Category carousel, but a     */
/* fully separate/duplicated CSS block (never shared selectors with it) so */
/* a future change to one can never silently move the other — Category is */
/* explicitly LOCKED. No uploaded logo on any term today, so cards render  */
/* as bold wordmark tiles; a real logo image an admin uploads later renders*/
/* inside this exact same fixed box via object-fit:contain (below),       */
/* never changing the card's own size — this is a reusable SHELL, not a    */
/* design built around today's text-only placeholder content.             */
.vnt39-shop-brands {
	margin: 0 0 20px;
}
/* The actual compact visual card — fixed size on every tier (only the      */
/* exact px values differ between desktop/tablet and mobile below), never  */
/* stretched to fill its container. This is what was broken before: the    */
/* old rule used `flex:1 1 110px` inside a `flex-wrap:wrap` row, so a       */
/* handful of long-name/trailing terms (MSI, Microsoft, Thirdwave) both     */
/* grew to fill leftover row width AND wrapped once they ran out of room.  */
.vnt39-shop-brands__item {
	display: flex;
	align-items: center;
	justify-content: center;
	box-sizing: border-box;
	width: 100%;
	height: 58px;
	padding: 8px 14px;
	border: 1px solid #e5e7eb;
	border-radius: 9px;
	background: #fff;
	text-decoration: none;
	transition: border-color 0.2s ease, box-shadow 0.2s ease;
}
@media ( hover: hover ) and ( pointer: fine ) {
	.vnt39-shop-brands__item:hover {
		border-color: var( --vnt39-brand, #8f0b18 );
		box-shadow: 0 6px 16px rgba( 0, 0, 0, 0.08 );
	}
}
.vnt39-shop-brands__logo {
	width: auto;
	height: auto;
	max-width: 100%;
	max-height: 100%;
	object-fit: contain; /* never cropped; the card's own 8px/14px padding is the logo's safe margin */
}
/* Optional per-brand background (term meta vnt39_brand_bg_id, set inline on the
   card): cover-fits inside the SAME border/radius/size; no image = the white
   background above. */
.vnt39-shop-brands__item--has-bg {
	background-color: #fff;
	background-position: center;
	background-repeat: no-repeat;
	background-size: cover;
}
.vnt39-shop-brands__word {
	font-size: 14px;
	font-weight: 800;
	color: #344054;
	letter-spacing: 0.02em;
	text-transform: uppercase;
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
}

/* ------------------------------------------------------------------ */
/* Desktop/tablet (>=768px) — ONE real Flickity row, every brand its own */
/* fixed-width cell (never grouped/paged, never wraps, never stretches). */
/* Hidden below 768px (mobile keeps the separate slider instead).        */
/* ------------------------------------------------------------------ */
.vnt39-shop-brands__row {
	display: none;
}
@media ( min-width: 768px ) {
	.vnt39-shop-brands {
		position: relative;
	}
	.vnt39-shop-brands__row {
		display: block;
		position: relative;
		padding: 0;
	}
	/* PRE-INIT LAYOUT-SHIFT FIX (2026-10-03) — measured live: both rows
	   (this one and the Category row) only become a one-row Flickity track
	   once their lazy Flickity chunk runs (it waits for the section to
	   actually enter the viewport). Until then their cells are plain
	   blocks and stack vertically — Category 1387px tall, Brand 791px —
	   then collapse to 172px/95px on init, shoving everything below
	   (Hot Sale, the whole shop) up by ~1900px: a huge layout shift, and
	   a broken-looking page if the Flickity chunk is slow/blocked. Until
	   `flickity-enabled` lands, lay the cells out as the SAME single
	   non-wrapping row Flickity builds, clipped. Post-init visuals are
	   untouched (`:not(.flickity-enabled)` stops matching). */
	.vnt39-shop-categories__row:not( .flickity-enabled ),
	.vnt39-shop-brands__row:not( .flickity-enabled ) {
		display: flex;
		flex-wrap: nowrap;
		overflow: hidden;
	}
	.vnt39-shop-categories__row:not( .flickity-enabled ) > *,
	.vnt39-shop-brands__row:not( .flickity-enabled ) > * {
		flex: none;
	}
	/* Gap folded into the cell's own border-box (padding-right), not a
	   plain CSS margin — same reason/fix already proven on the Category
	   carousel: a margin-right gap isn't counted by Flickity's own cell
	   measurement, which matters once autoPlay/wrapAround is driving
	   continuous motion across the full set. !important — Flatsome's own
	   `.flickity-slider>div:not(.col){width:100%!important;padding:0}`
	   (specificity 0,2,1 + !important) otherwise wins and silently zeroes
	   this. */
	.vnt39-shop-brands__row-cell {
		box-sizing: border-box !important;
		padding-right: 14px !important;
	}
	.vnt39-shop-brands__row.flickity-enabled .vnt39-shop-brands__row-cell,
	.vnt39-shop-brands__row .vnt39-shop-brands__row-cell.vnt39-shop-brands__row-cell {
		width: 164px !important;
	}
	.vnt39-shop-brands__row .flickity-page-dots {
		display: none !important;
	}
	/* ARROW — deliberately DUPLICATED from the LOCKED Category carousel's
	   own arrow rule (.vnt39-shop-categories__row .flickity-prev-next-
	   button), not shared, so this section can never accidentally change
	   Category's arrows (or vice versa) just by editing one of them —
	   "Category đã LOCK, tuyệt đối không đụng" is the explicit boundary
	   this task was given. Same round/white/overlay/opacity-fade
	   mechanism, including the margin-top vertical-center fix (not
	   `transform`, which Flickity's own PrevNextButton CSS already claims
	   for its icon's horizontal nudge — see Category's own fix notes for
	   the full diagnosis) and the min-height/min-width lock against
	   Flatsome's generic `button{min-height:2.5em}` reset. */
	.vnt39-shop-brands__row .flickity-prev-next-button {
		position: absolute;
		top: 50%;
		z-index: 2;
		display: inline-flex;
		align-items: center;
		justify-content: center;
		width: 36px;
		height: 36px;
		min-width: 36px !important;
		min-height: 36px !important;
		margin: 0;
		margin-top: -18px !important;
		border: 0;
		border-radius: 999px;
		background: rgba( 255, 255, 255, 0.9 );
		opacity: 0.25 !important;
		transition: opacity 0.2s ease, background-color 0.2s ease;
	}
	.vnt39-shop-brands__row:hover .flickity-prev-next-button {
		opacity: 1 !important;
	}
	.vnt39-shop-brands__row .flickity-prev-next-button.previous {
		left: 12px;
	}
	.vnt39-shop-brands__row .flickity-prev-next-button.next {
		right: 12px;
	}
	.vnt39-shop-brands__row .flickity-prev-next-button .arrow {
		fill: var( --vnt39-brand, #8f0b18 );
	}
	.vnt39-shop-brands__row .flickity-prev-next-button:disabled {
		opacity: 0.12 !important;
		cursor: default;
	}
}

/* Mobile slider — one row, ~3-4 visible depending on real viewport width */
/* (fixed-px cells, not a percentage, so the visible count simply falls   */
/* out of the math instead of being hard-coded), hidden until <=767px.    */
.vnt39-shop-brands__slider {
	display: none;
}
/* BRAND CARD GAP (2026-10-03) — the 10px padding-right below was meant as
   the gap between cards but never applied: Flatsome's own
   `.flickity-slider>div:not(.col){padding:0}` zeroes it (same fight already
   documented + fixed on the desktop row-cell and the Category cell), so the
   cards sat border-to-border like one bar. !important restores it; the cell
   width below grew 112 -> 121px (9px gap + the same 112px card) so the card
   itself doesn't shrink. */
.vnt39-shop-brands__slide {
	box-sizing: border-box !important;
	padding-right: 9px !important;
}
/* Dots hidden here regardless of the `pageDots:false` JS option — the
   lazyFlickity chunk-loader's own construction pass creates the page-dots
   element once from the element's `data-flickity-options` attribute
   before shop-page.js's own activeOptions merge takes effect, so the dots
   DOM exists even though the live instance's own `.options.pageDots`
   correctly reads false afterward (confirmed live). Not required by the
   mobile brand spec ("dots not required") and MASTER's own brand row
   never shows dots, so hiding here is the simpler, robust fix instead of
   fighting Flickity's two-phase init timing. */
.vnt39-shop-brands__slider .flickity-page-dots {
	display: none !important;
}
/* !important + 3-class selector — Flatsome's own flickity CSS has a rule,
   `.flickity-slider > div:not(.col){width:100%!important}`
   (specificity 0,2,1), that also matches every plain (non-.col) div cell;
   confirmed live via matched-rules inspection that Flatsome's rule ALSO
   carries !important, so a plain `!important` single-class override here
   still loses on specificity. This 3-class selector (0,3,0) unambiguously
   outranks it. */
.vnt39-shop-brands__slider.flickity-enabled .vnt39-shop-brands__slide,
.vnt39-shop-brands__slider .vnt39-shop-brands__slide.vnt39-shop-brands__slide {
	width: 121px !important;
}
.vnt39-shop-brands__slide .vnt39-shop-brands__item {
	width: 100%;
	height: 50px;
}

/* ------------------------------------------------------------------ */
/* "Mobile VNT39 – [câu động]" — LAYOUT PASS (2026-10-02): now its own    */
/* second row inside Hot Sale's header, centered across the FULL WIDTH    */
/* of the red box (not under the title on the left — see this element's  */
/* `.vnt39-shop-hotsale__rotating-row` wrapper, above, which does the     */
/* actual centering via `text-align:center`; `inline-flex` here is what   */
/* lets this component size to its own content and be centered by that   */
/* wrapper instead of stretching to the row's full width). The animation  */
/* rules below (slide/fade/reduced-motion) are UNCHANGED — only this      */
/* component's own display/margin/colors are new, to fit a centered row   */
/* inside a colored header instead of a left-aligned line on a plain      */
/* white page background. The stage's own width/height are still set      */
/* inline by JS (assets/js/shop-page.js) from the real rendered phrases   */
/* BEFORE any rotation starts, so swapping a short phrase for a long one  */
/* never shifts layout (or the centering — the wrapper re-centers         */
/* automatically around whatever width the stage currently measures).     */
/* ------------------------------------------------------------------ */
/* `align-items:center`, not `baseline` (2026-10-02) — real feedback was
   the rotating phrase sitting visibly ~6-7px higher than the static
   "Mobile VNT39 –" prefix. Root cause confirmed live: `__stage` only
   holds its text via an absolutely-positioned `__item` child (needed so
   swapping phrases never reflows the stage itself), and an absolutely
   positioned descendant does NOT contribute to its container's baseline
   per the flexbox spec — so `__stage`'s "baseline" synthesizes to its
   own empty margin-bottom edge, which sits ~6.5px above where the real
   (absolutely-positioned) text actually renders. `baseline` therefore
   aligned the prefix's TRUE text baseline against the stage's FAKE one.
   Both the prefix and the stage/active-item are the exact same measured
   height (24.6953px, confirmed live) once the stage is sized, so
   `center` lines them up to a sub-pixel (verified 0.15px) without
   depending on a baseline the stage structurally can't produce. */
.vnt39-shop-rotating-text {
	display: inline-flex;
	align-items: center;
	gap: 6px;
	margin: 0;
	font-size: 19px;
	font-weight: 700;
	line-height: 1.3;
}
.vnt39-shop-rotating-text__prefix {
	color: rgba( 255, 255, 255, 0.85 );
	white-space: nowrap;
}
.vnt39-shop-rotating-text__stage {
	position: relative;
	display: inline-block;
	overflow: hidden;
	vertical-align: bottom;
}
.vnt39-shop-rotating-text__item {
	position: absolute;
	left: 0;
	top: 0;
	white-space: nowrap;
	/* Warm gold — distinct from both the solid-white title above it and
	   the red/pink gradient behind it, so the "this part changes" accent
	   reads clearly (the same role brand-red played against the old
	   white background, before this moved onto a colored header). */
	color: #ffd166;
	opacity: 1;
	transform: translateY( 0 );
	transition: transform 380ms cubic-bezier( 0.22, 0.68, 0.32, 1 ), opacity 380ms ease;
}
.vnt39-shop-rotating-text__item[hidden] {
	display: none;
}
/* Offscreen measuring clone (assets/js/shop-page.js) — invisible but
   still a real, laid-out box (visibility:hidden, unlike display:none,
   still reports real dimensions via getBoundingClientRect), so it never
   visually doubles up with whichever real item is currently showing. */
.vnt39-shop-rotating-text__item--measure {
	visibility: hidden;
	pointer-events: none;
}
/* Outgoing phrase — slides UP and fades out (never left/right, never a
   marquee — task's own explicit rule). */
.vnt39-shop-rotating-text__item.is-leaving {
	opacity: 0;
	transform: translateY( -100% );
}
/* Incoming phrase's STARTING point only — `transition:none` so this is a
   silent, instant jump to "below, invisible" before the next animation
   frame switches to .is-active (which re-enables the transition above
   and animates it sliding up into place while fading in). */
.vnt39-shop-rotating-text__item.is-entering {
	opacity: 0;
	transform: translateY( 100% );
	transition: none;
}
/* prefers-reduced-motion — belt-and-suspenders: assets/js/shop-page.js
   already never starts the rotation timer at all when this is set (the
   single first phrase just stays visible, statically, forever — task's
   own "chỉ hiển thị text tĩnh" rule), so these rules are only a defensive
   fallback in case the class ever gets added by some other path. */
@media ( prefers-reduced-motion: reduce ) {
	.vnt39-shop-rotating-text__item {
		transition: none !important;
	}
}

@media ( max-width: 767px ) {
	.vnt39-shop-rotating-text {
		font-size: 14px;
	}
}
/* Narrow phones — one more size step down so "Mobile VNT39 –" plus a
   longer real phrase never wraps to a 2nd line or overflows (task rule:
   "vẫn ưu tiên 1 dòng... không overflow"). */
@media ( max-width: 374px ) {
	.vnt39-shop-rotating-text {
		font-size: 12px;
		gap: 4px;
	}
}

/* ------------------------------------------------------------------ */
/* "HOT SALE CUỐI TUẦN" — real WooCommerce products via Flatsome's own   */
/* native [ux_products type="slider"] (real Flickity under the hood,     */
/* same engine as every other product row on this site — not a hand-     */
/* rolled carousel). This file only styles the section frame/header/     */
/* countdown and re-skins the native slider's own arrows to match the    */
/* already-LOCKED Hero/Category arrow language — the product CARD itself */
/* (content-product.php, via style="default") is never touched here.     */
/* ------------------------------------------------------------------ */
.vnt39-shop-hotsale {
	/* VERTICAL SPACING PASS (2026-10-02) — target total gap before "CỬA
	   HÀNG" is 35–45px desktop / 30–36px tablet (768–1023, see that
	   tier's own override below) / 24–30px mobile (<=767, see that
	   tier's own override below). Flatsome's own GLOBAL page-title
	   wrapper (`.page-title-inner`, used on every WooCommerce archive
	   page sitewide — not something this section introduces or should
	   touch, confirmed live via a DOM walk from the real H1) always adds
	   a fixed 20px padding-top of its own before the H1, on top of
	   whatever this section's own margin-bottom is. This file's own
	   `.col{padding-bottom:30px}` gutter is separately zeroed for this
	   section's own [col] wrapper below (same `:has()` technique already
	   used for Hero/Category — see the Brand→Hot Sale gap note further
	   down for why the OTHER side needed the same fix), so the ONLY two
	   contributors left are this 20px fixed baseline and this margin —
	   20px here lands the real total at 40px (desktop), inside target.
	   No negative margins anywhere — the task explicitly asked for the
	   real sources to be found and the numbers to be derived from them,
	   not patched. */
	margin: 0 0 20px;
	padding: 20px 30px 22px;
	border-radius: 19px;
	background: linear-gradient( 135deg, #c8102e 0%, #e23a52 55%, #ff6f7d 100% );
	position: relative;
	overflow: hidden;
}
/* BRAND → HOT SALE GAP (2026-10-02) — the real source of the large gap
   real screenshots showed: Flatsome's own `.col{padding-bottom:30px}`
   gutter on the [col] wrapping Brand (inc/shop-page.php wraps every
   top-level section in its own [row][col span="12"]) was stacking on
   top of Brand's own 20px margin-bottom AND the old standalone
   rotating-text section's own margin/gutter in between — zeroing just
   this one wrapper [col]'s generic Flatsome gutter (not any of Brand's
   OWN styles — Brand itself stays exactly as LOCKED) leaves Brand's own
   20px margin-bottom as the entire gap, inside the task's 20–30px
   target. Same proven technique already used for Hero→Category. */
.col:has( > .col-inner > .vnt39-shop-brands ) {
	padding-bottom: 0;
}
/* HOT SALE → "CỬA HÀNG" GAP — same fix, other side: without this,
   Hot Sale's own 40px margin-bottom (above) would stack with this
   SAME generic 30px gutter on Hot Sale's own [col] wrapper, overshooting
   the task's 35–45px desktop target by 30px. */
.col:has( > .col-inner > .vnt39-shop-hotsale ) {
	padding-bottom: 0;
}
/* The REAL remaining source of the gap, confirmed live via DOM walk: the
   mobile-only Trade-in card (vnt39_shop_page_render_tradein_card(
   'mobile-top' ), rendered immediately after Hot Sale) is itself
   `display:none` at desktop/tablet width, but its own [col] wrapper is
   NOT — so its 30px gutter was silently still consuming vertical space
   between Hot Sale and "CỬA HÀNG" even though nothing visible sits
   there. Same zero-the-generic-gutter fix, not a change to the Trade-in
   card's own content/visibility logic. */
.col:has( > .col-inner > .vnt39-shop-tradein ) {
	padding-bottom: 0;
}
/* ONE-ROW GRID PASS (2026-10-02) — all 3 clusters (title/rotating-text/
   countdown) on a single row now, not stacked across two — real CSS Grid
   (`1fr auto 1fr`), deliberately NOT `justify-content:space-between` on a
   flex row: with space-between, the center item's position depends on
   how wide its TWO NEIGHBORS are (it sits in whatever leftover gap is
   between them, which shifts off-true-center the instant title/countdown
   widths differ) — real CSS Grid instead gives the center column a fixed
   geometric position (exactly between the two equal `1fr` side columns,
   which split the remaining width evenly regardless of their own
   content's width), so `justify-self:center` on the rotating-text column
   centers it on the HEADER's own true horizontal center, independent of
   title/countdown width. Explicit `grid-column` on each of the 3 direct
   children (not relying on DOM order) is what keeps the countdown in
   column 3 even when rotating-text is absent (disabled/< 2 phrases) and
   the grid would otherwise only have 2 children. 20px margin-bottom here
   is the "header → product carousel" gap (18–22px target). */
.vnt39-shop-hotsale__header {
	display: grid;
	grid-template-columns: 1fr auto 1fr;
	/* `baseline`, not `center` (2026-10-02) — real feedback was that the
	   title and the rotating text looked vertically "off" from each
	   other despite both already sitting dead-center of the same grid
	   row (confirmed via getBoundingClientRect: identical box centers).
	   The mismatch is optical, not geometric — 27px bold title text and
	   19px rotating text have different cap-height/line-height ratios,
	   so centering their BOXES doesn't align their GLYPHS. Aligning by
	   text baseline instead (the standard fix for mixed-size text in one
	   row) puts the actual letters on the same line; verified live that
	   the countdown's own boxed digits still look correctly centered
	   next to it (its internal `.vnt39-countdown__row` keeps its own
	   `align-items:center`, so this only changes where that whole block
	   sits against the other two columns, not its own internal layout). */
	align-items: baseline;
	gap: 10px 20px;
	margin-bottom: 20px;
}
.vnt39-shop-hotsale__header-right {
	grid-column: 3;
	justify-self: end;
}
.vnt39-shop-hotsale__rotating-row {
	grid-column: 2;
	justify-self: center;
	text-align: center;
}
.vnt39-shop-hotsale__title {
	grid-column: 1;
	justify-self: start;
	margin: 0;
	font-size: 27px;
	font-weight: 800;
	color: #fff;
	line-height: 1.25;
}
/* Countdown — same shared [data-unit] markup/JS already driving the
   single-product badge (inc/product.php, assets/js/components.js) and the
   homepage Autumn Deal digit boxes (assets/css/homepage.css); scoped
   re-skin only, zero new JS. The CSS custom properties below are the same
   names the Autumn Deal block already established for this exact
   component, so this is a proven pattern, not a new one. */
.vnt39-shop-hotsale .vnt39-countdown--hotsale {
	--countdown-number-bg: rgba( 255, 255, 255, 0.92 );
	--countdown-number-color: #c8102e;
	--countdown-sep-color: #fff;
	--countdown-number-size-desktop: 16px;
	display: inline-flex;
}
.vnt39-shop-hotsale .vnt39-countdown__row {
	display: flex;
	align-items: center;
	gap: 8px;
}
.vnt39-shop-hotsale .vnt39-countdown__prefix {
	font-size: 17px;
	font-weight: 700;
	color: #fff;
	white-space: nowrap;
}
.vnt39-shop-hotsale .vnt39-countdown__digits {
	display: flex;
	align-items: center;
	gap: 4px;
}
.vnt39-shop-hotsale .vnt39-countdown__box {
	min-width: 34px;
	padding: 6px 7px;
	border-radius: 6px;
	background: var( --countdown-number-bg );
	color: var( --countdown-number-color );
	font-size: var( --countdown-number-size-desktop );
	font-weight: 800;
	font-variant-numeric: tabular-nums;
	text-align: center;
	line-height: 1.3;
	box-sizing: border-box;
}
.vnt39-shop-hotsale .vnt39-countdown__box[hidden] {
	display: none;
}
.vnt39-shop-hotsale .vnt39-countdown__sep {
	font-size: var( --countdown-number-size-desktop );
	font-weight: 800;
	color: var( --countdown-sep-color );
}
.vnt39-shop-hotsale .vnt39-countdown__sep[hidden] {
	display: none;
}
/* Native Flatsome slider row — full width, no side padding so the arrows
   below can overlay flush with the section's own edges (same "no
   dedicated gutter" rule already used for Category/Brand). */
.vnt39-shop-hotsale__row {
	position: relative;
	margin: 0 -4px;
}
.vnt39-shop-hotsale__row .row-slider {
	padding: 0 4px;
}
.vnt39-shop-hotsale__row .flickity-page-dots {
	display: none !important;
}
/* ARROW — deliberately DUPLICATED from the LOCKED Category carousel's own
   arrow rule (not shared), with EVERY value forced `!important` AND every
   selector boosted to 4 classes (`.vnt39-shop-hotsale.vnt39-shop-hotsale
   .vnt39-shop-hotsale__row .flickity-prev-next-button...` pattern below):
   unlike the Category/Brand carousels (hand-built Flickity, no competing
   CSS), this section uses Flatsome's own NATIVE slider, which ships its
   own `.slider-nav-reveal .flickity-prev-next-button`/`...next`/
   `...previous` skin (height:100% — a tall vertical bar, not a circle;
   top:-2px; width:30px; background-color:rgba(0,0,0,.3); its own
   left/right/transform on .next/.previous) at up to 3 classes of
   specificity. A plain 2-3 class `!important` override here TIES that on
   raw specificity, which a live computed-style check confirmed resolves
   unpredictably per property depending on stylesheet load order (right/
   background lost even with !important; width/height/opacity won) —
   never something to depend on. Repeating `.vnt39-shop-hotsale` as its
   own ancestor class (harmless — it already wraps everything here) pushes
   every one of this block's selectors to 4+ classes, unambiguously ahead
   of Flatsome's 2-3, independent of load order. */
.vnt39-shop-hotsale.vnt39-shop-hotsale .vnt39-shop-hotsale__row .flickity-prev-next-button {
	position: absolute !important;
	top: 50% !important;
	bottom: auto !important;
	z-index: 2;
	display: inline-flex !important;
	align-items: center;
	justify-content: center;
	width: 36px !important;
	height: 36px !important;
	min-width: 36px !important;
	min-height: 36px !important;
	margin: 0 !important;
	margin-top: -18px !important;
	border: 0;
	border-radius: 999px !important;
	background: rgba( 255, 255, 255, 0.9 ) !important;
	opacity: 0.25 !important;
	transform: none !important;
	transition: opacity 0.2s ease, background-color 0.2s ease;
}
.vnt39-shop-hotsale.vnt39-shop-hotsale .vnt39-shop-hotsale__row:hover .flickity-prev-next-button {
	opacity: 1 !important;
}
.vnt39-shop-hotsale.vnt39-shop-hotsale .vnt39-shop-hotsale__row .flickity-prev-next-button.previous {
	left: 8px !important;
	right: auto !important;
}
.vnt39-shop-hotsale.vnt39-shop-hotsale .vnt39-shop-hotsale__row .flickity-prev-next-button.next {
	left: auto !important;
	right: 8px !important;
}
.vnt39-shop-hotsale.vnt39-shop-hotsale .vnt39-shop-hotsale__row .flickity-prev-next-button .arrow,
.vnt39-shop-hotsale.vnt39-shop-hotsale .vnt39-shop-hotsale__row .flickity-prev-next-button svg {
	fill: #c8102e !important;
}
.vnt39-shop-hotsale.vnt39-shop-hotsale .vnt39-shop-hotsale__row .flickity-prev-next-button:disabled {
	opacity: 0.12 !important;
	cursor: default;
}

@media ( max-width: 767px ) {
	.vnt39-shop-hotsale {
		margin-bottom: 7px;
		padding: 16px 14px 18px;
		border-radius: 16px;
	}
	/* MOBILE HEADER (2026-10-03, per user screenshot MASTER) — ROW 1: title
	   left + the moving phrase right on ONE line; ROW 2: countdown centered
	   across the whole box. The static "Mobile VNT39 –" prefix is dropped on
	   mobile only (display:none — it is still in the DOM, desktop/tablet are
	   untouched, and the phrase list/animation/JS are the same data). The
	   grid is 1fr + auto so the title takes whatever the phrase doesn't
	   need and can wrap rather than overflow; the countdown spans both
	   columns. Phrase items are right-anchored (not left:0) so whichever
	   phrase is showing hugs the box's right edge instead of floating at the
	   left of the JS-locked widest-phrase width. */
	.vnt39-shop-hotsale__header {
		display: grid;
		grid-template-columns: minmax( 0, 1fr ) auto;
		align-items: center;
		gap: 8px 10px;
		margin-bottom: 12px;
	}
	.vnt39-shop-hotsale__title {
		grid-column: 1;
		grid-row: 1;
		justify-self: start;
		min-width: 0;
		/* 15px, not 16: measured at 390px — header content box 352px, the
		   JS-locked widest-phrase stage 151px + 10px gap leaves 191px, and
		   "🔥 HOT SALE CUỐI TUẦN" is 195px wide at 16px (it wrapped to 2
		   lines) but 183px at 15px. */
		font-size: 15px;
	}
	.vnt39-shop-hotsale__rotating-row {
		grid-column: 2;
		grid-row: 1;
		justify-self: end;
		max-width: 60vw;
	}
	.vnt39-shop-hotsale__header-right {
		grid-column: 1 / -1;
		grid-row: 2;
		justify-self: center;
	}
	.vnt39-shop-hotsale .vnt39-shop-rotating-text {
		font-size: 12px;
	}
	.vnt39-shop-hotsale .vnt39-shop-rotating-text__prefix {
		display: none;
	}
	.vnt39-shop-hotsale .vnt39-shop-rotating-text__stage {
		max-width: 100%;
	}
	.vnt39-shop-hotsale .vnt39-shop-rotating-text__item {
		left: auto;
		right: 0;
		text-align: right;
	}
	.vnt39-shop-hotsale .vnt39-countdown--hotsale {
		--countdown-number-size-desktop: 12.5px;
	}
	.vnt39-shop-hotsale .vnt39-countdown__box {
		min-width: 26px;
		padding: 4px 5px;
	}
	.vnt39-shop-hotsale .vnt39-countdown__prefix {
		font-size: 12px;
	}

	/* EQUAL-HEIGHT CARDS (mobile Hot Sale only). Measured live at 390px
	   with 7 different real on-sale products: card heights 390/412/428px —
	   a 1-line vs 2-line title (16 vs 31px) and a non-square product photo
	   (155 vs 178px tall image box) were the two sources. Flickity absolutely
	   positions every cell and only sizes the VIEWPORT to the tallest one, so
	   each cell kept its own natural height and the card bottoms zig-zagged.
	   Fix, no fixed heights: (1) the image area is a 1:1 box (the product
	   photo is centred inside, not cropped), (2) the title reserves two
	   lines, (3) the card is a flex column whose promo pills sit at the
	   bottom (`margin-top:auto`), so image / freeship / title / price /
	   saving / countdown line up across cards, and (4) every card gets
	   `min-height: var(--vnt39-hs-card-h)` — the tallest card's NATURAL
	   height, measured by assets/js/shop-page.js (equalizeHotsaleCards) and
	   re-measured on resize/fonts/Flickity-ready. A pure-CSS
	   `height:100%` stretch was tried first and rejected: Flickity measures
	   cells to size its viewport, and a percentage height there collapsed
	   the viewport to 10px (measured). Scoped to .vnt39-shop-hotsale —
	   Product Card elsewhere (Search/Category/Home/Related) is untouched. */
	
	
	
	
	
	
	
}
/* Narrow phones (<=389px, incl. 375px): the 390px sizes above leave only
   ~125px for the phrase beside the title, so both step down one notch. */
@media ( max-width: 389px ) {
	.vnt39-shop-hotsale__title {
		font-size: 14px;
	}
	.vnt39-shop-hotsale .vnt39-shop-rotating-text {
		font-size: 11px;
	}
}

/* ------------------------------------------------------------------ */
/* Two-column Shop area — real Flatsome `.col.large-3`/`.col.large-9`   */
/* grid classes (same ones Flatsome's own category-right-sidebar.php    */
/* uses), reordered in source (sidebar first) to sit on the LEFT. Those */
/* classes alone are not responsive — collapsed to single column below */
/* 1024px here (the same tablet/mobile boundary the rest of this file   */
/* already uses).                                                       */
/* ------------------------------------------------------------------ */
.vnt39-shop-page-row {
	margin-top: 8px;
}
@media ( min-width: 1024px ) {
	.vnt39-shop-page-row {
		/* GUTTER FIX (2026-10-02) — this `gap` used to be 24px, stacked
		   ON TOP of Flatsome's own `.col{padding-left:30px}` gutter that
		   already sits between the sidebar and main columns (confirmed:
		   Flatsome's core CSS gives every `.col` a 30px left padding,
		   which is what actually separates `large-3` from `large-9`
		   here — this `gap` was a second, redundant gutter). Real-screen
		   feedback flagged the combined ~54px as visibly too wide versus
		   a normal single Flatsome gutter, so this is now 0 — the 30px
		   Flatsome padding alone is the gap. `flex-wrap:nowrap` stays
		   (harmless once the gap is 0, and keeps the two columns from
		   ever wrapping onto separate lines from sub-pixel rounding,
		   since 317.5px + 952.5px = 1270px is an exact, zero-margin fit
		   against the row's own width). */
		/* !important — Flatsome's own `.row` rule sets this via the
		   `flex-flow:row wrap` SHORTHAND (flatsome.css), which resolves
		   to the same longhand `flex-wrap` property my own declaration
		   targets; at equal specificity (.row vs .vnt39-shop-page-row
		   are both single-class selectors) that stylesheet's load order
		   wins over source order here, confirmed live via computed
		   style still reading "wrap" without this. */
		flex-wrap: nowrap !important;
		gap: 0;
	}
}
@media ( max-width: 1023px ) {
	.vnt39-shop-sidebar {
		display: none !important;
	}
	.vnt39-shop-main-col {
		flex-basis: 100% !important;
		max-width: 100% !important;
	}
}

/* MOBILE VERTICAL SPACING (<=767px, 2026-10-03) — measured live at 393px,
   top to bottom, every real source of the dead space between the last
   top-block section and the first product row (all Flatsome defaults, none
   of it intentional):
     Trade-in bottom ... H1 text   55px = 20 card margin + 10 `.col`
                                   padding-bottom + 20 `.page-title-inner`
                                   padding-top + 5 `.flex-col` padding
     H1 text ... breadcrumb text   24px = 14.4 H1 margin-bottom + leading
     breadcrumb ... toolbar        76px = 5 + 10 (the HIDDEN result-count
                                   `.flex-col`'s own 5+5 padding) + 8 row
                                   margin-top + 30 `.category-page-row`
                                   padding-top + 18 toolbar margin-top
     toolbar ... first card        28px
   Each is trimmed at its own wrapper — no negative margins, no element
   removed (the result-count column is only de-padded, not deleted). Targets:
   Trade-in -> title ~30, title -> breadcrumb ~14, breadcrumb -> toolbar
   ~32, toolbar -> grid 22 (all measured visible, glyph-to-box). */
@media ( max-width: 767px ) {
	/* Trade-in card's own [col] wrapper keeps a 10px padding-bottom that
	   stacks on the card's 20px margin. Source (found by scanning every
	   matching stylesheet rule, not guessed): Flatsome's generated INLINE
	   CSS `@media(max-width:549px){.col{padding-bottom:10px !important}}` —
	   `!important`, which is why the plain `.col:has(...)` zero above never
	   won on phones. Only this one col is overridden (via :has()), so every
	   other `.col` on the site keeps Flatsome's value. */
	.row > .col:has( > .col-inner > .vnt39-shop-tradein--mobile-top ) {
		padding-bottom: 0 !important;
	}
	/* `.page-title-inner{padding-top:20px}` is Flatsome's sitewide baseline
	   before an archive H1. Only dropped when the mobile Trade-in card
	   (which already supplies 20px of margin) sits directly above; on
	   pages with no top block (e.g. /cua-hang/page/2/) the H1 follows the
	   header directly and keeps the baseline. */
	body:has( .vnt39-shop-tradein--mobile-top ) .shop-page-title .page-title-inner {
		padding-top: 5px;
	}
	.woocommerce-shop .shop-page-title h1.shop-page-title {
		margin-bottom: 8px;
	}
	.woocommerce-shop .shop-page-title .page-title-inner > .flex-col:last-child {
		padding-top: 0;
		padding-bottom: 0;
	}
	/* `.row.` doubles the specificity: Flatsome's own `.category-page-row{
	   padding-top:30px}` (flatsome-shop.css) has the same single-class
	   specificity as this file's selector and loads AFTER it, so it won
	   (confirmed by scanning the matched rules). */
	.row.vnt39-shop-page-row {
		margin-top: 0;
		padding-top: 0;
	}
	.vnt39-shop-main-col .vnt39-shop-filters-wrap {
		margin: 22px 0;
	}
	/* The shared filter bar gets 5px above / 8px below on hover-capable
	   pointers (search-results.css, hover-clipping fix); on this narrow tier
	   give exactly that back so the tuned 22/22 spacing stays unchanged. */
	@media ( hover: hover ) and ( pointer: fine ) {
		.vnt39-shop-main-col .vnt39-shop-filters-wrap {
			margin: 17px 0 15px;
		}
	}
}

.vnt39-shop-sidebar {
	display: flex;
	flex-direction: column;
	gap: 16px;
}
/* STICKY SIDEBAR WITH INTERNAL SCROLL (2026-10-02, >=1024px only — MASTER
   is a real FPT screenshot). `top` reuses `--vnt39-nav-h`, the REAL sticky
   nav bar height header.js already measures live from the nav's own
   `offsetHeight` (assets/js/header.js:566) and exposes for exactly this
   kind of "how much is reserved at the top" question (header.css:2015
   already reads the same variable for its own scroll-offset math) — not
   a second, hard-coded guess at that height. +14px is the requested
   12–16px breathing room below it.
   `align-self:flex-start` is what actually fixes "sidebar stretches to
   the Product Grid's height, leaving a long blank gap below its own
   content": Flatsome's `.row` is a flex row with the flexbox-initial
   `align-items` (effectively `stretch`), so every `.col` — this sidebar
   included — normally stretches to match its tallest sibling (the
   product grid). Without overriding that for just this one flex item,
   `position:sticky` would have no visible room to "stick" within (its
   own box would already be exactly as tall as the row), AND the
   `max-height`/`overflow-y` below would never actually trigger, since
   the box would already be grid-height tall instead of content-height
   tall.
   `overscroll-behavior` is deliberately left at its default (`auto`),
   NOT `contain` — `contain` was tried first and confirmed LIVE to be the
   actual scroll-trap bug this section exists to avoid: per spec,
   `contain` blocks scroll chaining to the parent once the inner element
   can't scroll further in that direction, i.e. with the cursor still
   over an already-bottomed-out sidebar, further wheel ticks did nothing
   at all — `window.scrollY` measured unchanged across repeated scroll
   gestures. `auto` is the standard, no-op default every plain
   `overflow:auto` element already has site-wide (textareas, this same
   theme's own `.vnt39-spec-drawer__body`'s sibling cases, etc.) — it is
   what lets a further gesture fall through to the page once an inner
   element has nothing left to scroll, matching this task's own explicit
   "page vẫn phải tiếp tục cuộn tự nhiên" requirement. Note: the
   automation tool used to test this page could NOT demonstrate that
   chaining even on a trivial, unrelated control `<div
   style="overflow-y:auto">` with no CSS overrides at all (its synthetic
   wheel simulation just doesn't carry momentum/chaining past an
   already-maxed inner scrollable the way real OS trackpad/wheel input
   does) — confirmed that's a tooling limitation, not a CSS bug, before
   settling on `auto` here. No JS needed either way. */
@media ( min-width: 1024px ) {
	/* ROOT-OVERFLOW AUDIT (2026-10-03): an earlier pass also re-opened
	   Flatsome's `html{overflow-x:hidden}` / `body{overflow-x:clip}` for this
	   page on the belief that they were what broke `position:sticky`. They
	   were not. Re-tested live with a scroll of 1500px and the sidebar's
	   viewport `top` read back after each state: with those two Flatsome
	   rules fully RESTORED, with only `html` opened, and with only `body`
	   opened, the sidebar stays stuck at top:66px in all of them. The one
	   real culprit is the `.col{position:relative}` tie handled just below
	   (the computed `position` read `relative` until that was beaten), so the
	   html/body overrides were dropped — the root document's overflow is back
	   to Flatsome's own, with no scope at all for this feature to leak from. */
	.vnt39-shop-sidebar {
		/* !important — Flatsome's own `.col{position:relative}` (every
		   grid column, incl. this `<aside class="col large-3
		   vnt39-shop-sidebar">`) is the SAME specificity (one class) as
		   this selector; confirmed live that without `!important` the
		   computed `position` read back as `relative`, not `sticky`, no
		   matter what this rule set. */
		position: sticky !important;
		top: calc( var( --vnt39-nav-h, 52px ) + 14px );
		align-self: flex-start;
		max-height: calc( 100vh - var( --vnt39-nav-h, 52px ) - 28px );
		overflow-y: auto;
		overscroll-behavior: auto;
		/* Firefox: thin native scrollbar, no extra reserved gutter so the
		   sidebar's own width never shifts between scrollable/not. */
		scrollbar-width: thin;
		scrollbar-color: #d0d2d6 transparent;
	}
	/* Chrome/Safari/Edge — same "thin, flush to the right edge" scrollbar,
	   drawn as an overlay (no `scrollbar-gutter`), so it never changes
	   the sidebar's content width/layout, only appears while scrollable. */
	.vnt39-shop-sidebar::-webkit-scrollbar {
		width: 5px;
	}
	.vnt39-shop-sidebar::-webkit-scrollbar-track {
		background: transparent;
	}
	.vnt39-shop-sidebar::-webkit-scrollbar-thumb {
		background: #d0d2d6;
		border-radius: 999px;
	}
	.vnt39-shop-sidebar::-webkit-scrollbar-thumb:hover {
		background: #b0b3ba;
	}
}

/* "Thu cũ Đổi mới" card — real copy, real CTA destination. Red theme,   */
/* composed from HTML/CSS only (no real Trade-in photo asset exists).  */
.vnt39-shop-tradein {
	padding: 20px 18px;
	border-radius: var( --vnt39-radius-md, 10px );
	background: linear-gradient( 135deg, #a30d1b, #6e0a13 );
	color: #fff;
}
.vnt39-shop-tradein__title {
	margin: 0 0 4px;
	font-size: 20px;
	font-weight: 800;
	line-height: 1.25;
}
.vnt39-shop-tradein__subtitle {
	margin: 0 0 16px;
	font-size: 13px;
	color: rgba( 255, 255, 255, 0.85 );
	line-height: 1.4;
}
.vnt39-shop-tradein__cta {
	display: inline-flex;
	align-items: center;
	gap: 6px;
	padding: 9px 16px;
	border-radius: 999px;
	background: #fff;
	color: var( --vnt39-brand, #8f0b18 );
	font-size: 13px;
	font-weight: 800;
	text-decoration: none;
}

/* Sidebar variant only visible >=1024px; mobile-top variant only below. */
.vnt39-shop-tradein--sidebar {
	display: none;
}
@media ( min-width: 1024px ) {
	.vnt39-shop-tradein--sidebar {
		display: block;
	}
}
.vnt39-shop-tradein--mobile-top {
	margin: 0 0 20px;
}
@media ( min-width: 1024px ) {
	.vnt39-shop-tradein--mobile-top {
		display: none;
	}
}

/* "Dịch vụ sửa chữa Chuyên nghiệp" sidebar card — real /sua-chua/ CTA. */
.vnt39-shop-repair {
	padding: 18px;
	border: 1px solid #e5e7eb;
	border-radius: var( --vnt39-radius-md, 10px );
	background: #fff;
}
.vnt39-shop-repair__title {
	margin: 0;
	font-size: 16px;
	font-weight: 800;
	color: #252a31;
	line-height: 1.3;
}
.vnt39-shop-repair__subtitle {
	margin: 0 0 12px;
	font-size: 16px;
	font-weight: 800;
	color: var( --vnt39-brand, #8f0b18 );
	line-height: 1.3;
}
.vnt39-shop-repair__list {
	display: flex;
	flex-direction: column;
	gap: 6px;
	margin: 0 0 14px;
	padding: 0;
	list-style: none;
}
.vnt39-shop-repair__list li {
	position: relative;
	padding-left: 20px;
	font-size: 13px;
	font-weight: 600;
	color: #344054;
}
.vnt39-shop-repair__list li::before {
	content: "\2713";
	position: absolute;
	left: 0;
	color: var( --vnt39-brand, #8f0b18 );
	font-weight: 800;
}
.vnt39-shop-repair__cta {
	display: inline-flex;
	align-items: center;
	gap: 6px;
	padding: 9px 16px;
	border-radius: 999px;
	background: var( --vnt39-brand, #8f0b18 );
	color: #fff;
	font-size: 13px;
	font-weight: 800;
	text-decoration: none;
}

/* Sidebar trust stack (desktop, static). */
.vnt39-shop-trust-stack {
	display: flex;
	flex-direction: column;
	gap: 10px;
}
.vnt39-shop-trust-stack__item {
	display: flex;
	align-items: center;
	gap: 10px;
	padding: 12px 14px;
	border-radius: var( --vnt39-radius-md, 10px );
	background: #f7f7f8;
}
.vnt39-shop-trust-stack__icon {
	flex: none;
	display: flex;
	align-items: center;
	justify-content: center;
	width: 32px;
	height: 32px;
	border-radius: 50%;
	background: var( --vnt39-brand, #8f0b18 );
	color: #fff;
}
.vnt39-shop-trust-stack__icon .vnt39-icon {
	width: 17px;
	height: 17px;
}
.vnt39-shop-trust-stack__text {
	display: flex;
	flex-direction: column;
	min-width: 0;
}
.vnt39-shop-trust-stack__title {
	font-size: 12.5px;
	font-weight: 800;
	color: #252a31;
}
.vnt39-shop-trust-stack__subtitle {
	font-size: 11.5px;
	color: #667085;
}

/* "Bài viết liên quan" sidebar box (2026-10-02) — same card shell as
   `.vnt39-shop-repair` (white bg, 1px border, same radius token) and the
   same icon-left/text-right row shape as `.vnt39-shop-trust-stack__item`
   (thumbnail standing in for the icon), so it reads as part of the same
   sidebar, not a bolt-on from the blog page. Desktop/tablet only — this
   whole box lives inside `.vnt39-shop-sidebar`, already `display:none`
   below 1024px (see that rule above); no separate query needed here. */
.vnt39-shop-related-posts {
	padding: 18px;
	border: 1px solid #e5e7eb;
	border-radius: var( --vnt39-radius-md, 10px );
	background: #fff;
}
.vnt39-shop-related-posts__title {
	margin: 0 0 12px;
	font-size: 16px;
	font-weight: 800;
	color: #252a31;
	line-height: 1.3;
}
.vnt39-shop-related-posts__list {
	display: flex;
	flex-direction: column;
	gap: 10px;
}
.vnt39-shop-related-posts__item + .vnt39-shop-related-posts__item {
	padding-top: 10px;
	border-top: 1px solid #f1f2f4;
}
.vnt39-shop-related-posts__link {
	display: flex;
	align-items: flex-start;
	gap: 10px;
	color: inherit;
	text-decoration: none;
}
.vnt39-shop-related-posts__thumb {
	flex: none;
	display: block;
	overflow: hidden;
	width: 56px;
	height: 56px;
	border-radius: 8px;
	background: #f7f7f8;
}
.vnt39-shop-related-posts__thumb img {
	width: 100%;
	height: 100%;
	object-fit: cover;
}
.vnt39-shop-related-posts__body {
	display: flex;
	flex-direction: column;
	gap: 4px;
	min-width: 0;
	padding-top: 1px;
}
.vnt39-shop-related-posts__item-title {
	font-size: 12.5px;
	font-weight: 700;
	color: #252a31;
	line-height: 1.4;
	display: -webkit-box;
	overflow: hidden;
	-webkit-box-orient: vertical;
	-webkit-line-clamp: 2;
}
.vnt39-shop-related-posts__link:hover .vnt39-shop-related-posts__item-title {
	color: var( --vnt39-brand, #8f0b18 );
}
.vnt39-shop-related-posts__date {
	font-size: 11px;
	color: #667085;
}
.vnt39-shop-related-posts__more {
	display: block;
	margin-top: 14px;
	font-size: 13px;
	font-weight: 800;
	color: var( --vnt39-brand, #8f0b18 );
	text-decoration: none;
}
.vnt39-shop-related-posts__more:hover {
	text-decoration: underline;
}

/* ------------------------------------------------------------------ */
/* Mobile/tablet trust SLIDER — after pagination (hooked on             */
/* woocommerce_after_shop_loop priority 20). Only visible <1024px,      */
/* since the sidebar's static stack already covers >=1024px.            */
/* ------------------------------------------------------------------ */
.vnt39-shop-trust-slider-wrap {
	display: none;
	margin: 24px 0 8px;
}
@media ( max-width: 1023px ) {
	.vnt39-shop-trust-slider-wrap {
		display: block;
	}
}
.vnt39-shop-trust-slider-wrap__heading {
	margin: 0 0 14px;
	padding-left: 10px;
	border-left: 4px solid var( --vnt39-brand, #8f0b18 );
	font-size: 17px;
	font-weight: 800;
	color: #252a31;
}
.vnt39-shop-trust__slider {
	position: relative;
}
.vnt39-shop-trust__slide {
	width: 100%;
}
/* Same pre-init layout-shift fix as Category/Brand: until Flickity runs
   (it waits for this block to scroll into view) the slides stacked as 5
   full cards, then collapsed to one — a ~500px jump right above the
   footer. Until `flickity-enabled`, show the first slide only, in a row. */
.vnt39-shop-trust__slider:not( .flickity-enabled ) {
	display: flex;
	flex-wrap: nowrap;
	overflow: hidden;
}
.vnt39-shop-trust__slider:not( .flickity-enabled ) > .vnt39-shop-trust__slide {
	flex: 0 0 100%;
}
.vnt39-shop-trust__slide .vnt39-shop-trust-stack__item {
	margin: 0 6px;
}
/* Dot styling — the trust slider is visible (and active) from 0–1023px,
   a wider range than the category/mobile-only tier below, so its dots
   get their own media query instead of sharing the <=767px block. */
@media ( max-width: 1023px ) {
	.vnt39-shop-trust__slider .flickity-page-dots {
		position: static;
		display: flex;
		align-items: center;
		justify-content: center;
		gap: 7px;
		margin-top: 14px;
		padding: 0;
		list-style: none;
		line-height: 1;
	}
	.vnt39-shop-trust__slider .flickity-page-dots .dot {
		width: 7px;
		height: 7px;
		margin: 0;
		border-radius: 50%;
		background: #d0d5dd;
		opacity: 1;
		transition: background-color 0.2s ease, transform 0.2s ease;
	}
	.vnt39-shop-trust__slider .flickity-page-dots .dot.is-selected {
		background: var( --vnt39-brand, #8f0b18 );
		transform: scale( 1.15 );
	}
}

/* ------------------------------------------------------------------ */
/* Tablet (1024px is the real collapse point above; this just trims    */
/* outer spacing at the standalone 768–1023 tier).                      */
/* ------------------------------------------------------------------ */
@media ( max-width: 1023px ) {
	.vnt39-shop-categories,
	.vnt39-shop-brands {
		margin-bottom: 18px;
	}
	/* Hot Sale keeps its own target at this tier (30–36px total task
	   spec) — deliberately NOT folded into the shared 18px rule above.
	   13px own margin + the fixed 20px Flatsome page-title baseline
	   (see this section's own comment above) = ~33px total. */
	.vnt39-shop-hotsale {
		margin-bottom: 13px;
	}
}

/* HOT SALE HEADER — 768–1365px (2026-10-02). The desktop grid above keeps
   the 3 clusters on one row via `1fr auto 1fr`, but the CENTER column
   (rotating text) has a JS-LOCKED pixel width (measure() sizes it to the
   longest phrase so swapping phrases never shifts layout) while the two
   SIDE columns size to their own content. Confirmed live via
   getBoundingClientRect() at 1024px: the title's `1fr` track resolves
   smaller than the countdown's `1fr` track (919px header → title track
   201.8px vs countdown track 291.75px) because the countdown's content
   can't wrap (nowrap digits/prefix) so its content-width becomes its hard
   floor, while the grid gives it that floor FIRST and then hands ALL
   remaining space to the only track still "flexible" — which is the
   title's. At the default 27px/19px/17px sizes that remainder is too
   narrow for the title's single line, so it wraps to 2 lines and drops
   off the other clusters' shared center line. Fix is real content-width
   reduction (not a transform/overflow hack) on all 3 clusters, verified
   by re-measuring natural (nowrap) text widths at each candidate
   font-size with a throwaway clone — not guessed: at 768px the header's
   own content box is 663px, and title(20px)=243px + rotating(13px,
   prefix+stage+gap)=249px + countdown(stacked, see below)=129px + 2×8px
   column-gap = 637px, a verified ~26px margin before wrapping — and at
   1024/1365 the same sizes carry an even larger margin since the header
   only gets wider. The countdown additionally switches its row from
   horizontal to stacked (label above digits) ONLY at this tier: a
   horizontal "Kết thúc sau: 02:07:39" needs ~240px (its nowrap label is
   the expensive part), but stacked it needs only the digits' own
   ~130px width (digits still read left-to-right, the label just sits on
   its own line above them) — the single biggest saving in the whole
   budget, is was reusing the already-scoped `.vnt39-shop-hotsale
   .vnt39-countdown__*` selectors, not a change to the shared countdown
   component. */
@media ( min-width: 768px ) and ( max-width: 1365px ) {
	.vnt39-shop-hotsale__header {
		gap: 6px 8px;
	}
	.vnt39-shop-hotsale__title {
		font-size: 20px;
	}
	.vnt39-shop-rotating-text {
		font-size: 13px;
	}
	.vnt39-shop-hotsale .vnt39-countdown--hotsale {
		--countdown-number-size-desktop: 13px;
	}
	.vnt39-shop-hotsale .vnt39-countdown__row {
		flex-direction: column;
		align-items: flex-end;
		gap: 2px;
	}
	.vnt39-shop-hotsale .vnt39-countdown__prefix {
		font-size: 13px;
	}
	.vnt39-shop-hotsale .vnt39-countdown__digits {
		gap: 3px;
	}
	.vnt39-shop-hotsale .vnt39-countdown__box {
		min-width: 24px;
		padding: 4px 5px;
	}
}
/* 768–899px — equal `1fr auto 1fr` tracks mean the title's own column is
   HALF of (header width − center column − gaps), full stop — confirmed
   live that the countdown's comfortably-smaller content on the matching
   right-hand track does NOT hand its unused space to the title (each
   track sizes independently; no spill-over), so the single lever that
   matters here is the title's OWN single-line width fitting inside that
   half-share. At 768px specifically (header content box 678px) the
   768–1365 sizes above (20px title, 13px rotating) resolve a 197.96px
   shared half-share — too narrow for a 20px single-line title (measured
   ~243px) — so this narrower sub-tier shrinks title and rotating-text
   further, verified live via fresh same-origin iframe loads (never a
   resize of an existing one — a resize mid-transition can freeze the
   rotating text, a known artifact unrelated to real users). */
@media ( min-width: 768px ) and ( max-width: 899px ) {
	.vnt39-shop-hotsale__title {
		font-size: 17px;
	}
	.vnt39-shop-rotating-text {
		font-size: 11px;
	}
}

/* ------------------------------------------------------------------ */
/* Mobile (<=767px) — Category: 4 items/page, 2x2, manual swipe+dots,  */
/* no autoplay. Brand: one row, ~4 visible, autoplay 3000ms, swipe,    */
/* wrapAround. Trust: 1 card visible, autoplay 3000ms, swipe+dots.     */
/* All driven by assets/js/shop-page.js's matchMedia+lazyFlickity+     */
/* watchCSS:false pattern (Support Station's proven technique).         */
/* ------------------------------------------------------------------ */
@media ( max-width: 767px ) {
	/* .vnt39-shop-categories__row needs no rule here — it defaults to
	   display:none and only turns on >=768px (see its own block above). */
	.vnt39-shop-categories__slider {
		display: block;
		position: relative;
	}
	.vnt39-shop-categories__page {
		width: 100%;
	}
	/* MOBILE CATEGORY ARROWS (2026-10-03) — Flickity already builds the
	   prev/next buttons inside this slider (Flatsome styles them as 61px
	   tall bars at opacity 0), they just were never styled. Same round white
	   arrow as the desktop row / Hero, now with a light shadow, overlaid
	   inside the slider (absolute — no layout width or height of its own, so
	   cards don't move and no blank space is added). Vertical centre is the
	   middle of the CARD area, not of the slider: the slider also holds the
	   dots row (14px margin + 7px dots = 21px) below the cards, so `top:50%`
	   alone sat ~10px too low. On hover-capable devices hidden at rest and
	   revealed by hovering the whole section; on touch they rest at 90% and
	   swipe + dots are untouched (buttons are siblings of the track, never
	   over the drag surface except their own 32px circles). */
	.vnt39-shop-categories__slider .flickity-prev-next-button {
		position: absolute;
		top: calc( 50% - 10px );
		z-index: 2;
		display: inline-flex;
		align-items: center;
		justify-content: center;
		width: 32px;
		height: 32px;
		min-width: 32px !important;
		min-height: 32px !important;
		margin: 0;
		margin-top: -16px !important;
		padding: 0;
		border: 0;
		border-radius: 999px;
		background: #fff;
		box-shadow: 0 2px 8px rgba( 0, 0, 0, 0.18 );
		opacity: 0.9 !important;
		transition: opacity 0.2s ease;
	}
	.vnt39-shop-categories__slider .flickity-prev-next-button.previous {
		left: 4px;
	}
	.vnt39-shop-categories__slider .flickity-prev-next-button.next {
		right: 4px;
	}
	.vnt39-shop-categories__slider .flickity-prev-next-button .arrow {
		fill: var( --vnt39-brand, #8f0b18 );
	}
	.vnt39-shop-categories__slider .flickity-prev-next-button:disabled {
		opacity: 0.12 !important;
		box-shadow: none;
		cursor: default;
	}
	@media ( hover: hover ) and ( pointer: fine ) {
		.vnt39-shop-categories .vnt39-shop-categories__slider .flickity-prev-next-button:not( :disabled ) {
			opacity: 0 !important;
		}
		.vnt39-shop-categories:hover .vnt39-shop-categories__slider .flickity-prev-next-button:not( :disabled ) {
			opacity: 1 !important;
		}
	}
	/* Same pre-init layout-shift fix as the >=768px rows above (measured
	   live at 390px: Category 895px / Brand 687px tall until each lazy
	   Flickity chunk runs, then ~230px / ~95px): until `flickity-enabled`
	   lands, lay pages/slides out as the single clipped row Flickity will
	   build — first page / first slides visible, no vertical stack. */
	.vnt39-shop-categories__slider:not( .flickity-enabled ),
	.vnt39-shop-brands__slider:not( .flickity-enabled ) {
		display: flex;
		flex-wrap: nowrap;
		overflow: hidden;
	}
	.vnt39-shop-categories__slider:not( .flickity-enabled ) > .vnt39-shop-categories__page {
		flex: 0 0 100%;
	}
	.vnt39-shop-brands__slider:not( .flickity-enabled ) > * {
		flex: none;
	}

	.vnt39-shop-brands__row {
		display: none;
	}
	.vnt39-shop-brands__slider {
		display: block;
		position: relative;
	}

	.vnt39-shop-categories__slider .flickity-page-dots {
		position: static;
		display: flex;
		align-items: center;
		justify-content: center;
		gap: 7px;
		margin-top: 14px;
		padding: 0;
		list-style: none;
		line-height: 1;
	}
	.vnt39-shop-categories__slider .flickity-page-dots .dot {
		width: 7px;
		height: 7px;
		margin: 0;
		border-radius: 50%;
		background: #d0d5dd;
		opacity: 1;
		transition: background-color 0.2s ease, transform 0.2s ease;
	}
	.vnt39-shop-categories__slider .flickity-page-dots .dot.is-selected {
		background: var( --vnt39-brand, #8f0b18 );
		transform: scale( 1.15 );
	}
}

/* HERO TEXT BANNER (2026-10-06) — what the SAME hero slot shows on a storefront (Repair / Part) that has no uploaded banner yet
   (inc/shop-page.php, copy from the storefront config). Same slide box (3:1 desktop, --ar on phones), so the skeleton height never
   changes; an uploaded banner replaces it. */
.vnt39-shop-hero#fptLiteBlock .vnt39-shop-hero__slide { position: relative; }
.vnt39-shop-hero__text {
	position: absolute;
	inset: 0;
	display: flex;
	flex-direction: column;
	justify-content: center;
	align-items: flex-start;
	gap: 8px;
	padding: 0 7%;
	box-sizing: border-box;
	background: linear-gradient( 115deg, #7d0218 0%, #a30d1b 55%, #c9202f 100% );
	color: #fff;
}
.vnt39-shop-hero__text-title { font-size: clamp( 22px, 3.4vw, 42px ); line-height: 1.15; font-weight: 800; letter-spacing: .2px; }
.vnt39-shop-hero__text-sub { font-size: clamp( 13px, 1.5vw, 18px ); line-height: 1.4; opacity: .92; max-width: 640px; }
.vnt39-shop-hero__text-cta {
	display: inline-block;
	margin-top: 4px;
	padding: 8px 18px;
	border-radius: 999px;
	background: #fff;
	color: #a30d1b;
	font-size: 14px;
	font-weight: 700;
	text-decoration: none;
	transition: background-color .2s ease, color .2s ease;
}
.vnt39-shop-hero__text-cta:hover, .vnt39-shop-hero__text-cta:focus-visible { background: #155209; color: #fff; }

/* STOREFRONT CATEGORY NAVIGATION (2026-10-06) — selected group tile + the "Chọn theo dòng máy / model" row (inc/storefront.php). */
.vnt39-shop-categories__item.is-active { border-color: #a30d1b; box-shadow: 0 0 0 1px #a30d1b inset; }
.vnt39-shop-categories__item.is-active .vnt39-shop-categories__title { color: #a30d1b; }
/* Progressive navigation — level 1 buttons (device type / generation), level 2 panel, level 3 row; one open panel at a time.
   Without JS every panel is simply shown (.vnt39-sfnav-js is added by assets/js/storefront-nav.js). */
.vnt39-sf-nav { margin: 0 0 12px; padding: 14px 16px; background: #fff; border: 1px solid #e5e7eb; border-radius: var( --vnt39-radius-md, 12px ); }
.vnt39-sf-nav__title { margin: 0 0 10px; font-size: 14px; font-weight: 700; line-height: 1.3; text-transform: none; color: #1f2430; }
.vnt39-sf-nav__l1 { display: flex; gap: 8px; } /* one line; overflow behaviour = [data-vnt39-hrow] (components.css / hscroll.js) */
.vnt39-sf-nav__t1,
a.vnt39-sf-nav__chip.vnt39-sf-nav__t1 { display: inline-flex; align-items: center; gap: 6px; min-height: 36px; margin: 0; padding: 0 14px; border: 1px solid #d9dde3; border-radius: 999px; background: #fff; color: #1f2430; font: inherit; font-size: 13.5px; font-weight: 700; line-height: 1; letter-spacing: 0; text-transform: none; text-decoration: none; white-space: nowrap; cursor: pointer; box-shadow: none; transition: border-color .15s ease, color .15s ease, background-color .15s ease; }
.vnt39-sf-nav__t1:hover, .vnt39-sf-nav__t1:focus-visible { border-color: #a30d1b; color: #a30d1b; background: #fff; }
.vnt39-sf-nav__t1.is-active { border-color: #a30d1b; color: #a30d1b; background: #fdf1f2; }
.vnt39-sf-nav__t1.is-open { border-color: #a30d1b; }
.vnt39-sf-nav__chev { width: 7px; height: 7px; margin-top: -3px; border: solid currentColor; border-width: 0 2px 2px 0; transform: rotate( 45deg ); transition: transform .2s ease, margin .2s ease; }
.is-open > .vnt39-sf-nav__chev, [aria-expanded="true"] > .vnt39-sf-nav__chev { margin-top: 3px; transform: rotate( 225deg ); }
.vnt39-sf-nav__panel { margin: 12px 0 0; padding: 12px 0 0; border-top: 1px solid #eef0f3; }
.vnt39-sf-nav__panel[hidden] { display: none; }
html:not( .vnt39-sfnav-js ) .vnt39-sf-nav__panel[hidden] { display: block; }
.vnt39-sf-nav__l2, .vnt39-sf-nav__l3 { display: flex; gap: 8px; }
.vnt39-sf-nav__l3 { padding: 10px 12px; background: #f7f8fa; border-radius: 10px; }
.vnt39-hs:has( > .vnt39-sf-nav__l3 ) { margin: 10px 0 0; }
.vnt39-sf-nav__chip { display: inline-flex; align-items: center; min-height: 34px; padding: 0 13px; border: 1px solid #e0e3e8; border-radius: 999px; background: #fff; color: #303642; font-size: 13px; font-weight: 600; line-height: 1; text-decoration: none; white-space: nowrap; transition: border-color .15s ease, color .15s ease, background-color .15s ease; }
.vnt39-sf-nav__chip:hover, .vnt39-sf-nav__chip:focus-visible { border-color: #a30d1b; color: #a30d1b; }
.vnt39-sf-nav__chip.is-active { border-color: #a30d1b; background: #a30d1b; color: #fff; }
.vnt39-sf-nav__l3 .vnt39-sf-nav__chip { background: #fff; }
.vnt39-sf-nav__l3 .vnt39-sf-nav__chip.is-active { background: #a30d1b; }

/* Active selection above the grid */
.vnt39-sf-sel { display: flex; align-items: center; gap: 8px; }
.vnt39-hs:has( > .vnt39-sf-sel ) { margin: 0 0 14px; }
.vnt39-sf-sel__lab { font-size: 13px; font-weight: 600; color: #6a7080; }
.vnt39-sf-sel__chip { display: inline-flex; align-items: center; gap: 6px; min-height: 32px; padding: 0 12px; border: 1px solid #a30d1b; border-radius: 999px; background: #fdf1f2; color: #a30d1b; font-size: 13px; font-weight: 700; line-height: 1; text-decoration: none; }
.vnt39-sf-sel__chip span { font-size: 16px; line-height: 1; }
.vnt39-sf-sel__chip:hover, .vnt39-sf-sel__chip:focus-visible { background: #a30d1b; color: #fff; }

/* Group tiles of Repair / Part: first 6 (4 on phones) + "Xem tất cả" */
.vnt39-sf-tiles { display: grid; grid-template-columns: repeat( 6, minmax( 0, 1fr ) ); gap: 14px; }
.vnt39-sf-tiles:not( .is-expanded ) .vnt39-sf-tiles__cell:nth-child( n+7 ) { display: none; }
.vnt39-sf-tiles__more { display: flex; align-items: center; justify-content: center; gap: 8px; margin: 14px auto 0; padding: 0 20px; min-height: 38px; border: 1px solid #d9dde3; border-radius: 999px; background: #fff; color: #1f2430; font: inherit; font-size: 13.5px; font-weight: 700; line-height: 1; letter-spacing: 0; text-transform: none; cursor: pointer; box-shadow: none; transition: border-color .15s ease, color .15s ease; }
.vnt39-sf-tiles__more:hover, .vnt39-sf-tiles__more:focus-visible { border-color: #a30d1b; color: #a30d1b; background: #fff; }
.vnt39-sf-tiles__more.is-mobile-only { display: none; }
@media ( min-width: 768px ) and ( max-width: 1023px ) {
	.vnt39-sf-tiles { grid-template-columns: repeat( 4, minmax( 0, 1fr ) ); }
	.vnt39-sf-tiles:not( .is-expanded ) .vnt39-sf-tiles__cell:nth-child( n+5 ) { display: none; }
	.vnt39-sf-tiles__more.is-mobile-only { display: flex; }
}
@media ( max-width: 767px ) {
	.vnt39-sf-tiles { grid-template-columns: repeat( 2, minmax( 0, 1fr ) ); gap: 12px; }
	.vnt39-sf-tiles:not( .is-expanded ) .vnt39-sf-tiles__cell:nth-child( n+5 ) { display: none; }
	.vnt39-sf-tiles__more.is-mobile-only { display: flex; }
	.vnt39-sf-nav { padding: 12px; }
	.vnt39-sf-nav__t1, .vnt39-sf-nav__chip { min-height: 40px; }
	.vnt39-sf-nav__l3 { padding: 8px 10px; }
}
