/*
Theme Name:   Vespera Estate
Theme URI:    https://vesperaestate.com
Description:  A dark, cinematic luxury WordPress child theme for Vespera Estate, an imaginary night-harvest winery on the Sonoma Coast — midnight-cellar palette with candlelight-gold accents, oversized Cormorant display type, a centered-wordmark header with a full-screen overlay menu, full-viewport parallax hero with kinetic word reveals, a horizontal featured-wine rail, scrolling brand marquee, and refined micro-interactions throughout. Built on Hello Elementor. Ships with a hard-coded header/footer (works with the free version of Elementor — no Elementor Pro Theme Builder required) and a real Commerce7 storefront integration (shop, wine club, cart). Every new page created with Elementor automatically inherits the header, footer, and global typography/color system defined here.
Author:       Custom build
Template:     hello-elementor
Version:      3.0.1
Text Domain:  vespera
*/

/* =========================================================
   1. DESIGN TOKENS
   Swap these to re-skin the whole site from one place.
========================================================= */
/* v3 "midnight cellar" palette — the dark-luxury direction of the theme
   family (its siblings cover warm-ivory editorial and classic-valley
   looks): near-black aubergine like a cellar at night, warm linen text
   like candlelit paper, and gold as the single accent. Token NAMES are
   the stable API shared with the sibling skins (templates,
   inc/commerce7.php's variable bridge, and the admin docs all reference
   them), so the names stay while the VALUES flip to the dark scheme.
   Read them semantically: "cream" = page surface, "charcoal" = primary
   text, "clay" = primary accent, "olive" = secondary accent, "ink" =
   deepest surface. Because every rule in this file already pairs
   surface tokens with text tokens, the flip stays coherent site-wide. */
:root {
  --vsp-cream: #131016;          /* page background — midnight aubergine, a cellar after close */
  --vsp-cream-raised: #1b1620;   /* alternating raised sections, notices, secondary surfaces */
  --vsp-charcoal: #ece4d4;       /* body text, headings — warm candlelit linen */
  --vsp-charcoal-soft: #a2988c;  /* muted/secondary text */
  --vsp-clay: #c8a45e;           /* primary accent — candlelight gold (the capsule foil) */
  --vsp-clay-dark: #ac8843;      /* accent hover-darken — aged brass */
  --vsp-olive: #5c2434;          /* secondary accent — deep wine, used for the signal band */
  --vsp-ink: #0b080d;            /* deepest night-black — footer, overlay menu, high-contrast bands */
  --vsp-line: rgba(236, 228, 212, 0.14); /* hairline rules/borders — one consistent value everywhere */
  /* Explicit light-on-dark ink for text sitting over photos and the
     deepest bands — semantically clearer than reusing --vsp-charcoal
     in those spots, and one place to tune if the linen tone moves. */
  --vsp-light: #f4eee1;

  --vsp-font-heading: "Cormorant Garamond", Georgia, serif;              /* headings — high-contrast luxury serif */
  --vsp-font-body: "Outfit", "Helvetica Neue", Arial, sans-serif;        /* body / UI copy — clean geometric sans */
  --vsp-font-accent: "Cormorant Garamond", Georgia, serif;               /* italic accent — pull-quotes, logotype (same family, italic cut) */

  /* Tighter radii than the sibling skins — dark luxury reads sharper;
     big pill sweeps read playful, hairline-framed rectangles read like
     a wine label. */
  --vsp-radius: 6px;       /* cards, images, notices — larger surfaces */
  --vsp-radius-sm: 3px;    /* form fields, badges — small controls */
  --vsp-radius-lg: 10px;   /* heroes and other oversized media blocks */
  --vsp-container: 1280px;
  --vsp-space-xs: 8px;
  --vsp-space-sm: 16px;
  --vsp-space-md: 32px;
  /* Fluid rhythm — sections breathe wide on desktop without leaving
     phone layouts drowning in empty vertical space. */
  --vsp-space-lg: clamp(44px, 6vw, 76px);
  --vsp-space-xl: clamp(76px, 10vw, 144px);

  /* One shared motion vocabulary — every transition/animation in this
     file pulls from these so the whole site moves with one voice. */
  --vsp-ease: cubic-bezier(0.16, 1, 0.3, 1);        /* expo-out — fast start, long soft settle */
  --vsp-ease-spring: cubic-bezier(0.34, 1.4, 0.44, 1); /* slight overshoot — small playful controls only */
  --vsp-shadow-sm: 0 2px 12px rgba(0, 0, 0, 0.35);
  --vsp-shadow-md: 0 14px 36px rgba(0, 0, 0, 0.45);
  --vsp-shadow-lg: 0 30px 70px rgba(0, 0, 0, 0.55);
}

/* Brand-toned text selection — a small detail that makes the whole site
   feel deliberately designed the moment anyone highlights a sentence. */
::selection { background: var(--vsp-clay); color: var(--vsp-cream); }

/* =========================================================
   2. RESET / BASE
========================================================= */
*, *::before, *::after { box-sizing: border-box; }

/* Skip-to-content link (WCAG 2.4.1 "Bypass Blocks") — off-screen until
   keyboard-focused, then jumps straight above the header. Landing target
   is <main id="vsp-main">, opened in header.php right after </header> and
   closed in footer.php right before <footer>, so it wraps every
   template's content automatically with zero per-template changes. */
.vsp-skip-link {
  position: absolute;
  top: -100px;
  left: 0;
  z-index: 9999;
  background: var(--vsp-charcoal);
  color: var(--vsp-cream);
  padding: 12px 20px;
  font-family: var(--vsp-font-body);
  font-size: 0.9rem;
  text-decoration: none;
  transition: top 0.15s ease;
}
.vsp-skip-link:focus { top: 0; }

/* Baseline visible focus ring for every keyboard-focusable element that
   doesn't already define its own focus treatment — WCAG 2.4.7 "Focus
   Visible". Nothing in this file sets outline:none, so this is additive,
   not a fight against a suppressed default. */
:focus-visible {
  outline: 2px solid var(--vsp-clay);
  outline-offset: 2px;
}
/* The merlot outline above has ~1.2:1 contrast against merlot-dark —
   invisible on our own dark-background sections (footer, hero, club
   cards). WCAG 1.4.11 wants focus indicators at 3:1 against their
   background; gold-light clears that everywhere it's needed here. */
#vsp-footer :focus-visible,
.vsp-hero :focus-visible,
.vsp-club-card :focus-visible {
  outline-color: var(--vsp-light);
}

/* Root cause of every "pink button" bug chased throughout this project —
   it was never Commerce7, a branding plugin, or anything else in this
   file: the Hello Elementor PARENT theme's own reset.css gives every
   plain, unstyled <button>/[type="button"]/[type="submit"] a hardcoded
   pink (#c36) outline by default and a solid pink fill on hover/focus.
   Any element without more specific CSS of its own — e.g. Commerce7's
   icon-only cart-close button, which carries no class at all — falls
   straight into this default. Confirmed via live DevTools: the winning
   rule was listed as reset.css:1.
   Overridden here with the exact same selector list, using the site's
   neutral charcoal instead of pink, WITH !important — checked the actual
   live <link> order and this child theme's stylesheet loads BEFORE
   Hello Elementor's reset.css on this site (hello-elementor enqueues its
   own reset.css late), so an equal-specificity rule here without
   !important still loses the cascade tie to the parent's later rule.
   !important is the correct tool for exactly this one case: overriding a
   parent theme's core stylesheet whose load order we don't control.
   Any button with its own class (.vsp-btn, .c7-btn, .c7-link, etc) already
   has higher specificity and is completely unaffected regardless — this
   only changes genuinely unstyled buttons. */
/* Bare-button fallback restores a real bordered/outline look — a plain
   button with no box at all reads as broken, not "clean". A bordered
   outline that fills solid on hover is the right treatment for a TEXT
   button (e.g. one with no other class anywhere on the site). Anything
   that already has its own class (.vsp-btn, .c7-btn, .c7-btn--primary,
   .c7-link, etc) has higher specificity and is unaffected regardless. */
[type="button"], [type="submit"], button {
  background-color: transparent !important;
  border: 1px solid var(--vsp-charcoal) !important;
  border-radius: var(--vsp-radius-sm) !important;
  color: var(--vsp-charcoal) !important;
}
[type="button"]:focus, [type="button"]:hover,
[type="submit"]:focus, [type="submit"]:hover,
button:focus, button:hover {
  background-color: var(--vsp-charcoal) !important;
  border-color: var(--vsp-charcoal) !important;
  color: var(--vsp-cream) !important;
  text-decoration: none !important;
}

/* ...EXCEPT icon-only buttons — an SVG icon with no visible text label,
   like Commerce7's cart toggle and side-cart close button (both bare
   <button> elements containing only an icon, confirmed via DevTools).
   Boxing an icon in a bordered/filled button looks wrong; these get a
   completely chrome-free treatment instead, in every state.

   The cart toggle specifically also carries the .c7-link class, whose own
   rule (further down, .c7-link.c7-link — two classes, !important) is
   MORE specific than a plain button:has(svg) (two type-level selectors —
   class beats type at equal !important, regardless of source order), so
   it was winning and putting the border/hover-fill back. .c7-link.c7-link
   :has(svg) below is specific enough to beat that base rule, but ONLY
   when an svg icon is actually present — "Remove" (.c7-link, no icon) is
   completely unaffected and keeps its normal bordered/fill-on-hover look. */
button:has(svg), [type="button"]:has(svg),
.c7-link.c7-link:has(svg) {
  border: none !important;
  background-color: transparent !important;
  color: inherit !important;
  padding: 0 !important;
  min-height: 0 !important;
}
button:has(svg):hover, button:has(svg):focus,
[type="button"]:has(svg):hover, [type="button"]:has(svg):focus,
.c7-link.c7-link:has(svg):hover, .c7-link.c7-link:has(svg):focus {
  background-color: transparent !important;
  color: inherit !important;
}
html {
  scroll-behavior: smooth;
  -webkit-text-size-adjust: 100%;
  /* Prevents horizontal scroll from full-bleed sections. Deliberately on
     <html>, not <body>: overflow-x:hidden on body with no overflow-y set
     forces the browser to compute overflow-y as auto too (per the CSS
     Overflow spec, when only one axis is non-visible the other becomes
     auto), turning body into its own scroll container. That breaks any
     position:fixed descendant — including Commerce7's .c7-side-cart drawer
     — which then renders relative to that scrolling body box instead of
     the real viewport, producing a squished, mispositioned panel instead
     of a proper full-height right-side drawer. */
  overflow-x: hidden;
}

body {
  background: var(--vsp-cream);
  color: var(--vsp-charcoal);
  font-family: var(--vsp-font-body);
  font-size: 17px;
  /* Outfit's 300 cut IS shipped (see the fonts request in functions.php),
     but 300 on a dark background halates and thins out — light text on
     dark needs a touch more weight than the same face on white. */
  font-weight: 400;
  line-height: 1.7;
  letter-spacing: 0.015em;
  width: 100%;
}

img, video, iframe, table { max-width: 100%; height: auto; }
table { display: block; overflow-x: auto; }

@media (max-width: 600px) {
  body { font-size: 16px; }
}

h1, h2, h3, h4, h5, h6 {
  font-family: var(--vsp-font-heading);
  /* 500, not 600 — Cormorant carries more stroke contrast than most
     serifs; at display sizes the medium cut reads as luxury, the
     semibold as heavy. */
  font-weight: 500;
  color: var(--vsp-charcoal);
  line-height: 1.12;
  letter-spacing: 0;
  margin: 0 0 var(--vsp-space-sm);
  /* Ragged two-word orphan lines on big display headings read as broken;
     balance keeps multi-line headlines visually even (safe no-op where
     unsupported). */
  text-wrap: balance;
}

/* Oversized fluid display scale — Cormorant's small x-height earns a
   slightly larger clamp range than a workhorse serif would, and needs
   far LESS negative tracking (its letterforms are already narrow;
   crushing them further tangles the italics' terminals). */
h1 { font-size: clamp(3rem, 6.4vw, 5.8rem); line-height: 1.02; letter-spacing: -0.01em; }
h2 { font-size: clamp(2.2rem, 3.8vw, 3.7rem); line-height: 1.08; letter-spacing: -0.005em; }
h3 { font-size: clamp(1.5rem, 2.1vw, 2rem); }

p { margin: 0 0 var(--vsp-space-sm); }

/* Scoped away from Commerce7's own mount points (#c7-content, #c7-cart,
   #c7-account) — those regions must be styled entirely by commerce7.css's
   own --c7-link-color etc. (see inc/commerce7.php), not by our global
   anchor color, or its buttons/links pick up our merlot tint by accident.
   :where() keeps this at the same specificity as a plain "a" selector so
   it still loses cleanly to Commerce7's own more specific component
   classes on the rare occasion the two overlap. */
