/*
Theme Name: Vistara Labs
Theme URI: https://vistaralabs.ca
Description: Vistara Labs storefront theme. Child of Kadence. Reproduces the Squarespace 7.1 design system captured from the live site on 2026-08-19.
Author: Vistara Labs
Template: kadence
Version: 1.0.0
Text Domain: vistara
*/

/* ============================================================
   DESIGN TOKENS — extracted from vistaralabs.ca (Squarespace 7.1)
   Type: Roboto throughout, -2% tracking, no text-transform.
   ============================================================ */
:root {
  /* Brand */
  --vl-ink:          #000000;  /* body copy */
  --vl-slate:        #2B3E51;  /* headings, nav — 122 uses on live site */
  --vl-slate-deep:   #233642;
  --vl-slate-mid:    #314D5D;
  --vl-teal:         #239B97;  /* primary accent — 81 uses */
  --vl-teal-dark:    #158081;
  --vl-teal-mid:     #278884;
  --vl-blue:         #21609D;
  --vl-gold:         #C09159;
  /* Neutrals */
  --vl-white:        #FFFFFF;
  --vl-off-white:    #FCFCFC;
  --vl-paper:        #F6F7F6;
  --vl-mist:         #F0F1EF;
  --vl-rule:         #DBDBDB;
  --vl-grey:         #666666;
  --vl-btn-text:     #FAFAFA;
  /* Type */
  --vl-font: 'Roboto', -apple-system, BlinkMacSystemFont, 'Helvetica Neue', Arial, sans-serif;
  --vl-tracking: -0.02em;
  /* Rhythm */
  --vl-maxw: 1500px;
  --vl-gutter: 6vw;
}

/* ---- Global type ---- */
body, .content-bg, body.wp-site-blocks {
  font-family: var(--vl-font);
  letter-spacing: var(--vl-tracking);
  color: var(--vl-ink);
  background: var(--vl-white);
}
h1, h2, h3, h4, h5, h6,
.entry-title, .woocommerce-loop-product__title, .product_title {
  font-family: var(--vl-font);
  letter-spacing: var(--vl-tracking);
  font-weight: 400;
  text-transform: none;
  color: var(--vl-slate);
}

/* Live-site fluid scale, normalised to a 1280px viewport */
h1, .product_title.entry-title { font-size: clamp(1.9rem, 1.4rem + 1.6vw, 3.0rem); line-height: 1.18; }
h2 { font-size: clamp(1.5rem, 1.2rem + 1.1vw, 2.25rem); line-height: 1.22; }
h3 { font-size: clamp(1.25rem, 1.05rem + 0.7vw, 1.65rem); line-height: 1.3; }
p, li { font-size: 1.075rem; line-height: 1.62; letter-spacing: -0.016em; }

/* ---- Buttons: flat black, square, sentence case (matches live) ---- */
.wp-block-button__link,
.button, button, input[type="submit"],
.woocommerce a.button, .woocommerce button.button,
.woocommerce .single_add_to_cart_button,
.woocommerce #respond input#submit {
  font-family: var(--vl-font);
  font-weight: 400;
  letter-spacing: var(--vl-tracking);
  text-transform: none;
  color: var(--vl-btn-text);
  background: var(--vl-ink);
  border: 0;
  border-radius: 0;
  padding: 16px 21px;
  transition: background .18s ease;
}
.wp-block-button__link:hover,
.button:hover, button:hover,
.woocommerce a.button:hover, .woocommerce button.button:hover,
.woocommerce .single_add_to_cart_button:hover {
  background: var(--vl-slate);
  color: var(--vl-btn-text);
}
.button.alt, .woocommerce .button.alt { background: var(--vl-teal); }
.button.alt:hover, .woocommerce .button.alt:hover { background: var(--vl-teal-dark); }

/* ---- Secondary / outline button (the "hollow" variant on the hero) ---- */
.is-style-outline .wp-block-button__link, .button--outline {
  background: transparent; color: var(--vl-ink); border: 1px solid var(--vl-ink);
}
.is-style-outline .wp-block-button__link:hover, .button--outline:hover {
  background: var(--vl-ink); color: var(--vl-btn-text);
}

