/*
 * VibeSequence page components.
 * Styles for the Custom-HTML sections used on converted (Gutenberg) pages.
 * Values here reproduce what was measured on the live Elementor site on
 * 2026-08-21 (see special/.baseline-home.md) - this is a migration, not a
 * redesign, so don't "improve" spacing/sizing without updating the
 * baseline doc to log it as a deliberate change first.
 */

/* When a post/page has "site-post-title: disabled" (Astra's per-post title
   toggle, used for every converted post/page whose production source hides
   the theme's title bar), Astra's own PHP only suppresses the <h1> itself -
   the "By ... / <date>" entry-meta byline is a separate, unconditional
   template block and still renders. Astra tags the body with
   "ast-normal-title-enabled" only when the title is NOT disabled, so this
   selector reaches exactly the disabled case, matching what production
   actually shows (no title bar AND no byline) without touching core/parent
   theme files. Discovered while converting the blog posts (2026-08-22). */
body:not(.ast-normal-title-enabled) .entry-meta {
	display: none;
}

/* Accessibility: visible keyboard-focus states.
   Found during the 2026-08-22 site-wide QA pass: every link/button on the
   site (nav items, .vs-btn CTAs, quiz-answer buttons) computed
   outline:none with no other focus differentiator (no color/box-shadow
   change either) - a real keyboard-navigation gap inherited from Astra's
   own base styles, not something introduced by this rebuild. Using
   :focus-visible (not a blanket :focus) so this only shows for real
   keyboard/assistive-tech navigation, not mouse clicks - no visual
   change for mouse/touch users. */
a:focus-visible,
button:focus-visible,
input:focus-visible,
summary:focus-visible,
[tabindex]:focus-visible {
	outline: 3px solid var(--vs-color-lavender, #c39bff);
	outline-offset: 2px;
}

#quiz-root .quiz-answer-button:focus-visible {
	outline: 3px solid #fff;
	outline-offset: 3px;
	box-shadow: var(--vs-btn-shadow-hover, 0 14px 26px rgba(0,0,0,.20), 0 6px 12px rgba(0,0,0,.12));
}

.vs-hero,
.vs-tagline,
.vs-card,
.vs-value-row {
	max-width: 720px;
	margin: 0 auto;
	padding: 40px 20px;
	text-align: center;
	font-family: var(--vs-font-family);
}

.vs-hero h1 {
	color: var(--vs-text-heading);
	font-size: 44px;
	font-weight: 700;
	line-height: 1.15;
	margin: 0 0 16px;
}

@media (min-width: 782px) {
	.vs-hero h1 {
		font-size: 62px;
	}
}

.vs-lede {
	color: var(--vs-text-body);
	font-size: 18px;
	line-height: 1.5;
	margin: 0 0 24px;
}

/* Visual Batch 1 (2026-08-25): .vs-cta groups switched from bare block
   flow to flex+gap so multiple .vs-btn CTAs in one group (e.g. Community's
   5-button "#guide" section, the hero "Get Vibe Notes" / "Get the guide"
   pair) get real spacing on wrap instead of touching edge-to-edge -
   confirmed via genuine-375px measurement that wrapped rows previously sat
   with zero gap (row N's bottom === row N+1's top), reading as "crowded/
   colliding" even though never literally overlapping. justify-content
   defaults to center (matches every existing usage's inherited
   text-align:center) except Contact, which explicitly wants left alignment
   (see .vs-contact .vs-cta below) - Contact is out of scope for this batch
   and must keep its existing alignment. */
.vs-hero-cta,
.vs-cta {
	margin: 20px 0 0;
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	justify-content: center;
	gap: 14px 12px;
}

/* .vs-btn brought to visual parity with the approved #quiz-root
   .quiz-answer-button reference (measured live on /quiz/, 2026-08-25):
   box-shadow, hover lift + shadow-grow, active press, and the glossy
   ::before top highlight are all transferred using Quiz's own measured
   values (not invented) - this was the concrete cause of the "flatter
   appearance / insufficient shadow/depth" finding, since .vs-btn
   previously had no box-shadow at all. min-height 52px matches Quiz's
   touch-target height. Encoded directly here (not depending on WPCode
   821/3768/3765) so the same look renders identically with or without
   those legacy snippets - see root-cause notes in the batch report. */
.vs-btn {
	position: relative;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	background-image: var(--vs-gradient-primary);
	color: #fff;
	font-weight: var(--vs-nav-font-weight);
	font-size: 16px;
	text-decoration: none;
	border: 0;
	border-radius: var(--vs-radius-pill);
	padding: 14px 24px;
	min-height: 52px;
	box-sizing: border-box;
	line-height: 1.15;
	cursor: pointer;
	box-shadow: var(--vs-btn-shadow, 0 10px 18px rgba(0,0,0,.18), 0 3px 6px rgba(0,0,0,.10));
	transition: transform .12s ease, box-shadow .12s ease, filter .12s ease;
}

.vs-btn::before {
	content: "";
	position: absolute;
	left: 8px;
	right: 8px;
	top: 5px;
	height: 42%;
	border-radius: 999px;
	background: linear-gradient(to bottom, rgba(255,255,255,.28), rgba(255,255,255,0));
	pointer-events: none;
}

.vs-btn:hover,
.vs-btn:focus {
	filter: brightness(1.05);
	color: #fff;
	transform: translateY(-2px);
	box-shadow: var(--vs-btn-shadow-hover, 0 14px 26px rgba(0,0,0,.20), 0 6px 12px rgba(0,0,0,.12));
}

.vs-btn:active {
	transform: translateY(1px);
	box-shadow: var(--vs-btn-shadow-active, 0 6px 12px rgba(0,0,0,.14), 0 2px 5px rgba(0,0,0,.08));
}

@media (prefers-reduced-motion: reduce) {
	.vs-btn {
		transition: none;
	}
}

/* Root cause (found 2026-08-25): Astra's own
   ".ast-single-post .entry-content a" rule (specificity 0,2,1) sets
   text-decoration:underline and out-specifies a bare ".vs-btn"
   (0,1,0) wherever a CTA sits inside .entry-content - which is every
   page built from Gutenberg "wp:html" content, since Astra adds the
   ast-single-post body class site-wide here. This is the "underlined/
   link-like CTA text in some contexts" finding. Fixed with real
   specificity (0,2,2 - ties classes 2=2, wins on element count 2>1),
   not !important, following this file's existing "prefix with body"
   cascade-fight convention (see the .vs-manifesto-teaser h1 rule above). */
body .entry-content a.vs-btn {
	text-decoration: none;
}

.vs-tagline h2 {
	color: var(--vs-text-subheading);
	font-size: 18px;
	font-weight: 600;
	letter-spacing: 0.15em;
	text-transform: uppercase;
	margin: 0 0 12px;
}

.vs-tagline p {
	color: var(--vs-text-muted);
	font-size: 16px;
	margin: 0;
}

.vs-card h2 {
	color: var(--vs-text-subheading);
	font-size: 28px;
	font-weight: 600;
	margin: 0 0 16px;
}

.vs-card h3 {
	color: var(--vs-text-subheading);
	font-size: 18px;
	font-weight: 600;
	margin: 0 0 14px;
}

/* Explore's opening heading was promoted from h3 to h1 (Phase 2A closure patch) for
   semantic/SEO structure; this keeps its existing visual size unchanged. The "body"
   ancestor is a specificity bump only (matches the same elements either way) - needed
   because Elementor's global kit CSS (.elementor-kit-189 h1) still loads site-wide even
   on natively-rendered pages and ties .vs-manifesto-teaser h1 on specificity, winning
   font-weight on source order (2026-08-24 cascade patch). */
body .vs-manifesto-teaser h1 {
	color: var(--vs-text-subheading);
	font-size: 18px;
	font-weight: 600;
	margin: 0 0 14px;
}

.vs-card p {
	color: var(--vs-text-body);
	font-size: 16px;
	line-height: 1.6;
	margin: 0 0 14px;
}

/* Batch 4 (glass-card system restoration, 2026-08-29): .vs-card--tint has
   two structurally different live consumers, confirmed via DOM inspection
   before touching anything (per this batch's own mapping-first
   instruction):
   1. class="vs-card vs-card--tint" - a genuine standalone discrete card
      (Home's "Why vibesequence?", each Result page's "Breathe Together,
      Gently" / "Our trusted picks" intro block) - no nested cards inside.
   2. class="vs-guide-grid vs-card--tint" - Vibe Picks' two per-vibe
      grouping wrappers ("EMPOWERING picks", "ROMANTIC picks"), which
      themselves CONTAIN .vs-mini-card product cards as children.
   Glassing case 2 as well would nest a blurred/bordered/shadowed layer
   directly around already-glassed .vs-mini-card children (nested
   glass-on-glass, explicitly avoided per this batch's brief) - the
   :not(.vs-guide-grid) exclusion below scopes the upgrade to case 1 only.
   Case 2's wrapper stays a plain grouping shell; its own .vs-mini-card
   children (updated above) are the actual glass units there. */
.vs-card--tint:not(.vs-guide-grid) {
	background-color: var(--vs-card-tint);
	background-image: linear-gradient(160deg, rgba(255, 255, 255, .5), rgba(255, 255, 255, .16));
	border: 1px solid rgba(255, 255, 255, .55);
	backdrop-filter: blur(6px) saturate(1.05);
	-webkit-backdrop-filter: blur(6px) saturate(1.05);
	box-shadow: 0 10px 22px rgba(120, 72, 48, .12), 0 2px 6px rgba(120, 72, 48, .06);
}

.vs-card--tint {
	border-radius: 24px;
	max-width: 800px;
}

.vs-symbol-card .vs-symbol-icon {
	display: block;
	margin: 0 auto 16px;
}

/* Community's hero reuses the .vs-symbol-icon class on its "Women's
   World" mirror photo, but outside a .vs-symbol-card ancestor - so the
   rule above never matched it, and it was centering only by inheriting
   text-align:center from .vs-hero (fragile: works today, but not an
   explicit/guaranteed rule; measured centered at both 1280px and genuine
   375px in this environment, but hardened here per Issue 4 rather than
   left dependent on inheritance). */
/* Batch 5 (Community layout, 2026-08-29): confirmed real width violation -
   the <img> tag carries a hardcoded width="400" HTML attribute (paired
   with a wrong height="400" too, but height:auto below already overrides
   that safely) and no CSS max-width existed to cap it, so it rendered at
   a fixed 400px regardless of viewport - fine on desktop, but 400px vs a
   genuine ~371px mobile viewport measured a real 37px overflow past the
   right edge (400 > available ~331px content width). max-width:100% is
   a no-op at desktop (400px already fits its ~680px container) and
   exactly clamps to the container on mobile - no crop/distortion, same
   technique already used for other CSS-driven-height images.
   !important required: live cascade-checked in the actual mobile
   context and found a SECOND, broader Astra Page-Builder-template rule
   (".ast-page-builder-template .entry-content[data-ast-blocks-layout] >
   :not(...) > *", specificity ~0,4,0) that resets max-width:none two
   levels deep (grandchildren of .entry-content, which is exactly where
   this icon sits: entry-content > .vs-hero > img) - a broader reach than
   the direct-children-only rule already beaten (with !important) for
   .vs-hero/#highlights above, so a plain specificity bump alone wasn't
   enough here. */
.vs-hero > .vs-symbol-icon {
	display: block;
	max-width: 100% !important;
	height: auto;
	margin: 0 auto 16px;
}

/* Visual Batch 1A closure patch (2026-08-25) - Owner real-device QA found
   this specific image still reads as visibly off-center on a real phone,
   even though the element box itself measures perfectly centered (verified
   again here). Pixel-analyzed the source asset directly: the oval mirror
   frame sits at ~51.2% of the canvas width (not exactly 50%) and the
   handwritten "WOMEN'S WORLD" text - the actual visual focal point - reads
   consistently 1-2% right of canvas-center across three independent
   measurements (oval-bounds midpoint, restricted edge-energy centroid on
   the text band only, and a precise gridline visual check). A small
   leftward nudge compensates so the readable content (not just the file's
   bounding box) sits optically centered in the content column. Percentage-
   based so it scales correctly at any rendered width; does not crop,
   distort, or replace the source asset; scoped to this exact selector only
   (does not touch other .vs-symbol-icon usages, e.g. the Home Symbol
   section, which measured correctly centered and is untouched). */
.vs-hero > .vs-symbol-icon {
	transform: translateX(-3%);
}

.vs-symbol-card .vs-emphasis {
	color: var(--vs-text-heading);
	font-weight: 600;
	font-style: italic;
}

.vs-value-row {
	display: flex;
	flex-wrap: wrap;
	gap: 32px;
	max-width: 1080px;
	text-align: left;
}

.vs-value-card {
	flex: 1 1 280px;
	/* Batch 4 (glass-card system restoration, 2026-08-29): same flat/glass
	   gap as .vs-mini-card above - upgraded to the canonical warm-glass
	   recipe (Home's Empathy First/Trust Your Signals/Own Your Confidence
	   row only; the Batch 3 flex-column/CTA-alignment rules right below
	   are untouched). */
	background-color: var(--vs-card-tint);
	background-image: linear-gradient(160deg, rgba(255, 255, 255, .5), rgba(255, 255, 255, .16));
	border: 1px solid rgba(255, 255, 255, .55);
	backdrop-filter: blur(6px) saturate(1.05);
	-webkit-backdrop-filter: blur(6px) saturate(1.05);
	box-shadow: 0 10px 22px rgba(120, 72, 48, .12), 0 2px 6px rgba(120, 72, 48, .06);
	border-radius: 20px;
	padding: 28px;
	/* Batch 3 (button remediation, 2026-08-29): the 3 CTAs already share
	   identical typography/height/padding (.vs-btn base rule, untouched) -
	   Owner QA's only real defect here was the bottom baseline (28-143px
	   apart across the row, since body-copy length varies per card and
	   .vs-cta was a plain in-flow block). flex-column + margin-top:auto on
	   .vs-cta below is the same equal-bottom pattern used on
	   .vs-review-card/.vs-path-card/.vs-mini-card - CTA only, no change to
	   the card/image/copy above it. */
	display: flex;
	flex-direction: column;
}