a:where(:not(#c7-content *, #c7-cart *, #c7-account *)) {
  color: var(--vsp-clay);
  text-decoration: none;
  transition: color .2s ease;
}
a:where(:not(#c7-content *, #c7-cart *, #c7-account *)):hover { color: var(--vsp-clay); }

img { max-width: 100%; display: block; }

/* Eyebrows: gold small-caps tracked text led by a short hairline dash —
   the wine-label convention (a rule, then the appellation line) rather
   than the sibling skins' pill chips. The dash is a ::before, so no
   markup changes anywhere. */
.vsp-eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 12px;
  font-family: var(--vsp-font-body);
  font-size: 0.68rem;
  font-weight: 500;
  letter-spacing: 0.28em;
  text-transform: uppercase;
  color: var(--vsp-clay);
  margin-bottom: var(--vsp-space-sm);
}
.vsp-eyebrow::before {
  content: "";
  display: inline-block;
  width: 28px;
  height: 1px;
  background: currentColor;
  opacity: 0.7;
  flex-shrink: 0;
}
/* Centered section heads: the dash reads asymmetric there — mirror it
   with a matching ::after so the label sits balanced. */
.vsp-section-head .vsp-eyebrow::after,
.vsp-press-section .vsp-eyebrow::after,
.vsp-email-capture .vsp-eyebrow::after,
.vsp-age-gate-card .vsp-eyebrow::after,
.vsp-club-invite .vsp-eyebrow::after {
  content: "";
  display: inline-block;
  width: 28px;
  height: 1px;
  background: currentColor;
  opacity: 0.7;
  flex-shrink: 0;
}
/* Breadcrumb trail (functions.php, vsp_render_breadcrumbs()) — reuses
   .vsp-eyebrow's font/size/color/uppercase wholesale, but the chip
   outline is wrong for a multi-link trail; strip it back to plain
   tracked text, and tone down the letter-spacing (0.22em reads fine for
   one short word but is too spaced-out across a whole "Home / The
   Estate / Team" trail). */
.vsp-breadcrumbs.vsp-eyebrow {
  display: block;
  letter-spacing: 0.08em;
  margin-bottom: var(--vsp-space-md);
  border: none;
  border-radius: 0;
  padding: 0;
}
/* No hairline dash on a multi-link trail — that's a section-label
   flourish, not breadcrumb chrome. */
.vsp-breadcrumbs.vsp-eyebrow::before { display: none; }
.vsp-breadcrumbs a { color: var(--vsp-clay); text-decoration: none; }
.vsp-breadcrumbs a:hover { color: var(--vsp-clay); text-decoration: underline; }
.vsp-breadcrumbs [aria-current="page"] { color: var(--vsp-charcoal-soft); }
.vsp-breadcrumb-sep { margin: 0 6px; color: var(--vsp-charcoal-soft); opacity: 0.5; }

.vsp-container {
  max-width: var(--vsp-container);
  margin: 0 auto;
  padding: 0 var(--vsp-space-md);
}
@media (max-width: 600px) {
  .vsp-container { padding: 0 var(--vsp-space-sm); }
}

.vsp-btn { width: auto; }
@media (max-width: 480px) {
  .vsp-btn { width: 100%; justify-content: center; }
}

/* Sharp label-style buttons (hairline gold frame, tracked small caps)
   with a built-in arrow that nudges forward on hover — the arrow is a
   ::after glyph (no markup changes across any template), always present
   so hover never causes a width/layout shift. Pills read playful; the
   dark-luxury skin wants engraved-plate rectangles. */
.vsp-btn {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  padding: 15px 30px;
  font-family: var(--vsp-font-body);
  font-size: 0.72rem;
  font-weight: 500;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  border: 1px solid rgba(200, 164, 94, 0.55);
  color: var(--vsp-clay);
  background: transparent;
  border-radius: var(--vsp-radius-sm);
  cursor: pointer;
  transition: transform 0.3s var(--vsp-ease), box-shadow 0.3s var(--vsp-ease), background 0.25s ease, color 0.25s ease, border-color 0.25s ease;
}
.vsp-btn::after {
  content: "\2192"; /* → */
  font-size: 1.05em;
  line-height: 1;
  transition: transform 0.3s var(--vsp-ease-spring);
}
/* Fill with gold and drop to cellar-black text — the highest-contrast
   move available on this palette, so hover reads instantly. */
.vsp-btn:hover {
  background: var(--vsp-clay);
  border-color: var(--vsp-clay);
  color: var(--vsp-ink);
  transform: translateY(-2px);
  box-shadow: var(--vsp-shadow-md);
}
.vsp-btn:hover::after { transform: translateX(4px); }
.vsp-btn--solid { background: var(--vsp-clay); border-color: var(--vsp-clay); color: var(--vsp-ink); box-shadow: var(--vsp-shadow-sm); }
.vsp-btn--solid:hover { background: var(--vsp-clay-dark); border-color: var(--vsp-clay-dark); color: var(--vsp-ink); }
/* Ghost button used over hero photos — light text on a frosted dark
   chip (blur + scrim, not a solid fill) so it reads against any photo.
   backdrop-filter is safe here: this is a leaf-level <a>/<button> with
   no position:fixed descendants (unlike #vsp-header — see its comment). */
.vsp-btn--gold {
  border-color: rgba(244, 238, 225, 0.45);
  color: var(--vsp-light);
  background: rgba(11, 8, 13, 0.35);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
}
.vsp-btn--gold:hover { background: var(--vsp-clay); border-color: var(--vsp-clay); color: var(--vsp-ink); }

/* =========================================================
   3. PLACEHOLDER IMAGERY
   Elegant CSS-only stand-ins. Each block is tagged with the
   exact replacement spec in a corner label — search Unsplash /
   Pexels for the suggested term, drop the real photo in, then
   delete the .ph-tag span (or just leave it, it's display:none
   ready via .vsp-no-tags on <body> if you want to hide labels
   site-wide while still deciding).
========================================================= */
.ph-img {
  position: relative;
  width: 100%;
  min-height: 320px;
  border-radius: var(--vsp-radius);
  background-size: cover;
  background-position: center;
  /* Explicit no-repeat: the ph-* fallbacks below intentionally use
     background-IMAGE (not the background shorthand) so this size/repeat
     pair is never reset — a shorthand there once silently reverted
     size to auto and tiled hero photos on tall viewports. */
  background-repeat: no-repeat;
  overflow: hidden;
}
.ph-tag {
  position: absolute;
  right: 10px;
  bottom: 10px;
  z-index: 2;
  font-family: var(--vsp-font-body);
  font-size: 0.62rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  background: rgba(43, 36, 32, 0.72);
  color: #fff;
  padding: 5px 10px;
  border-radius: var(--vsp-radius-sm);
}
body.vsp-no-tags .ph-tag { display: none; }

.ph-hero {
  min-height: 640px;
  background-image: linear-gradient(155deg, #3a2b3d 0%, #1d1524 60%, #0b080d 100%);
}
.ph-vineyard-row {
  min-height: 260px;
  background-image: linear-gradient(160deg, #4a3a28 0%, #2b2118 55%, #14100c 100%);
}
.ph-wine {
  min-height: 380px;
  background-image: linear-gradient(165deg, #5c2434 0%, #351220 60%, #0b080d 100%);
}
.ph-club {
  min-height: 460px;
  background-image: linear-gradient(150deg, #6e5426 0%, #3f2530 55%, #0b080d 100%);
}
.ph-portrait {
  min-height: 480px;
  background-image: linear-gradient(160deg, #3c3230 0%, #1c1614 100%);
}
.ph-stone {
  min-height: 420px;
  background-image: linear-gradient(160deg, #4c4438 0%, #2e2820 55%, #17130e 100%);
}

/* =========================================================
   4. SITE HEADER + FULL-SCREEN OVERLAY MENU
   Three-zone bar: [Menu button + quick links] [wordmark] [Reserve +
   account + cart]. The FULL nav lives in the #vsp-overlay dialog.
========================================================= */
#vsp-header {
  position: sticky;
  top: 0;
  z-index: 999;
  background: transparent;
  box-shadow: 0 1px 0 var(--vsp-line);
  transition: box-shadow 0.35s ease;
}
#vsp-header.is-scrolled { box-shadow: 0 1px 0 var(--vsp-line), 0 12px 32px rgba(0, 0, 0, 0.35); }
/* Frosted dark-glass backdrop on its own ::before layer, NOT on
   #vsp-header itself — per the CSS spec, backdrop-filter makes an element
   a new containing block for its own position:fixed DESCENDANTS. #c7-cart
   lives inside this header (Commerce7 renders its .c7-side-cart drawer,
   position:fixed, as a descendant of it), so backdrop-filter directly on
   #vsp-header would resolve that drawer's fixed coordinates against the
   header's own small box instead of the viewport, squishing it into a
   short band instead of a full-height panel. This pseudo-element is a
   sibling of #c7-cart in the DOM (both children of #vsp-header), not an
   ancestor of it, so it can carry backdrop-filter safely — #c7-cart's
   containing-block chain never passes through it. */
#vsp-header::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: rgba(19, 16, 22, 0.55);
  backdrop-filter: blur(18px) saturate(140%);
  -webkit-backdrop-filter: blur(18px) saturate(140%);
  transition: background 0.35s ease;
}
/* Glass gets decisively more opaque once content is actually scrolling
   underneath it — at rest, over the hero photo, it stays airier. */
#vsp-header.is-scrolled::before { background: rgba(15, 12, 18, 0.88); }

/* Announcement ribbon — one gold-on-black line (shipping threshold,
   pouring hours) above the main bar; editable at Vespera Estate >
   Pages & Content > Hero. Collapses away on scroll like the old topbar
   so the shrunken sticky header stays minimal. */
.vsp-announce {
  background: var(--vsp-ink);
  color: var(--vsp-clay);
  max-height: 40px;
  opacity: 1;
  overflow: hidden;
  border-bottom: 1px solid rgba(200, 164, 94, 0.18);
  transition: max-height 0.25s ease, opacity 0.2s ease;
}
.vsp-announce .vsp-container {
  padding-top: 8px;
  padding-bottom: 8px;
  text-align: center;
  font-size: 0.66rem;
  font-weight: 500;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.vsp-header-main {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: var(--vsp-space-sm);
  /* Longhand top/bottom only — this element is ALSO .vsp-container (same
     div, both classes), which sets its own left/right padding via the
     "padding" shorthand. Using the shorthand here too would reset those
     to 0 (it appears later in the file, so it'd win the cascade). */
  padding-top: 20px;
  padding-bottom: 20px;
  transition: padding 0.25s ease;
}
.vsp-header-zone { display: flex; align-items: center; min-width: 0; }
.vsp-header-zone--left { justify-self: start; gap: clamp(16px, 2.5vw, 36px); }
.vsp-header-zone--right { justify-self: end; gap: clamp(12px, 1.6vw, 20px); }

/* "Menu" toggle — a two-line glyph plus a visible tracked label (an
   unlabeled hamburger on a luxury site reads as an afterthought; the
   word is the affordance). The !important border/background overrides
   keep the generic bare-button fallback (section 2) from boxing it. */
.vsp-nav-toggle {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  border: none !important;
  background: none !important;
  background-color: transparent !important;
  -webkit-tap-highlight-color: transparent;
  cursor: pointer;
  padding: 8px 2px;
  color: var(--vsp-charcoal) !important;
  font-family: var(--vsp-font-body);
  font-size: 0.72rem;
  font-weight: 500;
  letter-spacing: 0.24em;
  text-transform: uppercase;
}
.vsp-nav-toggle-icon {
  display: inline-flex;
  flex-direction: column;
  gap: 5px;
  width: 26px;
}
.vsp-nav-toggle-icon span {
  height: 1.5px;
  background: currentColor;
  transition: transform 0.35s var(--vsp-ease), width 0.35s var(--vsp-ease);
}
.vsp-nav-toggle-icon span:last-child { width: 65%; }
.vsp-nav-toggle:hover .vsp-nav-toggle-icon span:last-child { width: 100%; }
.vsp-nav-toggle:hover { color: var(--vsp-clay) !important; }

/* Quick links (vsp-quick menu location, ≤3 items) — desktop only. */
.vsp-quick-nav ul {
  list-style: none;
  display: flex;
  gap: 4px;
  margin: 0;
  padding: 0;
}
.vsp-quick-nav a {
  position: relative;
  display: inline-block;
  padding: 10px 4px;
  margin: 0 10px;
  font-size: 0.72rem;
  font-weight: 500;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--vsp-charcoal);
  transition: color 0.25s ease;
}
/* Sliding gold underline — grows in from the left on hover, retreats to
   the right on mouse-out (the transform-origin swap), stays drawn on the
   current page's item. */
.vsp-quick-nav a::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 4px;
  height: 1px;
  background: var(--vsp-clay);
  transform: scaleX(0);
  transform-origin: right;
  transition: transform 0.35s var(--vsp-ease);
}
.vsp-quick-nav a:hover,
.vsp-quick-nav .current-menu-item > a { color: var(--vsp-clay); }
.vsp-quick-nav a:hover::before,
.vsp-quick-nav .current-menu-item > a::before {
  transform: scaleX(1);
  transform-origin: left;
}

/* Centered wordmark — Cormorant italic, the same accent cut used for
   pull-quotes and stat numbers, so the logotype reads as a flourish
   rather than just a big heading. */
.vsp-logo {
  justify-self: center;
  text-align: center;
  font-family: var(--vsp-font-accent);
  font-style: italic;
  font-size: clamp(1.7rem, 2.4vw, 2.4rem);
  font-weight: 500;
  color: var(--vsp-charcoal);
  letter-spacing: 0.02em;
  line-height: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
}
.vsp-logo:hover { color: var(--vsp-charcoal); }
.vsp-logo img { max-height: 52px; width: auto; }
.vsp-logo-stack { display: flex; flex-direction: column; align-items: center; gap: 5px; }
.vsp-logo span.vsp-logo-sub {
  display: block;
  font-family: var(--vsp-font-body);
  font-style: normal;
  font-size: 0.54rem;
  letter-spacing: 0.34em;
  text-transform: uppercase;
  color: var(--vsp-clay);
  font-weight: 500;
}

/* Compact header Reserve button — the site's #1 action, persistent in
   the sticky bar (hidden on small screens, where the hero CTA and the
   floating reserve pill cover it instead). */
.vsp-header-reserve { padding: 10px 20px; font-size: 0.66rem; letter-spacing: 0.2em; }
.vsp-header-reserve::after { display: none; }

.vsp-header-actions {
  display: flex;
  align-items: center;
  gap: 16px;
}
/* Icon-only by default via the existing button:has(svg) bare-icon
   fallback (section 2) — no color/border rules needed here, just
   sizing/cursor to match the other header icon controls. */
.vsp-search-toggle { display: flex; align-items: center; cursor: pointer; padding: 4px; }
/* The dropdown panel toggled by #vsp-search-toggle (assets/js/theme.js) —
   [hidden] is all the show/hide logic needs (see the JS comment), this
   just adds the visual chrome once it's visible. */
.vsp-header-search {
  border-top: 1px solid var(--vsp-line);
  background: var(--vsp-cream);
  padding: var(--vsp-space-sm) 0;
}
.vsp-cart-link {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-family: var(--vsp-font-body);
  font-size: 0.78rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  border: 1px solid var(--vsp-charcoal);
  background: none;
  color: var(--vsp-charcoal);
  padding: 9px 18px;
  border-radius: var(--vsp-radius-sm);
  cursor: pointer;
}
.vsp-cart-link:hover { border-color: var(--vsp-clay); color: var(--vsp-clay); }
.vsp-cart-link--disabled { opacity: 0.5; cursor: not-allowed; }

/* ---------------------------------------------------------
   Full-screen overlay menu (#vsp-overlay, header.php). [hidden] is the
   show/hide source of truth (assets/js/theme.js); .is-open only drives
   the entrance/exit fade so reduced-motion visitors simply get an
   instant swap.
--------------------------------------------------------- */
#vsp-overlay {
  position: fixed;
  inset: 0;
  z-index: 1300; /* above the sticky header AND its scroll-progress hairline */
  background: rgba(11, 8, 13, 0.97);
  overflow-y: auto;
  opacity: 0;
  transition: opacity 0.35s ease;
}
#vsp-overlay.is-open { opacity: 1; }
body.vsp-overlay-open { overflow: hidden; }

.vsp-overlay-inner {
  max-width: var(--vsp-container);
  margin: 0 auto;
  min-height: 100%;
  display: flex;
  flex-direction: column;
  padding: var(--vsp-space-md);
}
.vsp-overlay-top {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding-bottom: var(--vsp-space-md);
  border-bottom: 1px solid rgba(236, 228, 212, 0.12);
}
.vsp-overlay-brand {
  font-family: var(--vsp-font-accent);
  font-style: italic;
  font-size: 1.4rem;
  color: var(--vsp-light);
}
.vsp-overlay-close {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  border: none !important;
  background: none !important;
  background-color: transparent !important;
  cursor: pointer;
  padding: 8px;
  color: var(--vsp-light) !important;
  font-family: var(--vsp-font-body);
  font-size: 0.72rem;
  letter-spacing: 0.24em;
  text-transform: uppercase;
}
.vsp-overlay-close span[aria-hidden] { font-size: 1.6rem; line-height: 1; }
.vsp-overlay-close:hover { color: var(--vsp-clay) !important; }

.vsp-overlay-columns {
  flex: 1;
  display: grid;
  grid-template-columns: 1.5fr 1fr;
  gap: var(--vsp-space-lg);
  align-items: center;
  padding: var(--vsp-space-lg) 0;
}

/* Primary nav at editorial display scale, with engraved index numerals
   via a CSS counter — no markup or nav-walker changes needed. */
#vsp-primary-nav ul {
  list-style: none;
  margin: 0;
  padding: 0;
  counter-reset: vsp-menu;
}
#vsp-primary-nav > ul > li { margin-bottom: clamp(2px, 0.8vh, 10px); }
#vsp-primary-nav > ul > li > a {
  counter-increment: vsp-menu;
  display: inline-flex;
  align-items: baseline;
  gap: 18px;
  font-family: var(--vsp-font-heading);
  font-weight: 500;
  font-size: clamp(1.7rem, 4.2vh, 2.9rem);
  line-height: 1.15;
  color: var(--vsp-light);
  transition: color 0.25s ease, transform 0.35s var(--vsp-ease);
}
#vsp-primary-nav > ul > li > a::before {
  content: counter(vsp-menu, decimal-leading-zero);
  font-family: var(--vsp-font-body);
  font-size: 0.62rem;
  font-weight: 500;
  letter-spacing: 0.2em;
  color: var(--vsp-clay);
  opacity: 0.85;
}
#vsp-primary-nav > ul > li > a:hover,
#vsp-primary-nav .current-menu-item > a {
  color: var(--vsp-clay);
  transform: translateX(8px);
}
/* Submenus render inline as an indented small-caps row under their
   parent — a hover dropdown makes no sense inside a full-screen menu. */
#vsp-primary-nav .sub-menu {
  display: flex;
  flex-wrap: wrap;
  gap: 2px 18px;
  margin: 2px 0 10px 44px;
  padding: 0;
}
#vsp-primary-nav .sub-menu a {
  font-family: var(--vsp-font-body);
  font-size: 0.72rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--vsp-charcoal-soft);
  transition: color 0.2s ease;
}
#vsp-primary-nav .sub-menu a:hover { color: var(--vsp-clay); }

.vsp-overlay-aside {
  border-left: 1px solid rgba(236, 228, 212, 0.12);
  padding-left: var(--vsp-space-lg);
  color: var(--vsp-charcoal-soft);
  font-size: 0.92rem;
}
.vsp-overlay-aside p { margin-bottom: 6px; }
.vsp-overlay-aside a { color: var(--vsp-light); }
.vsp-overlay-aside a:hover { color: var(--vsp-clay); }
.vsp-overlay-aside .vsp-btn { margin-top: var(--vsp-space-sm); }
.vsp-overlay-aside-block { margin-bottom: var(--vsp-space-md); }
.vsp-overlay-aside-label {
  display: block;
  font-size: 0.62rem;
  font-weight: 500;
  letter-spacing: 0.28em;
  text-transform: uppercase;
  color: var(--vsp-clay);
  margin-bottom: 10px;
}

@media (max-width: 900px) {
  .vsp-quick-nav { display: none; }
  .vsp-overlay-columns { grid-template-columns: 1fr; gap: var(--vsp-space-md); align-items: start; }
  .vsp-overlay-aside { border-left: none; border-top: 1px solid rgba(236, 228, 212, 0.12); padding-left: 0; padding-top: var(--vsp-space-md); }
  #vsp-primary-nav > ul > li > a { font-size: clamp(1.5rem, 3.4vh, 2.2rem); }
  #vsp-primary-nav .sub-menu { margin-left: 38px; }
}
@media (max-width: 780px) {
  .vsp-header-reserve { display: none; }
}

/* Commerce7 mounts real widgets into these — we only control spacing
   around them, not their internal markup (that's commerce7.css's job). */
#c7-account { display: flex; align-items: center; font-size: 0.85rem; }
.c7-buy-product, .c7-club-join-button { margin-top: var(--vsp-space-xs); }

/* #c7-cart lives in the header now (inc/commerce7.php, [c7_cart_trigger])
   — its intended location per Commerce7's own docs, right in the nav
   actions row. Two earlier attempts to reposition it with CSS
   (display:none, then position:fixed) both broke its internal count-badge
   logic, so this is deliberately hands-off: no positioning, no hiding,
   just normal inline-flex alignment matching its header siblings. */
#c7-cart { display: inline-flex; align-items: center; }

/* An earlier version of this rule reset EVERY descendant of #c7-cart
   (border/background/color, via a `#c7-cart *` wildcard). That assumed
   the drawer panel (.c7-side-cart) was NOT nested inside #c7-cart — it
   is, at least in this rendering — so the wildcard also stripped the
   drawer's own background and legitimately-styled buttons like "Proceed
   to Checkout", making the whole cart panel transparent. Reverted to the
   :has(svg) icon-button rule further down this file, which only targets
   genuinely icon-only buttons (the header toggle, the drawer's close X)
   without touching containers or intentional CTA buttons. */

/* Login — Commerce7 leaves this view visually bare by design; give it a
   card treatment matching the rest of the site. Scoped to .c7-form--login
   specifically (a real modifier class, confirmed in commerce7's own JS)
   rather than the bare .c7-form base class, which Commerce7 reuses for
   every form on the site — including the tiny inline add-to-cart form on
   each product tile. An earlier version of this rule targeted .c7-form
   generically and put the login card's huge padding/background on every
   product card's add-to-cart controls instead. */