/* ---- Rules & cards ---- */
hr, .wp-block-separator { border-color: var(--vl-rule); opacity: 1; }
.woocommerce ul.products li.product { text-align: center; }
.woocommerce ul.products li.product .price { color: var(--vl-slate); font-size: 1.05rem; }
.woocommerce ul.products li.product .woocommerce-loop-product__title {
  font-size: 1.05rem; color: var(--vl-slate); margin-top: .6em;
}

/* ---- Announcement marquee (rebuilt as a div, never an <h1>) ---- */
.vl-marquee {
  overflow: hidden; white-space: nowrap; background: var(--vl-white);
  border-block: 1px solid var(--vl-rule); padding: .85rem 0;
}
/* The track holds the message sequence twice. Translating exactly -50% puts the
   second copy where the first started, so the loop never shows a tail of empty
   space. Sliding a single copy by -100% is what left the gap before.

   80s, not 40s. The duration is fixed but the track is not: adding a second
   message doubled its width, so the same 40s doubled the speed to 114px/s.
   Fast motion makes every dropped frame visible, and "too fast" reads as
   "glitchy" even when nothing is actually stuttering. 80s restores the pace
   the single-message banner had. */
.vl-marquee__track {
  display: flex;
  width: max-content;

  /*
   * Plain linear, on no layer of its own. This is what it was before any of the
   * shimmer work and it is what it is again.
   *
   * `will-change: transform` was added here first and everything else followed
   * it. It pins the element to its own compositing layer for good, so the text
   * is rasterised once into a texture and that bitmap is then resampled as the
   * layer moves -- which is the shimmer. Without it Chrome composites the
   * animation for the duration and draws the text itself each frame, at the
   * right position, which is what it was doing when this banner was fine.
   *
   * `backface-visibility: hidden` asked for the same promotion by an older
   * mechanism; `background` and `steps()` were both attempts to make the
   * promoted layer behave. None of them are needed once the promotion is gone,
   * and each of them cost something.
   *
   * Do not add a compositing hint here. It is the thing that broke it.
   */
  animation: vl-scroll 28.5s linear infinite paused;
}
.vl-marquee__item {
  flex: 0 0 auto;
  /* Half the in-message gap, so the space between one item and the next reads
     the same as the space inside it and the rhythm stays even. Tracks the run
     of spaces in the message: seventeen of them, so roughly 3.1rem a side.

     Padding here is safe; padding on the track is not. Item padding is inside
     the item's own width, so the halves still measure equal and the -50% loop
     still lands. */
  padding: 0 3.1rem;
  font-size: 1.05rem; letter-spacing: var(--vl-tracking); color: var(--vl-ink);
  /* `pre`, not `nowrap`: the gap in the message is literal spaces and `nowrap`
     would collapse them. `pre` keeps them and still refuses to wrap. */
  white-space: pre;
}
/*
 * Both keyframes are percentages, and both must stay that way.
 *
 * This was `translateX(0)` to `translateX(-50%)`: a length interpolating to a
 * percentage. The compositor cannot own that -- a percentage resolves against
 * the element's own border-box, so a mixed-unit pair has to be re-resolved on
 * the main thread every frame. The animation still ran at a full frame rate
 * with an idle page, which is why it measured clean, and stuttered whenever
 * anything else wanted the main thread: an image decoding, a layout, a
 * collection. More of that happens on a desktop viewport than a phone one,
 * which is why only desktop looked bad.
 *
 * `translate3d` rather than `translateX` for the same reason -- it asks for the
 * transform to be handled as a 3D one, which keeps it on its own layer.
 */
@keyframes vl-scroll {
  from { transform: translate3d(0%, 0, 0); }
  to   { transform: translate3d(-50%, 0, 0); }
}
/* The banner never pauses. A stopped marquee under the cursor reads as a
   broken page, and the message is short enough that nobody needs to hold it
   still to finish reading. */
html.vl-fonts-settled .vl-marquee__track { animation-play-state: running; }
/* Deliberately scoped under the same class: written as a bare
   `.vl-marquee:hover` it would start the banner early on any pointer that
   happened to be resting there, which is the one case the gate above exists to
   prevent. */
html.vl-fonts-settled .vl-marquee:hover .vl-marquee__track { animation-play-state: running; }
@media (prefers-reduced-motion: reduce) {
  .vl-marquee__track { animation: none; width: 100%; justify-content: center; }
  .vl-marquee__item:not(:first-child) { display: none; }
}