/* Visual Batch 1 (2026-08-25) - Home images finding: both had margin
   "0 0 16px" (no auto), so with .vs-value-card being a plain block
   container (not itself centering its children) the image sat flush
   against the card's left padding - confirmed at genuine 375px (image
   left edge = card padding edge, ~88px off true center). margin:auto
   fixes alignment for both. Width/height also raised for .vs-value-photo
   specifically: the heart icon (alt "...heart icon") is a real icon and
   stays small; the other two images are real photos ("Smiling woman...",
   "Confident woman...") that read as a tiny avatar/thumbnail at 64px per
   the Owner's own description - no exact pre-migration px value could be
   recovered (no dormant Elementor source found locally), so 128px is a
   judgment call sized to read as a genuine section photo rather than an
   icon, while preserving the existing image files exactly. */
.vs-value-card .vs-value-icon,
.vs-value-card .vs-value-photo {
	display: block;
	border-radius: 999px;
	margin: 0 auto 16px;
	object-fit: cover;
}

.vs-value-card .vs-value-icon {
	width: 64px;
	height: 64px;
}

.vs-value-card .vs-value-photo {
	width: 128px;
	height: 128px;
}

.vs-value-card h3 {
	color: var(--vs-text-subheading);
	font-size: 22px;
	font-weight: 600;
	margin: 0 0 8px;
	text-align: left;
}

.vs-value-card p {
	color: var(--vs-text-body);
	font-size: 16px;
	line-height: 1.6;
	text-align: left;
}

.vs-value-card .vs-cta {
	margin-top: auto;
	padding-top: 20px;
}

.vs-quiz-cta h2 {
	font-size: 26px;
}

/* Explore page — path grid, guide/review card grids, vibe cards */

/* Explore redesign, Prototype A (2026-08-26). Three new components below:
   .vs-vibe-hero (the four Vibes, moved to primary early position, compact
   scale), .vs-wayfinder (Choose Your Path, now compact navigation instead
   of a full card grid), .vs-flashback (Crash Flashback, single instance,
   light-by-default with a JS-triggered one-time animated shift to a darker
   final treatment - see explore.js for the trigger and the state-model
   rationale). Featured Beginner Guides / Product Reviews / Mood & Sensation
   Tips keep their existing .vs-guide-grid/.vs-card-row/.vs-mini-card
   treatment unchanged in this checkpoint - only the upper page changed. */

/* --- Vibes hero grid ---
   Root cause of the "oversized" Production complaint: the four source
   photos are 600x900 (0.667 ratio), and the existing .vs-mini-card img
   rule is width:100%; height:auto - fully unconstrained. Combined with
   .vs-mini-card's flex-basis of 280px (wider than half of a 375px mobile
   viewport once padding/gap are subtracted), that forces a single card
   per row, so each portrait photo has rendered near full viewport width
   and therefore near full viewport HEIGHT (~450-500px per card, 4 cards
   stacked) - confirmed via the source dimensions, not guessed. The fix
   here is two things at once: a real 2-up CSS grid (not a flex-wrap that
   collapses to one column), and an explicit, capped image height
   (object-fit:cover crops the tall source into that box instead of
   dictating the box's height). */
.vs-vibe-hero {
	max-width: 1080px;
	margin: 0 auto;
	padding: 40px 20px;
	text-align: center;
	font-family: var(--vs-font-family);
}

.vs-vibe-hero-grid {
	display: grid;
	grid-template-columns: repeat(2, 1fr);
	gap: 14px;
	margin-top: 24px;
	text-align: left;
}

/* Refinement pass (2026-08-26): restrained "glass" surface treatment -
   the base peach tint stays (brand color unchanged), a soft white sheen
   gradient sits on top for the frosted look, backdrop-filter blurs the
   illustrated page background showing through, and a warm-toned (not
   cold black) shadow gives a gentle floating lift. Kept deliberately
   light: 6px blur, low-opacity sheen, no thick border - avoids the
   harsh/techy glassmorphism look explicitly ruled out for this pass. */
.vs-vibe-tile {
	background-color: var(--vs-card-tint);
	background-image: linear-gradient(160deg, rgba(255, 255, 255, .5), rgba(255, 255, 255, .16));
	border: 1px solid rgba(255, 255, 255, .55);
	border-radius: 18px;
	overflow: hidden;
	padding-bottom: 10px;
	backdrop-filter: blur(6px) saturate(1.05);
	-webkit-backdrop-filter: blur(6px) saturate(1.05);
	box-shadow: 0 10px 22px rgba(120, 72, 48, .12), 0 2px 6px rgba(120, 72, 48, .06);
}

.vs-vibe-tile__img {
	display: block;
	overflow: hidden;
	/* aspect-ratio (not a fixed px height) keeps the crop shape identical
	   regardless of tile width - a fixed height alone breaks badly on wide
	   viewports, where a 2-up grid inside this section's 1080px max-width
	   gives each tile ~530px of width against the same capped height,
	   producing a ~4:1 sliver crop instead of a recognizable portrait
	   (caught during prototyping via real getBoundingClientRect
	   measurement, not assumed). 4:3 keeps the source 600x900 photos
	   readable as faces at any tile width while staying visibly more
	   compact than the source's own 2:3 portrait ratio. */
	aspect-ratio: 4 / 3;
}

.vs-vibe-tile__img img {
	display: block;
	width: 100%;
	height: 100%;
	object-fit: cover;
	object-position: center 22%;
}

/* Refinement pass (2026-08-26): Empowering and Relaxing get their own
   object-position, left untouched for Romantic/Playful per Owner request.
   Diagnosed by inspecting each 600x900 source directly rather than
   guessing: empowering-lady-1.webp has generous empty background below
   the subject (arms-crossed pose sits in the upper half of the frame),
   so the shared center-22% crop was pulling the visible window down into
   mostly chest/cleavage with the face barely included. center-6% biases
   the same fixed box hard toward the top of the source, keeping hair +
   full face + a modest amount of blazer instead. relaxed-woman.webp was
   already reasonably composed (headphones + face + eyes closed all
   visible), just tight at the very top of the headphone band - center-14%
   (down from 22%) gives a touch more headroom without meaningfully
   changing the composition. Box size/aspect-ratio is unchanged for every
   tile - this only moves which part of each fixed-size window shows. */
.vs-vibe-tile--empowering .vs-vibe-tile__img img {
	object-position: center 6%;
}

/* Second follow-up (2026-08-26): the object-fit:contain "matted" treatment
   solved the cropping problem technically but read visually as a framed
   photo inside a card, inconsistent with the other three full-bleed
   tiles - Owner rejected the look, not the underlying fix. Root cause was
   never the CSS, it was the SOURCE: relaxed-woman.webp is 600x900 (0.667
   ratio), a poor match for this 4:3-ish box no matter how it's cropped or
   positioned. Replaced the source image instead (see explore-content.html
   / _prototype-explore-a.html) with an already-existing, already-approved
   asset - media-import/blog/relaxing-sensory-rest-blindfolded-woman-blog-
   grid.webp, 1200x900 (1.333 ratio, i.e. already almost exactly this
   tile's own ~1.353 box ratio) - so a plain object-fit:cover at dead
   center needs only a sliver of horizontal crop, no per-tile position
   tuning required. This override now exists only to document that
   Relaxing intentionally returned to the shared cover/center treatment;
   .vs-vibe-tile__img img's own defaults already produce this, so no
   property actually needs restating here. */

.vs-vibe-tile__label {
	margin: 10px 12px 2px;
	color: var(--vs-color-coral);
	font-size: 12px;
	font-weight: 700;
	letter-spacing: 0.08em;
	text-transform: uppercase;
}

.vs-vibe-tile__microcopy {
	margin: 0 12px;
	color: var(--vs-text-muted);
	font-size: 13px;
	line-height: 1.4;
}

.vs-vibe-hero-note {
	margin: 18px 0 0;
	color: var(--vs-text-muted);
	font-size: 13px;
	font-style: italic;
}

@media (min-width: 700px) {
	.vs-vibe-hero-grid {
		grid-template-columns: repeat(4, 1fr);
	}
}

/* --- Choose Your Path, now a compact wayfinder strip, not a card grid --- */
.vs-wayfinder {
	max-width: 1080px;
	margin: 0 auto;
	padding: 8px 20px 32px;
	text-align: center;
	font-family: var(--vs-font-family);
}

.vs-wayfinder h3 {
	color: var(--vs-text-subheading);
	font-size: 18px;
	font-weight: 600;
	margin: 0 0 16px;
}

.vs-wayfinder-row {
	display: grid;
	grid-template-columns: repeat(2, minmax(0, 1fr));
	gap: 10px;
	max-width: 420px;
	margin: 0 auto;
}

@media (min-width: 560px) {
	.vs-wayfinder-row {
		grid-template-columns: repeat(4, auto);
		max-width: none;
	}
}

/* Wayfinder items are clickable in-page navigation, so per Owner
   direction they use the site's real default .vs-btn system (gradient,
   pill radius, depth shadow, gloss ::before, hover/focus lift) wholesale
   - not a separate secondary/glass pill. Markup carries both
   "vs-wayfinder-item vs-btn"; this rule only scales .vs-btn down to a
   compact size (same pattern as .vs-review-card .vs-btn below). No
   background, border, color, or shadow is set here - all of that comes
   from .vs-btn so it stays visually identical to every other CTA on the
   site. Text wraps rather than truncates at narrow widths (measured:
   "Product Reviews"/"Body-Safe Basics" don't fit on one line in a 2-col
   155px-wide grid cell at genuine 375px) - matches .vs-btn's existing
   default behavior elsewhere (e.g. .vs-review-card .vs-btn below also
   wraps long labels rather than truncating; nowrap+ellipsis was specific
   to the old glass-pill treatment being replaced here). */
.vs-wayfinder-item.vs-btn {
	gap: 5px;
	padding: 10px 8px;
	font-size: 12.5px;
	min-height: 44px;
	line-height: 1.2;
}

.vs-wayfinder-item__icon {
	font-size: 16px;
	line-height: 1;
}

/* --- Crash Flashback interlude ---
   Default state (no class added) is the light, fully readable, fully
   functional panel - this IS the fallback for both no-JS and
   prefers-reduced-motion, by construction, not a separate code path (see
   explore.js). .vs-flashback--play, added once by JS on meaningful
   viewport entry, drives one CSS animation timeline across four layers:
   panel background/text tone, a crack overlay, a ripple overlay, and a
   content opacity dip - all keyframed against the same 0-100% timeline so
   they land in sync without any JS-side timing logic. Crack/ripple are
   pointer-events:none and sit behind the content in stacking order, so
   the CTA is never blocked, even mid-animation. Nothing here changes the
   panel's box size (absolute-positioned overlays only), so there is no
   layout shift. */
/* Same restrained glass treatment as the Vibe tiles, applied via
   background-color (not the background shorthand) specifically so the
   glass sheen (background-image) and blur survive the --play animation
   below untouched - the tone keyframe only animates background-color,
   letting the frosted look persist even once the panel goes dark. */
.vs-flashback {
	position: relative;
	overflow: hidden;
	max-width: 800px;
	margin: 40px auto;
	padding: clamp(32px, 6vw, 48px) 24px;
	border-radius: 24px;
	text-align: center;
	background-color: var(--vs-card-tint);
	background-image: linear-gradient(160deg, rgba(255, 255, 255, .55), rgba(255, 255, 255, .18));
	border: 1px solid rgba(255, 255, 255, .55);
	backdrop-filter: blur(7px) saturate(1.05);
	-webkit-backdrop-filter: blur(7px) saturate(1.05);
	box-shadow: 0 14px 30px rgba(120, 72, 48, .14), 0 3px 8px rgba(120, 72, 48, .07);
	--vs-flashback-dark-bg: #1b1030;
	--vs-flashback-dark-text: #f5eeff;
}

.vs-flashback__crack,
.vs-flashback__ripple {
	position: absolute;
	inset: 0;
	z-index: 2;
	opacity: 0;
	pointer-events: none;
}

/* Realism refinement (2026-08-26): redrawn as a genuine impact-point
   fracture - one origin (170,140) with 8 primary cracks radiating out to
   the panel edges at varied angles/lengths (a real shatter is uneven, not
   symmetric), plus 3 short secondary branches off the primary lines,
   which is what reads as "glass" rather than a decorative starburst. */
.vs-flashback__crack {
	background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 400 300'%3E%3Cg fill='none' stroke='%23f5eeff' stroke-linecap='round'%3E%3Cg stroke-width='1.6' opacity='0.6'%3E%3Cpath d='M170 140 L155 80 L165 20 L140 0'/%3E%3Cpath d='M170 140 L230 100 L280 60 L340 30'/%3E%3Cpath d='M170 140 L250 150 L330 140 L400 160'/%3E%3Cpath d='M170 140 L220 190 L270 240 L310 300'/%3E%3Cpath d='M170 140 L180 200 L160 260 L175 300'/%3E%3Cpath d='M170 140 L110 180 L70 230 L30 280'/%3E%3Cpath d='M170 140 L90 130 L20 145 L0 130'/%3E%3Cpath d='M170 140 L120 90 L70 50 L20 20'/%3E%3C/g%3E%3Cg stroke-width='1' opacity='0.4'%3E%3Cpath d='M230 100 L248 128'/%3E%3Cpath d='M220 190 L188 208'/%3E%3Cpath d='M110 180 L132 212'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E");
	background-size: cover;
	background-position: center;
	mix-blend-mode: overlay;
}

/* Realism refinement: concentric expanding rings (repeating-radial-
   gradient) instead of one soft blob - combined with the existing
   scale/opacity keyframe below, this reads as actual outward-moving
   ripple waves rather than a glow pulsing in place. */
.vs-flashback__ripple {
	background: repeating-radial-gradient(circle at 50% 42%, rgba(195, 155, 255, .4) 0px, rgba(195, 155, 255, .4) 2px, transparent 3px, transparent 20px);
}

.vs-flashback__content {
	position: relative;
	z-index: 3;
}

.vs-flashback__content h2 {
	margin: 0 0 12px;
}

.vs-flashback__content p {
	margin: 0 0 8px;
}

@media (prefers-reduced-motion: no-preference) {
	.vs-flashback--play {
		animation: vs-flashback-tone 2.6s ease forwards;
	}

	.vs-flashback--play .vs-flashback__crack {
		animation: vs-flashback-crack 2.6s ease forwards;
	}

	.vs-flashback--play .vs-flashback__ripple {
		animation: vs-flashback-ripple 2.6s ease forwards;
	}

	.vs-flashback--play .vs-flashback__content {
		animation: vs-flashback-content-fade 2.6s ease forwards;
	}

	/* Colored directly on the heading/paragraph (not inherited from the
	   wrapper) - Astra's own global heading/paragraph rules carry real
	   specificity that would otherwise win over an inherited value. */
	.vs-flashback--play .vs-flashback__content h2 {
		animation: vs-flashback-heading-tone 2.6s ease forwards;
	}

	.vs-flashback--play .vs-flashback__content p {
		animation: vs-flashback-body-tone 2.6s ease forwards;
	}
}