#c7-content .c7-form--login,
#c7-content .c7-login-form {
  max-width: 460px;
  margin: var(--vsp-space-lg) auto;
  padding: var(--vsp-space-lg) var(--vsp-space-md);
  background: var(--vsp-cream-raised);
  border: 1px solid rgba(236, 228, 212, 0.1);
  border-radius: var(--vsp-radius);
}
#c7-content .c7-club,
#c7-content .c7-reservation-type {
  padding: var(--vsp-space-md);
  background: var(--vsp-cream);
  border: 1px solid rgba(236, 228, 212, 0.08);
  border-radius: var(--vsp-radius);
}
@media (max-width: 600px) {
  #c7-content .c7-form--login,
  #c7-content .c7-login-form { padding: var(--vsp-space-md) var(--vsp-space-sm); margin: var(--vsp-space-md) auto; }
}

/* Wine/product cards — restyle Commerce7's default collection grid to
   match the site's editorial look (no underlined serif titles, on-brand
   pricing font). Card background/border is scoped to the "featured"
   variant ONLY (.c7-product-collection__products--featured .c7-product)
   — plain, non-featured grid items keep Commerce7's own bare background
   as-is, matching how Commerce7 itself distinguishes the two.
   Padding is intentionally left untouched on the featured card: Commerce7
   pairs its own 30px/20px/20px padding with a -60px top margin on
   .c7-product__image specifically so the bottle art pokes above the card
   by a calibrated amount — an earlier version of this rule overrode
   padding to a smaller value without adjusting for that, which crowded
   the image into the top of the card. */
#c7-content .c7-product-collection__product-list {
  gap: var(--vsp-space-md);
}

/* Commerce7 only caps image height (max-height:300px) — it doesn't fix a
   consistent height or center images within one, so bottle photos with
   different natural proportions (some tall and thin, some shorter/wider
   crops) each render at their own size, making a row of products look
   misaligned. Forcing a fixed-height flex box with object-fit:contain
   gives every product image the same footprint and vertically centers
   the bottle within it, regardless of the source photo's own dimensions.

   !important is required here — the shop-layout-branding-for-commerce7
   plugin's own "Shop Layout" feature injects its own !important rules for
   .c7-product__image / .c7-product__image img at every breakpoint (e.g.
   "max-height: 300px !important; width: auto !important; height: auto
   !important;", confirmed via the live page's actual injected CSS), and
   !important always wins over a normal declaration regardless of
   specificity — an earlier version of this rule had no !important and
   was being silently overridden by the plugin's, which is why the fix
   appeared to do nothing.

   Scoped specifically to .c7-product-collection — NOT a bare #c7-content
   .c7-product__image. .c7-product__image is reused on the single product
   detail page (its own main photo + thumbnail gallery, an entirely
   different layout with no "row of products" to align) and in any
   related-products rail, both of which this fix was also incorrectly
   reaching and distorting. Only the collection/grid listing actually has
   the multiple-different-bottle-photos-in-a-row problem this solves. */
/* Selector matches the plugin's own exact chain (#c7-content
   .c7-product-collection .c7-product .c7-product__image, confirmed via
   the live page's injected CSS) with .c7-product doubled for extra
   specificity — the plugin's rule goes THROUGH .c7-product, which made it
   more specific than an earlier version of this rule that skipped
   straight from .c7-product-collection to .c7-product__image, so the
   plugin's !important won on specificity even before considering load
   order, and none of this was actually applying yet.

   align-self:stretch (overriding the plugin's own flex-start) makes the
   image box stretch to match its row's full height — necessary because a
   2-line product title on one card versus a 1-line title on its neighbor
   makes their rows genuinely different heights, so a fixed height alone
   still lets image boxes start at different Y positions if one row is
   taller than the other. Using min-height rather than height is what
   lets stretch actually take effect: per the flexbox spec, align-self:
   stretch is a no-op once an item has an explicit (non-auto) height —
   min-height doesn't count as "explicit" the same way, so the box can
   still grow to fill the row while keeping a guaranteed floor. Centering
   the bottle within whatever height that resolves to (align-items:
   center) keeps every image on the same visual centerline regardless of
   how tall the text content beside it happens to be. */
/* Two prefixes, not one: #c7-content is present on the dedicated /collection
   page (page-templates/page-c7-content.php manually wraps its mount point
   in <div id="c7-content">), but vsp_c7_render_collection() — what powers
   the homepage's [c7_collection] embed — outputs a bare
   <div class="c7-product-collection"> with no #c7-content ancestor at all
   (inc/commerce7.php). An earlier version of this fix only had the
   #c7-content-prefixed selector, so it silently never matched on the
   homepage (0 specificity contest, just a selector that never applies) —
   the fix "worked" on /collection and did nothing on the homepage, which
   is why the bottle image stayed flush-left there specifically. The
   .vsp-collection-embed prefix (the wrapper front-page.php puts around the
   homepage's shortcode output) covers that second context the same way. */
#c7-content .c7-product-collection .c7-product.c7-product .c7-product__image,
.vsp-collection-embed .c7-product-collection .c7-product.c7-product .c7-product__image {
  display: flex !important;
  align-items: center !important;
  justify-content: center !important;
  align-self: stretch !important;
  height: auto !important;
  min-height: 340px !important;
}
#c7-content .c7-product-collection .c7-product.c7-product .c7-product__image img,
.vsp-collection-embed .c7-product-collection .c7-product.c7-product .c7-product__image img {
  width: 100% !important;
  height: 100% !important;
  max-width: 100% !important;
  max-height: 100% !important;
  object-fit: contain !important;
}
/* Confirmed via live DOM inspection that .c7-product__image wraps its img
   in an <a> (<div class="c7-product__image"><a><img></a></div>) — the <a>,
   not the img, is the actual direct flex item under the display:flex rule
   above. Left unstyled, that <a> has no explicit size, so it shrink-wraps
   to the img's own intrinsic pixel dimensions (confirmed 180x600 for the
   seed Chardonnay photo — a tall, narrow cutout) instead of filling the
   box, which is why justify-content: center above had an undersized item
   to center and bottle photos rendered flush left with dead space beside
   them.

   height is a fixed px value here, NOT 100% — .c7-product__image only sets
   min-height (a floor, kept deliberately so align-self:stretch can still
   equalize row heights per the comment above), never an explicit height,
   so it has no definite height of its own in the common case. height:100%
   on the <a> had nothing real to resolve against and fell back toward the
   img's own intrinsic height (600px for the seed photo), which is what
   blew the card up to nearly full-viewport-tall on mobile the first time
   this shipped. A fixed px height matching the container's own min-height
   floor gives object-fit:contain on the img below a real, bounded box to
   scale into instead. */
#c7-content .c7-product-collection .c7-product.c7-product .c7-product__image a,
.vsp-collection-embed .c7-product-collection .c7-product.c7-product .c7-product__image a {
  display: flex !important;
  align-items: center !important;
  justify-content: center !important;
  width: 100% !important;
  height: 340px !important;
}
#c7-content .c7-product-collection__products--featured .c7-product {
  background: var(--vsp-cream);
  border: 1px solid rgba(236, 228, 212, 0.08);
  border-radius: var(--vsp-radius);
}
#c7-content .c7-product__title a {
  text-decoration: none;
}
#c7-content .c7-product__title a:hover {
  text-decoration: underline;
}
#c7-content .c7-product__add-to-cart__price {
  font-family: var(--vsp-font-body);
}
/* Quantity box + Add to Cart button default to flexbox's own nowrap —
   comfortable at normal widths, but with zero margin for error on the
   narrowest phones (~320px) once a product page adds its own side
   padding on top. Letting this pair wrap is a safe, invisible-until-
   actually-needed safety net rather than a visible layout change. */
#c7-content .c7-product__add-to-cart__form {
  flex-wrap: wrap;
}

/* Quantity field: Commerce7 styles the box from --c7-field-bg (now midnight)
   but never sets `color` on the input. Chrome's UA stylesheet then paints
   fieldtext — black — so the number vanishes on the wines listing.
   Same mount is used by [c7_buy] on the filterable catalog. */
#c7-content .c7-product__add-to-cart input,
#c7-content .c7-product__add-to-cart select,
.c7-buy-product input,
.c7-buy-product select {
  color: var(--vsp-charcoal) !important;
  -webkit-text-fill-color: var(--vsp-charcoal) !important;
  background-color: var(--vsp-cream-raised) !important;
  color-scheme: dark;
  caret-color: var(--vsp-clay);
}

/* Homepage embeds the collection widget just below our own "Featured
   Wines" section head — Commerce7 also renders its own collection-name
   heading ("Wines") at the top of the widget itself, duplicating ours.
   Broadened from the original direct-child (>) selectors: the live
   widget nests its heading deeper than __content's immediate children
   (screenshot-confirmed the "Wines" h-tag was still rendering), so match
   ANY h1/h2 inside the collection that is NOT part of a product card —
   product titles themselves can legitimately be headings. Hidden only in
   this homepage wrapper; the dedicated /collection page still shows
   Commerce7's own heading normally, since nothing else labels it there. */
.vsp-collection-embed .c7-product-collection :is(h1, h2, .c7-h1):not(.c7-product *) {
  display: none;
}

/* Homepage collection embed — proper wine cards.
   The widget's own layout renders each product as a loose image-beside-
   text row on a bare background (screenshot-confirmed), which reads as
   an unstyled directory listing between the theme's otherwise polished
   sections. Restack each product as a centered vertical card: bottle on
   top, then name / region / note / price / buy. !important throughout
   because Commerce7 + the shop-layout plugin inject their own !important
   layout rules (see the .c7-product__image saga above) — matching their
   specificity game is the established pattern in this file.
   Scoped to .vsp-collection-embed only: the /collection page keeps
   Commerce7's own full-page layout, which works fine there. */
.vsp-collection-embed .c7-product-collection__product-list {
  display: grid !important;
  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)) !important;
  gap: var(--vsp-space-sm) !important;
  align-items: stretch !important;
}
.vsp-collection-embed .c7-product.c7-product {
  display: flex !important;
  flex-direction: column !important;
  align-items: center !important;
  text-align: center !important;
  margin: 0 !important;
  padding: var(--vsp-space-sm) var(--vsp-space-sm) var(--vsp-space-md) !important;
  background: var(--vsp-cream);
  border: 1px solid var(--vsp-line);
  border-radius: var(--vsp-radius);
  transition: transform 0.35s var(--vsp-ease), box-shadow 0.35s var(--vsp-ease);
}
.vsp-collection-embed .c7-product.c7-product:hover {
  transform: translateY(-4px);
  box-shadow: var(--vsp-shadow-md);
}
.vsp-collection-embed .c7-product__title {
  font-size: 1.3rem;
  line-height: 1.2;
  margin: 14px 0 4px;
}
.vsp-collection-embed .c7-product__title a { color: var(--vsp-charcoal); }
.vsp-collection-embed .c7-product__title a:hover { color: var(--vsp-clay); }
/* Region/appellation line ("Walla Walla", "Napa Valley") as a quiet
   small-caps kicker under the name rather than body-copy gray text. */
.vsp-collection-embed .c7-product__subtitle,
.vsp-collection-embed .c7-product__region {
  font-size: 0.72rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--vsp-olive);
}
/* Tasting-note paragraph: muted, measure-capped, clamped to three lines
   so one wordy note can't stretch its whole card row. The :not() guards
   keep this off the price/add-to-cart area's own text. */
.vsp-collection-embed .c7-product p:not([class*="price"]):not([class*="add-to-cart"] *) {
  color: var(--vsp-charcoal-soft);
  font-size: 0.92rem;
  line-height: 1.6;
  max-width: 40ch;
  margin: 8px auto;
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.vsp-collection-embed .c7-product__add-to-cart__price {
  font-weight: 600;
  font-size: 1.05rem;
  color: var(--vsp-charcoal);
}
/* The buy row (qty + add-to-cart) hugs the card's bottom edge so every
   card's buy button lines up across the row regardless of note length. */
.vsp-collection-embed .c7-product__add-to-cart,
.vsp-collection-embed .c7-product__add-to-cart__form {
  margin-top: auto !important;
  justify-content: center !important;
}

/* Product grid on small phones — two columns of wine cards gets cramped
   under ~480px (image, title, and price all squeeze together); drop to
   a single column so each card keeps readable proportions. Also shrink
   the fixed 260px image box from the alignment fix above — that height
   was sized for a multi-column desktop/tablet grid; once the grid is a
   single column on a narrow phone, 260px of image before any text even
   starts reads as a lot of scrolling for not much content. */
@media (max-width: 480px) {
  #c7-content .c7-product-collection__products--featured .c7-product-collection__product-list,
  #c7-content .c7-product-collection__product-list.c7-product-collection__product-list,
  .vsp-collection-embed .c7-product-collection__products--featured .c7-product-collection__product-list,
  .vsp-collection-embed .c7-product-collection__product-list.c7-product-collection__product-list {
    grid-template-columns: 1fr !important;
  }
  #c7-content .c7-product-collection .c7-product.c7-product .c7-product__image,
  .vsp-collection-embed .c7-product-collection .c7-product.c7-product .c7-product__image {
    min-height: 280px !important;
  }
  #c7-content .c7-product-collection .c7-product.c7-product .c7-product__image a,
  .vsp-collection-embed .c7-product-collection .c7-product.c7-product .c7-product__image a {
    height: 280px !important;
  }
}

/* Side-cart drawer line items — confirmed via live DevTools inspection to
   use a different, older set of classes (.c7-order-item__title, .c7-link)
   than the rest of the storefront (.c7-product__title etc), styled by a
   separate stylesheet ("c7-base.css") that hardcodes a pink/rose color
   (#c36) directly — including a solid fill on hover — rather than
   through any --c7-* custom property.

   Rather than hardcoding our own merlot here (which would work, but then
   permanently locks these specific elements out of a Commerce7 branding
   plugin's Brand Kit — it can only ever adjust --c7-* variables, never
   this file's literal color), these rules are pointed at the SAME
   --c7-* variables the rest of the storefront already uses. This theme's
   own bridge (top of this file) sets those variables to the brand colors
   below by default; a branding plugin overriding e.g. --c7-primary-color
   flows through here exactly like it does for every other Commerce7
   button, with no special-casing needed.

   Specificity is deliberately doubled (.c7-link.c7-link, not just
   .c7-link) and !important is still required — c7-base.css is injected
   by Commerce7's JS at runtime (after this stylesheet), so a same-
   specificity rule there wins any tie regardless of !important on either
   side. Unscoped/global on purpose: the drawer is portaled to <body> by
   Commerce7's own JS, so it isn't actually a descendant of #c7-content
   or #c7-cart, and .c7-link/.c7-order-item__title also appear on the
   dedicated /cart page outside any of those wrappers too. */
/* Product title — a plain text link, never a boxed button. */
.c7-order-item__title a,
.c7-order-item .c7-order-item__title a {
  color: var(--c7-link-color) !important;
  border: none !important;
  background-color: transparent !important;
}
.c7-order-item__title a:hover {
  color: var(--c7-primary-color-dark) !important;
}

/* "Remove" etc (.c7-link) — this one IS an actual button control, so it
   keeps a real bordered/outline look that fills on hover, same pattern as
   the generic bare-button fallback above, just routed through Commerce7's
   own --c7-primary-* variables instead of the theme's own charcoal/merlot
   — so a Commerce7 branding plugin's Brand Kit can still control this
   specific element's color, unlike the generic fallback which isn't
   Commerce7-specific and has no reason to defer to it. */
.c7-link.c7-link {
  color: var(--c7-link-color) !important;
  border: 1px solid var(--c7-primary-color) !important;
  background-color: transparent !important;
  padding: 0.45em 0.9em !important;
  line-height: 1.25 !important;
  min-height: 2.35em !important;
  display: inline-flex !important;
  align-items: center !important;
  justify-content: center !important;
  box-sizing: border-box !important;
}
.c7-link.c7-link:hover {
  color: var(--c7-primary-button-text-color) !important;
  background-color: var(--c7-primary-button-bg-hover) !important;
  border-color: var(--c7-primary-button-bg-hover) !important;
}
.c7-order-item__price.c7-order-item__price,
.c7-order-item__price.c7-order-item__price span,
.c7-order-item__total.c7-order-item__total,
.c7-product__price.c7-product__price,
#c7-content .c7-product__add-to-cart__price {
  font-size: 1rem !important;
  font-weight: 500 !important;
  line-height: 1.3 !important;
}

/* Commerce7 reuses the SAME bare .c7-link class for the login page's
   "Forgot your password?" / "Create one" links (confirmed via user-
   supplied DOM: <a class="c7-link" href="/profile/forgot-password">,
   <a class="c7-link" href="/profile/create-account">) — the boxed-
   button look above is right for "Remove" in the cart, but wrong here:
   two bordered boxes next to the real "Log In" button reads as three
   competing buttons instead of one primary action plus two subtle
   secondary links. Scoped by the exact confirmed href rather than a
   guessed ancestor wrapper (e.g. ".c7-account-login") — the cart's
   "Remove" link isn't a real page link at all, so there's no risk of
   this ever matching it by accident, unlike a class/ancestor guess that
   could go wrong in either direction. */
a.c7-link[href*="/profile/forgot-password"],
a.c7-link[href*="/profile/create-account"] {
  border: none !important;
  background-color: transparent !important;
  color: var(--c7-link-color) !important;
  text-decoration: underline !important;
  padding: 0 !important;
  min-height: 0 !important;
  display: inline !important;
}
a.c7-link[href*="/profile/forgot-password"]:hover,
a.c7-link[href*="/profile/create-account"]:hover {
  background-color: transparent !important;
  color: var(--c7-primary-color-dark) !important;
}

/* "Proceed to Checkout" and other primary CTAs (.c7-btn.c7-btn--primary)
   — confirmed real selector via browser inspection (an <a> styled as a
   filled button, e.g. #c7-side-cart__summary__proceed). The earlier fix
   here guessed at a --c7-primary-button-text-color-hover CSS variable,
   which had no effect — Commerce7's own hover state isn't reading a
   variable for this element at all, so only a direct override works.
   Same specificity-doubling + !important pattern as .c7-link.c7-link
   above, for the same reason: c7-base.css loads after this stylesheet
   via Commerce7's own JS, so an equal-specificity rule there would win
   any tie regardless of !important on either side. */