/* ---- Trust bar ("Third-Party Tested · 99%+ Purity · …") ---- */
.vl-trustbar {
  background: var(--vl-paper); text-align: center; padding: 1.1rem 1rem;
  color: var(--vl-slate); font-weight: 500; letter-spacing: var(--vl-tracking);
}

/* ---- Research-use disclaimer, shown site-wide under product content ---- */
.vl-disclaimer {
  border-top: 1px solid var(--vl-rule); margin-top: 2.5rem; padding-top: 1.25rem;
  font-size: .85rem; line-height: 1.55; color: var(--vl-grey);
}

/* ---- Payment instructions on thank-you page + emails ---- */
.vl-payment-instructions {
  background: var(--vl-paper); border-left: 3px solid var(--vl-teal);
  padding: 1.1rem 1.25rem; margin: 1.5rem 0; font-size: .95rem; line-height: 1.6;
}
.vl-payment-instructions strong { color: var(--vl-slate); }

/* Front page supplies its own hero in content — drop Kadence's empty title band. */
.home .entry-hero-container-inner,
.home .page-hero-section,
.home .entry-hero { display: none; }

/* ============================================================
   IMAGE SIZING
   Squarespace served images up to 1400px and the imported markup
   carried no constraints, so everything rendered at natural size.
   ============================================================ */
img, .wp-block-image img, .entry-content img {
  max-width: 100%;
  height: auto;
}
/* Product gallery: cap it so the vial doesn't dominate the page. */
.woocommerce div.product div.images { max-width: 480px; margin-inline: auto; }
.woocommerce div.product div.images img { width: 100%; height: auto; }
@media (min-width: 992px) {
  .woocommerce div.product div.images { max-width: 420px; }
}
/* Catalogue tiles: uniform height so the grid reads as a grid. */
.woocommerce ul.products li.product img {
  aspect-ratio: 3 / 4;
  object-fit: contain;
  width: 100%;
  height: auto;
  background: var(--vl-paper);
}
/* Content images inside imported pages. */
.entry-content img { max-width: min(100%, 720px); height: auto; }

/* ============================================================
   FOOTER — "not a laboratory" notice
   ============================================================ */
.vl-footer-notice {
  background: var(--vl-paper);
  border-top: 1px solid var(--vl-rule);
  padding: 1.5rem 1.25rem;
}
.vl-footer-notice__inner {
  max-width: 900px; margin-inline: auto;
  font-size: .8rem; line-height: 1.6; color: var(--vl-grey); text-align: center;
}

/* ============================================================
   AGE / RESEARCHER GATE

   A dark card on a dimmed, blurred page. The palette is the site's own slate
   family — --vl-slate into --vl-slate-deep for the card, --vl-teal for the
   divider and the accept button — so the first screen a visitor ever sees is
   recognisably Vistara rather than a bolted-on dialog.

   Square corners, like every other panel here. The reference this was drawn
   from is rounded; the rest of the site is not, and consistency wins on the
   one screen that sets the visitor's expectation of everything after it.
   ============================================================ */
html.vl-gate-open, html.vl-gate-open body { overflow: hidden; }

/* Hidden unless <head> said otherwise. The gate ships in the markup and this
   rule keeps it off screen for a visitor who has already confirmed; the class
   below is set before first paint for everyone else, so the gate is painted in
   the first frame rather than revealed a beat later. */
.vl-gate {
  position: fixed; inset: 0; z-index: 99999;
  display: none;
  align-items: center; justify-content: center;
  /* Deeper and blurrier than the promo's veil. That one interrupts a page the
     visitor is already reading and has to stay polite about it; this one is a
     stop. The page behind should be legibly *there* and not readable. */
  background: rgba(15, 27, 35, .78);
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
  padding: 1.25rem;
}
html.vl-gate-open .vl-gate { display: flex; }