@keyframes vs-flashback-tone {
	0%, 52% { background-color: var(--vs-card-tint); }
	78%, 100% { background-color: var(--vs-flashback-dark-bg); }
}

@keyframes vs-flashback-crack {
	0%, 8% { opacity: 0; }
	18%, 48% { opacity: 1; }
	74%, 100% { opacity: 0; }
}

@keyframes vs-flashback-ripple {
	0%, 12% { opacity: 0; transform: scale(.35); }
	38% { opacity: .85; transform: scale(1.1); }
	68%, 100% { opacity: 0; transform: scale(1.55); }
}

@keyframes vs-flashback-content-fade {
	0%, 28% { opacity: 1; }
	50% { opacity: .05; }
	72%, 100% { opacity: 1; }
}

@keyframes vs-flashback-heading-tone {
	0%, 52% { color: var(--vs-text-heading); }
	78%, 100% { color: var(--vs-flashback-dark-text); }
}

@keyframes vs-flashback-body-tone {
	0%, 52% { color: var(--vs-text-body); }
	78%, 100% { color: var(--vs-flashback-dark-text); }
}

/* Explore redesign, Prototype B (2026-08-27) - lower page. Four new
   silhouettes, deliberately different from each other and from the
   upper-page Vibe tiles, so the page doesn't read as one repeated card
   formula:
     .vs-guide-list  - a compact horizontal row-list (image-left, title +
                        one line, no button - the whole row is the link).
     .vs-review-grid - a proper image-top grid, but square/product-photo
                        framing (object-fit:contain, not cover - these are
                        product-on-white/flat-lay shots, and contain
                        guarantees the product itself is never
                        accidentally cropped, unlike a portrait photo).
     .vs-explore-tips - reuses the existing .vs-faq/.vs-faq-item
                        <details>/<summary> architecture (already built
                        for Blog posts) rather than inventing a new
                        accordion, with a glass-surface override scoped to
                        .vs-explore-tips only - the shared .vs-faq-item
                        rule (used on real Blog posts) is untouched.
     .vs-safety-callout / .vs-explore-quiz-cta - one-off compact blocks,
                        each its own small silhouette. */

.vs-guide-list-section {
	max-width: 760px;
	margin: 0 auto;
	padding: 40px 20px;
	text-align: center;
	font-family: var(--vs-font-family);
}

.vs-guide-list {
	margin-top: 24px;
	display: flex;
	flex-direction: column;
	gap: 10px;
	text-align: left;
}

.vs-guide-row {
	display: flex;
	align-items: center;
	gap: 14px;
	background-color: var(--vs-card-tint);
	background-image: linear-gradient(160deg, rgba(255, 255, 255, .5), rgba(255, 255, 255, .16));
	border: 1px solid rgba(255, 255, 255, .55);
	border-radius: 16px;
	padding: 10px 14px 10px 10px;
	backdrop-filter: blur(6px) saturate(1.05);
	-webkit-backdrop-filter: blur(6px) saturate(1.05);
	box-shadow: 0 8px 18px rgba(120, 72, 48, .10), 0 2px 5px rgba(120, 72, 48, .05);
	text-decoration: none;
	transition: transform .12s ease, box-shadow .12s ease;
}

.vs-guide-row:hover,
.vs-guide-row:focus-visible {
	transform: translateY(-2px);
	box-shadow: 0 12px 24px rgba(120, 72, 48, .14), 0 3px 7px rgba(120, 72, 48, .07);
}

.vs-guide-row__img {
	flex: 0 0 64px;
	width: 64px;
	height: 64px;
	border-radius: 12px;
	overflow: hidden;
	background: rgba(255, 255, 255, .45);
	display: flex;
	align-items: center;
	justify-content: center;
}

.vs-guide-row__img img {
	display: block;
	width: 100%;
	height: 100%;
	object-fit: cover;
}

/* Placeholder for guides with no confirmed existing asset yet (see the
   Owner-facing asset map) - an icon, not an invented photo, so it never
   pretends to be a real image. */
.vs-guide-row__img--pending {
	font-size: 22px;
}

.vs-guide-row__body {
	flex: 1 1 auto;
	min-width: 0;
}

.vs-guide-row__eyebrow {
	color: var(--vs-color-coral);
	font-size: 11px;
	font-weight: 700;
	letter-spacing: .06em;
	text-transform: uppercase;
	margin: 0 0 2px;
}

.vs-guide-row__title {
	color: var(--vs-text-heading);
	font-size: 16px;
	font-weight: 600;
	margin: 0 0 3px;
}

.vs-guide-row__desc {
	color: var(--vs-text-body);
	font-size: 13px;
	line-height: 1.4;
	margin: 0;
}

.vs-guide-row__arrow {
	flex: 0 0 auto;
	color: var(--vs-color-coral);
	font-size: 18px;
	font-weight: 700;
}

/* --- Product Reviews & Picks: image-top grid, product/flat-lay framing --- */
.vs-review-section {
	max-width: 1080px;
	margin: 0 auto;
	padding: 40px 20px;
	text-align: center;
	font-family: var(--vs-font-family);
}

.vs-review-grid {
	display: grid;
	grid-template-columns: repeat(2, 1fr);
	gap: 14px;
	margin-top: 24px;
	text-align: left;
}

@media (min-width: 700px) {
	.vs-review-grid {
		grid-template-columns: repeat(4, 1fr);
	}
}

.vs-review-card {
	background-color: var(--vs-card-tint);
	background-image: linear-gradient(160deg, rgba(255, 255, 255, .5), rgba(255, 255, 255, .16));
	border: 1px solid rgba(255, 255, 255, .55);
	border-radius: 18px;
	overflow: hidden;
	backdrop-filter: blur(6px) saturate(1.05);
	-webkit-backdrop-filter: blur(6px) saturate(1.05);
	box-shadow: 0 10px 22px rgba(120, 72, 48, .12), 0 2px 6px rgba(120, 72, 48, .06);
	padding-bottom: 12px;
	/* Batch 3 (button remediation, 2026-08-29): flex-column + margin-top:auto
	   on .vs-btn below, same equal-bottom pattern already used by
	   .vs-path-card/.vs-mini-card, so all 4 review cards' CTAs share one
	   bottom baseline regardless of title/microcopy line count. */
	display: flex;
	flex-direction: column;
}

/* Refinement pass (2026-08-27, round 2): pixel-sampled the actual source
   photos - three of the four (FERRI, candle, headphones-hands) are shot on
   near-BLACK backgrounds, not white as first assumed; the black runs 80-
   120px deep into the 800x800 frame, well past the old 16px padding, so it
   was fully visible at the card's corners. A CSS filter can't turn black
   into peach without also crushing the product's own true colors, so
   instead of touching the filter, a peach vignette overlay (::after,
   painted above the <img>) fades the outer ring of the box toward the
   wrapper's own peach tone - transparent at the center where the product
   sits, warm at the edges where the raw background would otherwise show.
   Padding also nudged up slightly for a touch more breathing room/mat
   feel, closer to the Vibe-tile card language. */
.vs-review-card__img {
	position: relative;
	display: block;
	aspect-ratio: 1 / 1;
	overflow: hidden;
	background: linear-gradient(160deg, rgba(255, 226, 204, .9), rgba(255, 205, 178, .65));
}

.vs-review-card__img::after {
	content: "";
	position: absolute;
	inset: 0;
	pointer-events: none;
	background: radial-gradient(ellipse at center, transparent 52%, rgba(255, 214, 186, .5) 82%, rgba(255, 198, 166, .78) 100%);
}

.vs-review-card__img img {
	display: block;
	width: 100%;
	height: 100%;
	object-fit: contain;
	padding: 22px;
	box-sizing: border-box;
	filter: sepia(.12) saturate(1.08) brightness(.99);
}

/* FERRI photo specifically read as a tiny floating icon - its source is
   an 800x800 dark-background shot where the product occupies only a
   small centered fraction of the frame (confirmed via pixel bounding-box:
   content spans roughly x 42-58%, y 32-71% of the source). Box ratio
   already matches the source ratio exactly (1:1), so object-fit alone
   can't enlarge it - cover and contain produce the same result when the
   ratios match. A deliberate zoom (scale, not a crop-position change) is
   the only way to make the product fill more of the frame. Scale raised
   from 1.85 to 2.15 (still comfortably under the ~2.33 point where the
   product's own tip would start clipping, computed from that bounding
   box) for more presence per Owner feedback; the card's own
   overflow:hidden (existing rule) clips the overscan cleanly. Padding
   stays 0 here (unlike the other three cards) so the zoom has the full
   frame to work with - the vignette above still softens its corners. */
.vs-review-card--ferri .vs-review-card__img img {
	padding: 0;
	transform: scale(2.15);
}

/* Beginner Vibrator Guide photo (replaced 2026-08-27, see the img src
   comment above) has its own light peach/lavender studio background,
   unlike FERRI/candle/headphones' dark backgrounds - the shared corner
   vignette (a color tint layered on top) can soften a dark edge but can't
   remove a light image's own hard rectangular boundary against the
   wrapper's peach gradient, which was reading as a "picture in a frame."
   A radial alpha mask feathers the photo's own edges to transparent
   instead of tinting them, so the wrapper's peach shows through smoothly
   with no seam - scoped to this one card only, the other three keep their
   existing (already-approved) treatment untouched.

   Round 2 (2026-08-27): Owner still read the first pass (fade starting at
   62% radius) as too boxed. Widened the transition so the fade begins
   noticeably earlier (45%) and reaches further out (88% ellipse) - a
   longer, more gradual dissolve instead of "sharp until near the edge."
   Paired with a card-specific, stronger peach overlay (below) so the
   blend reads warm, not just faded. Center of the frame, where the
   products themselves sit, stays fully opaque - only the outer third
   softens, so product readability is unaffected. */