.c7-btn.c7-btn--primary:hover,
.c7-btn.c7-btn--primary:focus {
  color: var(--c7-primary-button-text-color) !important;
  background-color: var(--c7-primary-button-bg-hover) !important;
  border-color: var(--c7-primary-button-bg-hover) !important;
}

/* Product page image gallery (confirmed real markup via browser
   inspection): Commerce7 already renders a proper main-image +
   clickable-thumbnail-strip structure on its own
   (.c7-product-detail__photo__thumbs wraps one <a><img></a> per extra
   image) — it just needs the thumbnails constrained to thumbnail SIZE.
   Our own global "img { max-width: 100%; display: block; }" rule
   (this file, section 2) was blowing each one up to the full width of
   .c7-product__image instead, which is what made multiple images stack
   as full-width "rows" rather than sit in a compact strip below the
   main photo. No slider/carousel needed — this is that gallery,
   correctly sized. */
/* Main product image — a direct child <img> of .c7-product__image,
   confirmed via inspection (sibling to .c7-product-detail__photo__thumbs,
   not inside it). Different product photos have wildly different
   aspect ratios (a tall bottle shot vs. a wide banner image, seen in
   the same product's own gallery) — with no fixed box, each one renders
   at its own natural height, so the whole layout visibly jumps every
   time a different thumbnail is clicked. A fixed-height box + object-fit
   fixes the jump; object-position: center (contain's own default, set
   explicitly here) keeps differently-shaped images centered in it
   instead of pinned to one corner. */
.c7-product__image > img {
  width: 100% !important;
  height: 460px !important;
  object-fit: contain !important;
  object-position: center !important;
}
@media (max-width: 600px) {
  .c7-product__image > img { height: 320px !important; }
}
.c7-product-detail__photo__thumbs {
  display: flex !important;
  flex-wrap: wrap !important;
  gap: 8px !important;
  margin-top: 8px !important;
}
.c7-product-detail__photo__thumbs a {
  display: block !important;
  width: 64px !important;
  flex: 0 0 64px !important;
}
.c7-product-detail__photo__thumbs img {
  width: 64px !important;
  height: 64px !important;
  object-fit: cover !important;
  border-radius: var(--vsp-radius) !important;
  border: 1px solid rgba(236, 228, 212, 0.15) !important;
  cursor: pointer;
}
.vsp-wine-view-link {
  display: inline-block;
  margin-top: 8px;
  font-size: 0.78rem;
  letter-spacing: 0.06em;
  text-decoration: underline;
}

/* Cart drawer line items (.c7-order-item) — Commerce7's own CSS never
   gives this a mobile breakpoint at all (checked directly in
   commerce7.css: zero @media rules reference .c7-order-item anywhere).
   It's a fixed 3-column flex row — 60px image, a flexible details column,
   and a pricing column carrying price + quantity dropdown + line total —
   that stays a single row at every viewport width. On a 320–375px phone
   that pricing column doesn't have room to sit beside the details column,
   so it was overflowing/cramping (visible as a forced horizontal
   scrollbar inside the drawer). Wrapping the row and giving pricing its
   own full-width line below the details — indented to align under them,
   past the image + gap — fixes that without touching desktop at all,
   since flex-wrap only has an effect once content actually doesn't fit.

   !important on every declaration here, not just flex-basis — an earlier
   version of this fix left flex-wrap (on both .c7-order-item and
   .c7-order-item__pricing) and the margins unprotected while only
   flex:0 0 100% carried !important. Confirmed via live DOM inspection
   the class names here are exactly right (.c7-order-item,
   .c7-order-item__pricing, .c7-order-item__price/__quantity/__total all
   match), so the fix not working live wasn't a selector mismatch — it's
   that Commerce7's own CSS sets flex-wrap: nowrap on .c7-order-item with
   its own !important (same pattern documented elsewhere in this file for
   .c7-product__image etc), which was winning over our unprotected
   `wrap` and leaving .c7-order-item__pricing told to be 100% wide inside
   a row that refused to actually wrap onto a second line — that
   contradiction is what forced the horizontal scroll. .c7-order-item__
   pricing's own three children (price/quantity/total) also get
   display:flex + flex-wrap so a long "was/now" price plus the quantity
   dropdown can drop onto their own line instead of staying forced into
   one row that's wider than the 70px-indented space available for it. */
@media (max-width: 480px) {
  .c7-order-item {
    display: flex !important;
    flex-wrap: wrap !important;
  }
  .c7-order-item__pricing {
    display: flex !important;
    flex: 0 0 100% !important;
    flex-wrap: wrap !important;
    align-items: center !important;
    gap: 6px 12px !important;
    margin-left: 70px !important;
    margin-top: 6px !important;
    max-width: calc(100% - 70px) !important;
  }
  .c7-order-item__price,
  .c7-order-item__quantity,
  .c7-order-item__total {
    flex: 0 0 auto !important;
  }
  .c7-side-cart__summary-container { padding: 16px 20px; }
}

.vsp-c7-page { min-height: 50vh; }
/* Tighter than the default .vsp-section top padding — the Wines Catalog's
   hero (page-templates/page-c7-content.php) should sit close beneath its
   photo, not leave a large gap, matching the reference layout. */
.vsp-hero + .vsp-c7-page { padding-top: var(--vsp-space-md); }

/* "Back to Wines" link on Commerce7 product pages (/product/...) — this
   sits outside #c7-content, so it's ours to style; Commerce7's own
   product widget has no built-in way back to the collection. */
.vsp-back-link { margin-bottom: var(--vsp-space-md); }
.vsp-back-link a { color: var(--vsp-charcoal-soft); text-decoration: none; font-size: 0.9rem; }
.vsp-back-link a:hover { color: var(--vsp-clay); text-decoration: underline; }

/* Checkout template intentionally has no header/footer — just center the
   Commerce7 content column and keep it comfortable on small screens. */
body.vsp-c7-checkout-body { background: var(--vsp-cream); }
.vsp-checkout-wrap {
  max-width: 760px;
  margin: 0 auto;
  padding: var(--vsp-space-lg) var(--vsp-space-md);
}
@media (max-width: 600px) {
  .vsp-checkout-wrap { padding: var(--vsp-space-md) var(--vsp-space-sm); }
}
@media (max-width: 380px) {
  /* Extra-narrow phones (iPhone SE and similar, ~320-375px) — Commerce7's
     own checkout layout already handles this width well internally (15
     dedicated @media rules in commerce7.css, confirmed), it just needs
     every spare pixel of width we can hand it rather than our own wrapper
     padding eating into it. */
  .vsp-checkout-wrap { padding: var(--vsp-space-md) 10px; }
}

/* Small-screen header: the three-zone grid stays (Menu / wordmark /
   actions) — the full nav is already in the overlay on every viewport,
   so nothing needs to restack or expand inline here. */
@media (max-width: 600px) {
  .vsp-nav-toggle-label { display: none; } /* the glyph alone at phone width */
  .vsp-header-main { padding-top: 14px; padding-bottom: 14px; }
}
@media (max-width: 480px) {
  .vsp-logo { font-size: 1.3rem; gap: 6px; }
  .vsp-logo span.vsp-logo-sub { font-size: 0.5rem; letter-spacing: 0.22em; }
  .vsp-header-actions { gap: 10px; }
  #c7-account { font-size: 0.72rem; }
  .vsp-announce .vsp-container { letter-spacing: 0.12em; }
}

/* =========================================================
   5. WINE / CLUB CARDS (used by Commerce7 shortcodes)
========================================================= */
.vsp-section { padding: var(--vsp-space-xl) 0; }
/* Compact variant — small connective sections (the homepage stats strip)
   that shouldn't command a full section's worth of vertical air. */
.vsp-section--tight { padding: var(--vsp-space-lg) 0; }
.vsp-section--cream { background: var(--vsp-cream-raised); }
.vsp-section-head { max-width: 680px; margin: 0 auto var(--vsp-space-lg); text-align: center; }
.vsp-section-head p { color: var(--vsp-charcoal-soft); font-size: 1.05rem; }

/* Horizontal image+details layout, matching Commerce7's own default
   storefront product listing (see readme.txt section 2) — no card
   background/border/padding "box" at all, just image + text sitting
   directly on the page, the same plain presentation Commerce7's own
   listing uses. The bottle image keeps its natural aspect ratio,
   uncropped, at a fixed column width instead of a fixed-height box. */
.vsp-wine-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(440px, 1fr));
  gap: var(--vsp-space-xl) var(--vsp-space-lg);
}
.vsp-wine-card {
  display: flex;
  align-items: flex-start;
  gap: var(--vsp-space-md);
}
/* Only wines marked in Settings > Commerce7 > "Featured wine slugs" get
   this — everything else stays the plain, boxless layout above. A subtle
   left accent rather than a filled background box, so it stands out
   without looking like a different, heavier card style bolted on. */
.vsp-wine-card--featured { border-left: 3px solid var(--vsp-clay); padding-left: var(--vsp-space-sm); }
.vsp-wine-featured-tag {
  display: inline-block;
  color: var(--vsp-clay);
  font-size: 0.7rem;
  font-weight: 500;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  margin-bottom: 6px;
}
/* Wraps .vsp-wine-img so the scarcity badge below has an absolute-
   position anchor without changing the image's own flex sizing — the
   wrapper takes over the flex-item role .vsp-wine-img used to play
   directly, with identical width so the layout is unchanged otherwise. */
.vsp-wine-card .vsp-wine-img-wrap { position: relative; flex: 0 0 170px; width: 170px; }
.vsp-wine-card .vsp-wine-img {
  display: block;
  margin: 0 auto;
  width: 170px;
  height: auto;
  max-height: 380px;
  object-fit: contain;
}
/* Distinct from .vsp-wine-featured-tag (subtle gold text, "this wine is
   worth noticing") — this is urgency, not curation, so it gets an actual
   filled pill rather than plain text, positioned on the image itself
   where it can't be missed while scanning the grid. */
.vsp-wine-scarcity-badge {
  position: absolute;
  top: 8px;
  left: 8px;
  background: var(--vsp-clay);
  color: var(--vsp-cream);
  font-family: var(--vsp-font-body);
  font-size: 0.68rem;
  font-weight: 500;
  letter-spacing: 0.04em;
  padding: 5px 10px;
  border-radius: var(--vsp-radius-sm);
  max-width: calc(100% - 16px);
}
.vsp-wine-card-body { flex: 1; min-width: 0; text-align: left; padding-top: 4px; }
.vsp-wine-card h3 { margin-bottom: 2px; font-size: 1.5rem; line-height: 1.2; }
.vsp-wine-card h3 a { color: inherit; text-decoration: none; }
.vsp-wine-card h3 a:hover { text-decoration: underline; }
.vsp-wine-card > a { display: block; }
.vsp-wine-subtitle { color: var(--vsp-charcoal-soft); font-size: 1rem; margin-bottom: var(--vsp-space-sm); }
.vsp-wine-teaser { color: var(--vsp-charcoal); font-size: 0.98rem; line-height: 1.6; margin-bottom: var(--vsp-space-sm); }
.vsp-wine-vintage { color: var(--vsp-charcoal-soft); font-size: 0.85rem; margin-bottom: var(--vsp-space-xs); }
.vsp-wine-price { color: var(--vsp-clay); font-weight: 500; margin-bottom: var(--vsp-space-sm); }
@media (max-width: 560px) {
  .vsp-wine-card { flex-direction: column; align-items: center; text-align: center; }
  .vsp-wine-card .vsp-wine-img { width: 170px; flex: none; max-height: 300px; }
  .vsp-wine-card-body { text-align: center; padding-top: 0; }
  /* The featured card's left accent bar (border-left + padding-left)
     only makes sense in the desktop horizontal layout — once the card
     stacks into a centered column, that one-sided left padding skews
     the whole centered stack off-center. Switch to a top bar instead,
     which stays balanced regardless of centering. */
  .vsp-wine-card--featured {
    border-left: none;
    padding-left: 0;
    border-top: 3px solid var(--vsp-clay);
    padding-top: var(--vsp-space-sm);
  }
}
.vsp-wine-meta {
  color: var(--vsp-charcoal-soft);
  font-size: 0.78rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  margin-bottom: 4px;
}

/* Wines Catalog widget (inc/commerce7.php, vsp_c7_render_wines_catalog())
   + assets/js/wines-catalog.js */
/* Two zones: type-filter pills on the left, search + varietal + sort
   grouped together on the right — each group sizes to its own content
   (nowrap) rather than being forced into equal shares, so the row stays
   on one line down to the 900px breakpoint below, where it stacks. */
.vsp-catalog-controls {
  display: flex;
  flex-wrap: nowrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--vsp-space-md);
  margin-bottom: var(--vsp-space-lg);
  padding-bottom: var(--vsp-space-md);
  border-bottom: 1px solid rgba(236, 228, 212, 0.12);
}
.vsp-catalog-filter { display: flex; flex-wrap: nowrap; gap: 8px; flex: 0 0 auto; }
.vsp-catalog-controls-right { display: flex; flex-wrap: nowrap; align-items: center; gap: var(--vsp-space-sm); flex: 0 0 auto; }
/* Search, dropdowns, and type buttons all share this same pill shape now
   — one consistent control style across the row instead of rounded
   buttons next to rectangular boxes. */
.vsp-catalog-pill,
.vsp-catalog-select,
.vsp-catalog-search {
  background: transparent;
  border: 1px solid rgba(236, 228, 212, 0.25) !important;
  border-radius: 999px !important;
  padding: 8px 18px !important;
  font-family: var(--vsp-font-body);
  font-size: 0.85rem;
  color: var(--vsp-charcoal) !important;
}
.vsp-catalog-pill { cursor: pointer; }
.vsp-catalog-pill.is-active,
.vsp-catalog-pill:hover,
.vsp-catalog-pill:focus {
  background: var(--vsp-clay) !important;
  border-color: var(--vsp-clay) !important;
  color: var(--vsp-cream) !important;
}
.vsp-catalog-select { background: var(--vsp-cream) !important; min-width: 160px; }
/* Icon-first, not text-first: at rest this is just the magnifying glass
   in a circle-ish pill (42px, no room for the placeholder to actually
   show) — it only expands into a real text field once focused or once
   it has a value. :not(:placeholder-shown) is what keeps it expanded
   after a value is typed and the field loses focus, instead of snapping
   back to icon-only and hiding what's currently filtering the grid.
   The wrapper keeps a FIXED 42px width/height reserved in the flex row
   at all times — the input itself is absolutely positioned inside it and
   expands past that boundary on focus, rather than actually resizing its
   flex item. Actually changing the flex item's width made the whole row
   reflow on focus (justify-content: space-between recalculates gaps
   whenever any item's size changes), visibly shoving the centered type
   pills sideways every time search was clicked. */
.vsp-catalog-search-wrap {
  position: relative;
  flex: 0 0 auto;
  width: 42px;
  height: 38px;
}
.vsp-catalog-search-icon {
  position: absolute;
  top: 50%;
  left: 15px;
  transform: translateY(-50%);
  color: var(--vsp-charcoal-soft);
  pointer-events: none;
  z-index: 2;
}
.vsp-catalog-search {
  position: absolute;
  top: 0;
  left: 0;
  background: var(--vsp-cream) !important;
  width: 42px;
  min-width: 0;
  padding-left: 34px !important;
  transition: width 0.2s ease;
  z-index: 1;
}
.vsp-catalog-search:focus,
.vsp-catalog-search:not(:placeholder-shown) {
  width: 200px;
  z-index: 3;
}
.vsp-catalog-search::placeholder { color: var(--vsp-charcoal-soft); }
/* Removes the default red/browser "X" clear-button styling some browsers
   apply to type="search" so it matches the rest of the field's chrome. */
.vsp-catalog-search::-webkit-search-cancel-button { -webkit-appearance: none; }
.vsp-catalog-empty {
  text-align: center;
  color: var(--vsp-charcoal-soft);
  padding: var(--vsp-space-lg) 0;
}
.vsp-catalog [hidden] { display: none; }

/* --- Mobile / tablet --- */
@media (max-width: 900px) {
  /* auto-fit + a fixed 440px minimum overflows any container narrower
     than that instead of shrinking (a well-known CSS Grid trap) — force
     a single column once there's no room for two anyway. Also tighten
     the 112px/64px desktop gap — stacked single-column cards on a phone
     don't need that much air between them. */
  .vsp-wine-grid { grid-template-columns: 1fr; gap: var(--vsp-space-lg); }

  /* Two groups side by side needs real width to read cleanly — stack
     them instead of letting the row get cramped. */
  .vsp-catalog-controls { flex-wrap: wrap; justify-content: center; margin-bottom: var(--vsp-space-md); padding-bottom: var(--vsp-space-sm); }
  .vsp-catalog-filter,
  .vsp-catalog-controls-right { flex: 1 1 100%; justify-content: center; }
}
@media (max-width: 640px) {
  .vsp-catalog-select,
  .vsp-catalog-search { width: 100%; min-width: 0; }
  .vsp-catalog-search-wrap { width: 100%; }
  .vsp-catalog-controls-right { flex-direction: column; align-items: stretch; }
}

.vsp-club-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: var(--vsp-space-md);
}
.vsp-club-card {
  background: var(--vsp-charcoal);
  color: var(--vsp-cream);
  padding: var(--vsp-space-lg) var(--vsp-space-md);
  border-radius: var(--vsp-radius);
  text-align: center;
}
.vsp-club-card h3 { color: var(--vsp-cream); }
.vsp-club-card .vsp-eyebrow { color: var(--vsp-clay); }

.vsp-c7-notice {
  border: 1px dashed var(--vsp-clay);
  background: var(--vsp-cream-raised);
  color: var(--vsp-charcoal-soft);
  padding: var(--vsp-space-md);
  text-align: center;
  font-size: 0.9rem;
  border-radius: var(--vsp-radius);
}