/* ---- Arrival -------------------------------------------------------------
   The gate is present from the first frame now, so this is not a reveal --
   it is the gate's own arrival, playing from frame zero rather than landing
   on a page the visitor has already started reading. Same treatment as the
   discount popup: the veil fades and the panel rises a little behind it.

   Keyed off the same root class that shows the gate, with
   `animation-fill-mode: both`, rather than a second class added by JS once the
   panel is already up. A second class costs a frame where the element is
   visible and un-animated, and if it ever failed to land it would leave a
   fixed inset:0 veil at opacity 0 over the whole page -- invisible, and
   swallowing every click. Tied to `vl-gate-open` the only states are "gone"
   and "animating in".

   Its own keyframes rather than the promo's: identical values, but named
   here so the two cannot break each other silently across files. If one is
   retuned the other should be retuned to match.
   ------------------------------------------------------------------------ */
@keyframes vl-gate-veil {
  from { opacity: 0; }
  to   { opacity: 1; }
}
@keyframes vl-gate-rise {
  from { opacity: 0; transform: translateY(28px) scale(.97); }
  to   { opacity: 1; transform: none; }
}

html.vl-gate-open .vl-gate {
  animation: vl-gate-veil .42s ease-out both;
}
html.vl-gate-open .vl-gate .vl-gate__panel {
  animation: vl-gate-rise .64s cubic-bezier(.16, 1, .3, 1) 90ms both;
}

/* ---- Leaving --------------------------------------------------------------
   Quicker than arriving. The hero wordmark begins its fade the moment this
   clears, so the exit gets out of its way.

   `.is-out` carries its own `display` because the script drops `vl-gate-open`
   from <html> on the same click — scroll has to come back immediately — and
   that is the class the show rule hangs off. Without this the panel vanished
   on the frame the button was pressed and the exit animation, written and
   commented, never ran once. Specificity 0-2-0 beats the `display: none`
   above at 0-1-0, and the entrance rules stop matching the moment the root
   class goes, so nothing here has to out-weigh them.

   `pointer-events: none` for the 220ms it is still on screen: it is a
   full-page veil over a page the visitor has just been let into, and it
   should not be eating the first click they make.
   ------------------------------------------------------------------------ */
.vl-gate.is-out { display: flex; pointer-events: none; }
.vl-gate.is-out { animation: vl-gate-veil .22s ease reverse both; }
.vl-gate.is-out .vl-gate__panel { animation: vl-gate-rise .22s ease reverse both; }

@media (prefers-reduced-motion: reduce) {
  html.vl-gate-open .vl-gate,
  html.vl-gate-open .vl-gate .vl-gate__panel,
  .vl-gate.is-out,
  .vl-gate.is-out .vl-gate__panel { animation: none; }
}

/* ---- The card ---- */
.vl-gate__panel {
  position: relative;
  width: 100%; max-width: 540px;
  padding: 2.75rem 2.5rem 2.25rem;
  text-align: center;
  background: linear-gradient(180deg, var(--vl-slate) 0%, var(--vl-slate-deep) 100%);
  /* A hairline of white rather than a border colour. On a dark ground a solid
     edge reads as a box drawn round the content; 10% white reads as the edge
     catching light, which is what lifts the card off the veil. */
  border: 1px solid rgba(255, 255, 255, .10);
  border-radius: 0;
  box-shadow: 0 30px 80px rgba(0, 0, 0, .55);
  max-height: 92vh; overflow-y: auto;
}

/* ---- Logo ---- */
.vl-gate__logo { margin: 0 0 1.5rem; }
.vl-gate__logo img {
  display: block; margin: 0 auto;
  width: clamp(132px, 40%, 168px); height: auto;
  /* The supplied lockup is navy and teal on transparent: the navy maple leaf
     and the "VISTARA" half of the wordmark all but disappear against this
     card. brightness(0) flattens every opaque pixel to black and invert(1)
     lifts it to white — the transparent facet gaps stay transparent, so the
     leaf keeps its structure and the helix and mountains still read. A white
     wordmark variant without shipping a second file for it.
     If a light lockup is ever added to the media library, drop this filter
     and point the gate at that attachment instead. */
  filter: brightness(0) invert(1);
}

/* ---- Divider ---- */
.vl-gate__rule {
  display: block; width: 72px; height: 3px;
  margin: 0 auto 1.6rem;
  background: var(--vl-teal);
}