.vs-review-card--beginner .vs-review-card__img img {
	-webkit-mask-image: radial-gradient(ellipse 88% 88% at center, #000 45%, transparent 100%);
	mask-image: radial-gradient(ellipse 88% 88% at center, #000 45%, transparent 100%);
}

.vs-review-card--beginner .vs-review-card__img::after {
	background: radial-gradient(ellipse at center, transparent 38%, rgba(255, 214, 186, .55) 70%, rgba(255, 198, 166, .85) 100%);
}

.vs-review-card__title {
	margin: 8px 12px 2px;
	color: var(--vs-text-heading);
	font-size: 14px;
	font-weight: 600;
	line-height: 1.3;
}

.vs-review-card__microcopy {
	margin: 0 12px 8px;
	color: var(--vs-text-muted);
	font-size: 12px;
	font-style: italic;
}

/* Refinement pass (2026-08-27): the plain coral text-link CTA no longer
   matched the site's default button language, now already established for
   Body-Safe Basics (.vs-safety-callout .vs-btn). Same real .vs-btn used
   there, sized down further here since review cards are narrower (~160px
   at 2-column mobile width vs a full-width safety callout) - smaller font
   and tighter padding than the .vs-mini-card/.vs-safety-callout compact
   size so the longer CTA strings ("Read the mood-setting story ->") still
   fit cleanly without an awkward mid-word wrap.

   Batch 3 correction (button remediation, 2026-08-29): Owner QA flagged
   these 4 CTAs as too small/badge-like versus the rest of the site's
   compact CTAs (.vs-path-card/.vs-mini-card at 14px/10px 18px) - the
   10px/7px 10px sizing above went past "compact" into a genuinely
   different, undersized button language. Restored to the same 14px
   readable-type family used everywhere else; padding kept slightly
   tighter (10px 14px, not 18px) than .vs-path-card/.vs-mini-card only
   because this card is measurably narrower, not to shrink the type.
   align-self:flex-start + margin-top:auto is the same bottom-alignment
   pattern already in use on .vs-path-card/.vs-mini-card above, now
   applied here now that the parent is flex-column (see .vs-review-card). */
.vs-review-card .vs-btn {
	margin-left: 12px;
	margin-right: 12px;
	margin-top: auto;
	align-self: flex-start;
	font-size: 14px;
	padding: 10px 14px;
	min-height: 0;
	line-height: 1.3;
	text-align: center;
}

/* --- Mood & Sensation Tips: reuses .vs-faq, glass-scoped to Explore only --- */
.vs-explore-tips {
	max-width: 720px;
}

.vs-explore-tips .vs-faq-item {
	background-color: var(--vs-card-tint);
	background-image: linear-gradient(160deg, rgba(255, 255, 255, .45), rgba(255, 255, 255, .14));
	border: 1px solid rgba(255, 255, 255, .5);
	backdrop-filter: blur(5px);
	-webkit-backdrop-filter: blur(5px);
	box-shadow: 0 6px 14px rgba(120, 72, 48, .08);
}

/* --- Body-Safe Basics: a small dedicated safety callout, not an accordion
   row or an orphaned anchor. Consolidates the two safety-adjacent tips
   (lube/comfort, protection) into one clearly-labeled block instead of
   duplicating them as both accordion rows and a callout - see the Owner
   report for the full reasoning. --- */
.vs-safety-callout {
	max-width: 720px;
	margin: 20px auto 0;
	padding: 22px 24px;
	border-radius: 18px;
	text-align: left;
	font-family: var(--vs-font-family);
	background-color: var(--vs-card-tint);
	background-image: linear-gradient(160deg, rgba(255, 255, 255, .5), rgba(255, 255, 255, .16));
	border: 1px solid rgba(255, 255, 255, .55);
	backdrop-filter: blur(6px) saturate(1.05);
	-webkit-backdrop-filter: blur(6px) saturate(1.05);
	box-shadow: 0 10px 22px rgba(120, 72, 48, .10), 0 2px 6px rgba(120, 72, 48, .05);
}

.vs-safety-callout__eyebrow {
	display: flex;
	align-items: center;
	gap: 8px;
	color: var(--vs-color-coral);
	font-size: 12px;
	font-weight: 700;
	letter-spacing: .06em;
	text-transform: uppercase;
	margin: 0 0 8px;
}

.vs-safety-callout h4 {
	color: var(--vs-text-heading);
	font-size: 17px;
	font-weight: 700;
	margin: 0 0 14px;
}

/* Refinement pass (2026-08-27): Owner mobile QA found the previous
   inline-link-inside-a-sentence presentation unclear about what was
   actually clickable. Each item now separates into three distinct,
   unambiguous roles: a bold (non-link) title, one line of plain-text
   copy, and one explicit "Read [Title] ->" CTA link - so the tappable
   element is never in question. */
.vs-safety-callout__items {
	display: flex;
	flex-direction: column;
	gap: 16px;
}

.vs-safety-callout__item-title {
	color: var(--vs-text-heading);
	font-size: 15px;
	font-weight: 700;
	margin: 0 0 4px;
}

.vs-safety-callout__item-copy {
	color: var(--vs-text-body);
	font-size: 14px;
	line-height: 1.6;
	margin: 0 0 8px;
}

/* Refinement pass 2 (2026-08-27): Owner mobile QA found the plain coral
   text-link CTA too weak/ambiguous next to the section's plain-text title
   and copy - if something is clickable it should look clickable. Swapped
   for the site's real default .vs-btn (same pill/gradient button used
   everywhere else), scaled down using the exact same compact-card pattern
   already established for .vs-mini-card .vs-btn, so this isn't a new
   button style - it's the existing one at the existing compact size. */
.vs-safety-callout .vs-btn {
	margin-top: 4px;
	font-size: 14px;
	padding: 10px 18px;
	min-height: 0;
}

/* --- Closing CTA: cross-link into the existing Quiz --- */
.vs-explore-quiz-cta {
	max-width: 600px;
	margin: 48px auto 20px;
	padding: 32px 24px;
	border-radius: 24px;
	text-align: center;
	font-family: var(--vs-font-family);
	background-color: var(--vs-card-tint);
	background-image: linear-gradient(160deg, rgba(255, 255, 255, .55), rgba(255, 255, 255, .2));
	border: 1px solid rgba(255, 255, 255, .55);
	backdrop-filter: blur(7px) saturate(1.05);
	-webkit-backdrop-filter: blur(7px) saturate(1.05);
	box-shadow: 0 14px 28px rgba(120, 72, 48, .12), 0 3px 8px rgba(120, 72, 48, .06);
}

.vs-explore-quiz-cta h3 {
	color: var(--vs-text-heading);
	font-size: 22px;
	font-weight: 700;
	margin: 0 0 8px;
}

.vs-explore-quiz-cta p {
	color: var(--vs-text-body);
	font-size: 15px;
	margin: 0 0 18px;
}

.vs-manifesto-teaser,
.vs-path-section,
.vs-guide-grid,
.vs-vibe-grid {
	max-width: 1080px;
	margin: 0 auto;
	padding: 40px 20px;
	text-align: center;
	font-family: var(--vs-font-family);
}

.vs-voice-tabs {
	display: flex;
	justify-content: center;
	gap: 10px;
	margin: 16px 0 24px;
}

.vs-voice-tab {
	font-size: 14px;
	padding: 8px 18px;
}

.vs-path-grid,
.vs-card-row {
	display: flex;
	flex-wrap: wrap;
	gap: 24px;
	margin-top: 24px;
	text-align: left;
}

/* Batch 4 (glass-card system restoration, 2026-08-29): this was the
   older/flat card generation - solid var(--vs-card-tint) fill, no border,
   no blur, no shadow - predating the warm-glass recipe established for
   Explore's .vs-vibe-tile/.vs-guide-row/.vs-review-card (2026-08-26/27).
   Two visual generations coexisted because Explore was redesigned in
   place (new classes) while these two, still consumed by Result pages
   (.vs-mini-card x3 per page - "Core traits"/"Tiny moves"/"Keep
   exploring"), Vibe Picks (.vs-mini-card x8 - real product recommendation
   cards), and Community (.vs-mini-card x4 - the transformation/story
   cards, NOT resized here per Owner instruction), were never touched
   during that pass. .vs-path-card has zero live consumers site-wide
   (confirmed via fetch+regex across Home/Manifesto/About/Contact/Shop) -
   updated alongside .vs-mini-card only because they share this rule, not
   because it's rendering anywhere. Same canonical recipe as .vs-vibe-tile
   etc., border-radius kept at the existing 20px (already inside the
   approved 16-24px range, no dimension change). */
.vs-path-card,
.vs-mini-card {
	flex: 1 1 240px;
	background-color: var(--vs-card-tint);
	background-image: linear-gradient(160deg, rgba(255, 255, 255, .5), rgba(255, 255, 255, .16));
	border: 1px solid rgba(255, 255, 255, .55);
	border-radius: 20px;
	padding: 24px;
	backdrop-filter: blur(6px) saturate(1.05);
	-webkit-backdrop-filter: blur(6px) saturate(1.05);
	box-shadow: 0 10px 22px rgba(120, 72, 48, .12), 0 2px 6px rgba(120, 72, 48, .06);
	display: flex;
	flex-direction: column;
}

.vs-path-card h4,
.vs-mini-card h4 {
	color: var(--vs-text-subheading);
	font-size: 18px;
	font-weight: 600;
	margin: 0 0 8px;
}

.vs-path-card p,
.vs-mini-card p {
	color: var(--vs-text-body);
	font-size: 15px;
	line-height: 1.55;
	margin: 0 0 12px;
}

.vs-mini-card img {
	border-radius: 14px;
	width: 100%;
	height: auto;
	object-fit: cover;
	margin-bottom: 14px;
}

.vs-eyebrow {
	color: var(--vs-color-coral);
	font-size: 12px;
	font-weight: 700;
	letter-spacing: 0.1em;
	text-transform: uppercase;
	margin: 0 0 6px !important;
}

.vs-microcopy {
	color: var(--vs-text-muted);
	font-style: italic;
	font-size: 14px !important;
}

.vs-coming-soon {
	color: var(--vs-text-muted);
	font-size: 14px !important;
	font-weight: 600;
}

.vs-path-card .vs-btn,
.vs-mini-card .vs-btn {
	align-self: flex-start;
	margin-top: auto;
	font-size: 14px;
	padding: 10px 18px;
	/* Visual Batch 1 (2026-08-25): reset the new sitewide .vs-btn
	   min-height:52px back to content-based sizing here - these
	   deliberately-compact in-card CTAs measured 52px tall (forced) after
	   the base-button parity fix, visibly bulkier than their original
	   ~36px card-scale size. Shadow/hover/underline-fix are kept (still
	   wanted here), only the height constraint is scoped back down. */
	min-height: 0;
}

/* Vibe Manifesto */

.vs-manifesto {
	max-width: 640px;
}

.vs-manifesto h1 {
	color: var(--vs-text-heading);
	font-size: 36px;
	font-weight: 700;
	margin: 0 0 20px;
}

.vs-manifesto-quote {
	margin: 24px auto;
	padding: 20px 24px;
	border-left: 4px solid var(--vs-color-coral);
	background: #fff;
	border-radius: 0 16px 16px 0;
	text-align: left;
}

.vs-manifesto-quote p {
	font-style: italic;
	font-weight: 600;
	color: var(--vs-text-heading);
}

.vs-manifesto-quote span {
	display: inline-block;
	margin-top: 6px;
	font-style: normal;
	font-weight: 400;
	color: var(--vs-text-muted);
	font-size: 14px;
}

/* About page */

.vs-about {
	max-width: 720px;
	text-align: left;
}

.vs-about h1 {
	color: var(--vs-text-heading);
	font-size: 36px;
	font-weight: 700;
	text-align: center;
	margin: 0 0 20px;
}

.vs-about ul {
	color: var(--vs-text-body);
	font-size: 16px;
	line-height: 1.7;
	padding-left: 22px;
	margin: 0 0 20px;
}

.vs-about .vs-cta {
	text-align: center;
}

/* Contact page */

.vs-contact {
	max-width: 720px;
	text-align: left;
}

.vs-contact h1 {
	color: var(--vs-text-heading);
	font-size: 34px;
	font-weight: 700;
	text-align: center;
	margin: 0 0 16px;
}

.vs-contact h2 {
	color: var(--vs-text-subheading);
	font-size: 22px;
	font-weight: 600;
	text-align: left;
	margin: 28px 0 10px;
}

.vs-contact .vs-cta {
	text-align: left;
	justify-content: flex-start;
}

.vs-calendly-embed {
	margin: 20px 0;
	border-radius: 16px;
	overflow: hidden;
}

.vs-email {
	font-size: 18px;
	font-weight: 600;
	color: var(--vs-text-heading);
}

/* Shop pages */

.vs-shop-hero {
	max-width: 720px;
}

.vs-shop-hero h1 {
	color: var(--vs-text-heading);
	font-size: 40px;
	font-weight: 700;
	margin: 6px 0 16px;
}

.vs-signature .vs-card-row {
	justify-content: center;
}

.vs-disclaimer {
	max-width: 720px;
	margin: 24px auto 0;
	color: var(--vs-text-muted);
	font-size: 13px;
	text-align: center;
}

/* Vibe result pages */

/* Batch 6 (Relaxing hero/SOMA separation, 2026-08-29): root cause of the
   Owner-reported "hero and SOMA merged/side-by-side" defect was pure
   markup - the approved new Relaxing hero portrait (relaxing-result-
   hero.webp) had been placed as a sibling <img> INSIDE the SOMA
   "Breathe Together, Gently" .vs-card--tint section, immediately next to
   the SOMA seminar image (relaxing_soma.webp) - both bare <img
   class="vs-symbol-icon"> with no layout CSS (no rule targets a bare
   .vs-symbol-icon; it only gets sizing when scoped under .vs-symbol-card
   or .vs-hero >, neither of which matched here), so both rendered
   inline/side-by-side at their native flow position. Fixed by moving the
   hero <img> into its own .vs-shop-hero section (content-only change, no
   CSS needed there - this exactly matches the working structure already
   used by Playful/Empowering/Romantic's hero sections). This rule only
   covers the residual "SOMA sits too close to hero" spacing gap: every
   section-to-section pair on Result pages measured a true 0px gap
   (box-edges touch, each side's own 40px .vs-card padding is the only
   separation) - consistent site behavior, not unique to Relaxing, so
   left untouched globally. Scoped via body.page-id-1347 (Relaxing has no
   slug-based body class available, unlike page-community elsewhere) +
   the hero-adjacent-sibling selector, so this does NOT apply to Playful/
   Empowering/Romantic's own (different-content, not Owner-flagged)
   first .vs-card--tint section. 40px reuses this file's own established
   section-rhythm value (same as .vs-card's own padding, Batch 3B's
   FAQ-to-CTA gap) rather than a new number. */
body.page-id-1347 .vs-shop-hero + .vs-card--tint {
	margin-top: 40px;
}

.vs-testimonials {
	max-width: 1000px;
}

.vs-testimonials .vs-card-row {
	justify-content: center;
}

.vs-testimonials .vs-manifesto-quote {
	flex: 1 1 220px;
	margin: 0;
}

.vs-result-nav {
	max-width: 900px;
	margin: 20px auto 40px;
	text-align: center;
}

.vs-result-nav .vs-cta {
	display: flex;
	flex-wrap: wrap;
	gap: 12px;
	justify-content: center;
}

/* Quiz page — ported verbatim from production's scoped <style> block
   next to #quiz-root (Merriweather font + custom card/button styling,
   distinct from the site's usual .vs-btn/vs-card look - preserved as-is,
   not unified, since this is migration not redesign). */

#quiz-root.vs-quiz-page {
	max-width: min(1100px, 92vw);
	margin: 24px auto;
	padding: 0 8px;
	font-family: 'Merriweather', Georgia, serif;
	color: #1f2937;
}

#quiz-root .quiz-question {
	background: var(--vs-card-bg, rgba(255,127,127,.075));
	border: 1px solid var(--vs-card-border, rgba(17,24,39,.06));
	border-radius: var(--vs-card-radius, 24px);
	box-shadow: var(--vs-card-shadow, 0 18px 40px rgba(0,0,0,.10), 0 3px 10px rgba(0,0,0,.06));
	padding: clamp(18px, 3.2vw, 36px);
	margin: 18px 0;
	text-align: center;
}

#quiz-root h3 {
	margin: 0 0 16px;
	font-weight: 800;
	font-size: clamp(20px, 2.6vw, 26px);
}

#quiz-root .answers {
	display: flex;
	flex-wrap: wrap;
	gap: 12px;
	justify-content: center;
}

#quiz-root .quiz-answer-button {
	border: 0;
	border-radius: 999px;
	padding: 14px 22px;
	min-height: 52px;
	font: 700 15px/1.15 'Poppins', system-ui, sans-serif;
	color: #fff;
	cursor: pointer;
	position: relative;
	background: var(--vs-grad, linear-gradient(135deg, #FF7F7F, #C39BFF));
	box-shadow: var(--vs-btn-shadow, 0 10px 18px rgba(0,0,0,.18), 0 3px 6px rgba(0,0,0,.10));
	transition: transform .12s ease, box-shadow .12s ease;
}

#quiz-root .quiz-answer-button::before {
	content: "";
	position: absolute;
	left: 8px; right: 8px; top: 5px;
	height: 42%;
	border-radius: 999px;
	background: linear-gradient(to bottom, rgba(255,255,255,.28), rgba(255,255,255,0));
	pointer-events: none;
}

#quiz-root .quiz-answer-button:hover {
	transform: translateY(-2px);
	box-shadow: var(--vs-btn-shadow-hover, 0 14px 26px rgba(0,0,0,.20), 0 6px 12px rgba(0,0,0,.12));
}

#quiz-root .quiz-answer-button:active {
	transform: translateY(1px);
	box-shadow: var(--vs-btn-shadow-active, 0 6px 12px rgba(0,0,0,.14), 0 2px 5px rgba(0,0,0,.08));
}

@media (prefers-reduced-motion: reduce) {
	#quiz-root .quiz-answer-button {
		transition: none;
	}
}

#quiz-root .quiz-hidden {
	display: none !important;
	visibility: hidden !important;
	opacity: 0 !important;
	height: 0 !important;
}
#quiz-root .quiz-question.show {
	display: block !important;
	visibility: visible !important;
	opacity: 1 !important;
	height: auto !important;
}

#quiz-root [id^="quiz-q"] {
	scroll-margin-top: var(--vs-sticky-offset, 110px);
}