/* Team page (page-templates/page-team.php) */
.vsp-team-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: var(--vsp-space-md);
}
.vsp-team-card {
  background: var(--vsp-cream);
  border: 1px solid rgba(236, 228, 212, 0.08);
  border-radius: var(--vsp-radius);
  overflow: hidden;
}
.vsp-team-photo { width: 100%; min-height: 280px; object-fit: cover; }
.vsp-team-card-body { padding: var(--vsp-space-sm) var(--vsp-space-sm) var(--vsp-space-md); }
.vsp-team-card h2 { margin-bottom: 2px; font-size: 1.3rem; }
.vsp-team-role {
  color: var(--vsp-clay);
  font-size: 0.72rem;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  margin-bottom: var(--vsp-space-xs);
}

/* Full-bleed clay (terracotta) section band — a warm editorial pull-quote
   interruption. Cream text throughout, since it's sitting on the mid-tone
   accent rather than the page's own light background. */
/* Deep-wine band — the one saturated moment on the page (testimonials),
   a pour of the product itself across the midnight palette. */
.vsp-section--signal { background: var(--vsp-olive); color: var(--vsp-light); }
.vsp-eyebrow--on-signal,
h2.vsp-on-signal { color: var(--vsp-light); }
/* On the wine band the gold dash still reads; the label text goes light
   (set above), and the dash inherits it via currentColor automatically. */

/* Testimonials & Press (front-page.php) — oversized italic pull-quotes
   directly on the wine band, no card border/background needed since the
   band itself already separates this from the rest of the page. */
.vsp-testimonial-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: var(--vsp-space-lg) var(--vsp-space-md);
}
.vsp-testimonial-card {
  padding-top: var(--vsp-space-md);
  text-align: left;
}
.vsp-testimonial-quote {
  font-family: var(--vsp-font-accent);
  font-style: italic;
  font-size: clamp(1.4rem, 2vw, 1.8rem);
  line-height: 1.35;
  letter-spacing: -0.01em;
  color: var(--vsp-light);
  margin-bottom: var(--vsp-space-sm);
}
.vsp-testimonial-quote::before { content: "\201C"; color: var(--vsp-clay); opacity: 0.8; }
.vsp-testimonial-quote::after { content: "\201D"; color: var(--vsp-clay); opacity: 0.8; }
.vsp-testimonial-source {
  display: block;
  color: rgba(244, 238, 225, 0.75);
  font-family: var(--vsp-font-body);
  font-size: 0.72rem;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}

/* "As Seen In" press strip (front-page.php) — an editorial "masthead
   citation" treatment (thin rule above/below each name, italic serif,
   generous spacing) rather than a cramped row of dashed to-do boxes,
   since most sites run this section before any real logos are on hand.
   Real logo images still get the classic muted/grayscale-until-hover
   treatment once uploaded; the placeholder state below is what should
   look intentional and finished on its own, not like a stand-in. */
.vsp-press-section { text-align: center; padding-top: var(--vsp-space-xl); padding-bottom: var(--vsp-space-xl); }
/* inline-block, not block — the eyebrow is a bordered chip now, and a
   block-level chip's pill outline would stretch the full container
   width; the section's own text-align:center centers it fine. */
.vsp-press-section .vsp-eyebrow { display: inline-block; margin-bottom: var(--vsp-space-xs); }
.vsp-press-heading { margin-bottom: var(--vsp-space-lg); }
/* Continuous sliding "masthead" strip instead of wrapping to a second
   row once there are more publications than fit one line — front-
   page.php renders the item list twice (the second copy aria-hidden,
   tabindex="-1", visual-only) and this animates the combined row exactly
   -50% of its own width, which is precisely one full copy's width, so
   the moment the first copy has scrolled fully off, the (identical)
   second copy is sitting exactly where the first one started — the loop
   restarts with no visible seam. Fade mask softens the hard edges where
   logos enter/exit instead of an abrupt clip. Paused on hover/focus so
   a visitor can actually click a logo; disabled entirely under
   prefers-reduced-motion (vestibular-disorder safety, not just a nicety
   — continuous motion is exactly the kind of thing that spec exists for). */
.vsp-press-strip-outer {
  overflow: hidden;
  max-width: 960px;
  margin: 0 auto;
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent);
  mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent);
}
.vsp-press-strip {
  display: flex;
  align-items: center;
  gap: var(--vsp-space-lg);
  width: max-content;
  animation: vsp-press-scroll 32s linear infinite;
}
.vsp-press-strip-dup { display: contents; }
.vsp-press-strip-outer:hover .vsp-press-strip,
.vsp-press-strip-outer:focus-within .vsp-press-strip { animation-play-state: paused; }
@keyframes vsp-press-scroll {
  from { transform: translateX(0); }
  to { transform: translateX(-50%); }
}
@media (prefers-reduced-motion: reduce) {
  .vsp-press-strip { animation: none; }
}
/* Descendant selectors, not direct-child (>) — the duplicated set for
   the seamless loop sits inside .vsp-press-strip-dup (display:contents),
   which removes it from the BOX tree for layout but not the DOM tree,
   so a > combinator here would silently miss every duplicated item. */
.vsp-press-strip a,
.vsp-press-strip .vsp-press-logo-placeholder {
  flex: 0 0 auto;
  min-width: 160px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.vsp-press-strip img {
  max-height: 38px;
  width: auto;
  margin: 0 auto;
  filter: grayscale(100%);
  opacity: 0.55;
  transition: filter 0.25s ease, opacity 0.25s ease;
}
.vsp-press-strip a:hover img,
.vsp-press-strip a:focus-visible img { filter: grayscale(0%); opacity: 1; }
/* Real publication logos are trademarked, so an unset row shows this
   labeled placeholder instead of a fabricated logo image — same honesty-
   over-lorem-ipsum approach as every other ph-img placeholder. Thin
   rules above/below (not a box outline all the way round) read as a
   citation/masthead credit rather than an unfinished to-do item — the
   .ph-img default box/min-height/dashed-border look is overridden
   entirely rather than adapted, since none of it fits this treatment. */
.vsp-press-logo-placeholder {
  width: auto !important;
  min-height: 0 !important;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--vsp-space-sm) var(--vsp-space-xs);
  border: none;
  border-top: 1px solid rgba(236, 228, 212, 0.16);
  border-bottom: 1px solid rgba(236, 228, 212, 0.16);
  border-radius: 0;
  background: none;
}
.vsp-press-logo-placeholder .ph-tag {
  position: static;
  background: none;
  color: var(--vsp-charcoal-soft);
  padding: 0;
  font-family: var(--vsp-font-accent);
  font-size: 1.05rem;
  font-style: italic;
  letter-spacing: 0.02em;
  text-transform: none;
  white-space: nowrap;
}

/* Wine Club tier comparison (/club, page-templates/page-c7-content.php)
   — same gold-rule accent as Testimonials, but keeps a defined card
   boundary (unlike the pull-quotes) since this is a pricing comparison,
   where visitors benefit from clear separation between options rather
   than open editorial spacing. */
.vsp-club-tier-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: var(--vsp-space-md);
  align-items: start;
}
.vsp-club-tier-card {
  background: var(--vsp-cream);
  border: 1px solid rgba(236, 228, 212, 0.1);
  border-top: 2px solid var(--vsp-clay);
  padding: var(--vsp-space-lg) var(--vsp-space-md);
  text-align: center;
}
.vsp-club-tier-price {
  color: var(--vsp-clay);
  font-family: var(--vsp-font-accent);
  font-style: italic;
  font-size: 1.6rem;
  margin-bottom: var(--vsp-space-xs);
}
.vsp-club-tier-card .c7-club-join-button { margin-top: var(--vsp-space-sm); }

/* Social feed photo grid (front-page.php) — a hand-curated grid, not a
   live Instagram API embed: the Graph API requires an app review and an
   access token that expires and needs periodic renewal, which is a poor
   fit for a theme meant to be handed off to a non-technical client with
   no ongoing developer support. A client swaps photo URLs here the same
   way they already do for Team photos. */
.vsp-social-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: var(--vsp-space-xs);
}
.vsp-social-grid a { display: block; position: relative; aspect-ratio: 1 / 1; overflow: hidden; border-radius: var(--vsp-radius); }
.vsp-social-grid img { width: 100%; height: 100%; object-fit: cover; display: block; transition: transform 0.5s var(--vsp-ease), filter 0.4s ease; }
.vsp-social-grid a:hover img { transform: scale(1.06); filter: brightness(1.05) saturate(1.1); }
/* A whisper of warm tint sweeping up on hover — signals "this is a link
   out" without a heavy icon overlay. */
.vsp-social-grid a::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(0deg, rgba(125, 32, 53, 0.28), transparent 60%);
  opacity: 0;
  transition: opacity 0.35s ease;
  pointer-events: none;
}
.vsp-social-grid a:hover::after { opacity: 1; }
@media (prefers-reduced-motion: reduce) {
  .vsp-social-grid img { transition: none; }
  .vsp-social-grid a:hover img { transform: none; }
}

/* Homepage email capture (front-page.php) — the same real [c7_subscribe]
   widget as the footer's "Stay in the Loop", but this section sits on a
   light background so it needs its own light-palette version rather than
   the footer's off-white-on-merlot styling. Structural fixes (column
   .c7-form__group, full-width .c7-form__field) match the footer's for
   the same confirmed-via-DevTools reasons — see style.css's "FOOTER"
   section for the full explanation. The submit button is left
   unstyled here on purpose: the global bare-button fallback (section 2)
   already renders charcoal-on-transparent, which is already correct
   against this light background, unlike the footer's dark one. */
.vsp-email-capture { max-width: 480px; margin: 0 auto; text-align: center; }
.vsp-email-capture .c7-form__group {
  display: flex !important;
  flex-direction: column !important;
  align-items: center !important;
  width: 100% !important;
  gap: var(--vsp-space-sm) !important;
  margin-top: var(--vsp-space-md);
}
.vsp-email-capture .c7-form__field { width: 100% !important; margin: 0 !important; padding: 0 !important; }
.vsp-email-capture label {
  display: block !important;
  width: 100%;
  margin: 0 0 6px !important;
  color: var(--vsp-charcoal-soft);
  font-size: 0.78rem;
  text-align: left;
}
.vsp-email-capture input[type="email"] {
  width: 100% !important;
  height: auto !important;
  padding: 6px 0 !important;
  border: none !important;
  border-bottom: 1px solid rgba(236, 228, 212, 0.3) !important;
  background: transparent !important;
  color: var(--vsp-charcoal) !important;
  border-radius: 0 !important;
  margin: 0 !important;
  box-sizing: border-box !important;
}
.vsp-email-capture input[type="email"]:focus { border-bottom-color: var(--vsp-clay) !important; outline: none; }
.vsp-email-capture button,
.vsp-email-capture [type="submit"] {
  width: auto !important;
  padding: 10px 24px !important;
}

/* Site search (searchform.php) — reused as-is by the header's search
   toggle panel, 404.php, and search.php, so it's defined once, generic,
   not scoped to any one of those. All three sit on the theme's normal
   off-white/light backgrounds, so unlike the newsletter form this needs
   no separate light/dark variant.

   The icon sits INSIDE the input as an absolutely-positioned overlay
   button (same idea as the Wines Catalog's own search, inc/commerce7.php
   .vsp-catalog-search-wrap) rather than as a second element next to it —
   a bordered icon button beside a bordered input reads as two
   disconnected boxes rather than one search bar, especially once the
   input's own focus-visible outline (section 2 — WCAG 2.4.7, kept
   deliberately rather than suppressed) adds a second, differently-
   colored ring around just the input on open (the panel auto-focuses
   it, see assets/js/theme.js). One box, one border, is the fix — not
   fighting the accessibility outline. */
.vsp-search-form-wrap { margin-bottom: var(--vsp-space-md); }
.vsp-search-form-field { position: relative; max-width: 480px; }
.vsp-search-input {
  width: 100%;
  padding: 11px 46px 11px 16px;
  border: 1px solid rgba(236, 228, 212, 0.25);
  border-radius: var(--vsp-radius-sm);
  font-family: var(--vsp-font-body);
  font-size: 0.95rem;
  color: var(--vsp-charcoal);
  background: var(--vsp-cream);
  box-sizing: border-box;
}
.vsp-search-submit {
  position: absolute;
  top: 50%;
  right: 4px;
  transform: translateY(-50%);
  display: flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  background: transparent !important;
  border: none !important;
  /* !important: the global button:has(svg) icon-only fallback (section 2)
     already sets color:inherit !important, which would otherwise win
     over a plain (non-important) color here regardless of this rule's
     own specificity. */
  color: var(--vsp-charcoal-soft) !important;
}
.vsp-search-submit:hover { color: var(--vsp-clay) !important; background: transparent !important; }

/* Contact form (page-templates/page-contact.php) */
.vsp-contact-form label {
  display: block;
  font-family: var(--vsp-font-body);
  font-size: 0.78rem;
  font-weight: 500;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  margin-bottom: 6px;
}
.vsp-contact-form input[type="text"],
.vsp-contact-form input[type="email"],
.vsp-contact-form textarea {
  width: 100%;
  padding: 12px 14px;
  border: 1px solid rgba(236, 228, 212, 0.2);
  border-radius: var(--vsp-radius-sm);
  background: var(--vsp-cream);
  color: var(--vsp-charcoal);
  font-family: var(--vsp-font-body);
  font-size: 1rem;
}
.vsp-contact-form input:focus-visible,
.vsp-contact-form textarea:focus-visible {
  outline: 2px solid var(--vsp-clay);
  outline-offset: 1px;
}
.vsp-contact-form button[type="submit"] { margin-top: var(--vsp-space-xs); }
/* Honeypot — off-screen (not display:none) so unsophisticated bots that
   check for that specific property still fill it in, while aria-hidden
   plus tabindex="-1" (on the input, in page-contact.php) keeps it fully
   out of the way for screen reader and keyboard users. */
.vsp-hp-field {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}
.vsp-form-notice {
  padding: var(--vsp-space-sm) var(--vsp-space-md);
  border-radius: var(--vsp-radius-sm);
  margin-bottom: var(--vsp-space-md);
  font-size: 0.95rem;
}
.vsp-form-notice--success { background: var(--vsp-cream-raised); border: 1px solid var(--vsp-clay); color: var(--vsp-charcoal); }
.vsp-form-notice--error { background: #fbeaea; border: 1px solid #c0392b; color: #7a2418; }

/* Blog archive (archive.php) */
.vsp-blog-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  gap: var(--vsp-space-md);
}
.vsp-blog-card {
  background: var(--vsp-cream);
  border: 1px solid var(--vsp-line);
  border-radius: var(--vsp-radius);
  overflow: hidden;
  display: flex;
  flex-direction: column;
  transition: transform 0.4s var(--vsp-ease), box-shadow 0.4s var(--vsp-ease), border-color 0.25s ease;
}
.vsp-blog-card:hover {
  transform: translateY(-6px);
  box-shadow: var(--vsp-shadow-lg);
  border-color: rgba(125, 32, 53, 0.45);
}
.vsp-blog-card-media { display: block; overflow: hidden; }
.vsp-blog-card-media img {
  display: block;
  width: 100%;
  height: 220px;
  object-fit: cover;
  transition: transform 0.6s var(--vsp-ease);
}
/* Image zooms gently inside its clipped frame while the card lifts —
   the two motions together are what make the hover feel dimensional. */
.vsp-blog-card:hover .vsp-blog-card-media img { transform: scale(1.06); }
@media (prefers-reduced-motion: reduce) {
  .vsp-blog-card, .vsp-blog-card-media img { transition: none; }
  .vsp-blog-card:hover { transform: none; }
  .vsp-blog-card:hover .vsp-blog-card-media img { transform: none; }
}
.vsp-blog-card-media .ph-img { min-height: 220px; }
.vsp-blog-card-body { padding: var(--vsp-space-md) var(--vsp-space-sm) var(--vsp-space-md); }
.vsp-blog-meta {
  display: block;
  color: var(--vsp-clay);
  font-size: 0.72rem;
  font-weight: 500;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  margin-bottom: var(--vsp-space-xs);
}
.vsp-blog-card h2 { font-size: 1.4rem; margin-bottom: var(--vsp-space-xs); }
.vsp-blog-card h2 a { color: inherit; text-decoration: none; }

/* Events archive card — reuses .vsp-blog-card wholesale, this is just the
   date badge overlaid on the card image (/events, archive-vsp_event.php).
   Scoped to .vsp-event-card specifically rather than changing the shared
   .vsp-blog-card-media rule, so real blog post cards are unaffected. */
.vsp-event-card .vsp-blog-card-media { position: relative; }
.vsp-event-date-badge {
  position: absolute;
  top: var(--vsp-space-xs);
  left: var(--vsp-space-xs);
  background: var(--vsp-clay);
  color: var(--vsp-cream);
  font-family: var(--vsp-font-body);
  font-size: 0.72rem;
  font-weight: 500;
  letter-spacing: 0.04em;
  padding: 6px 12px;
  border-radius: var(--vsp-radius-sm);
}
.vsp-blog-card h2 a:hover { color: var(--vsp-clay); }

.vsp-pagination {
  display: flex;
  justify-content: center;
  gap: var(--vsp-space-xs);
  margin-top: var(--vsp-space-lg);
  flex-wrap: wrap;
}
.vsp-pagination .page-numbers {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 40px;
  height: 40px;
  padding: 0 10px;
  border: 1px solid rgba(236, 228, 212, 0.15);
  border-radius: var(--vsp-radius-sm);
  color: var(--vsp-charcoal);
  text-decoration: none;
}
.vsp-pagination .page-numbers.current { background: var(--vsp-clay); border-color: var(--vsp-clay); color: var(--vsp-cream); }
.vsp-pagination .page-numbers:hover:not(.current) { border-color: var(--vsp-clay); }