/* ---- Copy ---- */
.vl-gate__title {
  margin: 0 0 .9rem;
  color: var(--vl-white);
  font-weight: 500; line-height: 1.12;
  letter-spacing: var(--vl-tracking);
  font-size: clamp(1.75rem, 1.2rem + 2.2vw, 2.5rem);
}
.vl-gate__body {
  margin: 0 auto 1.9rem; max-width: 30rem;
  color: rgba(255, 255, 255, .84);
  font-size: 1.02rem; line-height: 1.55;
}

/* ---- Actions ----
   Side by side, exit on the left, the way the reference reads: the primary
   action sits where the eye finishes. Equal flex basis so the two are the same
   width whatever the translated strings turn out to be — FR runs long. */
.vl-gate__actions {
  display: flex; justify-content: center;
  gap: .85rem; margin: 0 0 1.6rem;
}
.vl-gate__exit,
.vl-gate__enter {
  flex: 1 1 0; min-width: 0;
  display: flex; align-items: center; justify-content: center;
  padding: 1.05rem 1.1rem;
  font-family: var(--vl-font); font-size: .98rem; font-weight: 500;
  letter-spacing: var(--vl-tracking); line-height: 1.2;
  border-radius: 0; cursor: pointer; text-decoration: none;
  transition: background .18s ease, border-color .18s ease, color .18s ease;
}
/* Secondary: an outline, not a second filled button. Leaving is an option, not
   an invitation. */
.vl-gate__exit {
  background: transparent; color: rgba(255, 255, 255, .86);
  border: 1px solid rgba(255, 255, 255, .30);
}
.vl-gate__exit:hover, .vl-gate__exit:focus {
  background: rgba(255, 255, 255, .07);
  border-color: rgba(255, 255, 255, .55);
  color: var(--vl-white);
}
.vl-gate__enter {
  background: var(--vl-teal); color: var(--vl-white);
  border: 1px solid var(--vl-teal);
}
.vl-gate__enter:hover, .vl-gate__enter:focus {
  background: var(--vl-teal-dark); border-color: var(--vl-teal-dark);
}
/* Focus is visible against the card, not the button: a teal ring on a teal
   button is no ring at all. */
.vl-gate__exit:focus-visible, .vl-gate__enter:focus-visible {
  outline: 2px solid var(--vl-white); outline-offset: 3px;
}

/* ---- Fine print ---- */
.vl-gate__fine {
  margin: 0;
  color: rgba(255, 255, 255, .52);
  font-size: .82rem; line-height: 1.5;
}

/* Phones. `column-reverse` rather than reordering the markup: the DOM keeps
   exit-then-accept, which is the left-to-right reading order on a wide screen
   and the tab order everywhere, while the thumb gets the accept button on top
   where it is reached without stretching. */
@media (max-width: 520px) {
  .vl-gate { padding: 1rem; }
  .vl-gate__panel { padding: 2.1rem 1.35rem 1.85rem; }
  .vl-gate__logo { margin-bottom: 1.25rem; }
  .vl-gate__rule { margin-bottom: 1.35rem; }
  .vl-gate__body { margin-bottom: 1.6rem; font-size: .98rem; }
  .vl-gate__actions { flex-direction: column-reverse; gap: .7rem; margin-bottom: 1.35rem; }
  .vl-gate__exit, .vl-gate__enter { padding: 1rem 1.1rem; }
}

/* ---- Nothing runs behind the gate ----------------------------------------
   `vl-gate-pending` is set in <head> before first paint and released 220ms
   after accept, once the veil has actually gone (see vistara_gate_hold_hero()).
   The hero wordmark's hold lives in home.css beside the animation itself; the
   rest is here, beside the gate that causes it.

   Both are held with `animation-play-state` rather than switched off, so each
   one plays from frame zero when the class comes off instead of being skipped.
   That is the whole point: a first-time visitor should watch the site arrive
   after they accept, not be handed a page that already finished arriving while
   they were reading a dialog.

     · Marquee and ticker — infinite scrollers, so nothing is "missed", but
       they are motion behind a stop and they cost battery on a phone that is
       showing a dialog. Released, the marquee also restarts from its first
       frame rather than resuming mid-slide.

   WAS THREE HOLDS, NOW TWO (29 Aug) -- and the hero's, in home.css, makes the
   third guarantee that is unchanged. A `html.vl-gate-pending .vl-reveal.is-in`
   rule stood here to hold the catalogue's card fade-ins: the
   IntersectionObserver fired on whatever was in the viewport at load and its
   2.5s safety net revealed the rest regardless, so behind a gate read for even
   five seconds every card on /peptides/ had already faded in. The reveal system
   itself is now retired at the owner's direction (see vistara_reveal_script()
   in inc/shop-archive.php — products render immediately), so `.vl-reveal` is
   never applied and that rule matched nothing at all. Removed rather than left
   as dead weight. Nothing else about the gate changed: the veil, the scroll
   lock, the hero hold, the marquee and the ticker all behave exactly as before,
   and the reviews carousel's JS timer still checks `vl-gate-pending` for itself
   in inc/homepage.php.

   The marquee's own `:hover` rule at 0-3-0 is not a threat: the veil is on top
   and is not a descendant of .vl-marquee, so it cannot be hovered while this
   class is set.
   ------------------------------------------------------------------------ */