/* Blog archive card grid images - Visual Batch 1B (2026-08-26).

   The /blog/ archive is rendered by an Elementor Pro "Posts" widget (not a
   native Astra/Gutenberg template), wrapped in the unique class
   .vs-blog-grid. Source-confirmed via computed style + document.styleSheets
   inspection (not inferred from class names) that card image cropping came
   entirely from Elementor's own bundled widget-posts.min.css combined with
   a page-specific generated file (post-4260.css) using a padding-bottom
   aspect-ratio box: 66% desktop, 50% mobile (<=767px). That desktop/mobile
   mismatch was itself part of the Owner's complaint - the same image
   cropped measurably harder on mobile than desktop. Elementor centers the
   crop at dead-center via its own scale/translate math, which on several
   sampled portrait/square-source images cut into faces/heads.

   Fix approved by Owner (Visual Batch 1B ratio comparison, three options
   tested live against Production: 66/82/100%): one consistent 82%
   image-box ratio at every breakpoint, rendered via native
   object-fit:cover with a slight upward object-position bias (matches how
   the sampled face photos are actually composed) instead of Elementor's
   dead-center crop. img stays position:absolute/inset:0 - required for
   the padding-bottom aspect-ratio box to render at all (confirmed live:
   position:static made the image contribute its own flow height on top of
   the padding, inflating the wrapper instead of filling it). The wrapper
   itself needs no position rule here - confirmed via getComputedStyle that
   .elementor-post__thumbnail already computes position:relative from
   Elementor's own base CSS.

   !important is necessary (source-backed, not a default choice): this
   overrides Elementor Pro's own generated widget-posts.min.css and
   post-4260.css rules, which this theme does not control. Scoped to
   .vs-blog-grid, confirmed as the unique class wrapping only this one
   Posts widget instance on the whole site - cannot affect any other
   Elementor Posts widget, and does not touch individual post pages (those
   render featured images through the separate .vs-rbg-hero mechanism,
   confirmed unaffected).

   Per Owner's explicit acceptance boundary: this materially reduces but
   does not mathematically eliminate cropping on extreme portrait sources
   (still ~45% of frame height cropped on a 0.67-ratio image, down from
   ~56% at Elementor's own default). The goal is a coherent, non-awkward
   Blog grid, not zero crop for every possible future image - future
   poorly-composing sources should be handled via Featured Image selection,
   not per-post CSS overrides. */
.vs-blog-grid .elementor-posts-container .elementor-post__thumbnail {
	padding-bottom: 82% !important;
}

.vs-blog-grid .elementor-posts-container .elementor-post__thumbnail img {
	position: absolute !important;
	inset: 0 !important;
	width: 100% !important;
	height: 100% !important;
	transform: none !important;
	object-fit: cover !important;
	object-position: center 25% !important;
}

/* Blog posts - long-form article body. Reuses .vs-card's h2/h3/p rules
   (combine as class="vs-card vs-post"), same left-aligned/720px pattern
   as .vs-about, plus an optional in-content h1 for posts whose production
   source hides the theme's native title bar (site-post-title: disabled). */

.vs-post {
	max-width: 720px;
	text-align: left;
}

.vs-post h1 {
	color: var(--vs-text-heading);
	font-size: 36px;
	font-weight: 700;
	text-align: center;
	margin: 0 0 20px;
}

.vs-post h2 {
	margin-top: 32px;
}

.vs-post ul,
.vs-post ol {
	color: var(--vs-text-body);
	font-size: 16px;
	line-height: 1.7;
	padding-left: 22px;
	margin: 0 0 20px;
}

.vs-post figure {
	margin: 24px 0;
}

.vs-post figure img {
	display: block;
	width: 100%;
	height: auto;
	border-radius: 16px;
}

.vs-post figure figcaption {
	color: var(--vs-text-muted);
	font-size: 13px;
	text-align: center;
	margin-top: 8px;
}

/* Blog post FAQ accordions - native <details>/<summary>, no JS needed. */

.vs-faq {
	max-width: 720px;
	margin: 8px auto 40px;
	text-align: left;
}

.vs-faq h2 {
	color: var(--vs-text-subheading);
	font-size: 24px;
	font-weight: 600;
	margin: 0 0 16px;
}

.vs-faq-item {
	background: var(--vs-card-tint);
	border-radius: 16px;
	padding: 4px 20px;
	margin: 0 0 12px;
}

.vs-faq-item summary {
	color: var(--vs-text-heading);
	font-weight: 600;
	font-size: 16px;
	padding: 14px 0;
	cursor: pointer;
	list-style: none;
}

.vs-faq-item summary::-webkit-details-marker {
	display: none;
}

.vs-faq-item summary::after {
	content: "+";
	float: right;
	font-weight: 700;
	color: var(--vs-color-coral);
}

.vs-faq-item[open] summary::after {
	content: "\2212";
}

.vs-faq-item p {
	color: var(--vs-text-body);
	font-size: 15px;
	line-height: 1.6;
	margin: 0 0 16px;
}

/* Batch 3B (Community "10-Minute Vibe Guide" / FAQ cluster, 2026-08-29) */

/* Owner QA: the first 4 vibe buttons (Empowering/Playful/Relaxing/Romantic)
   are plain auto-width .vs-btn, so shorter labels (Playful, measured
   106px) sat visibly narrower than longer ones (Empowering, 153px) - read
   as accidental sizing rather than a designed group. min-width equalizes
   all 4 to the widest label's own natural width (153px, rounded up for
   breathing room); "Open the full guide" (5th button, longer label,
   203px) is deliberately left auto-width so it stays wider on purpose,
   not coincidentally - same .vs-btn family/gradient/radius/depth
   throughout, only width changes. Scoped to #guide only. */
#guide .vs-cta a.vs-btn:nth-child(-n+4) {
	min-width: 160px;
}

/* Owner QA: FAQ rows stretched near edge-to-edge (measured 1901px on a
   1920px viewport, computed max-width: none). Root cause: Astra's own
   inline Page-Builder-template style
   (".ast-page-builder-template .entry-content[data-ast-blocks-layout] > *")
   resets max-width:none on every direct child of the content area,
   including #faq - it beats .vs-faq's own existing max-width:720px rule
   above purely on selector specificity (2 classes + 1 attribute selector
   outranks 1 class), even though .vs-faq is defined later in this file.
   #guide/#join don't visibly show the same reset because their content
   has no full-width background fill; #faq's accordion rows do (var(
   --vs-card-tint) background), so the stretch was only visible there.
   This re-asserts the SAME 720px value .vs-faq already declares (not a
   new number) with enough specificity (id + 2 classes) to win reliably -
   !important added defensively, matching this file's established pattern
   for beating Astra/Elementor injected styles (see footer.css's MailerLite
   override for the same rationale). */
body.page-community #faq.vs-faq {
	max-width: 720px !important;
	margin: 8px auto 40px !important;
}

/* Owner QA: the closing .vs-cta (Get Vibe Notes / Peek the guide) sat only
   12px below the last FAQ row - adjacent block margins collapsed down to
   the last .vs-faq-item's own 12px bottom margin instead of the base
   .vs-cta rule's 20px top margin, reading as if the buttons belonged to
   the last FAQ row rather than a new action zone. 40px matches this
   file's own established section-level rhythm (.vs-card's padding,
   .vs-faq's own bottom margin above) rather than an arbitrary gap. */
body.page-community #faq > .vs-cta {
	margin-top: 40px;
}

/* Batch 5 (Community layout + responsive normalization, 2026-08-29)

   ROOT CAUSE, confirmed via the same cascade-inspection technique used for
   the Batch 3B FAQ-width bug: Astra's own inline Page-Builder-template rule
   (".ast-page-builder-template .entry-content[data-ast-blocks-layout] > *")
   resets max-width:none on EVERY direct child of the content area. This is
   not unique to #faq - live-checked and confirmed it also strips the
   intended max-width from .vs-hero (720px -> computed "none", rendered
   1901px on a 1920px viewport) and #highlights.vs-guide-grid (1080px ->
   "none", rendered 1901px) - both lose the cascade fight purely on
   selector specificity (2 classes + 1 attribute selector outranks 1
   class), same as #faq before its Batch 3B fix. This alone is why the
   transformation cards measured ~447px wide: #highlights' .vs-card-row is
   flex with .vs-mini-card at flex:1 1 240px (flex-grow:1) - handed an
   unconstrained ~1861px-wide row instead of the intended ~1000px, the 4
   cards simply grew to fill it ((1861 - 3*24px gaps) / 4 = 447px). #join/
   #challenge show the identical "none" computed max-width too (confirmed
   live) but are NOT touched here - out of this batch's explicit scope
   (only the transformation-card grid + hero/symbol-icon were named as
   defects), flagged in the batch report instead of fixed pre-emptively.
   .vs-hero is fixed here because it's a direct prerequisite for the
   "coherent, not-dominant" page feel this batch targets, and because an
   unconstrained hero directly contradicts the design system's own
   intended 720px reading-column width for intro copy. */
body.page-community .vs-hero {
	max-width: 720px !important;
}

body.page-community #highlights.vs-guide-grid {
	max-width: 1080px !important;
}

/* Batch 5B (2026-08-29): flagged-but-deferred in Batch 5's report - #join
   ("Stay in the Vibe" newsletter) and #challenge ("7-Day Starter
   Challenge") are both hit by the identical Astra max-width:none reset
   (confirmed live: computed max-width "none", rendered 1901px on a
   1920px viewport). Both carry class="vs-card" only, so their intended,
   already-authored width is .vs-card's own 720px - the same value
   already restored for .vs-hero and #faq - not a new number. */
body.page-community #join.vs-card,
body.page-community #challenge.vs-card {
	max-width: 720px !important;
}

/* Restoring #highlights' own max-width above already shrinks the 4 cards
   from ~447px to ~242px through the SAME existing flex:1 1 240px math
   (no .vs-mini-card change needed for the width fix itself) - this
   matches Explore's already-approved .vs-review-grid 4-up reference width
   (~250px cards in the same 1080px container) rather than an invented
   number. max-width here is a deliberate ceiling so this stays true even
   if the container's available width ever changes again, without
   depending solely on incidental flex math - Community-scoped
   (#highlights prefix) precisely because .vs-mini-card's own flex-basis/
   flex-grow are shared with Result pages (x3 each) and Vibe Picks (x8),
   and this batch's brief explicitly requires Community-only geometry
   changes to that shared class.

   Batch 5B correction (2026-08-29): this ceiling was originally
   unconditional, which also shrank genuine ~375px mobile cards from their
   natural ~312px (filling the single-column mobile row width) down to a
   capped 260px - not the intent; mobile card width was never the
   complaint, only desktop's 447px was. Live-tested the actual flex-wrap
   transition (off-screen iframes at 10px increments, not guessed): with
   no cap, cards stay genuinely single-column up to ~560-600px, then the
   row naturally wraps to 2-up, then to a clean 4-up only once available
   row width reaches ~1032px (matches 4 x 240px flex-basis + 3 x 24px
   gaps exactly) - a continuous, cap-independent transition with no
   "wrong number of columns" risk at any width. 700px reused as the
   min-width gate (not invented) because it's the same breakpoint already
   established in this file for the structurally closest reference
   components - Explore's .vs-vibe-hero-grid and .vs-review-grid, both
   glass 4-up card grids in the same 1080px-max container - rather than
   picking a new number specific to this one rule. */
@media (min-width: 700px) {
	#highlights .vs-mini-card {
		max-width: 260px;
	}
}

/* Owner QA: cards read as "inconsistent in height" - not because their
   outer boxes actually differed (align-items:stretch + the existing
   .vs-mini-card .vs-btn margin-top:auto already forced equal card height
   and flush CTA bottoms, verified: all 4 measured the same 813px height
   pre-fix), but because the four source images have wildly different
   native aspect ratios (0.8, 0.667, 1.0, 0.667) and the shared
   .vs-mini-card img rule uses height:auto, so each image's RENDERED
   height varied from 397px to 596px - a ~200px swing in how much of each
   card was "photo" vs "text", breaking the sense of one designed
   collection even though the card edges lined up. aspect-ratio (not a
   crop-position hack) normalizes every image to the same proportional
   box; object-fit:cover (already set on the shared .vs-mini-card img
   rule, inherited here) crops without distorting. 4:5 chosen because it
   exactly matches the least-distorted source (Romantic, 800x1000) and
   only moderately crops the others - a considered choice, not an
   invented number. Scoped to #highlights only - Result pages/Vibe Picks'
   .vs-mini-card img keep their existing height:auto behavior untouched. */
#highlights .vs-mini-card img {
	aspect-ratio: 4 / 5;
}

/* Legal pages (Privacy Policy / Terms of Use / Affiliate Disclosure)
   Ported from production's scoped per-page Elementor <style> blocks
   (#vs-privacy-policy.vs-legal-section, #vs-terms.vs-legal-section,
   #vs-affiliate-disclosure.vs-legal-section) - all 3 pages use the exact
   same design (only the section id and content differ on production), so
   consolidated into one shared .vs-legal-section rule set here instead of
   duplicating it 3x, consistent with the .vs-card/.vs-post normalization
   pattern used for the blog posts. */
.vs-legal-section {
	padding: clamp(32px, 6vw, 56px) 16px;
}

.vs-legal-container {
	max-width: min(960px, 96vw);
	margin: 0 auto;
	background: rgba(255, 255, 255, 0.72);
	border-radius: 24px;
	border: 1px solid rgba(15, 23, 42, 0.06);
	box-shadow:
		0 18px 40px rgba(0, 0, 0, 0.12),
		0 4px 12px rgba(0, 0, 0, 0.06);
	padding: clamp(24px, 4vw, 40px);
	backdrop-filter: blur(6px);
}

.vs-legal-eyebrow {
	margin: 0 0 6px;
	font-size: 0.9rem;
	letter-spacing: 0.12em;
	text-transform: uppercase;
	color: rgba(55, 65, 81, 0.9);
	font-weight: 600;
}

.vs-legal-title {
	margin: 0 0 16px;
	font-family: "Poppins", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
	font-size: clamp(26px, 3vw, 32px);
	line-height: 1.2;
	color: #111827;
	letter-spacing: 0.02em;
}

.vs-legal-section h2 {
	margin: 24px 0 10px;
	font-family: "Poppins", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
	font-size: clamp(19px, 2.1vw, 22px);
	color: #111827;
}

.vs-legal-section h3 {
	margin: 18px 0 8px;
	font-family: "Poppins", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
	font-size: 1.02rem;
	color: #111827;
}

.vs-legal-section p,
.vs-legal-section li {
	font-family: "Merriweather", Georgia, "Times New Roman", serif;
	font-size: 1rem;
	line-height: 1.7;
	color: #374151;
}

.vs-legal-section ul {
	padding-left: 1.4rem;
	margin: 0 0 12px;
}

.vs-legal-section li + li {
	margin-top: 4px;
}

.vs-legal-section a {
	color: #FF7F7F;
	font-weight: 600;
	text-decoration: none;
}