/* Single post (single.php) */
.vsp-post-hero img { display: block; width: 100%; max-height: 480px; object-fit: cover; }
.vsp-post-container { max-width: 760px; }
.vsp-post-header { text-align: center; margin-bottom: var(--vsp-space-lg); }
.vsp-post-meta { color: var(--vsp-charcoal-soft); font-size: 0.9rem; }
.vsp-post-content { font-size: 1.05rem; line-height: 1.75; }
.vsp-post-content > * + * { margin-top: var(--vsp-space-sm); }
.vsp-post-content img { max-width: 100%; height: auto; border-radius: var(--vsp-radius); }
.vsp-post-footer { margin-top: var(--vsp-space-xl); padding-top: var(--vsp-space-md); border-top: 1px solid rgba(236, 228, 212, 0.1); }
.vsp-post-terms { color: var(--vsp-charcoal-soft); font-size: 0.9rem; }
.vsp-post-nav {
  display: flex;
  justify-content: space-between;
  gap: var(--vsp-space-sm);
  margin: var(--vsp-space-md) 0;
  flex-wrap: wrap;
}
.vsp-post-nav a { text-decoration: none; color: var(--vsp-clay); font-weight: 500; }
.vsp-post-nav a:hover { text-decoration: underline; }
.vsp-post-back { text-align: center; margin-top: var(--vsp-space-md); }
@media (max-width: 600px) {
  .vsp-post-nav { flex-direction: column; text-align: center; }
}

/* .c7-buy-product (Commerce7's own quantity + Add to Cart mount point —
   used both by our own wine cards and inside Commerce7's official
   [c7_collection] widget on the homepage) renders its qty input/button
   as its own internal flex/block row, which text-align:center on a
   surrounding wrapper never reaches (that only affects inline content,
   not how a block/flex child arranges ITS OWN children). Once card
   layouts stack to a centered single column on mobile, that left this
   row visibly left-aligned while everything else around it centered.
   Forcing display:flex + justify-content:center here takes control of
   that arrangement directly instead of relying on inherited text-align. */
@media (max-width: 600px) {
  .c7-buy-product { display: flex !important; justify-content: center !important; }
}

/* =========================================================
   6. REUSABLE PAGE-BUILDING BLOCKS
   Apply these as "CSS Classes" (Advanced tab) on Elementor
   Section/Container + Column widgets so freehand pages still
   match the site system. See readme.txt for usage per block.
========================================================= */

/* Sticky header gets a touch shorter once you've scrolled — keeps
   more screen real-estate on phones. */
#vsp-header.is-scrolled .vsp-header-main { padding-top: 12px; padding-bottom: 12px; }
#vsp-header.is-scrolled .vsp-announce { max-height: 0; opacity: 0; border-bottom-color: transparent; }

/* Header is NOT sticky on mobile. Commerce7 renders its own fixed/sticky
   elements on small screens (mobile checkout/cart action bars — see e.g.
   .c7-account-club-shipment__mobile-actions in commerce7.css, position:
   fixed), and two independent fixed/sticky layers both anchored to the
   viewport edge can end up overlapping each other, which is what caused
   cart/checkout content to render underneath the header instead of below
   it. Trading the always-visible nav for guaranteed no-collision is the
   right call on the limited vertical space of a phone screen anyway. */
@media (max-width: 900px) {
  #vsp-header { position: static; }
}

/* Floating cart button — the mobile answer to "no sticky header" above:
   one small fixed element instead of a whole fixed header bar, so it
   can't collide with Commerce7's own fixed cart-drawer/checkout
   elements the way a sticky header did. Toggled by assets/js/theme.js
   once scrolled well past the header. Deliberately does NOT use
   transform/filter/backdrop-filter/contain — any of those would make
   THIS element a new containing block for Commerce7's .c7-side-cart
   drawer (rendered as ITS descendant), breaking its own position:fixed
   coordinates exactly like the backdrop-filter bug documented on
   #vsp-header above. position:fixed alone doesn't have that effect, so
   plain positioning + a couple of harmless visual properties is safe. */
#c7-cart.vsp-cart-float {
  position: fixed;
  bottom: 20px;
  right: 20px;
  z-index: 998;
  width: 56px;
  height: 56px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--vsp-cream);
  border: 1px solid var(--vsp-clay);
  border-radius: 50%;
  box-shadow: 0 4px 14px rgba(43, 36, 32, 0.25);
}
@media (min-width: 901px) {
  /* Never float on desktop — the sticky header already keeps the cart
     visible at all times there. */
  #c7-cart.vsp-cart-float { position: static; width: auto; height: auto; background: none; border: none; box-shadow: none; }
}

/* Hero: full-bleed edge-to-edge photo bands — the dark skin goes
   cinematic poster-scale where the sibling skins used inset rounded
   cards. The section blends straight into the page's own near-black,
   so every hero reads as the page opening up rather than a framed
   picture. Shared by every inner page's top hero and the homepage's
   promo bands; the homepage's own first hero is the separate
   .vsp-hero-home below. */
.vsp-hero {
  position: relative;
  display: flex;
  /* Bottom-anchored content — the photo owns the top two-thirds, the
     headline sits grounded at the base where the scrim is deepest (and
     therefore where text contrast is guaranteed). */
  align-items: flex-end;
  justify-content: flex-start;
  text-align: left;
  color: var(--vsp-light);
  padding: var(--vsp-space-xl) 0 var(--vsp-space-lg);
  overflow: hidden;
}
/* The band's inner content still aligns to the global container grid
   even though the section itself is full-bleed. */
.vsp-hero .vsp-hero-content {
  margin-left: max(var(--vsp-space-md), calc((100vw - var(--vsp-container)) / 2 + var(--vsp-space-md)));
  margin-right: var(--vsp-space-md);
}
@media (min-width: 601px) {
  /* Viewport-proportional cinematic height (svh, so mobile-browser chrome
     never causes a jump), capped so ultra-tall monitors don't get an
     absurdly stretched photo. Compact/band variants below override this. */
  .vsp-hero.ph-img { min-height: min(78svh, 780px); }
}
.vsp-hero::before {
  content: "";
  position: absolute;
  inset: 0;
  /* Nearly clear at the top so the photo reads, then deepening decisively
     into the page's own near-black at the bottom — the band literally
     dissolves into the page background, which is what makes the
     full-bleed treatment feel seamless on this palette. */
  background: linear-gradient(180deg, rgba(11, 8, 13, 0.42) 0%, rgba(11, 8, 13, 0.3) 40%, rgba(15, 12, 18, 0.94) 100%);
}
/* Film-grain texture over every hero photo — an inline SVG turbulence
   tile, so no extra asset request. Kept faint (soft-light, low opacity):
   it should read as photographic warmth, not a visible pattern.
   pointer-events:none so hero buttons/links underneath stay clickable. */
.vsp-hero::after {
  content: "";
  position: absolute;
  inset: 0;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)' opacity='0.5'/%3E%3C/svg%3E");
  mix-blend-mode: soft-light;
  opacity: 0.4;
  pointer-events: none;
}
.vsp-hero-content {
  position: relative;
  z-index: 1;
  max-width: 800px;
}
.vsp-hero-content h1 {
  font-size: clamp(3rem, 7vw, 6.4rem);
  line-height: 1;
  letter-spacing: -0.01em;
}
.vsp-hero-content h1,
.vsp-hero-content h2,
.vsp-hero-content p,
.vsp-hero-content .vsp-eyebrow { text-shadow: 0 1px 4px rgba(0, 0, 0, 0.45); }
.vsp-hero-content h1 { color: var(--vsp-light); }
.vsp-hero-content h2 { color: var(--vsp-light); }
/* Over a photo the eyebrow keeps its gold dash treatment — gold already
   reads against both the photo's bright and dark regions here thanks to
   the scrim, so no frosted chip needed. */
.vsp-hero-content .vsp-eyebrow { color: var(--vsp-clay); }
/* A short, punchy "why us" line — deliberately smaller and italic rather
   than styled like the main subtext above it, so it reads as a distinct
   supporting hook, not a continuation of the same sentence. */
.vsp-hero-differentiator {
  color: var(--vsp-light);
  font-family: var(--vsp-font-accent);
  font-style: italic;
  font-size: 1.02rem;
  opacity: 0.9;
  margin-top: -8px;
}
@media (max-width: 600px) {
  .vsp-hero { padding: var(--vsp-space-lg) 0; min-height: 380px; }
  .vsp-hero .vsp-hero-content { margin-left: var(--vsp-space-sm); margin-right: var(--vsp-space-sm); }
}

/* ---------------------------------------------------------
   HOMEPAGE HERO (.vsp-hero-home) — the one true full-viewport moment.
   The photo lives on its own .vsp-hero-bg layer (shared with the night
   band) so it can carry the slow Ken Burns drift + JS parallax without
   ever touching background-size. Content bottom-left; scroll cue
   bottom-right.
--------------------------------------------------------- */
.vsp-hero-home {
  position: relative;
  min-height: 100svh;
  display: flex;
  align-items: flex-end;
  overflow: hidden;
  color: var(--vsp-light);
}
/* Shared parallax photo layer — bled taller than its frame (inset -12%
   top/bottom) so the JS translate never exposes a raw edge. */
.vsp-hero-bg {
  position: absolute;
  inset: -12% 0;
  background-size: cover;
  background-position: center;
  will-change: transform;
}
.vsp-hero-home::before,
.vsp-night-band::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  background: linear-gradient(180deg, rgba(11, 8, 13, 0.5) 0%, rgba(11, 8, 13, 0.28) 45%, rgba(15, 12, 18, 0.95) 100%);
}
.vsp-hero-home::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)' opacity='0.5'/%3E%3C/svg%3E");
  mix-blend-mode: soft-light;
  opacity: 0.4;
  pointer-events: none;
}
.vsp-hero-home-inner {
  position: relative;
  z-index: 2;
  width: 100%;
  max-width: var(--vsp-container);
  margin: 0 auto;
  padding: calc(var(--vsp-space-xl) + 60px) var(--vsp-space-md) var(--vsp-space-lg);
}
.vsp-hero-home-inner h1 {
  max-width: 12ch;
  margin-bottom: var(--vsp-space-sm);
}
.vsp-hero-sub { max-width: 52ch; font-size: 1.08rem; }
.vsp-hero-home-inner .vsp-btn-row { justify-content: flex-start; }
/* The transparency line: address · hours, quiet but above the fold. */
.vsp-hero-meta {
  margin-top: var(--vsp-space-sm);
  font-size: 0.72rem;
  font-weight: 500;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: rgba(244, 238, 225, 0.72);
}
.vsp-hero-meta span { margin: 0 10px; color: var(--vsp-clay); }

/* Scroll cue — a thin vertical line with a travelling pulse (see the
   motion section) + tracked label, bottom-right of the hero. */
.vsp-scroll-cue {
  position: absolute;
  z-index: 2;
  right: clamp(20px, 4vw, 56px);
  bottom: var(--vsp-space-lg);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
  font-size: 0.6rem;
  letter-spacing: 0.34em;
  text-transform: uppercase;
  color: rgba(244, 238, 225, 0.75);
  writing-mode: vertical-rl;
}
.vsp-scroll-cue:hover { color: var(--vsp-clay); }
.vsp-scroll-cue-line {
  width: 1px;
  height: 64px;
  background: rgba(244, 238, 225, 0.3);
  position: relative;
  overflow: hidden;
}
@media (max-width: 780px) {
  .vsp-scroll-cue { display: none; }
}

/* ---------------------------------------------------------
   MARQUEE — slow-scrolling brand ribbon between the hero and the wine
   rail. Two identical .vsp-marquee-set tracks; the CSS animation (motion
   section) slides the pair by exactly -50% for a seamless loop. Under
   reduced motion the animation never runs and the single visible set
   just sits as a static strip.
--------------------------------------------------------- */
.vsp-marquee {
  overflow: hidden;
  border-top: 1px solid var(--vsp-line);
  border-bottom: 1px solid var(--vsp-line);
  background: var(--vsp-ink);
  padding: 18px 0;
}
.vsp-marquee-track {
  display: flex;
  width: max-content;
}
.vsp-marquee-set {
  display: flex;
  align-items: baseline;
  flex-shrink: 0;
}
.vsp-marquee-word {
  font-family: var(--vsp-font-heading);
  font-size: clamp(1.3rem, 2.4vw, 2rem);
  font-weight: 500;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--vsp-charcoal);
  opacity: 0.85;
  white-space: nowrap;
}
.vsp-marquee-word--italic {
  font-style: italic;
  text-transform: none;
  color: var(--vsp-clay);
}
.vsp-marquee-dot { margin: 0 clamp(16px, 2.5vw, 36px); color: var(--vsp-clay); opacity: 0.6; }

/* ---------------------------------------------------------
   CHAPTER HEADS — oversized ghost numerals ("01") beside each homepage
   section's eyebrow/heading, turning the long scroll into a numbered
   story. The numeral is aria-hidden decoration in the markup.
--------------------------------------------------------- */
.vsp-chapter-head {
  display: flex;
  align-items: flex-start;
  gap: clamp(16px, 3vw, 40px);
  margin-bottom: var(--vsp-space-lg);
}
.vsp-chapter-head--bare { margin-bottom: var(--vsp-space-sm); }
.vsp-chapter-head-text { flex: 1; min-width: 0; }
.vsp-chapter-head-text > p { max-width: 56ch; color: var(--vsp-charcoal-soft); }
.vsp-chapter-num {
  font-family: var(--vsp-font-heading);
  font-size: clamp(3.4rem, 7vw, 6rem);
  font-weight: 500;
  line-height: 0.8;
  color: transparent;
  -webkit-text-stroke: 1px rgba(200, 164, 94, 0.5);
  user-select: none;
  flex-shrink: 0;
}
/* No text-stroke support → faint solid gold instead of invisible. */
@supports not (-webkit-text-stroke: 1px black) {
  .vsp-chapter-num { color: rgba(200, 164, 94, 0.35); }
}
.vsp-chapter-num--on-dark { -webkit-text-stroke-color: rgba(244, 238, 225, 0.4); }
.vsp-rail-nav { display: flex; gap: 10px; align-self: center; }
.vsp-rail-btn {
  width: 48px;
  height: 48px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 1.1rem;
  border-radius: 50% !important;
  cursor: pointer;
  transition: opacity 0.2s ease;
}
.vsp-rail-btn:disabled { opacity: 0.3; cursor: default; }
@media (max-width: 900px) {
  .vsp-rail-nav { display: none; } /* thumb-swipe is the control here */
  .vsp-chapter-head { flex-wrap: wrap; }
}

/* ---------------------------------------------------------
   FEATURED WINE RAIL — full-bleed horizontal scroll-snap track whose
   leading pad aligns the first card to the container's left edge.
--------------------------------------------------------- */
.vsp-wine-rail-track {
  list-style: none;
  margin: 0;
  display: flex;
  gap: 24px;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  padding: 8px max(var(--vsp-space-md), calc((100vw - var(--vsp-container)) / 2 + var(--vsp-space-md))) var(--vsp-space-md);
  scrollbar-width: none; /* rail arrows + swipe are the affordance */
}
.vsp-wine-rail-track::-webkit-scrollbar { display: none; }
.vsp-wine-rail-card {
  flex: 0 0 clamp(240px, 24vw, 320px);
  scroll-snap-align: start;
}
.vsp-wine-rail-card > a {
  display: block;
  color: var(--vsp-charcoal);
  border: 1px solid var(--vsp-line);
  border-radius: var(--vsp-radius);
  background: var(--vsp-cream-raised);
  overflow: hidden;
  transition: transform 0.35s var(--vsp-ease), border-color 0.3s ease, box-shadow 0.35s ease;
}
.vsp-wine-rail-card > a:hover {
  transform: translateY(-6px);
  border-color: rgba(200, 164, 94, 0.55);
  box-shadow: var(--vsp-shadow-md);
  color: var(--vsp-charcoal);
}
.vsp-wine-rail-media {
  position: relative;
  display: block;
  aspect-ratio: 3 / 4;
  overflow: hidden;
}
.vsp-wine-rail-media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  filter: saturate(0.9);
  transition: transform 0.6s var(--vsp-ease), filter 0.4s ease;
}
.vsp-wine-rail-card > a:hover .vsp-wine-rail-media img { transform: scale(1.05); filter: saturate(1.05); }
.vsp-wine-rail-index {
  position: absolute;
  top: 12px;
  left: 14px;
  font-family: var(--vsp-font-heading);
  font-size: 1rem;
  color: rgba(244, 238, 225, 0.85);
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.5);
}
.vsp-wine-rail-body {
  display: block;
  padding: 18px 20px 20px;
  border-top: 1px solid var(--vsp-line);
}
.vsp-wine-rail-line {
  display: block;
  font-size: 0.62rem;
  font-weight: 500;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--vsp-charcoal-soft);
  margin-bottom: 6px;
}
.vsp-wine-rail-name {
  display: block;
  font-family: var(--vsp-font-heading);
  font-size: 1.6rem;
  line-height: 1.1;
  color: var(--vsp-charcoal);
}
.vsp-wine-rail-price {
  display: block;
  margin-top: 4px;
  font-family: var(--vsp-font-accent);
  font-style: italic;
  font-size: 1.05rem;
  color: var(--vsp-clay);
}
.vsp-wine-rail-cta {
  display: inline-block;
  margin-top: 12px;
  font-size: 0.62rem;
  font-weight: 500;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--vsp-clay);
}
/* Flagship name at label scale — the one wine that earns display type. */
.vsp-flagship-name {
  font-family: var(--vsp-font-accent);
  font-style: italic;
  font-size: clamp(2.8rem, 5vw, 4.6rem);
}