html.vl-gate-pending .vl-marquee__track,
html.vl-gate-pending .vl-ticker__track { animation-play-state: paused; }

/* Site logo. The supplied file is a square lockup that already contains the
   wordmark, so it is constrained by HEIGHT (capping width makes the header as
   tall as the logo is wide) and given enough height for the wordmark to read.
   A horizontal lockup would allow a shorter header — swap the file and drop
   max-height to ~44px if one becomes available. */
.site-branding img, .site-header-item img.custom-logo, img.custom-logo {
  max-height: 72px;
  max-width: none;
  width: auto;
  height: auto;
}
@media (max-width: 767px) {
  .site-branding img, img.custom-logo { max-height: 52px; }
}

/* The logo already contains the wordmark — don't print the site title beside it. */
.site-branding .site-title, .site-title-wrap .site-title { display: none; }

/* ============================================================
   WHITE GROUND
   The site background is white throughout; the new vial photography
   is shot on a near-white ground and sits on it directly.
   Kadence ships a pale blue-grey title band — override it.
   ============================================================ */
html, body,
.content-bg, #inner-wrap, .site,
.entry-hero, .entry-hero-container-inner,
.product-archive-hero-section .entry-hero-container-inner,
.page-hero-section .entry-hero-container-inner {
  background-color: var(--vl-white);
}
/* Product tiles sit on white, separated by a hairline rather than a fill. */
.woocommerce ul.products li.product img { background: var(--vl-white); }
/* Footer notice: white with a rule, not a grey slab. */
.vl-footer-notice { background: var(--vl-white); }
/* Trust bar keeps a whisper of tint so it still reads as a band. */
.vl-trustbar { background: var(--vl-white); border-block: 1px solid var(--vl-rule); }

/* ============================================================
   HEADER — the live site shows a letterspaced text wordmark,
   not the logo image. The logo art is used in the hero and footer.
   ============================================================ */
.site-header-main-section-left .custom-logo-link,
.site-header-main-section-left img.custom-logo { display: none !important; }
.site-branding .site-title,
.site-title-wrap .site-title {
  display: block !important;
  font-family: var(--vl-font);
  font-weight: 400;
  text-transform: uppercase;
  letter-spacing: .06em;
  font-size: clamp(1.25rem, 1rem + .8vw, 1.85rem);
  color: #000;
  line-height: 1.1;
  margin: 0;
}
.site-branding .site-title a { color: #000; text-decoration: none; }
#masthead, .site-header { background: #FAFAFA; border-bottom: 0; }
.site-header .header-navigation .menu-item > a {
  font-size: 1rem; letter-spacing: -0.01em; color: #000;
}
.site-header .header-navigation .current-menu-item > a,
.site-header .header-navigation .current_page_item > a { text-decoration: underline; text-underline-offset: 6px; }

/* Marquee sits under the header, matching the live build. */
.vl-marquee { border-top: 0; border-bottom: 0; padding: 1.15rem 0; background: #fff; }
.vl-marquee__item { font-size: clamp(1rem, .85rem + .6vw, 1.45rem); color: #000; }

/* Row cart circle is for the single-product picker only; in a catalogue
   card (Related products) it overlapped the stepper. Cards have their own
   cart button. Aug 27. */
li.product .vl-cartctl__rowcart,
.vl-tile .vl-cartctl__rowcart { display: none !important; }