.vs-legal-section a:hover,
.vs-legal-section a:focus-visible {
	text-decoration: underline;
}

.vs-legal-contact {
	font-weight: 600;
	margin: 4px 0 18px;
}

@media (max-width: 640px) {
	.vs-legal-container {
		border-radius: 18px;
		padding: 20px 16px;
	}
}

/* Crash Flashback / Story pages (Story Index, Prologue, Episode 1) -
   migrated 2026-08-22, reclassified from paused to core content.

   Judgment call, disclosed: like the Quiz page above, this content gets a
   verbatim-ported, distinctly-scoped visual treatment (dark-panel "reading
   mode" chat bubbles, custom pill buttons) instead of being folded into
   the generic .vs-card/.vs-btn look - collapsing it would misrepresent
   how production actually presents this content (a serialized narrative
   feature, not a marketing page). Values below are copied from two real
   production sources: (1) each page's own scoped inline <style> where one
   exists (Story Index, Prologue), and (2) for Episode 1 - which has NO
   inline <style> of its own - section "15. STORIES / EPISODES / QUOTES /
   POLAROID" of production's sitewide "VibeSequence - CLEAN CORE STYLES v2"
   stylesheet (WordPress's Additional CSS / wp-custom-css, loaded on every
   page). That bundle defines --vs-a/--vs-b/--vs-grad/--vs-btn-shadow(-hover
   etc.)/--vs-card-bg/--vs-card-border/--vs-card-radius/--vs-img-radius/
   --vs-card-shadow-soft as real :root custom properties - this theme
   deliberately never defines those properties anywhere (same pattern
   already used by the Quiz CSS above and footer.css's MailerLite styles),
   so they're kept as fallback-only var(--x, <real-production-value>)
   calls here too, consistent with that established convention. */

/* --- Story Index (#vs-story-index) --- */
#vs-story-index {
	max-width: 760px;
	margin: 40px auto 56px;
	padding: 28px 22px;
	text-align: center;
	position: relative;
	z-index: 5;
	font-family: Inter, Poppins, system-ui, sans-serif;
}

#vs-story-index .vs-story-card {
	padding: 32px 24px;
	border-radius: 28px;
	background: rgba(255, 255, 255, .48);
	border: 1px solid rgba(17, 24, 39, .08);
	box-shadow:
		0 18px 38px rgba(17, 24, 39, .10),
		0 6px 14px rgba(17, 24, 39, .07);
	backdrop-filter: blur(10px);
}

#vs-story-index .vs-story-title {
	margin: 0 0 10px;
	color: #111827;
	font-size: clamp(34px, 5vw, 54px);
	line-height: 1.05;
	letter-spacing: -0.04em;
	font-weight: 900;
	text-align: center;
}

#vs-story-index .vs-story-sub {
	margin: 0 auto 24px;
	max-width: 520px;
	color: #6B7280;
	font-size: 17px;
	line-height: 1.55;
}

#vs-story-index .vs-story-actions {
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: 16px;
	margin: 0;
	padding: 0;
	list-style: none;
	position: relative;
	z-index: 10;
}

#vs-story-index .vs-story-btn {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: min(100%, 320px);
	min-height: 52px;
	padding: 0 26px;
	border-radius: 999px;
	color: #ffffff;
	text-decoration: none;
	font: 800 16px Poppins, Inter, system-ui, sans-serif;
	line-height: 1;
	white-space: nowrap;
	position: relative;
	z-index: 20;
	background: linear-gradient(225deg, #C39BFF, #FF7F7F);
	box-shadow:
		0 12px 22px rgba(0, 0, 0, .16),
		0 4px 8px rgba(0, 0, 0, .10);
	transition: transform .18s ease, box-shadow .18s ease, filter .18s ease;
}

#vs-story-index .vs-story-btn::before {
	content: "";
	position: absolute;
	left: 7px;
	right: 7px;
	top: 5px;
	height: 22px;
	border-radius: 999px;
	background: linear-gradient(to bottom, rgba(255, 255, 255, .32), rgba(255, 255, 255, 0));
	pointer-events: none;
}

#vs-story-index .vs-story-btn:hover {
	transform: translateY(-2px);
	box-shadow:
		0 16px 28px rgba(0, 0, 0, .18),
		0 7px 12px rgba(0, 0, 0, .12);
	filter: saturate(1.05);
}

@media (max-width: 640px) {
	#vs-story-index {
		margin: 28px auto 44px;
		padding: 20px 14px;
	}

	#vs-story-index .vs-story-card {
		padding: 28px 18px;
		border-radius: 24px;
	}

	#vs-story-index .vs-story-btn {
		width: 100%;
	}
}

/* --- Prologue (#vs-prologue) --- */
#vs-prologue {
	font-family: Arial, 'Poppins', sans-serif;
	color: #111827;
	isolation: isolate;
	--coral: #FF7F7F;
	--lavender: #C39BFF;
	--ink: #111827;
	--muted: #6B7280;
	--wa-green: #BEE8AE;
	--wa-green-border: #96D18A;
}

#vs-prologue .vs-container {
	max-width: 980px;
	margin: 0 auto;
	padding: clamp(32px, 4vw, 56px) 20px;
}

#vs-prologue .vs-panel {
	background: rgba(255, 127, 127, .14);
	border: 1px solid rgba(255, 127, 127, .28);
	border-radius: 28px;
	box-shadow: 0 10px 24px rgba(0, 0, 0, .08);
	backdrop-filter: blur(2px);
	padding: clamp(28px, 4vw, 44px);
	overflow: visible;
	position: relative;
	z-index: 0;
}

#vs-prologue .vs-panel h1 {
	font-size: clamp(28px, 4.2vw, 40px);
	line-height: 1.2;
	margin: 0 0 16px;
	text-align: left;
}

#vs-prologue .vs-panel p {
	font-size: 18px;
	line-height: 1.85;
	margin: 0 0 14px;
	text-align: left;
}

/* Romance section - serif/italic narrative voice */
#vs-prologue .vs-romance p {
	font-family: Georgia, "Times New Roman", Times, serif;
	font-style: italic;
	letter-spacing: .1px;
}

/* WhatsApp-style chat bubble */
#vs-prologue .vs-romance .vs-chat {
	display: flex;
	gap: 10px;
	align-items: flex-end;
	margin: 6px 0 16px;
}

#vs-prologue .vs-romance .vs-chat-bubble {
	position: relative;
	max-width: 75%;
	padding: 10px 14px;
	line-height: 1.55;
	font-size: 18px;
	background: var(--wa-green);
	color: var(--ink);
	border: 1px solid var(--wa-green-border);
	border-radius: 18px 18px 18px 6px;
	box-shadow: 0 2px 6px rgba(0, 0, 0, .12);
	font-family: Arial, 'Poppins', sans-serif;
	font-style: normal;
	text-align: left;
}

#vs-prologue .vs-romance .vs-chat-bubble::after {
	content: "";
	position: absolute;
	left: -6px;
	bottom: 0;
	width: 12px;
	height: 12px;
	transform: translateY(-2px) rotate(45deg);
	background: var(--wa-green);
	border-left: 1px solid var(--wa-green-border);
	border-bottom: 1px solid var(--wa-green-border);
	border-bottom-left-radius: 3px;
}

/* Prologue's own CTA - "Read Episode 1" (ultra-3D pill, scoped) */
#vs-prologue .pg-cta {
	margin: 26px 0 8px;
	position: relative;
	background: transparent;
}

#vs-prologue .pg-btn {
	display: inline-block;
	width: 100%;
	max-width: 280px;
	text-align: center;
	text-decoration: none;
	font-weight: 700;
	font-size: 15px;
	color: #fff;
	border-radius: 999px;
	padding: 14px 18px;
	box-sizing: border-box;
	background: linear-gradient(225deg, var(--coral), var(--lavender));
	position: relative;
	z-index: 1;
	box-shadow:
		0 24px 36px rgba(0, 0, 0, .30),
		0 10px 18px rgba(0, 0, 0, .18),
		inset 0 2px 0 rgba(255, 255, 255, .35);
	filter: drop-shadow(0 10px 18px rgba(0, 0, 0, .18));
	transition: transform .2s ease, box-shadow .2s ease, filter .2s ease;
}

#vs-prologue .pg-btn::before {
	content: "";
	position: absolute;
	left: 12px;
	right: 12px;
	top: 6px;
	height: 10px;
	border-radius: 999px;
	background: linear-gradient(to bottom, rgba(255, 255, 255, .55), rgba(255, 255, 255, 0));
	pointer-events: none;
}

#vs-prologue .pg-btn::after {
	content: "";
	position: absolute;
	left: 8%;
	right: 8%;
	bottom: -14px;
	height: 22px;
	background: radial-gradient(50% 50% at 50% 50%, rgba(0, 0, 0, .30), rgba(0, 0, 0, 0) 70%);
	filter: blur(2px);
	z-index: -1;
	pointer-events: none;
}

#vs-prologue .pg-btn:hover {
	transform: translateY(-2px);
	box-shadow:
		0 28px 42px rgba(0, 0, 0, .32),
		0 14px 22px rgba(0, 0, 0, .20),
		inset 0 2px 0 rgba(255, 255, 255, .4);
	filter: drop-shadow(0 14px 22px rgba(0, 0, 0, .20));
}

#vs-prologue .pg-btn:active {
	transform: translateY(0);
}

#vs-prologue .pg-btn:focus-visible {
	outline: 2px solid rgba(195, 155, 255, .9);
	outline-offset: 3px;
}

#vs-prologue .pg-sub {
	color: var(--muted);
	font-size: 14px;
	margin-top: 10px;
}

#vs-prologue .pg-sub a.pg-sub-link {
	color: #7A5CFF;
	text-decoration: underline;
	font-weight: 700;
}

#vs-prologue .pg-sub a.pg-sub-link:hover {
	opacity: .9;
}

@media (max-width: 600px) {
	#vs-prologue .vs-panel {
		border-radius: 20px;
	}

	#vs-prologue .vs-panel p {
		font-size: 17px;
	}
}

@media (prefers-reduced-motion: reduce) {
	#vs-prologue .pg-btn {
		transition: none;
	}
}

/* --- Episode 1 (#vs-episode) - ported from the sitewide "CLEAN CORE
   STYLES v2" bundle's own #vs-episode rules (production has no per-page
   <style> block for this one), var() fallbacks are that bundle's real
   :root values, see note above --- */
#vs-episode .vs-panel {
	max-width: 820px;
	margin: 0 auto;
	padding: 24px 20px;
	background: var(--vs-card-bg, rgba(255, 127, 127, .075));
	border: 1px solid var(--vs-card-border, rgba(17, 24, 39, .06));
	border-radius: var(--vs-img-radius, 16px);
	box-shadow: var(--vs-card-shadow-soft, 0 12px 26px rgba(0, 0, 0, .10), 0 4px 10px rgba(0, 0, 0, .06));
}

#vs-episode .vs-date {
	display: block;
	width: max-content;
	margin: 0 auto 18px;
	padding: 10px 16px;
	color: #111;
	background: #fff;
	border: 1px solid #E5E7EB;
	border-radius: 999px;
	box-shadow: 0 1px 0 rgba(0, 0, 0, .04), 0 8px 16px rgba(0, 0, 0, .06);
	font-family: Georgia, "Times New Roman", Times, serif;
	font-weight: 700;
	font-size: clamp(22px, 3.2vw, 32px);
	line-height: 1.15;
}

#vs-episode .vs-romance {
	display: flex;
	flex-direction: column;
	gap: 12px;
}

#vs-episode .vs-chat {
	display: flex;
}

#vs-episode .vs-chat.me {
	justify-content: flex-end;
}

#vs-episode .vs-chat:not(.me) {
	justify-content: flex-start;
}

#vs-episode .vs-chat-bubble {
	position: relative;
	display: inline-block;
	max-width: min(720px, 86%);
	padding: 12px 14px;
	border-radius: 18px;
	line-height: 1.55;
	color: #0F172A;
	background: #FFF;
	border: 1px solid rgba(0, 0, 0, .06);
	box-shadow: 0 1px 0 rgba(0, 0, 0, .05), 0 8px 16px rgba(0, 0, 0, .06);
}

#vs-episode .vs-chat.me .vs-chat-bubble {
	background: #DCF8C6;
	border-color: rgba(0, 0, 0, .10);
}

#vs-episode .vs-chat .vs-chat-bubble::after {
	content: "";
	position: absolute;
	bottom: -2px;
	width: 0;
	height: 0;
	border: 8px solid transparent;
	filter: drop-shadow(0 1px 0 rgba(0, 0, 0, .05));
}

#vs-episode .vs-chat.me .vs-chat-bubble::after {
	right: -4px;
	border-left-color: #DCF8C6;
	border-right: 0;
}

#vs-episode .vs-chat:not(.me) .vs-chat-bubble::after {
	left: -4px;
	border-right-color: #FFF;
	border-left: 0;
}

#vs-episode .vs-romance > p {
	margin: 2px 6px 10px;
	font-style: italic;
	color: #6B7280;
}

#vs-episode .vs-sub .vs-btn {
	box-shadow: var(--vs-btn-shadow, 0 10px 18px rgba(0, 0, 0, .18), 0 3px 6px rgba(0, 0, 0, .10));
}

@media (max-width: 640px) {
	#vs-episode .vs-panel {
		padding: 18px 14px;
	}

	#vs-episode .vs-chat-bubble {
		max-width: 92vw;
	}
}

/* --- "Doors are open" join banner - used on Episode 1, ported verbatim
   from that page's own inline <style> (a thin horizontal CTA strip
   distinct from the site's usual .vs-card, only used in this narrative
   context) --- */
.vs-join-band {
	margin: 28px 0;
}

.vs-join-inner {
	max-width: 980px;
	margin: 0 auto;
	padding: 14px 16px;
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 12px;
	background: rgba(255, 127, 127, .075);
	border: 1px solid rgba(0, 0, 0, .06);
	border-radius: 14px;
	box-shadow: 0 10px 18px rgba(0, 0, 0, .10), 0 3px 6px rgba(0, 0, 0, .08);
}

.vs-join-copy {
	margin: 0;
	color: #374151;
	font-size: 16px;
}

@media (max-width: 640px) {
	.vs-join-inner {
		flex-direction: column;
		text-align: center;
		padding: 16px;
	}
}