/* ---------------------------------------------------------
   NIGHT HARVEST BAND — full-bleed parallax statement (chapter 03).
--------------------------------------------------------- */
.vsp-night-band {
  position: relative;
  overflow: hidden;
  padding: var(--vsp-space-xl) 0;
  color: var(--vsp-light);
}
.vsp-night-band-content { position: relative; z-index: 2; }
.vsp-night-quote {
  margin: 0 0 var(--vsp-space-sm);
  max-width: 22ch;
}
.vsp-night-quote p {
  font-family: var(--vsp-font-accent);
  font-style: italic;
  font-size: clamp(1.9rem, 4vw, 3.4rem);
  line-height: 1.18;
  color: var(--vsp-light);
  margin: 0;
  text-wrap: balance;
}
.vsp-night-text { max-width: 52ch; color: rgba(244, 238, 225, 0.82); margin-bottom: var(--vsp-space-md); }

/* ---------------------------------------------------------
   WINE CLUB INVITATION CARD (chapter 04) — a gold-framed centered
   invitation, like the front of a membership card.
--------------------------------------------------------- */
.vsp-club-section { padding-top: var(--vsp-space-xl); }
.vsp-club-invite {
  position: relative;
  max-width: 760px;
  margin: 0 auto;
  text-align: center;
  padding: var(--vsp-space-xl) clamp(24px, 5vw, 72px);
  background: var(--vsp-cream-raised);
  border: 1px solid rgba(200, 164, 94, 0.4);
  border-radius: var(--vsp-radius);
}
/* Inner hairline, inset 10px — the double-rule border of an engraved
   invitation, done with a pseudo-element instead of a second wrapper. */
.vsp-club-invite::before {
  content: "";
  position: absolute;
  inset: 10px;
  border: 1px solid rgba(200, 164, 94, 0.18);
  border-radius: calc(var(--vsp-radius) - 3px);
  pointer-events: none;
}
.vsp-club-invite-num {
  position: absolute;
  top: -0.35em;
  left: 50%;
  transform: translateX(-50%);
  background: var(--vsp-cream);
  padding: 0 18px;
  font-size: clamp(2.6rem, 5vw, 4rem);
}
.vsp-club-invite-heading {
  font-family: var(--vsp-font-accent);
  font-style: italic;
  font-size: clamp(2.4rem, 4.6vw, 3.8rem);
}
.vsp-club-invite-text { max-width: 46ch; margin-left: auto; margin-right: auto; color: var(--vsp-charcoal-soft); }
.vsp-club-benefits {
  list-style: none;
  margin: var(--vsp-space-md) auto;
  padding: 0;
  max-width: 460px;
  text-align: left;
}
.vsp-club-benefits li {
  position: relative;
  padding: 12px 0 12px 30px;
  border-bottom: 1px solid var(--vsp-line);
  font-size: 0.95rem;
}
.vsp-club-benefits li:last-child { border-bottom: none; }
.vsp-club-benefits li::before {
  content: "\2726"; /* ✦ four-pointed star — the night-sky motif */
  position: absolute;
  left: 2px;
  top: 12px;
  color: var(--vsp-clay);
  font-size: 0.8rem;
}
.vsp-club-invite .vsp-btn-row { justify-content: center; }

/* ---------------------------------------------------------
   PARALLAX SPLIT MEDIA — the frame clips; the media layer inside is
   bled taller (inset -12%) and drifted by JS, same mechanism as the
   hero background.
--------------------------------------------------------- */
.vsp-parallax-frame { position: relative; overflow: hidden; }
.vsp-parallax-frame .vsp-parallax-media {
  position: absolute;
  inset: -12% 0;
  background-size: cover;
  background-position: center;
  will-change: transform;
}

/* ---------------------------------------------------------
   FLOATING RESERVE PILL — mobile-only booking shortcut (footer.php),
   the conversion counterpart to the floating cart. Hidden until JS adds
   .is-visible past the scroll threshold; bottom-LEFT so it never
   collides with the bottom-right cart pill.
--------------------------------------------------------- */
.vsp-reserve-float {
  position: fixed;
  bottom: 20px;
  left: 20px;
  z-index: 998;
  display: none;
  padding: 13px 22px;
  font-size: 0.68rem;
  font-weight: 500;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  background: var(--vsp-clay);
  color: var(--vsp-ink);
  border-radius: 999px;
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.5);
  opacity: 0;
  transform: translateY(8px);
  transition: opacity 0.3s ease, transform 0.3s var(--vsp-ease);
}
.vsp-reserve-float:hover { color: var(--vsp-ink); background: var(--vsp-clay-dark); }
@media (max-width: 900px) {
  .vsp-reserve-float { display: inline-flex; pointer-events: none; }
  .vsp-reserve-float.is-visible { opacity: 1; transform: none; pointer-events: auto; }
}

/* Wraps the optional live c7-reservation-availability widget when it's
   embedded inline in the homepage Reservation CTA band (front-page.php) —
   Commerce7's own widget renders light/card-style internal markup, which
   needs an opaque light backdrop to stay legible against the band's dark
   photo overlay, unlike the plain text/buttons around it. */
.vsp-reservation-widget-embed {
  background: var(--vsp-cream);
  border-radius: var(--vsp-radius);
  padding: var(--vsp-space-sm);
  margin: var(--vsp-space-sm) 0;
  max-width: 420px;
}

/* Shorter variant for mid-page promo bands (e.g. wine club CTA) that reuse
   the hero's full-bleed overlay treatment without the full hero height. */
.vsp-hero.vsp-hero--band { min-height: 420px; }
@media (max-width: 600px) {
  .vsp-hero.vsp-hero--band { min-height: 340px; }
}

/* Shorter still, for every OTHER page's single top-of-page hero (How We
   Work, Visit, Wines, Club, Reservation, Profile, single/archive Event)
   — the full 620px cinematic height only earns its keep as the
   homepage's own first impression; on an inner page it was pushing real
   content an extra screen's height down for no reason. */
@media (min-width: 601px) {
  .vsp-hero.vsp-hero--compact.ph-img { min-height: 440px; }
}
/* The homepage stacks two of these back to back (Wine Club CTA, then
   Reservation CTA) — as separate rounded cards now (not full-bleed), the
   normal card margin between them already reads as two distinct photo
   moments; no special zero-gap/divider treatment needed. */
.vsp-hero.vsp-hero--band + .vsp-hero.vsp-hero--band { margin-top: var(--vsp-space-md); }

/* Horizontal CTA button groups (hero, promo bands, visit section). */
.vsp-btn-row {
  display: flex;
  gap: 16px;
  justify-content: center;
  flex-wrap: wrap;
  margin-top: var(--vsp-space-sm);
}
@media (max-width: 480px) {
  .vsp-btn-row { flex-direction: column; align-items: stretch; }
}

/* Centered CTA under a section (e.g. "View Full Collection"). */
.vsp-section-cta { text-align: center; margin-top: var(--vsp-space-lg); }

/* Two-up split (image / text) — reorders to image-on-top on mobile. */
.vsp-split {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: clamp(32px, 5vw, 80px);
  align-items: center;
}
/* Split-section photos get the big-media radius plus a soft grounded
   shadow — they're the largest surfaces inside content sections and
   earn the same sweep as the heroes. */
.vsp-split-media {
  border-radius: var(--vsp-radius-lg);
  box-shadow: var(--vsp-shadow-md);
}
.vsp-split.vsp-split--reverse .vsp-split-media { order: 2; }
.vsp-split.vsp-split--reverse .vsp-split-text { order: 1; }
@media (max-width: 780px) {
  .vsp-split { grid-template-columns: 1fr; gap: var(--vsp-space-md); }
  .vsp-split .vsp-split-media,
  .vsp-split.vsp-split--reverse .vsp-split-media { order: 1 !important; }
  .vsp-split .vsp-split-text,
  .vsp-split.vsp-split--reverse .vsp-split-text { order: 2 !important; }
}

/* Homepage in-page "chapter" nav (front-page.php) — desktop only. Active
   dot + smooth scroll are handled in assets/js/theme.js. */
.vsp-section-nav {
  position: fixed;
  right: 24px;
  top: 50%;
  transform: translateY(-50%);
  z-index: 500;
  display: flex;
  flex-direction: column;
  gap: 16px;
}
.vsp-section-nav a {
  position: relative;
  display: block;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: rgba(236, 228, 212, 0.25);
  transition: background 0.2s ease, transform 0.2s ease;
}
.vsp-section-nav a:hover,
.vsp-section-nav a:focus-visible,
.vsp-section-nav a.is-active {
  background: var(--vsp-clay);
  transform: scale(1.35);
}
/* Label stays invisible until hover/focus — the dots alone are enough of
   a landmark once you know what they do, so there's no need for
   permanently-visible text competing with the actual page content. */
.vsp-section-nav-label {
  position: absolute;
  right: 20px;
  top: 50%;
  transform: translateY(-50%);
  white-space: nowrap;
  background: var(--vsp-charcoal);
  color: var(--vsp-cream);
  font-family: var(--vsp-font-body);
  font-size: 0.7rem;
  letter-spacing: 0.04em;
  padding: 5px 10px;
  border-radius: var(--vsp-radius-sm);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.2s ease;
}
.vsp-section-nav a:hover .vsp-section-nav-label,
.vsp-section-nav a:focus-visible .vsp-section-nav-label { opacity: 1; }
@media (max-width: 900px) {
  .vsp-section-nav { display: none; }
}
@media (prefers-reduced-motion: reduce) {
  .vsp-section-nav a { transition: none; }
  .vsp-section-nav-label { transition: none; }
}

/* Homepage photo gallery (front-page.php) — full-bleed edge-to-edge
   (deliberately outside .vsp-container, unlike every other homepage
   section), asymmetric mosaic: one large tile (a) plus four smaller
   ones (b-e), matching a magazine spread rather than a boxed gallery
   widget. Named grid-template-areas rather than nth-child/auto-flow so
   each tile's exact position is explicit and easy to reason about. */
.vsp-gallery-mosaic {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  grid-template-rows: repeat(2, 260px);
  grid-template-areas:
    "a a b c"
    "a a d e";
  gap: 4px;
}
/* overflow:hidden + a scaling <img> inside (rather than background-size
   growing on the tile itself, the earlier version's approach) — needed
   now that each tile holds a real <img loading="lazy"> instead of a CSS
   background-image, which is what actually makes lazy-loading possible. */
.vsp-gallery-tile { overflow: hidden; }
/* Warm full color, not the old grayscale-until-hover — a winery's photo
   wall should look like harvest and wine, not an industrial archive.
   At rest images sit just slightly softened so the mosaic reads as one
   set; hover brings each tile to full vibrancy with a gentle zoom. */
.vsp-gallery-tile img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
  display: block;
  filter: saturate(0.88);
  transition: transform 0.5s var(--vsp-ease), filter 0.4s ease;
}
.vsp-gallery-tile:hover img { transform: scale(1.05); filter: saturate(1.08); }
.vsp-gallery-tile--a { grid-area: a; }
.vsp-gallery-tile--b { grid-area: b; }
.vsp-gallery-tile--c { grid-area: c; }
.vsp-gallery-tile--d { grid-area: d; }
.vsp-gallery-tile--e { grid-area: e; }
@media (max-width: 900px) {
  .vsp-gallery-mosaic {
    grid-template-columns: repeat(2, 1fr);
    grid-template-rows: repeat(3, 200px);
    grid-template-areas:
      "a a"
      "b c"
      "d e";
  }
}
@media (max-width: 480px) {
  .vsp-gallery-mosaic {
    grid-template-columns: 1fr;
    grid-template-rows: repeat(5, 220px);
    grid-template-areas:
      "a"
      "b"
      "c"
      "d"
      "e";
  }
}
/* Respect the same motion-sensitivity concern as the press strip's
   marquee — the hover-zoom is decorative, not load-bearing. */
@media (prefers-reduced-motion: reduce) {
  .vsp-gallery-tile { transition: none; }
}

/* Three-up stat row (est. year / acres / cases produced, etc) — thin
   vertical rules between figures instead of plain grid gaps, and an
   italic serif treatment on the numbers, matching the same editorial-
   citation language as the "As Seen In" strip and Testimonials pull-
   quotes rather than reading as a plainer, older part of the page. */
.vsp-stats-row {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--vsp-space-md);
  text-align: center;
}
.vsp-stats-row > div { position: relative; padding: 0 var(--vsp-space-sm); }
.vsp-stats-row > div + div::before {
  content: "";
  position: absolute;
  left: 0;
  top: 6px;
  bottom: 6px;
  width: 1px;
  background: rgba(236, 228, 212, 0.15);
}
.vsp-stats-row .vsp-stat-num {
  font-family: var(--vsp-font-accent);
  font-style: italic;
  font-size: clamp(2.6rem, 5vw, 4.2rem);
  line-height: 1.1;
  letter-spacing: -0.02em;
  color: var(--vsp-clay);
  display: block;
}
.vsp-stats-row .vsp-stat-label {
  font-size: 0.72rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--vsp-charcoal-soft);
}
@media (max-width: 600px) {
  .vsp-stats-row { grid-template-columns: 1fr; gap: var(--vsp-space-md); }
  .vsp-stats-row > div + div::before { display: none; }
}

/* Generic responsive gap/padding helpers for Elementor sections. */
@media (max-width: 600px) {
  .vsp-section { padding: var(--vsp-space-md) 0; }
  .vsp-section-head { margin-bottom: var(--vsp-space-sm); }
}

/* =========================================================
   7. FOOTER
========================================================= */
/* Deepest night-black, one shade below the page itself, separated by a
   single gold hairline — on this palette the footer grounds the page by
   going darker, not by floating as a rounded card. */
#vsp-footer {
  background: var(--vsp-ink);
  color: var(--vsp-light);
  padding-top: var(--vsp-space-xl);
  margin-top: var(--vsp-space-lg);
  border-top: 1px solid rgba(200, 164, 94, 0.25);
}
#vsp-footer a { color: var(--vsp-light); transition: color 0.2s ease, opacity 0.2s ease; }
#vsp-footer a:hover { color: var(--vsp-clay); }

/* Closing invitation row — headline + reserve CTA above the link grid. */
.vsp-footer-invite {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--vsp-space-md);
  padding-bottom: var(--vsp-space-lg);
  margin-bottom: var(--vsp-space-lg);
  border-bottom: 1px solid rgba(244, 238, 225, 0.14);
}
.vsp-footer-invite-heading {
  font-family: var(--vsp-font-accent);
  font-style: italic;
  font-size: clamp(1.8rem, 3.4vw, 3rem);
  color: var(--vsp-light);
  max-width: 22ch;
  margin-bottom: 0;
}
.vsp-footer-invite .vsp-btn { flex-shrink: 0; }
@media (max-width: 780px) {
  .vsp-footer-invite { flex-direction: column; align-items: flex-start; }
}
/* Oversized closing wordmark — hairline-outlined display type at the
   very bottom of every page, a quiet brand flourish that costs nothing.
   Outline rendering only where text-stroke exists; elsewhere it falls
   back to a faint solid fill. aria-hidden in footer.php: decorative. */
.vsp-footer-wordmark {
  font-family: var(--vsp-font-accent);
  font-style: italic;
  font-weight: 500;
  font-size: clamp(2.6rem, 9vw, 7.5rem);
  line-height: 1.1;
  text-align: center;
  white-space: nowrap;
  overflow: hidden;
  color: rgba(247, 242, 233, 0.08);
  padding: var(--vsp-space-lg) 0 var(--vsp-space-sm);
  user-select: none;
}
@supports (-webkit-text-stroke: 1px black) {
  .vsp-footer-wordmark {
    color: transparent;
    -webkit-text-stroke: 1px rgba(247, 242, 233, 0.22);
  }
}
#vsp-footer h4 {
  color: var(--vsp-light);
  font-family: var(--vsp-font-body);
  font-size: 0.78rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  margin-bottom: var(--vsp-space-sm);
}
.vsp-footer-grid {
  display: grid;
  grid-template-columns: 1.4fr 1fr 1fr 1.2fr;
  gap: var(--vsp-space-lg);
  padding-bottom: var(--vsp-space-lg);
  border-bottom: 1px solid rgba(247,242,233,0.16);
}
.vsp-footer-grid ul { list-style: none; margin: 0; padding: 0; }
.vsp-footer-grid li { margin-bottom: 10px; font-size: 0.92rem; }
/* [c7_subscribe] — email-only (inc/commerce7.php dropped
   data-has-name-field), matching the minimal single-field pattern real
   winery footers use (e.g. Joseph Phelps: underline input, small label,
   compact outline button) instead of a boxed multi-field form. Actual
   rendered markup confirmed via live DevTools inspection:
     .c7-subscribe > form.c7-form > div.c7-form__group >
       div.c7-form__field (label+input) + button.c7-btn
   !important throughout because Commerce7's own base CSS is injected by
   its JS after this stylesheet loads, so an equal-specificity rule there
   would otherwise win the cascade tie regardless of source order (same
   reasoning as the .c7-link/.c7-btn overrides further down this file). */
.vsp-footer-newsletter .c7-form__group {
  display: flex !important;
  flex-direction: column !important;
  align-items: flex-start !important;
  width: 100% !important;
  gap: var(--vsp-space-sm) !important;
}
/* Commerce7's own CSS gives .c7-form__field its own margin/padding,
   designed for a wide multi-column layout — zeroed out so the flex gap
   above is the only source of spacing between the field and the button. */
.vsp-footer-newsletter .c7-form__field { width: 100% !important; margin: 0 !important; padding: 0 !important; }
.vsp-footer-newsletter label {
  display: block !important;
  width: 100% !important;
  margin: 0 0 6px !important;
  color: var(--vsp-light);
  font-size: 0.78rem;
}
/* Underline instead of a boxed input — same minimal treatment as the
   Joseph Phelps reference this was matched to. */