/* WooCommerce mobile gallery phantom-overflow fix, added 2026-08-23
 * (mobile-QA reconciliation pass).
 *
 * WooCommerce's flexslider gallery renders its slide track
 * (.woocommerce-product-gallery__wrapper) at an inline width of N*100%
 * (e.g. 2400% for 13 slides) and relies on its direct parent
 * (.flex-viewport, which does correctly compute overflow-x:hidden) to
 * clip it. That clip works for painting - nothing visibly renders past
 * the viewport edge, confirmed via elementFromPoint() sampling - but the
 * slide track's layout/scrollable-overflow still propagates past
 * .flex-viewport's clip and inflates document.documentElement.scrollWidth
 * (and therefore window.innerWidth) on mobile, forcing the browser to
 * treat the page as ~127-166px wider than the real 375px viewport with
 * nothing actually there to show for it - confirmed empirically: hiding
 * .woocommerce-product-gallery__wrapper entirely does not change
 * scrollWidth, so the gallery's own content isn't the visible cause; only
 * overflow-x:hidden on <html> itself (not body alone) fully absorbs it.
 *
 * Confirmed this is a PRE-EXISTING PRODUCTION DEFECT, not something this
 * migration introduced: the same phantom-overflow signature (same
 * flex-viewport structure, same "hiding the gallery doesn't change
 * scrollWidth" result) reproduces on live Production
 * (vibesequence.com/product/the-romantic-tee-deep-colors/, read-only
 * check) at 375px, there scrolling to ~542px.
 *
 * Fix: absorb the phantom overflow at the <html> root, scoped to the
 * mobile breakpoint only. Tried scoping this further to WooCommerce
 * single-product pages only via `html:has(body.single-product)` first -
 * CSS.supports('selector(:has(a))') and Element.matches() both reported
 * true in this environment's DevTools-protocol JS context, but the
 * actual stylesheet parser silently dropped the whole @media block
 * containing it (confirmed via document.styleSheets - the rule never
 * appeared in the parsed CSSOM at all), so it never took effect. Rather
 * than ship a selector proven unreliable in exactly the environment it
 * was tested in, this uses plain `html` with only the mobile media-query
 * scoping - universally supported, no bleeding-edge selector dependency.
 * This is a no-op in effect on every other page: Home and /community/
 * were already confirmed to have zero horizontal overflow at 375px
 * without this rule, so <html> never has anything to clip there; it only
 * does real work on the WooCommerce single-product template where the
 * phantom overflow actually exists. Desktop layout, gallery
 * functionality (thumbnails, zoom, variation image switching), and every
 * non-mobile page are all untouched - this rule affects zero visible
 * pixels anywhere, since nothing was ever painting in the phantom zone it
 * removes scroll capacity for.
 */
@media (max-width: 767px) {
	html {
		overflow-x: hidden;
	}
}

/* --- Read Next (centralized post-to-post discovery, theme/special/read-next.php) ---
   Deliberately self-contained: uses global design tokens (--vs-card-tint,
   --vs-color-coral, etc.) rather than anything Explore-specific, since
   this renders at the bottom of real Blog post content, which doesn't
   have Explore's illustrated peach body background. Compact and single-
   column by default so it never needs a breakpoint to be mobile-safe. */
.vs-read-next {
	max-width: 720px;
	margin: 40px auto 0;
	padding: 20px 22px;
	border-radius: 18px;
	background: var(--vs-card-tint);
	border: 1px solid rgba(0, 0, 0, .06);
	font-family: var(--vs-font-family);
}

.vs-read-next__eyebrow {
	color: var(--vs-color-coral);
	font-size: 12px;
	font-weight: 700;
	letter-spacing: .06em;
	text-transform: uppercase;
	margin: 0 0 12px;
}

.vs-read-next__list {
	display: flex;
	flex-direction: column;
	gap: 10px;
}

.vs-read-next__item {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 12px;
	padding: 12px 14px;
	border-radius: 12px;
	background: rgba(255, 255, 255, .55);
	text-decoration: none;
	transition: transform .12s ease, background .12s ease;
}

.vs-read-next__item:hover,
.vs-read-next__item:focus-visible {
	background: rgba(255, 255, 255, .85);
	transform: translateY(-1px);
}

.vs-read-next__item-title {
	color: var(--vs-text-heading);
	font-size: 15px;
	font-weight: 600;
	line-height: 1.35;
}

.vs-read-next__item-arrow {
	flex: 0 0 auto;
	color: var(--vs-color-coral);
	font-size: 16px;
	font-weight: 700;
}

/* --- Expert Opinion: Explore entry banner + category archive + future
   post attribution byline. Reuses the existing warm-glass recipe
   (see .vs-vibe-tile/.vs-guide-row/.vs-review-card above) rather than
   inventing a new visual language. Renders on Explore (.vs-expert-banner)
   and on the /category/expert-opinion/ archive (everything else here) -
   both pull from this one sitewide components.css, no page-specific
   stylesheet. The archive's post grid itself intentionally reuses
   .vs-card-row/.vs-mini-card unchanged (see category-expert-opinion.php)
   rather than a parallel card class. */

/* Direction 02 "The Threshold" (approved 2026-09-07, replaces the first-pass
   glass-card banner above). Candlelight-in-a-dim-room radial glow rising
   into deep plum, thin inset gold frame, centered invitation-card
   composition. Every text color below is set explicitly on every element
   and every link state (:link/:visited/:hover/:active/:focus) on purpose -
   this banner's palette is intentionally its own (plum/gold/cream), not
   the sitewide --vs-color-coral/--vs-text-* tokens, so nothing here may
   rely on inheriting color from an ancestor or from browser/theme default
   link styling (which would otherwise show as default link-blue, exactly
   the bug this replaced). */

.vs-expert-banner,
.vs-expert-banner:link,
.vs-expert-banner:visited,
.vs-expert-banner:hover,
.vs-expert-banner:active,
.vs-expert-banner:focus {
	color: #fbead9;
}

.vs-expert-banner {
	display: block;
	position: relative;
	overflow: hidden;
	max-width: 720px;
	margin: 0 auto 32px;
	padding: 40px 36px 34px;
	text-align: center;
	text-decoration: none;
	font-family: var(--vs-font-family);
	background: radial-gradient(120% 90% at 50% -10%, #ff9f80 0%, #b24a5e 32%, #3a1c30 72%);
	border-radius: 22px;
	box-shadow: 0 18px 34px rgba(58, 28, 48, .3), 0 3px 8px rgba(58, 28, 48, .16);
	transition: transform .28s cubic-bezier(.2, .7, .3, 1), box-shadow .28s ease;
}

.vs-expert-banner::before {
	content: "";
	position: absolute;
	inset: 10px;
	border-radius: 15px;
	border: 1px solid rgba(232, 201, 160, .32);
	transition: border-color .25s ease, box-shadow .25s ease;
	pointer-events: none;
}

.vs-expert-banner::after {
	content: "";
	position: absolute;
	inset: 0;
	opacity: .06;
	pointer-events: none;
	background-image: repeating-linear-gradient(115deg, #fff 0 1px, transparent 1px 26px);
}

.vs-expert-banner:hover,
.vs-expert-banner:focus-visible {
	transform: translateY(-5px) scale(1.008);
	box-shadow: 0 30px 56px rgba(58, 28, 48, .4), 0 6px 14px rgba(58, 28, 48, .2);
}

.vs-expert-banner:hover::before,
.vs-expert-banner:focus-visible::before {
	border-color: rgba(232, 201, 160, .62);
	box-shadow: inset 0 0 18px rgba(232, 201, 160, .15);
}

.vs-expert-banner:focus-visible {
	outline: 2px solid #fbead9;
	outline-offset: 4px;
}

.vs-expert-banner__rule {
	display: block;
	width: 38px;
	height: 1px;
	margin: 0 auto 18px;
	background: #e8c9a0;
	opacity: .7;
	position: relative;
}

.vs-expert-banner__title {
	display: block;
	position: relative;
	font-size: 26px;
	font-weight: 700;
	color: #fbead9;
	text-shadow: 0 1px 4px rgba(20, 8, 16, .35);
	margin: 0 0 12px;
}

.vs-expert-banner__desc {
	display: block;
	position: relative;
	max-width: 380px;
	margin: 0 auto 24px;
	font-size: 15px;
	line-height: 1.55;
	color: #f6d9c9;
}

.vs-expert-banner__cta {
	display: inline-flex;
	align-items: center;
	gap: 8px;
	position: relative;
	padding: 13px 26px;
	border-radius: 99px;
	border: 1px solid rgba(232, 201, 160, .55);
	color: #fbead9;
	font-size: 12px;
	font-weight: 700;
	letter-spacing: .08em;
	text-transform: uppercase;
	background: transparent;
	transition: background .25s ease, color .25s ease, border-color .25s ease;
}

.vs-expert-banner:hover .vs-expert-banner__cta,
.vs-expert-banner:focus-visible .vs-expert-banner__cta {
	background: linear-gradient(120deg, #ff9f80, #e8c9a0);
	color: #3a1c30;
	border-color: transparent;
}

.vs-expert-banner__arrow {
	display: inline-block;
	color: inherit;
	transition: transform .2s ease;
}

.vs-expert-banner:hover .vs-expert-banner__arrow,
.vs-expert-banner:focus-visible .vs-expert-banner__arrow {
	transform: translateX(4px);
}

@media (max-width: 600px) {
	.vs-expert-banner {
		margin: 0 20px 32px;
		padding: 30px 22px 26px;
	}

	.vs-expert-banner__title {
		font-size: 22px;
		color: #fbead9;
	}

	.vs-expert-banner__desc {
		font-size: 13.5px;
		max-width: 300px;
		color: #f6d9c9;
	}

	.vs-expert-banner__cta {
		display: flex;
		width: 100%;
		justify-content: center;
		padding: 14px 18px;
		color: #fbead9;
	}
}

@media (prefers-reduced-motion: reduce) {
	.vs-expert-banner,
	.vs-expert-banner::before,
	.vs-expert-banner__cta,
	.vs-expert-banner__arrow {
		transition: none;
	}

	.vs-expert-banner:hover,
	.vs-expert-banner:focus-visible {
		transform: none;
	}
}

/* Category archive - single-column editorial layout. .vs-expert-page is
   the one wrapper everything sits inside (see category-expert-opinion.php
   docblock for why: Astra's .ast-container flex-splits multiple direct-
   child blocks into side-by-side columns - this single wrapper is what
   prevents that on every viewport, not a media query). */

/* Fills the flex row .ast-container creates (Astra only sizes its own
   #primary/#secondary children, so an unstyled child otherwise shrinks to
   content width and sits flush-left instead of centering - see
   category-expert-opinion.php's docblock). Each content block below
   centers itself independently inside this full-width pass-through, in
   normal block layout where margin:auto is reliable. */
.vs-expert-page {
	width: 100%;
}

.vs-expert-hero {
	max-width: 900px;
	margin: 0 auto;
	padding: 48px 20px 8px;
	text-align: center;
	font-family: var(--vs-font-family);
}

.vs-expert-hero h1 {
	color: var(--vs-text-heading);
	font-size: 34px;
	font-weight: 700;
	line-height: 1.25;
	margin: 0 0 14px;
}

@media (min-width: 782px) {
	.vs-expert-hero h1 {
		font-size: 44px;
	}
}

.vs-expert-hero__sub {
	color: var(--vs-text-body);
	font-size: 16px;
	line-height: 1.55;
	margin: 0 0 10px;
}

.vs-expert-hero__disclaimer {
	color: var(--vs-text-muted);
	font-size: 13px;
	line-height: 1.5;
	max-width: 480px;
	margin: 0 auto;
}

.vs-expert-empty {
	max-width: 480px;
	margin: 32px auto 56px;
	padding: 20px 24px;
	text-align: center;
	background-color: var(--vs-card-tint);
	background-image: linear-gradient(160deg, rgba(255, 255, 255, .5), rgba(255, 255, 255, .16));
	border: 1px solid rgba(255, 255, 255, .55);
	border-radius: 16px;
	backdrop-filter: blur(6px) saturate(1.05);
	-webkit-backdrop-filter: blur(6px) saturate(1.05);
	font-family: var(--vs-font-family);
}

.vs-expert-empty .vs-coming-soon {
	margin: 0;
}

/* Archive card grid - echoes /blog/'s proven thumbnail-top card language
   (same 82% image-crop ratio + upward object-position bias, same
   3/2/1-column breakpoint behavior) in the site's own warm-glass card
   recipe, since the Elementor widget that builds /blog/ only renders
   inside Elementor's own pipeline and can't be reused directly here.
   Empty on launch - styled ahead of the first real Expert Opinion post,
   not populated now. */

.vs-expert-grid {
	display: grid;
	grid-template-columns: repeat(3, 1fr);
	gap: 24px;
	max-width: 1080px;
	margin: 40px auto 56px;
	padding: 0 20px;
	box-sizing: border-box;
	text-align: left;
}

@media (max-width: 900px) {
	.vs-expert-grid {
		grid-template-columns: repeat(2, 1fr);
	}
}

@media (max-width: 600px) {
	.vs-expert-grid {
		grid-template-columns: 1fr;
	}
}

.vs-expert-card {
	display: flex;
	flex-direction: column;
	background-color: var(--vs-card-tint);
	background-image: linear-gradient(160deg, rgba(255, 255, 255, .5), rgba(255, 255, 255, .16));
	border: 1px solid rgba(255, 255, 255, .55);
	border-radius: 18px;
	overflow: hidden;
	backdrop-filter: blur(6px) saturate(1.05);
	-webkit-backdrop-filter: blur(6px) saturate(1.05);
	box-shadow: 0 10px 22px rgba(120, 72, 48, .12), 0 2px 6px rgba(120, 72, 48, .06);
}

.vs-expert-card__thumb {
	display: block;
	position: relative;
	padding-bottom: 82%;
	background: rgba(255, 255, 255, .35);
}

.vs-expert-card__thumb img {
	position: absolute;
	inset: 0;
	width: 100%;
	height: 100%;
	object-fit: cover;
	object-position: center 25%;
}

.vs-expert-card__body {
	padding: 18px 20px 20px;
	display: flex;
	flex-direction: column;
	flex: 1 1 auto;
}

.vs-expert-card__title {
	color: var(--vs-text-heading);
	font-size: 17px;
	font-weight: 600;
	text-decoration: none;
	margin: 0 0 8px;
}

.vs-expert-card__excerpt {
	color: var(--vs-text-body);
	font-size: 14px;
	line-height: 1.55;
	margin: 0 0 14px;
}

.vs-expert-card__more {
	margin-top: auto;
	align-self: flex-start;
	color: var(--vs-color-coral);
	font-size: 13px;
	font-weight: 700;
	text-decoration: none;
}

/* ============================================================
   Result-page product/recommendation banners (Premiumize batch).
   Replaces the previous bare "<section class='vs-card vs-card--tint'>
   <a><img class='vs-symbol-icon'></a><h2>...<p class='vs-cta'><a
   class='vs-btn'>" structure - that image class carried zero layout CSS
   (confirmed: .vs-symbol-icon only has rules scoped under
   .vs-symbol-card/.vs-hero>, neither of which matched these four usages),
   so the product photos rendered at native pixel size with no framing at
   all. New structure is a single <a class="vs-result-banner
   vs-result-banner--<page>"> - one real link, no nested <a> inside, CTA
   rendered as a styled <span> ("visible affordance", not a second link)
   per the task's explicit anti-nested-link requirement. Base class holds
   the shared dimensional/interaction mechanics (frame, lift, shadow,
   focus, reduced-motion); each --modifier supplies only the palette
   sampled from that banner's own image, per instruction not to invent
   new colors. Quality benchmark: the already-proven .vs-playful-banner
   inline style on the "Curiosity Without Pressure" post (same lift +
   layered-shadow + press language, adapted per-palette instead of
   copied verbatim - that reference is a single full-bleed graphic with
   no separate heading/copy, these four keep their existing h2/p/CTA
   marketing copy so the treatment wraps around real content instead). */

.vs-result-banner {
	display: block;
	position: relative;
	max-width: 640px;
	margin: 40px auto;
	padding: 12px 12px 30px;
	border-radius: 30px;
	text-align: center;
	text-decoration: none;
	cursor: pointer;
	font-family: var(--vs-font-family);
	transition: transform .22s cubic-bezier(.2, .7, .3, 1), box-shadow .22s ease, filter .18s ease;
}

.vs-result-banner:hover,
.vs-result-banner:focus-visible {
	transform: translateY(-6px) scale(1.006);
	filter: brightness(1.02);
}

.vs-result-banner:active {
	transform: translateY(-1px) scale(.997);
}

.vs-result-banner:focus-visible {
	outline: 2px solid currentColor;
	outline-offset: 6px;
}

.vs-result-banner__frame {
	display: block;
	position: relative;
	border-radius: 21px;
	overflow: hidden;
	margin-bottom: 22px;
}

.vs-result-banner__frame img {
	display: block;
	width: 100%;
	height: auto;
}

.vs-result-banner h2 {
	color: var(--vs-text-heading);
	font-size: 24px;
	font-weight: 700;
	margin: 0 0 10px;
}

.vs-result-banner p {
	color: var(--vs-text-body);
	font-size: 15px;
	line-height: 1.55;
	max-width: 440px;
	margin: 0 auto 22px;
}

/* CTA restored to the site's own established "premium 3D pill" language
   (see .vs-btn above: glass top-highlight pseudo-element + dual-layer
   soft/tight shadow + lift-on-hover + press-on-active) - the base rule
   here only supplies the shared mechanics; each --modifier below tints
   the shadow/glow to that banner's own palette instead of the generic
   black .vs-btn uses, since these sit on a colored frame, not a plain
   background. */
.vs-result-banner__cta {
	position: relative;
	display: inline-flex;
	align-items: center;
	gap: 8px;
	padding: 12px 26px;
	border-radius: 99px;
	color: #fff;
	font-size: 13px;
	font-weight: 700;
	letter-spacing: .04em;
	text-transform: uppercase;
	transition: transform .18s ease, box-shadow .18s ease, filter .18s ease;
}

.vs-result-banner__cta::before {
	content: "";
	position: absolute;
	left: 8px;
	right: 8px;
	top: 4px;
	height: 42%;
	border-radius: 999px;
	background: linear-gradient(to bottom, rgba(255, 255, 255, .32), rgba(255, 255, 255, 0));
	pointer-events: none;
}

.vs-result-banner:hover .vs-result-banner__cta,
.vs-result-banner:focus-visible .vs-result-banner__cta {
	transform: translateY(-2px);
	filter: brightness(1.05);
}

.vs-result-banner:active .vs-result-banner__cta {
	transform: translateY(1px);
	filter: brightness(.98);
}

.vs-result-banner__arrow {
	display: inline-block;
	transition: transform .2s ease;
}

.vs-result-banner:hover .vs-result-banner__arrow,
.vs-result-banner:focus-visible .vs-result-banner__arrow {
	transform: translateX(4px);
}

@media (max-width: 600px) {
	.vs-result-banner {
		margin: 32px 15px;
		padding: 8px 8px 24px;
		border-radius: 24px;
	}

	.vs-result-banner__frame {
		border-radius: 17px;
		margin-bottom: 18px;
	}

	.vs-result-banner h2 {
		font-size: 21px;
	}
}

@media (prefers-reduced-motion: reduce) {
	.vs-result-banner,
	.vs-result-banner__arrow,
	.vs-result-banner__cta {
		transition: none;
	}

	.vs-result-banner:hover,
	.vs-result-banner:focus-visible,
	.vs-result-banner:active {
		transform: none;
	}

	.vs-result-banner:hover .vs-result-banner__cta,
	.vs-result-banner:focus-visible .vs-result-banner__cta,
	.vs-result-banner:active .vs-result-banner__cta {
		transform: none;
	}
}

/* --- Empowering / SVAKOM Mora Neo: lilac-to-white body, violet/pink
   gradient wordmark already baked into the image. --- */

.vs-result-banner--empowering .vs-result-banner__frame {
	background: linear-gradient(160deg, #f2e3ff, #ffffff);
}

.vs-result-banner--empowering {
	box-shadow: 0 3px 0 rgba(147, 51, 234, .16), 0 20px 40px rgba(147, 51, 234, .16), 0 4px 10px rgba(147, 51, 234, .1);
}

.vs-result-banner--empowering:hover,
.vs-result-banner--empowering:focus-visible {
	box-shadow: 0 5px 0 rgba(147, 51, 234, .18), 0 30px 54px rgba(147, 51, 234, .2), 0 6px 14px rgba(147, 51, 234, .13);
}

.vs-result-banner--empowering h2 {
	color: #5b2a86;
}

.vs-result-banner--empowering .vs-result-banner__cta {
	background: linear-gradient(120deg, #a855f7, #ec4899);
	box-shadow: 0 10px 18px rgba(147, 51, 234, .28), 0 3px 6px rgba(147, 51, 234, .16);
}

.vs-result-banner--empowering:hover .vs-result-banner__cta,
.vs-result-banner--empowering:focus-visible .vs-result-banner__cta {
	box-shadow: 0 14px 26px rgba(147, 51, 234, .34), 0 6px 12px rgba(147, 51, 234, .2);
}

.vs-result-banner--empowering:active .vs-result-banner__cta {
	box-shadow: 0 6px 12px rgba(147, 51, 234, .24), 0 2px 5px rgba(147, 51, 234, .14);
}

/* --- Playful / Lovense Gemini: pink-to-white body, magenta wordmark
   already baked into the image. --- */

.vs-result-banner--playful .vs-result-banner__frame {
	background: linear-gradient(160deg, #ffe1f0, #fff6fa);
}

.vs-result-banner--playful {
	box-shadow: 0 3px 0 rgba(219, 39, 119, .16), 0 20px 40px rgba(219, 39, 119, .15), 0 4px 10px rgba(219, 39, 119, .1);
}

.vs-result-banner--playful:hover,
.vs-result-banner--playful:focus-visible {
	box-shadow: 0 5px 0 rgba(219, 39, 119, .18), 0 30px 54px rgba(219, 39, 119, .19), 0 6px 14px rgba(219, 39, 119, .13);
}

.vs-result-banner--playful h2 {
	color: #9c1458;
}

.vs-result-banner--playful .vs-result-banner__cta {
	background: linear-gradient(120deg, #ff5fa8, #d6196e);
	box-shadow: 0 10px 18px rgba(219, 39, 119, .28), 0 3px 6px rgba(219, 39, 119, .16);
}

.vs-result-banner--playful:hover .vs-result-banner__cta,
.vs-result-banner--playful:focus-visible .vs-result-banner__cta {
	box-shadow: 0 14px 26px rgba(219, 39, 119, .34), 0 6px 12px rgba(219, 39, 119, .2);
}

.vs-result-banner--playful:active .vs-result-banner__cta {
	box-shadow: 0 6px 12px rgba(219, 39, 119, .24), 0 2px 5px rgba(219, 39, 119, .14);
}

/* --- Relaxing / breathwork session: real airy photography, not a
   product graphic - deliberately the quietest treatment (soft shadow
   only, no colored mat, no gradient CTA) per the instruction to
   premiumize this one through composition and hierarchy, not stronger
   color. CTA reuses the site's own brand gradient rather than a
   product-specific palette, since this recommends a session, not a
   product with its own brand colors. --- */

.vs-result-banner--relaxing .vs-result-banner__frame {
	background: #fff;
}

.vs-result-banner--relaxing {
	box-shadow: 0 16px 32px rgba(148, 163, 184, .18), 0 4px 10px rgba(148, 163, 184, .12);
}

.vs-result-banner--relaxing:hover,
.vs-result-banner--relaxing:focus-visible {
	box-shadow: 0 24px 46px rgba(148, 163, 184, .22), 0 6px 14px rgba(148, 163, 184, .15);
}

.vs-result-banner--relaxing .vs-result-banner__cta {
	background: var(--vs-gradient-primary);
	box-shadow: 0 10px 18px rgba(255, 127, 127, .22), 0 3px 6px rgba(195, 155, 255, .14);
}

.vs-result-banner--relaxing:hover .vs-result-banner__cta,
.vs-result-banner--relaxing:focus-visible .vs-result-banner__cta {
	box-shadow: 0 14px 26px rgba(255, 127, 127, .28), 0 6px 12px rgba(195, 155, 255, .18);
}

.vs-result-banner--relaxing:active .vs-result-banner__cta {
	box-shadow: 0 6px 12px rgba(255, 127, 127, .2), 0 2px 5px rgba(195, 155, 255, .12);
}

/* --- Romantic / Lovense Exomoon: same image as the proven
   ".vs-playful-banner" blog reference - reuses its dark bevel-frame
   language directly (deep plum-to-crimson mat, inset sheen, raised-edge
   shadow) since this is the one banner where the benchmark's own colors
   ARE the image's own colors. h2/p deliberately stay in the page's
   normal heading/body color (not tinted red) so only the framed image
   itself carries the drama - the surrounding copy keeps the page's warm
   peach identity, per instruction that the dark asset should read as
   intentionally framed, not dropped in. --- */

.vs-result-banner--romantic .vs-result-banner__frame {
	background: linear-gradient(180deg, #2b0308 0%, #130104 58%, #7a0614 100%);
	border: 1px solid rgba(255, 49, 86, .38);
	box-shadow: inset 0 2px 0 rgba(255, 255, 255, .28), inset 0 -10px 18px rgba(255, 49, 86, .12);
}

.vs-result-banner--romantic {
	box-shadow: 0 6px 0 #780613, 0 20px 40px rgba(16, 0, 5, .32), 0 4px 10px rgba(16, 0, 5, .2);
}

.vs-result-banner--romantic:hover,
.vs-result-banner--romantic:focus-visible {
	box-shadow: 0 8px 0 #980919, 0 30px 54px rgba(16, 0, 5, .4), 0 6px 14px rgba(16, 0, 5, .26);
}

.vs-result-banner--romantic:active {
	box-shadow: 0 2px 0 #980919, 0 12px 22px rgba(16, 0, 5, .3);
}

.vs-result-banner--romantic .vs-result-banner__cta {
	background: linear-gradient(120deg, #ff3156, #a3081c);
	box-shadow: 0 10px 18px rgba(255, 49, 86, .3), 0 3px 6px rgba(163, 8, 28, .2);
}

.vs-result-banner--romantic:hover .vs-result-banner__cta,
.vs-result-banner--romantic:focus-visible .vs-result-banner__cta {
	box-shadow: 0 14px 26px rgba(255, 49, 86, .36), 0 6px 12px rgba(163, 8, 28, .24);
}

.vs-result-banner--romantic:active .vs-result-banner__cta {
	box-shadow: 0 6px 12px rgba(255, 49, 86, .26), 0 2px 5px rgba(163, 8, 28, .16);
}

/* Reusable expert-attribution byline - ready for the first real Expert
   Opinion post, not wired into any post-rendering pipeline yet and not
   populated with any data (see plan: no fabricated expert content). */

.vs-expert-byline {
	display: flex;
	gap: 16px;
	align-items: flex-start;
	background-color: var(--vs-card-tint);
	background-image: linear-gradient(160deg, rgba(255, 255, 255, .5), rgba(255, 255, 255, .16));
	border: 1px solid rgba(255, 255, 255, .55);
	border-radius: 16px;
	padding: 20px;
	backdrop-filter: blur(6px) saturate(1.05);
	-webkit-backdrop-filter: blur(6px) saturate(1.05);
	box-shadow: 0 8px 18px rgba(120, 72, 48, .10), 0 2px 5px rgba(120, 72, 48, .05);
	margin: 0 0 28px;
	text-align: left;
	font-family: var(--vs-font-family);
}

.vs-expert-byline__photo {
	flex: 0 0 64px;
	width: 64px;
	height: 64px;
	border-radius: 50%;
	object-fit: cover;
	background: rgba(255, 255, 255, .45);
}

.vs-expert-byline__body {
	flex: 1 1 auto;
	min-width: 0;
}

.vs-expert-byline__eyebrow {
	color: var(--vs-color-coral);
	font-size: 11px;
	font-weight: 700;
	letter-spacing: .06em;
	text-transform: uppercase;
	margin: 0 0 4px;
}

.vs-expert-byline__name {
	color: var(--vs-text-heading);
	font-size: 17px;
	font-weight: 700;
	margin: 0 0 2px;
}

.vs-expert-byline__credentials {
	color: var(--vs-text-muted);
	font-size: 13px;
	font-weight: 600;
	margin: 0 0 8px;
}

.vs-expert-byline__bio {
	color: var(--vs-text-body);
	font-size: 14px;
	line-height: 1.55;
	margin: 0 0 10px;
}

.vs-expert-byline__links {
	display: flex;
	flex-wrap: wrap;
	gap: 8px 14px;
}

.vs-expert-byline__links a {
	color: var(--vs-color-coral);
	font-size: 13px;
	font-weight: 600;
	text-decoration: none;
}

.vs-expert-byline__links a:hover {
	text-decoration: underline;
}