.vsp-footer-newsletter input[type="email"] {
  width: 100% !important;
  height: auto !important;
  padding: 6px 0 !important;
  border: none !important;
  border-bottom: 1px solid rgba(247,242,233,0.4) !important;
  background: transparent !important;
  color: var(--vsp-light) !important;
  border-radius: 0 !important;
  margin: 0 !important;
  box-sizing: border-box !important;
}
.vsp-footer-newsletter input[type="email"]:focus { border-bottom-color: var(--vsp-clay) !important; outline: none; }
/* The widget's own submit button (.c7-btn.c7-btn--primary, confirmed via
   live DevTools) still falls back to this file's global bare-button
   treatment (section 2) for its border/text color — which uses
   --vsp-charcoal, nearly invisible against this footer's dark merlot-dark
   background. Same off-white/gold treatment as the footer's legal links
   (verified 9.17:1 contrast) instead, kept compact/left-aligned (not
   full width) to match the reference's small "SIGN UP" button rather
   than a large full-width box. */
.vsp-footer-newsletter button,
.vsp-footer-newsletter [type="submit"] {
  width: auto !important;
  align-self: flex-start !important;
  padding: 10px 24px !important;
  border-color: var(--vsp-light) !important;
  color: var(--vsp-light) !important;
}
.vsp-footer-newsletter button:hover, .vsp-footer-newsletter button:focus,
.vsp-footer-newsletter [type="submit"]:hover, .vsp-footer-newsletter [type="submit"]:focus {
  background-color: var(--vsp-clay) !important;
  border-color: var(--vsp-clay) !important;
  color: var(--vsp-light) !important;
}
/* Circular outline icon buttons (inline SVGs in footer.php) — fill to
   burgundy on hover. Sized as real 38px touch targets, not bare glyphs. */
.vsp-social { display: flex; gap: 10px; margin-top: var(--vsp-space-sm); }
.vsp-social a {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 38px;
  height: 38px;
  border: 1px solid rgba(247,242,233,0.3);
  border-radius: 50%;
  color: rgba(247,242,233,0.8);
  transition: background 0.25s ease, border-color 0.25s ease, color 0.25s ease, transform 0.25s var(--vsp-ease);
}
.vsp-social a:hover, .vsp-social a:focus-visible {
  background: var(--vsp-clay);
  border-color: var(--vsp-clay);
  color: var(--vsp-light);
  transform: translateY(-2px);
}
.vsp-bottom-bar {
  display: flex;
  justify-content: space-between;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--vsp-space-sm);
  padding: var(--vsp-space-sm) 0;
  font-size: 0.75rem;
  color: rgba(247,242,233,0.65);
}
.vsp-legal-links { display: flex; gap: 16px; flex-wrap: wrap; }
/* CC BY/BY-SA attribution for the 4 Wikimedia-sourced homepage photos —
   see the comment above this markup in footer.php. Deliberately smaller/
   quieter than .vsp-bottom-bar itself (this is compliance text, not
   something a visitor needs to notice), but still real, legible, and
   linked, not hidden. */
.vsp-photo-credits {
  padding-bottom: var(--vsp-space-sm);
  font-size: 0.68rem;
  color: rgba(247,242,233,0.5);
  text-align: center;
}
.vsp-photo-credits a { color: rgba(247,242,233,0.65); }
.vsp-photo-credits a:hover { color: var(--vsp-light); }
.vsp-legal-links a { color: var(--vsp-light); text-decoration: none; }
.vsp-legal-links a:hover { text-decoration: underline; }

@media (max-width: 900px) {
  .vsp-footer-grid { grid-template-columns: 1fr 1fr; }
  .vsp-bottom-bar { flex-direction: column; gap: 10px; text-align: center; }
  .vsp-legal-links { justify-content: center; }
}
@media (max-width: 560px) {
  .vsp-footer-grid { grid-template-columns: 1fr; }
}

/* =========================================================
   8. AGE GATE
   Visibility is controlled entirely by the "vsp-age-gate-active" class on
   <html> (added by an inline script in wp_head, see inc/age-gate.php) —
   this rule is the only thing that shows the gate at all, so a visitor
   with JS disabled never sees it (fails open, not closed).

   Layout: a full-bleed blurred photo (.vsp-age-gate-bg) + a darkening
   overlay (.vsp-age-gate-overlay), with a centered white card
   (.vsp-age-gate-card) holding either the date-of-birth form or, once
   assets/js/age-gate.js determines the entered date is under the
   configured minimum age, the refusal message — never both at once.
========================================================= */
.vsp-age-gate { display: none; position: fixed; inset: 0; z-index: 100000; }
html.vsp-age-gate-active .vsp-age-gate { display: block; }
html.vsp-age-gate-active body { overflow: hidden; }

/* Scaled up before the blur so the softened edges never show a hard,
   unblurred rim at the viewport's boundary. */
.vsp-age-gate-bg {
  position: absolute;
  inset: 0;
  background-size: cover;
  background-position: center;
  filter: blur(10px);
  transform: scale(1.1);
}
.vsp-age-gate-overlay {
  position: absolute;
  inset: 0;
  background: rgba(236, 228, 212, 0.5);
}
.vsp-age-gate-panel {
  position: relative;
  z-index: 1;
  min-height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--vsp-space-md);
  box-sizing: border-box;
  overflow-y: auto;
}
.vsp-age-gate-card {
  position: relative;
  background: var(--vsp-cream);
  color: var(--vsp-charcoal);
  width: 100%;
  max-width: 460px;
  padding: var(--vsp-space-lg) var(--vsp-space-md);
  text-align: center;
  border-radius: var(--vsp-radius);
  box-shadow: 0 24px 64px rgba(0, 0, 0, 0.35);
  box-sizing: border-box;
}
.vsp-age-gate-card .vsp-eyebrow { color: var(--vsp-clay); }
.vsp-age-gate-card h2 {
  font-family: var(--vsp-font-heading);
  font-size: 1.9rem;
  margin: var(--vsp-space-xs) 0 var(--vsp-space-sm);
}
.vsp-age-gate-intro {
  color: var(--vsp-charcoal-soft);
  margin-bottom: var(--vsp-space-md);
}

.vsp-age-gate-field { text-align: left; margin-bottom: var(--vsp-space-sm); }
.vsp-age-gate-field label,
.vsp-age-gate-dob-field label {
  display: block;
  font-family: var(--vsp-font-body);
  font-size: 0.78rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--vsp-charcoal-soft);
  margin-bottom: 6px;
}
.vsp-age-gate-field select,
.vsp-age-gate-dob-field input {
  width: 100%;
  box-sizing: border-box;
  padding: 10px 12px;
  border: 1px solid rgba(236, 228, 212, 0.3);
  border-radius: var(--vsp-radius-sm);
  /* One shade below the dark card, light text — a white field here would
     glare inside the midnight palette. */
  background: var(--vsp-ink);
  color: var(--vsp-charcoal);
  font-family: var(--vsp-font-body);
  font-size: 0.95rem;
}
/* Plain number inputs render their browser-default spin arrows, which
   crowd a 3-up YYYY/MM/DD row — hidden the same way a search field's
   native "x" clear button typically gets hidden, purely cosmetic. */
.vsp-age-gate-dob-field input::-webkit-outer-spin-button,
.vsp-age-gate-dob-field input::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}
.vsp-age-gate-dob-field input { -moz-appearance: textfield; }

.vsp-age-gate-dob {
  display: flex;
  gap: 12px;
  margin-bottom: var(--vsp-space-sm);
}
.vsp-age-gate-dob-field { flex: 1; text-align: left; min-width: 0; }

.vsp-age-gate-error {
  color: #a33;
  font-size: 0.85rem;
  text-align: left;
  margin: -8px 0 var(--vsp-space-sm);
}

.vsp-age-gate-remember {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  text-align: left;
  font-size: 0.82rem;
  line-height: 1.4;
  color: var(--vsp-charcoal-soft);
  margin-bottom: var(--vsp-space-md);
  cursor: pointer;
}
.vsp-age-gate-remember input { margin-top: 3px; flex-shrink: 0; }

.vsp-age-gate-submit {
  display: block;
  width: 100%;
  background: var(--vsp-clay);
  color: var(--vsp-cream);
  border: none;
  padding: 14px;
  font-family: var(--vsp-font-body);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  font-size: 0.85rem;
  cursor: pointer;
  border-radius: var(--vsp-radius);
  transition: background 0.2s ease;
}
.vsp-age-gate-submit:hover { background: var(--vsp-clay-dark); }

.vsp-age-gate-disclaimer {
  font-size: 0.72rem;
  line-height: 1.6;
  color: var(--vsp-charcoal-soft);
  margin: var(--vsp-space-md) 0 0;
}

.vsp-age-gate-refusal { display: none; }
.vsp-age-gate.vsp-age-gate--refused .vsp-age-gate-form { display: none; }
.vsp-age-gate.vsp-age-gate--refused .vsp-age-gate-refusal { display: block; }
.vsp-age-gate-refusal p { color: var(--vsp-charcoal-soft); }

@media (max-width: 480px) {
  .vsp-age-gate-card { padding: var(--vsp-space-md) var(--vsp-space-sm); }
  .vsp-age-gate-dob { gap: 8px; }
}

/* =========================================================
   9. MOTION & SCROLL REVEAL
   Every animation in this section is wrapped in a
   prefers-reduced-motion guard — nothing here fires unless the visitor's
   OS explicitly allows motion, and the sitewide .vsp-reveal-init class
   (added by theme.js only once IntersectionObserver is confirmed
   available and reduced-motion is NOT set) makes the whole system
   opt-in and no-op safely on any browser/user that doesn't want it —
   elements are plain, fully visible, unanimated by default.
========================================================= */
@media (prefers-reduced-motion: no-preference) {
  /* Reading-progress hairline (header.php / theme.js) — inside the
     motion guard: a continuously-moving bar is exactly the kind of
     ambient motion reduced-motion users opt out of. Sits above the
     sticky header (z-index 999). */
  #vsp-scroll-progress {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 2px;
    z-index: 1200;
    background: var(--vsp-clay);
    transform: scaleX(0);
    transform-origin: left;
    pointer-events: none;
  }

  /* Blur-and-rise reveal — the soft rack-focus entrance (opacity +
     lift + blur resolving together) that reads as "current" versus the
     plain fade-up it replaces. Blur is small (6px) and transitions on
     the element's own filter, so it stays comfortably GPU-composited. */
  .vsp-reveal-init [data-vsp-reveal] {
    opacity: 0;
    transform: translateY(32px) scale(0.985);
    filter: blur(6px);
    transition:
      opacity 0.9s var(--vsp-ease),
      transform 0.9s var(--vsp-ease),
      filter 0.9s var(--vsp-ease);
    transition-delay: var(--vsp-reveal-delay, 0s);
  }
  .vsp-reveal-init [data-vsp-reveal].is-visible {
    opacity: 1;
    transform: none;
    filter: none;
  }

  /* Kinetic hero headline — theme.js splits every .vsp-hero-content h1
     into masked word spans (outer span clips, inner span slides up from
     below its own baseline, each word on its own small delay). The h1
     itself gets .vsp-kinetic and is excluded from the generic child
     fade below so the two systems never double-animate the same text.
     Only ever active alongside .vsp-reveal-init (JS guard), so
     reduced-motion/no-IO visitors get the untouched plain heading. */
  .vsp-hero-content h1.vsp-kinetic { opacity: 1; }
  .vsp-hero-content.is-visible > h1.vsp-kinetic { animation: none; }
  .vsp-hero-content h1 .vsp-word {
    display: inline-block;
    overflow: hidden;
    vertical-align: top;
    /* Serif descenders/overshoot need a little clearance inside the
       clip box or letters like "g"/"y" get shaved during the reveal —
       Fraunces descenders run deep, so err generous. */
    padding-bottom: 0.18em;
    margin-bottom: -0.18em;
  }
  .vsp-hero-content h1 .vsp-word > span {
    display: inline-block;
    transform: translateY(115%);
    transition: transform 1s var(--vsp-ease);
    transition-delay: var(--vsp-word-delay, 0s);
  }
  .vsp-hero-content.is-visible h1 .vsp-word > span { transform: none; }
  /* Gallery mosaic + card grids stagger their children a little on the
     way in — set via inline --vsp-reveal-delay on each item (theme.js)
     rather than nth-child, since grid item order in the DOM doesn't
     always match visual left-to-right/top-to-bottom order once the CSS
     grid-area layout (the gallery mosaic) or a flex-wrap reflow kicks in. */

  /* Hero content: staggers in the moment each hero becomes visible —
     theme.js observes every .vsp-hero-content (not just the homepage's
     above-the-fold one) and adds .is-visible when it enters the
     viewport, so this fires instantly for a hero already on screen at
     load and correctly WAITS for scroll on the club/reservation CTA
     bands further down the page, instead of animating those away
     before anyone scrolls to them. */
  .vsp-hero-content > * { opacity: 0; }
  .vsp-hero-content.is-visible > * {
    animation: vsp-hero-in 0.8s cubic-bezier(0.16, 1, 0.3, 1) forwards;
  }
  .vsp-hero-content.is-visible > *:nth-child(1) { animation-delay: 0.05s; }
  .vsp-hero-content.is-visible > *:nth-child(2) { animation-delay: 0.15s; }
  .vsp-hero-content.is-visible > *:nth-child(3) { animation-delay: 0.25s; }
  .vsp-hero-content.is-visible > *:nth-child(4) { animation-delay: 0.35s; }
  .vsp-hero-content.is-visible > *:nth-child(5) { animation-delay: 0.45s; }
  @keyframes vsp-hero-in {
    from { opacity: 0; transform: translateY(18px); }
    to   { opacity: 1; transform: translateY(0); }
  }

  /* Ken Burns drift on the homepage hero photo — safe now because the
     photo lives on its own .vsp-hero-bg layer (front-page.php) and the
     animation is transform-only scale, never background-size (the v1
     background-size zoom broke cover behavior and tiled photos — see
     the git history). Runs once, very slowly, settling at a slight
     hold-scale so the JS parallax that also writes transform on this
     layer takes over seamlessly: the animation only runs until its end
     state, and both effects compose because the parallax script starts
     writing AFTER first scroll, when the entrance has finished. */
  /* No fill-mode on purpose: a filling animation would keep overriding
     the inline transform the parallax script writes on this same layer
     forever after it ends. The animation's end state (scale(1)) is
     identical to the un-animated state, so releasing it causes no snap. */
  .vsp-hero-home .vsp-hero-bg {
    animation: vsp-kenburns 2.6s var(--vsp-ease);
  }
  @keyframes vsp-kenburns {
    from { transform: scale(1.07); }
    to   { transform: scale(1); }
  }

  /* Marquee ribbon — the duplicated track slides by exactly -50% for a
     seamless loop. Outside this motion guard the animation never runs
     and the strip sits static. */
  .vsp-marquee-track { animation: vsp-marquee-scroll 36s linear infinite; }
  @keyframes vsp-marquee-scroll {
    from { transform: translateX(0); }
    to   { transform: translateX(-50%); }
  }

  /* Scroll cue — a soft gold pulse travelling down the hairline. */
  .vsp-scroll-cue-line::after {
    content: "";
    position: absolute;
    left: 0;
    top: -40%;
    width: 100%;
    height: 40%;
    background: var(--vsp-clay);
    animation: vsp-cue-drop 2.2s var(--vsp-ease) infinite;
  }
  @keyframes vsp-cue-drop {
    0%   { top: -40%; }
    70%  { top: 100%; }
    100% { top: 100%; }
  }

  /* Overlay menu links stagger up as the menu opens. Capped at ten
     items; anything deeper just arrives with the last delay. */
  #vsp-overlay .vsp-overlay-columns { opacity: 0; transform: translateY(24px); transition: opacity 0.5s var(--vsp-ease), transform 0.6s var(--vsp-ease); transition-delay: 0.08s; }
  #vsp-overlay.is-open .vsp-overlay-columns { opacity: 1; transform: none; }
  #vsp-overlay #vsp-primary-nav > ul > li { opacity: 0; transform: translateY(18px); transition: opacity 0.5s var(--vsp-ease), transform 0.6s var(--vsp-ease); }
  #vsp-overlay.is-open #vsp-primary-nav > ul > li { opacity: 1; transform: none; }
  #vsp-overlay.is-open #vsp-primary-nav > ul > li:nth-child(1) { transition-delay: 0.1s; }
  #vsp-overlay.is-open #vsp-primary-nav > ul > li:nth-child(2) { transition-delay: 0.16s; }
  #vsp-overlay.is-open #vsp-primary-nav > ul > li:nth-child(3) { transition-delay: 0.22s; }
  #vsp-overlay.is-open #vsp-primary-nav > ul > li:nth-child(4) { transition-delay: 0.28s; }
  #vsp-overlay.is-open #vsp-primary-nav > ul > li:nth-child(5) { transition-delay: 0.34s; }
  #vsp-overlay.is-open #vsp-primary-nav > ul > li:nth-child(6) { transition-delay: 0.4s; }
  #vsp-overlay.is-open #vsp-primary-nav > ul > li:nth-child(7) { transition-delay: 0.46s; }
  #vsp-overlay.is-open #vsp-primary-nav > ul > li:nth-child(n+8) { transition-delay: 0.52s; }
}

/* Stat numbers count up from 0 on scroll-in (theme.js) — the CSS here
   just keeps layout stable (tabular figures so digits don't jitter the
   surrounding label's position as the count changes width). */
.vsp-stat-num { font-variant-numeric: tabular-nums; }

/* Card lift/tilt on hover, layered on top of each card type's own
   background-color transition (section 5/6) rather than replacing it. */
@media (prefers-reduced-motion: no-preference) {
  .vsp-wine-card--featured,
  .vsp-testimonial-card,
  .vsp-team-card,
  .vsp-club-tier-card {
    transition: transform 0.35s cubic-bezier(0.16, 1, 0.3, 1), box-shadow 0.35s ease;
  }
  .vsp-team-card:hover,
  .vsp-club-tier-card:hover {
    transform: translateY(-6px);
    box-shadow: 0 20px 40px rgba(236, 228, 212, 0.14);
  }
}
