/* ===================================================================
   DTF Station Vietnam — BASE LAYER + VN-ONLY COMPONENTS

   The design system itself lives in assets/design.css, copied verbatim
   from the DTF Station US redesign (~/Projects/dtfstation-us-site/
   shared/design/) — with ONE override here: the typeface is Be Vietnam
   Pro, because Poppins cannot set Vietnamese (see below). Load order in
   build/build.mjs's shell() is:

       main.css  ->  design.css  ->  blog.css / chat.css

   WHAT BELONGS HERE
   1. Document-level resets and the brand wash, which cannot be scoped.
   2. Components that exist only on the Vietnam site and therefore have
      no US counterpart: the announcement bar, the EN/VI language
      toggle, the homepage rotator, the per-machine downloads
      accordion, the product-detail page and the page head.

   Everything in group 2 is scoped to `.gs` (which sits on <body>) and
   is written in the SAME vocabulary as design.css — same tokens, same
   radii, same shadow language — so the two read as one system.

   WHAT DOES NOT BELONG HERE: anything that also exists in the US
   design. Change it there and re-copy design.css instead, or the two
   sites drift, which is what this split exists to prevent.
   =================================================================== */

*, *::before, *::after { box-sizing: border-box; }
* { margin: 0; padding: 0; }

html {
  scroll-behavior: smooth;
  -webkit-text-size-adjust: 100%;
}

body {
  /* --bg is the brand canvas #fbfbfd; the wash sits on top of it in ::before.
     Stated literally too, because this file loads before design.css defines
     .gs, and an unstyled first paint should still be the right ground. */
  background: #fbfbfd;
  position: relative;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overflow-x: hidden;
  /* font-family and color come from .gs in design.css, which body carries. */
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  * { animation: none !important; transition: none !important; }
}

/* ===================================================================
   THE BRAND WASH
   Three soft radials — gold from the top-left, teal from the top-right
   and the bottom-right — over the page ground. Taken from the locked
   social-card system, same as the US site.

   FIXED, not scrolling. Painted onto a 5,000px document those radials
   would stretch into meaningless bands and the corner relationship, the
   whole point of them, would be lost. Fixed to the viewport the page
   scrolls over one washed surface instead.

   This is also why cards are pure #fff while the page is #fbfbfd: white
   reads as slightly raised against a tinted ground.
   =================================================================== */
body::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background: var(--brand-wash);
}


/* <picture> is only a WebP delivery wrapper (build.mjs webpify). Make it
   invisible to layout so the <img> inside keeps being the grid/flex item
   its banner, card or rotator cell was written for. */
.gs picture { display: contents; }


/* ===================================================================
   COMPATIBILITY ALIASES

   chat.css and blog.css were written against the old token names and
   are loaded on every page. Rather than fork them, the old names are
   re-pointed at the new values here. Every one of them is a rename, not
   a different colour.

     --accent-dark  ->  --accent-ink  (the AA-safe teal for text)
     --radius       ->  --r-card
     --bg-dark      ->  --tint-ink

   Inline styles inside the section sources use --accent-dark too. When
   those are all gone, these can be.
   =================================================================== */
.gs {
  --accent-dark: var(--accent-ink);
  --radius: var(--r-card);
  --bg-dark: var(--tint-ink);
}


/* ===================================================================
   TYPEFACE — Be Vietnam Pro, NOT Poppins.

   The US design system sets --font to Poppins. Poppins has no Vietnamese
   subset: 41 of the 74 accented characters Vietnamese uses (ộ ề ậ ự ắ
   ấ ổ ợ ữ and their capitals) fall outside its coverage and get drawn by
   whatever face the browser falls back to — two typefaces inside one
   word, on a site whose own language is Vietnamese. Checked with
   tools/font-vietnamese-check.mjs on 2026-09-04: Poppins missing
   41/74, Be Vietnam Pro full coverage.

   Be Vietnam Pro is a geometric sans designed for Vietnamese and close
   enough to Poppins in Latin that the family resemblance with
   dtfstation.com holds. Same four weights, so nothing else in the design
   system changes. build.mjs inlines the @font-face rules in <head> and the
   files are served from /assets/fonts (tools/sync-fonts.mjs).

   ⚠ `html .gs`, NOT `.gs` — AND THIS IS NOT COSMETIC. design.css declares
   `--font: Poppins…` on `.gs`, the identical selector, and build.mjs links
   main.css BEFORE design.css. At equal specificity the later sheet wins, so
   this override lost, and from 2026-09-04 until 2026-09-08 the site actually
   rendered in Poppins — every Vietnamese accent drawn by a system fallback,
   which is the precise bug this block was written to prevent. It was invisible
   because Poppins covers unaccented Latin perfectly, so English pages looked
   right and only the accents were wrong. Found when self-hosting the font
   showed the browser downloading no font file at all: the page asked Google
   for Be Vietnam Pro and then never used the family.
   The extra `html` gives (0,1,1) against design.css's (0,1,0) and wins
   regardless of load order. Do not "tidy" it back to `.gs`.
   =================================================================== */
html .gs {
  --font: "Be Vietnam Pro", Poppins, "Google Sans", Inter, -apple-system, BlinkMacSystemFont,
          "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  font-family: var(--font);
}


/* ===================================================================
   ANNOUNCEMENT BAR  (VTG 2026)

   Sitewide strip above the nav, rendered by src/sections/header.liquid
   and switched by SITE.announcement in build/build.mjs.

   IN NORMAL FLOW, NOT FIXED. The old bar was position:fixed, which
   forced every page to carry a calc(var(--nav-h) + N) top padding and
   made --nav-h lie about the nav's height. Here the bar is an ordinary
   block, the nav is sticky at top:var(--anno-h), and --anno-h is stamped
   on :root by an inline script in <head> BEFORE paint — measuring it
   afterwards is what makes a sticky nav jump on load.
   =================================================================== */
.gs .anno {
  position: relative; z-index: 101;
  height: var(--anno-h, 44px);
  display: flex; align-items: center;
  background: var(--accent); color: var(--ink);
}
.gs .anno-in {
  max-width: var(--maxw); margin: 0 auto; width: 100%;
  padding: 0 24px;
  display: flex; align-items: center; justify-content: center;
}
/* min-height for the same reason design.css gives .btn-text one: this is a
   standalone action, not a link inside a sentence, so WCAG 2.2's inline
   exception does not apply. It measured 21px on a phone. */
.gs .anno-link { display: inline-flex; align-items: center; gap: 10px; font-size: 14px; min-width: 0; min-height: 24px; }
.gs .anno-tag {
  flex: 0 0 auto; font-size: 11px; font-weight: 600;
  letter-spacing: var(--track-eyebrow); text-transform: uppercase;
  background: var(--ink); color: #fff; padding: 3px 10px; border-radius: var(--r-chip);
}
.gs .anno-head { font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.gs .anno-cta { font-weight: 600; white-space: nowrap; }
.gs .anno-link:hover .anno-cta { text-decoration: underline; }
.gs .anno-sm { display: none; }
@media (max-width: 860px) {
  .gs .anno-lg { display: none; }
  .gs .anno-sm { display: inline; }
  .gs .anno-link { font-size: 13px; gap: 8px; }
}
@media (max-width: 420px) {
  .gs .anno-tag { font-size: 10px; padding: 3px 7px; }
  .gs .anno-link { gap: 6px; }
}


/* ===================================================================
   NAV ADDITIONS

   design.css styles header.nav, .nav-inner, .nav-links, .drop-menu and
   the burger. These two controls are Vietnam-only.
   =================================================================== */

/* -------------------------------------------------------------------
   STICKY OFFSET, AND HIDE-ON-SCROLL

   TWO THINGS HERE, and the first is a bug fix the second depends on.

   1. THE OFFSET. design.css sticks the nav at top:var(--anno-h),
      because on the US Shopify theme the announcement bar is
      position:fixed and the nav has to clear it. Here the bar is in
      NORMAL FLOW (see the ANNOUNCEMENT BAR block above), so it scrolls
      away — and a 44px offset left a 44px band at the top of the
      window with page content sliding through it ABOVE the nav, on
      every page (measured 2026-09-08). For a bar that scrolls away the
      correct offset is 0: the nav then pins at the exact moment the
      bar clears, with no gap and no overlap.
      design.css is generated by tools/sync-design.mjs and must not be
      hand-edited, so the override lives here; #nav supplies the
      specificity to beat `.gs header.nav`.

   2. HIDE ON SCROLL DOWN, SHOW ON SCROLL UP (Jason, 2026-09-08). Only
      the `top` fix and the .subnav offset are here — THE MOVING IS
      design.css's JOB. The US design system landed the same feature
      independently and under the same class name:

        html.nav-up .gs header.nav { transform: translateY(-100%); }
        .gs header.nav { transition: transform .28s ease; }   + a
        prefers-reduced-motion block that turns the transition off

      so this file deliberately does NOT also transform the nav. Two
      mechanisms for one behaviour is precisely what produced the two
      bugs described above. What is still ours: the JS that toggles the
      class (the design system ships no JS — the US drives it from
      Nav.astro), and .subnav, because the US's product bar is .pnav and
      design.css moves that one, not ours.

   --nav-shift is declared on .gs, not :root, because --nav-h is a .gs
   token and would not resolve on <html>. It equals design.css's own
   -100%: the nav is --nav-h plus a 1px bottom border.
   ------------------------------------------------------------------- */
.gs { --nav-shift: 0px; }
html.nav-up .gs { --nav-shift: calc(-1px - var(--nav-h)); }

.gs header.nav#nav { top: 0; }

/* EN / VI toggle. A segmented pill, sized to sit beside a 14px nav link
   without becoming the loudest thing in the bar. */
.gs .lang {
  display: flex; align-items: center;
  border: 1px solid var(--line); border-radius: var(--r-chip); overflow: hidden;
}
.gs .lang-btn {
  background: transparent; border: 0; font-family: inherit;
  font-size: 12px; font-weight: 500; letter-spacing: .02em;
  padding: 5px 11px; cursor: pointer; color: var(--ink-faint); transition: .18s;
}
.gs .lang-btn:hover { color: var(--ink); }
.gs .lang-btn.active { background: var(--ink); color: #fff; }

/* The Shop button. Filled with the brand hue, which takes DARK text —
   white on #49BFBC is 2.22:1 and fails. Same rule as .btn-accent. */
.gs .nav-shop {
  font-size: 14px; font-weight: 500; padding: 8px 18px;
  border-radius: var(--r-chip); background: var(--accent); color: var(--ink);
  transition: background .18s, box-shadow .18s;
}
.gs .nav-shop:hover { background: var(--accent-hover); box-shadow: var(--shadow-lift); }

/* On a phone the language toggle moves to the footer so the burger and
   the Contact button always fit. */
@media (max-width: 600px) { .gs .nav .lang { display: none; } }


/* ===================================================================
   PAGE HEAD — the top of every inner page.

   The US site opens its inner pages with a Banner. Vietnam's pages open
   with a centred statement instead, because most of them (Support, Why,
   Downloads, Contact) have no product shot to put in a banner's art
   column, and a banner with an empty right half is worse than no banner.

   Same type scale as the design system's .display / .lede, so this is a
   layout variant of the system rather than a second one.
   =================================================================== */
.gs .page-head { padding: clamp(44px, 5.5vw, 76px) 0 clamp(24px, 3vw, 40px); }
.gs .page-head .wrap { max-width: 56rem; text-align: center; }
.gs .page-head .eyebrow { display: block; margin-bottom: 16px; }
/* The eyebrow doubles as a breadcrumb on the product and category pages, and
   a breadcrumb is a navigation control, not prose — 16px on a phone. Padding
   rather than min-height because the eyebrow is inline text; the same shape
   design.css uses for .foot-legal a. */
.gs .eyebrow a { display: inline-block; padding: 4px 0; }
.gs .page-head h1 {
  font-size: clamp(38px, 6.2vw, 76px); line-height: 1.03;
  letter-spacing: var(--track-display); font-weight: 600;
  margin-bottom: 18px; text-wrap: balance;
}
.gs .page-head > .wrap > p {
  font-size: clamp(17px, 1.5vw, 20px); color: var(--ink-soft);
  line-height: 1.55; max-width: 42rem; margin: 0 auto;
}
.gs .page-head .head-actions {
  display: flex; flex-wrap: wrap; gap: 12px; justify-content: center; margin-top: 28px;
}


/* ===================================================================
   PILLARS — the three (or four) positioning statements.

   ⚠️ DELIBERATELY NOT CARDS. A row of cards is what makes a page read as
   a catalogue; this is what the US site uses in its place and the
   reasoning carries over. A hairline in the accent, a heading, a
   paragraph.

   Vietnam's version keeps the icon the old design used — it is doing
   real work on Support and Contact, where the four items are channels
   rather than arguments and the glyph is how you find the right one.
   =================================================================== */
.gs .pillars {
  list-style: none; margin: 0; padding: 0;
  display: grid; grid-template-columns: repeat(3, 1fr);
  gap: clamp(28px, 3.5vw, 56px);
}
.gs .pillars.four { grid-template-columns: repeat(4, 1fr); gap: clamp(24px, 2.6vw, 40px); }
.gs .pillars > li { border-top: 1px solid var(--accent); padding-top: 22px; }
.gs .pillars .ic {
  width: 44px; height: 44px; border-radius: 14px;
  background: var(--accent-tint); display: grid; place-items: center; margin-bottom: 16px;
}
.gs .pillars .ic svg {
  width: 22px; height: 22px; stroke: var(--accent-ink);
  fill: none; stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round;
}
.gs .pillars h3 {
  font-size: clamp(20px, 1.9vw, 26px); line-height: 1.16;
  letter-spacing: var(--track-heading); margin: 0 0 12px; text-wrap: balance;
}
.gs .pillars.four h3 { font-size: clamp(18px, 1.5vw, 21px); }
.gs .pillars p { font-size: 15.5px; line-height: 1.62; color: var(--ink-soft); margin: 0 0 18px; }
.gs .pillars .pill-links { display: flex; flex-wrap: wrap; gap: 6px 18px; }
@media (max-width: 1000px) { .gs .pillars.four { grid-template-columns: 1fr 1fr; } }
@media (max-width: 760px) {
  .gs .pillars, .gs .pillars.four { grid-template-columns: 1fr; gap: 34px; }
}


/* ===================================================================
   FIGURES — "37+ countries served".

   The figure IS the argument, so it is allowed to be the largest thing
   on the page after the hero headline.
   =================================================================== */
.gs .manu { display: grid; grid-template-columns: 1.1fr 0.9fr; gap: clamp(32px, 5vw, 80px); align-items: center; }
.gs .figures { display: grid; gap: 0; margin: 0; padding: 0; }
.gs .figures > div { padding: clamp(22px, 2.4vw, 32px) 0; }
.gs .figures > div + div { border-top: 1px solid var(--line); }
.gs .figures dt {
  font-size: 12px; font-weight: 500; letter-spacing: var(--track-eyebrow);
  text-transform: uppercase; color: var(--ink-faint);
}
.gs .figures dd {
  margin: 8px 0 0; font-size: clamp(52px, 7vw, 96px); font-weight: 700;
  line-height: .92; letter-spacing: -0.03em; font-variant-numeric: tabular-nums;
}
.gs .figures dd span { color: var(--accent-ink); }
@media (max-width: 900px) { .gs .manu { grid-template-columns: 1fr; } }


/* ===================================================================
   PILLS — plain text pills for roll widths, pack sizes, show facts.

   The design system already has a `.chip`, but that is the round-icon
   browse chip with asymmetric padding for a 42px avatar. These are text.
   =================================================================== */
.gs .pill-row { display: flex; flex-wrap: wrap; gap: 8px; }
.gs .pill {
  font-size: 13px; font-weight: 500;
  /* --accent-ink is derived to pass on WHITE. On the teal tint these sit
     on it drops below AA, so this is the same hue one step darker. */
  color: #2a7876;
  background: var(--tint-teal); padding: 6px 13px; border-radius: var(--r-chip);
}
/* On a genuinely dark ground — a photographic banner or a dark rotator
   slide — the teal tint disappears; outline the pill instead.
   ⚠️ NOT `.on-dark .pill`. In this design `.cta.on-dark` and
   `.banner.on-dark:not(.has-bg)` are LIGHT (washed white); only the
   photographic and rotator treatments are actually dark. */
.gs .banner.has-bg .pill, .gs .rot-slide.t-brand .pill, .gs .rot-slide.t-ink .pill {
  color: rgba(255, 255, 255, .88);
  background: transparent; box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .28);
}

/* The one fact on a slide that has to be unmissable — the booth number.
   Filled with the brand hue, which takes DARK text: white on #49BFBC is
   2.22:1 and fails. Same rule as .btn-accent and the announcement tag. */
.gs .pill-strong {
  background: var(--accent); color: var(--ink);
  font-weight: 600; box-shadow: none;
}
.gs .banner.has-bg .pill-strong,
.gs .rot-slide.t-brand .pill-strong,
.gs .rot-slide.t-ink .pill-strong {
  background: var(--accent); color: var(--ink); box-shadow: none;
}



/* ===================================================================
   HOMEPAGE ROTATOR

   Ported from the US site's BannerRotator. Two rules it must not break,
   both learned the hard way over there:

   1. CONTAINED, NOT FULL-BLEED. A dark band running edge to edge on an
      otherwise light site fights the site instead of sitting in it. This
      is inset in the content column like every other unit and earns its
      presence through scale and motion.
   2. EVERY SLIDE IS A COMPLETE STATEMENT. A hero is a single-focus
      stage, so a slide that is only a campaign fragment collapses at
      that size. Each slide here is a whole proposition.

   Motion pauses on hover and focus, stops under prefers-reduced-motion,
   and the dots are real buttons — auto-advance is never the only way
   through.
   =================================================================== */
.gs .rot { padding: 20px 0 0; }
.gs .rot-frame { position: relative; border-radius: var(--r-banner); overflow: hidden; display: grid; }
.gs .rot-slide {
  grid-area: 1 / 1; position: relative;
  display: grid; grid-template-columns: 1.04fr 0.96fr;
  align-items: center; gap: clamp(20px, 4vw, 56px);
  padding: clamp(32px, 4vw, 64px);
  /* 2026-09-04 — CUT. Jason: "ridiculously tall". The US site runs this at
     clamp(420px, 52vh, 560px) because its hero is the whole argument; here
     the banner sits under an announcement bar AND a nav, so at 560px it ate
     the viewport and the first section never showed above the fold. The copy
     is what should set the height now, with a floor so a short slide does not
     collapse and a cap so a long one cannot run away. */
  min-height: clamp(300px, 34vh, 420px);
  isolation: isolate;
  opacity: 0; visibility: hidden; transition: opacity .55s ease;
}
.gs .rot-slide.is-on { opacity: 1; visibility: visible; }
@media (prefers-reduced-motion: reduce) { .gs .rot-slide { transition: none; } }

/* Treatments — near-black for the flagship, the brand hue for the colour
   story, a soft neutral for everything else. */
.gs .rot-slide.t-ink { background: #14181a; color: #fff; }
.gs .rot-slide.t-brand {
  background:
    radial-gradient(120% 150% at 88% 12%, rgba(73, 191, 188, .34) 0%, rgba(73, 191, 188, 0) 58%),
    linear-gradient(118deg, #08302F 0%, #145755 54%, #0E4442 100%);
  color: #fff;
}
.gs .rot-slide.t-light { background: var(--bg-alt); color: var(--ink); box-shadow: inset 0 0 0 1px var(--line-soft); }

/* A slide whose whole surface is a photograph. The scrim guarantees the
   headline's contrast rather than trusting the picture: which part of the
   frame is darkest depends on the crop at each viewport width. */
.gs .rot-bg {
  position: absolute; inset: 0; max-width: none;
  width: 100%; height: 100%; object-fit: cover;
  object-position: 30% center; z-index: -2;
}
.gs .rot-scrim {
  position: absolute; inset: 0; z-index: -1;
  background: linear-gradient(96deg,
    rgba(10, 12, 13, .93) 0%, rgba(10, 12, 13, .82) 32%,
    rgba(10, 12, 13, .38) 62%, rgba(10, 12, 13, .10) 100%);
}
/* A photographic slide has no separate art column. */
.gs .rot-slide:has(.rot-bg) { grid-template-columns: 1fr; }
.gs .rot-slide:has(.rot-bg) .rot-body { max-width: 34rem; }

.gs .rot-body { position: relative; }
.gs .rot-body h1, .gs .rot-body h2 {
  font-size: clamp(34px, 5.2vw, 68px); line-height: 1.02;
  letter-spacing: var(--track-display); margin: 14px 0 0;
  text-wrap: balance;
  /* Stated, never inherited — a white headline has silently turned
     near-black on this design before. */
  color: inherit;
}
.gs .rot-body p { font-size: clamp(16px, 1.35vw, 20px); line-height: 1.55; margin: 20px 0 0; max-width: 32rem; }
.gs .t-ink .rot-body p, .gs .t-brand .rot-body p { color: rgba(255, 255, 255, .75); }
.gs .t-light .rot-body p { color: var(--ink-soft); }
.gs .t-ink .rot-body .eyebrow, .gs .t-brand .rot-body .eyebrow { color: rgba(255, 255, 255, .62); }
.gs .rot-actions { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 34px; }
.gs .t-ink .rot-actions .btn-ghost, .gs .t-brand .rot-actions .btn-ghost { color: #fff; border-color: rgba(255, 255, 255, .42); }
.gs .t-ink .rot-actions .btn-ghost:hover, .gs .t-brand .rot-actions .btn-ghost:hover { background: rgba(255, 255, 255, .14); }

/* The product stage. The glow is what makes a cut-out sit IN the slide
   rather than on top of it — without it a PNG on flat colour looks
   pasted no matter how big it is. */
.gs .rot-art { display: grid; place-items: center; position: relative; isolation: isolate; }
.gs .rot-art::before {
  content: ""; position: absolute; z-index: -1;
  width: min(120%, 560px); aspect-ratio: 1.5 / 1; border-radius: 50%;
  filter: blur(60px); opacity: .55; pointer-events: none;
}
.gs .t-ink .rot-art::before   { background: radial-gradient(ellipse at center, rgba(73,191,188,.30), transparent 66%); }
.gs .t-brand .rot-art::before { background: radial-gradient(ellipse at center, rgba(255,255,255,.24), transparent 66%); }
.gs .t-light .rot-art::before { background: radial-gradient(ellipse at center, rgba(20,24,26,.14), transparent 66%); }
.gs .rot-art img { width: 100%; max-width: 520px; max-height: 360px; height: auto; object-fit: contain; }
/* A rendered plate rather than a cut-out product: it carries its own
   background, so it gets a radius and a shadow to read as a framed image
   instead of a rectangle dropped on the slide. */
.gs .rot-art img.plate {
  max-width: 620px; border-radius: 16px;
  box-shadow: 0 24px 60px -18px rgba(0, 0, 0, .55);
}
.gs .t-brand .rot-art img.plate, .gs .t-ink .rot-art img.plate { filter: none; }
.gs .t-light .rot-art img { filter: var(--shadow-product); }
.gs .t-ink .rot-art img, .gs .t-brand .rot-art img { filter: var(--shadow-product-dark); }

.gs .rot-dots {
  position: absolute; left: clamp(40px, 5vw, 68px); bottom: clamp(22px, 2.6vw, 30px);
  display: flex; gap: 8px; z-index: 2;
}
.gs .rot-dot {
  width: 26px; height: 20px; padding: 8px 0; border: 0; background: none;
  cursor: pointer; display: grid; align-items: center;
}
/* The hit area is 26x20 for thumbs; the visible mark is the 26x3 bar. */
.gs .rot-dot span { display: block; height: 3px; border-radius: 999px; background: rgba(255, 255, 255, .34); transition: background .25s ease; }
.gs .rot-dot[aria-selected='true'] span { background: #fff; }
.gs .rot-dot:hover span { background: rgba(255, 255, 255, .6); }
.gs .rot-dot:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: 4px; }
.gs .rot-frame:has(.rot-slide.t-light.is-on) .rot-dot span { background: rgba(20, 24, 26, .22); }
.gs .rot-frame:has(.rot-slide.t-light.is-on) .rot-dot[aria-selected='true'] span { background: var(--ink); }

@media (max-width: 860px) {
  .gs .rot-slide {
    grid-template-columns: 1fr;
    /* The vh floor is a desktop device. On a phone the content sets the
       height or the hero eats the screen before a word is read. */
    min-height: 0; padding: 26px 24px 52px; gap: 18px;
  }
  .gs .rot-art { order: -1; }
  /* On a phone the slide is nearly square while the photograph is a wide
     letterbox, so the subject crops out of frame entirely. The photo becomes
     a real block above the copy instead, with no scrim over it. */
  .gs .rot-slide:has(.rot-bg) { grid-template-rows: auto auto; }
  .gs .rot-slide:has(.rot-bg) .rot-body { max-width: none; }
  /* ⚠️ NO NEGATIVE-MARGIN BLEED HERE. `width: calc(100% + 48px)` sized the
     grid COLUMN, not just the image: the track grew past the frame, the
     slide pushed the page wider than the viewport, and the nav's burger and
     Contact button ended up off-screen. The photo sits inside the slide's
     padding instead — it still reads as a block above the copy. */
  .gs .rot-bg {
    position: static; width: 100%; margin: 0;
    height: auto; aspect-ratio: 3 / 2; object-position: 50% 40%; order: -1;
  }
  .gs .rot-slide:has(.rot-bg) { padding-top: 24px; }
  /* A grid item defaults to min-width:auto, so a wide child can still force
     the track open. Belt and braces for every slide, not just this one. */
  .gs .rot-slide > * { min-width: 0; }
  .gs .rot-scrim { display: none; }
  .gs .rot-art img { max-width: 230px; max-height: 150px; }
  .gs .rot-art::before { width: 150%; filter: blur(40px); }
  .gs .rot-dots { left: 24px; bottom: 22px; }
}


/* ===================================================================
   PRODUCT DETAIL PAGE  (/product?id=…)

   The one page on the site that is rendered by JavaScript from
   pd-printers.js / pd-accessories.js, so its shell has to exist in CSS
   rather than be composed out of design.css components in markup.
   =================================================================== */

/* Sticky product sub-nav, under the site nav. */
.gs .subnav {
  /* max() because --nav-shift clears the nav's 1px bottom border too; without
     the clamp this bar pins one pixel above the window when the nav hides. */
  position: sticky; top: max(0px, calc(var(--nav-h) + var(--nav-shift, 0px))); z-index: 90;
  background: rgba(255, 255, 255, .82);
  backdrop-filter: saturate(180%) blur(20px);
  -webkit-backdrop-filter: saturate(180%) blur(20px);
  border-bottom: 1px solid rgba(20, 24, 26, .07);
}
.gs .subnav .in {
  max-width: var(--maxw); margin: 0 auto; height: 52px; padding: 0 24px;
  display: flex; align-items: center; justify-content: space-between; gap: 20px;
}
.gs .subnav .pn { font-size: 16px; font-weight: 600; letter-spacing: var(--track-heading); }
.gs .subnav .lk { display: flex; gap: 20px; align-items: center; }
.gs .subnav .lk a { font-size: 13px; font-weight: 500; color: var(--ink-soft); }
.gs .subnav .lk a:hover { color: var(--ink); }
.gs .subnav .lk .btn { padding: 7px 16px; font-size: 13px; }

/* The product hero: a washed flagship card with the machine on it. Same
   treatment as .banner.t-ink in the design system, which is what the
   flagship gets everywhere else. */
.gs .ov-hero { padding: clamp(28px, 3vw, 44px) 0 0; }
.gs .ov-stage {
  border-radius: var(--r-banner);
  background-color: var(--bg); background-image: var(--brand-wash-card);
  box-shadow: var(--edge);
  padding: clamp(34px, 4vw, 64px) clamp(24px, 4vw, 64px) clamp(24px, 3vw, 44px);
  text-align: center;
}
.gs .ov-stage .eyebrow { display: block; margin-bottom: 14px; }
.gs .ov-stage h1 {
  font-size: clamp(38px, 6.2vw, 76px); line-height: 1.03;
  letter-spacing: var(--track-display); font-weight: 600; margin-bottom: 16px;
}
.gs .ov-stage .sub {
  font-size: clamp(17px, 1.5vw, 21px); color: var(--ink-soft);
  max-width: 40rem; margin: 0 auto; line-height: 1.5;
}
.gs .ov-stage .head-actions { justify-content: center; }
.gs .ov-stage .shot { margin-top: clamp(28px, 3vw, 44px); }
.gs .ov-stage .shot img {
  max-width: 860px; width: 100%; margin: 0 auto;
  filter: var(--shadow-product-lg);
}

/* Alternating feature blocks, rendered per product. */
.gs .feat { display: grid; grid-template-columns: 1fr 1fr; gap: clamp(28px, 4vw, 56px); align-items: center; }
.gs .feat .fx h2 { font-size: clamp(27px, 3.3vw, 42px); line-height: 1.08; letter-spacing: var(--track-display); margin-bottom: 14px; }
.gs .feat .fx p { font-size: 17px; color: var(--ink-soft); line-height: 1.6; }
.gs .feat .fm {
  background: #fff; box-shadow: var(--edge); border-radius: var(--r-card);
  min-height: 340px; display: grid; place-items: center; padding: 36px; overflow: hidden;
}
.gs .feat .fm img { max-width: 100%; max-height: 300px; object-fit: contain; }
.gs .feat.rev { direction: rtl; }
.gs .feat.rev > * { direction: ltr; }
.gs .billboard img { width: 100%; border-radius: var(--r-banner); box-shadow: var(--shadow-card); }
.gs .feat-grid { display: grid; grid-template-columns: 1fr 1fr; gap: var(--gap); }
.gs .feat-grid .ft { border-radius: var(--r-card); overflow: hidden; background: #fff; box-shadow: var(--edge); }
.gs .feat-grid .ft img { width: 100%; }
@media (max-width: 860px) {
  .gs .feat { grid-template-columns: 1fr; }
  .gs .feat .fm { min-height: 260px; }
  .gs .subnav .pn { display: none; }
}
@media (max-width: 700px) { .gs .feat-grid { grid-template-columns: 1fr; } }

/* "At a glance" figures. Hairline above each, no boxes — the same
   restraint as the pillars, so the band does not read as more tiles. */
.gs .snap { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: clamp(20px, 3vw, 48px); }
.gs .snap .s { border-top: 1px solid var(--line); padding-top: 18px; }
.gs .snap .s .n {
  font-size: clamp(34px, 4.4vw, 58px); font-weight: 700; line-height: .95;
  letter-spacing: -0.03em; font-variant-numeric: tabular-nums;
}
.gs .snap .s .l {
  margin-top: 8px; font-size: 12px; font-weight: 500;
  letter-spacing: var(--track-eyebrow); text-transform: uppercase; color: var(--ink-faint);
}

/* The build configurator's right-hand column (design.css owns .cfg,
   .cfg-hero, .cfg-opt and the summary). */
.gs .cfg-panel .eyebrow { display: block; margin-bottom: 12px; }
.gs .cfg-panel h1 {
  font-size: clamp(30px, 4vw, 48px); line-height: 1.05;
  letter-spacing: var(--track-display); font-weight: 600; margin-bottom: 12px;
}
.gs .pdp-tagline { font-size: 18px; color: var(--ink-soft); line-height: 1.5; margin-bottom: 22px; }


/* ===================================================================
   DOWNLOAD RESOURCES — per-machine accordion.

   A <details> per machine so a visitor scans machine names, not 60 file
   rows. Retinted to the new system: white surfaces on the washed page,
   the hairline --edge instead of a border, teal only on the file icons.
   =================================================================== */
.gs .dl-cat { max-width: 860px; margin: 0 auto clamp(32px, 4vw, 48px); }
.gs .dl-cat > h2 { font-size: clamp(21px, 2vw, 26px); letter-spacing: var(--track-heading); margin-bottom: 16px; }
.gs .dl-machine {
  background: #fff; box-shadow: var(--edge); border-radius: 18px;
  margin-bottom: 10px; overflow: hidden;
}
.gs .dl-machine > summary {
  display: flex; align-items: center; gap: 16px; padding: 14px 20px;
  cursor: pointer; list-style: none; user-select: none;
}
.gs .dl-machine > summary::-webkit-details-marker { display: none; }
.gs .dl-machine > summary:hover { background: var(--bg-alt); }
.gs .dl-machine summary img { width: 54px; height: 42px; object-fit: contain; flex: 0 0 auto; filter: var(--shadow-product); }
.gs .dl-machine .mn { font-weight: 500; font-size: 16px; flex: 1; }
.gs .dl-machine .mc { font-size: 12.5px; color: var(--ink-faint); }
.gs .dl-machine .chev {
  width: 18px; height: 18px; stroke: var(--ink-faint); fill: none;
  stroke-width: 2; stroke-linecap: round; stroke-linejoin: round;
  transition: transform .25s; flex: 0 0 auto;
}
.gs .dl-machine[open] .chev { transform: rotate(180deg); }
.gs .dl-machine[open] > summary { border-bottom: 1px solid var(--line-soft); }
.gs .dl-files { padding: 12px 20px 16px; display: grid; gap: 8px; }
.gs .dl-file {
  display: flex; align-items: center; gap: 14px;
  padding: 11px 14px; border-radius: 12px; background: var(--bg-alt); transition: .16s;
}
.gs .dl-file:hover { background: var(--accent-tint); }
.gs .dl-file .fi {
  width: 34px; height: 34px; border-radius: 10px; background: #fff;
  box-shadow: var(--edge); display: grid; place-items: center; flex: 0 0 auto;
}
.gs .dl-file .fi svg,
.gs .dl-file .dlarrow {
  stroke: var(--accent-ink); fill: none; stroke-width: 1.8;
  stroke-linecap: round; stroke-linejoin: round;
}
.gs .dl-file .fi svg { width: 17px; height: 17px; }
.gs .dl-file .fn { flex: 1; font-size: 15px; font-weight: 500; }
.gs .dl-file .fmeta { font-size: 12px; color: var(--ink-faint); }
.gs .dl-file .dlarrow { width: 17px; height: 17px; flex: 0 0 auto; opacity: .5; transition: opacity .16s; }
.gs .dl-file:hover .dlarrow { opacity: 1; }

/* A file row with nothing behind it yet. Deliberately NOT an anchor — it must
   not take a click, show a pointer, or look reachable. The library is on hold
   until the refreshed US documents land (DOWNLOADS.md). */
.gs .dl-file.is-soon { background: transparent; box-shadow: inset 0 0 0 1px var(--line-soft); cursor: default; opacity: .72; }
.gs .dl-file.is-soon:hover { background: transparent; }
.gs .dl-file.is-soon .fi { box-shadow: inset 0 0 0 1px var(--line-soft); }
.gs .dl-file.is-soon .fi svg { stroke: var(--ink-faint); }
.gs .dl-notice {
  margin: 18px auto 0; max-width: 40rem; font-size: 15px; line-height: 1.6;
  color: var(--ink-soft); background: var(--accent-tint);
  padding: 14px 18px; border-radius: 14px;
}

/* Help-centre language notice. The articles are English until the
   translation lands; this says so rather than leaving a reader to work it
   out. Vietnamese first — that is the site's own language. */
.gs .help-lang-note {
  max-width: 44rem; margin: 20px auto 0; padding: 13px 18px;
  border-radius: 14px; background: var(--accent-tint);
  font-size: 14.5px; line-height: 1.6; color: var(--ink-soft);
}
.gs .help-lang-note b { color: var(--ink); font-weight: 600; }
.gs .help-lang-note a { color: var(--accent-ink); font-weight: 500; text-decoration: underline; }
.gs .help-lang-note .help-lang-en { display: block; margin-top: 4px; font-size: 13px; color: var(--ink-faint); }


/* ===================================================================
   FOOTER — brand column + language toggle.

   design.css styles footer.site, .foot-grid and .foot-base. Vietnam
   adds a first column carrying the positioning line and the Zalo /
   phone details, which is the fastest contact route in this market and
   so belongs above the fold of the footer, not inside a link list.
   =================================================================== */
.gs .foot-grid { grid-template-columns: 1.5fr repeat(3, 1fr); }
.gs .foot-brand p { color: var(--ink-soft); max-width: 26rem; line-height: 1.6; }
.gs .foot-brand .foot-logo img { height: 26px; width: auto; margin-bottom: 16px; }
.gs .foot-contact { margin-top: 16px; font-size: 13.5px; color: var(--ink-soft); line-height: 1.7; }
.gs .foot-contact a { color: var(--accent-ink); font-weight: 500; }
.gs .foot-contact a:hover { text-decoration: underline; }
.gs .foot-lang { display: flex; gap: 8px; }
.gs .foot-lang button {
  background: none; border: 1px solid var(--line); color: var(--ink-soft);
  border-radius: var(--r-chip); padding: 5px 14px;
  font-family: inherit; font-size: 12px; font-weight: 500; cursor: pointer; transition: .18s;
}
.gs .foot-lang button:hover { color: var(--ink); border-color: #c4c7c5; }
.gs .foot-lang button.active { background: var(--ink); color: #fff; border-color: var(--ink); }
@media (max-width: 860px) { .gs .foot-grid { grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); } }


/* ===================================================================
   FLOATING CONTACT — the AI chat launcher lives in chat.css. Nothing
   here; kept as a marker so the next person looks in the right file.
   =================================================================== */


/* ===================================================================
   JUMP CHIPS — the product line as a row of chips at the top of a
   category page, each one scrolling to that product further down.

   Same chip design as the homepage browse row on purpose: someone who
   has seen the homepage already knows what these are. The difference
   is these navigate WITHIN the page, which is why the active one is
   marked as you scroll — a row that looks like site navigation but only
   scrolls needs to show where you have got to, or it feels like nothing
   happened. main.js does the marking.
   =================================================================== */
.gs .jump-chips .chip { scroll-snap-align: start; }
.gs .jump-chips .chip.is-here { border-color: var(--accent); box-shadow: inset 0 0 0 1px var(--accent); }
/* Anchor targets sit under a sticky nav; a plain jump would land with the
   heading hidden behind it. */
.gs .jump-target { scroll-margin-top: calc(var(--nav-h) + 24px); }


/* ===================================================================
   COLOUR SWATCHES — the Chroma ink lineup.
   =================================================================== */
.gs .swatches { display: flex; flex-wrap: wrap; gap: 12px 22px; margin-top: 4px; }
.gs .swatch { display: inline-flex; align-items: center; gap: 9px; font-size: 14px; font-weight: 500; color: var(--ink-soft); }
.gs .swatch i { width: 22px; height: 22px; border-radius: 50%; box-shadow: inset 0 0 0 1px rgba(0, 0, 0, .1); flex: 0 0 auto; }


/* ===================================================================
   PHOTOGRAPHIC BANNER

   design.css styles `.banner.on-dark.has-bg` (the near-black ground and
   the light type) but not the image and scrim themselves — on the US
   site those live inside its Banner component. They are here for the
   same reason: only a couple of banners on this site are composed
   scenes rather than cut-out products.

   Use it when the photograph has its OWN background and lighting. A
   studio shot dropped into the right-hand art slot gets letterboxed
   against a flat tint and the composition is wasted.
   =================================================================== */
.gs .banner.has-bg { position: relative; overflow: hidden; isolation: isolate; }
.gs .banner .banner-bg {
  position: absolute; inset: 0; width: 100%; height: 100%;
  max-width: none; object-fit: cover; border-radius: 0; z-index: -2;
}
/* Strongest on the left where the copy sits, fading out so the lit
   subject in the middle of the shot keeps its contrast. The headline is
   over the darkest part of the frame, but "darkest part" depends on the
   crop at each viewport width — this guarantees contrast rather than
   trusting the photograph. */
.gs .banner .banner-scrim {
  position: absolute; inset: 0; z-index: -1;
  background: linear-gradient(100deg,
    rgba(10, 12, 13, .92) 0%,
    rgba(10, 12, 13, .78) 28%,
    rgba(10, 12, 13, .35) 55%,
    rgba(10, 12, 13, .15) 100%);
}
.gs .banner.has-bg .banner-body { position: relative; max-width: 34rem; }
/* On a phone the banner is nearly square while the source is a wide
   letterbox, so the subject crops out of frame entirely. Stack instead:
   a near-solid scrim with the copy over the top. */
@media (max-width: 720px) {
  .gs .banner .banner-scrim {
    background: linear-gradient(180deg,
      rgba(10, 12, 13, .72) 0%,
      rgba(10, 12, 13, .88) 60%,
      rgba(10, 12, 13, .94) 100%);
  }
}


/* ===================================================================
   PER-PRINTER PRODUCT PAGE  (/printers/r1, /printers/r2-pro, …)

   Ported from the US site's pages/printers/[id].astro, which is where
   this design was worked out. Structure, top to bottom: sticky sub-nav,
   hero with the character line and a four-figure stat row, three claims,
   a six-photograph feature essay, a six-shot detail grid, specs, the
   matched shaker and press, CTA.

   The photo essay is the point of the page: it is what stops a product
   page being a datasheet. Do not "simplify" it back to a spec table.
   =================================================================== */

/* A photograph from the pp-* library. build.mjs emits a <picture> that
   picks 700w on phones and 1400w above; the ratio crops with object-fit
   rather than by re-cutting the file, so one photo can run 3:2 in the
   essay and 4:3 in the detail grid. */
.gs .photo {
  display: block; overflow: hidden;
  border-radius: var(--r-card); box-shadow: var(--shadow-card);
  background: var(--bg-alt);
}
.gs .photo img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* ---- sticky sub-nav ----
   Sits under the main nav and follows the reader down the page. Same
   frosted treatment as the main bar so the two read as one piece of
   chrome. It stacks below both the announcement bar and the nav. */
.gs .pnav {
  /* max() because --nav-shift clears the nav's 1px bottom border too; without
     the clamp this bar pins one pixel above the window when the nav hides. */
  position: sticky; top: max(0px, calc(var(--nav-h) + var(--nav-shift, 0px))); z-index: 90;
  background: rgba(255, 255, 255, .78);
  backdrop-filter: saturate(180%) blur(20px);
  -webkit-backdrop-filter: saturate(180%) blur(20px);
  border-bottom: 1px solid rgba(20, 24, 26, .07);
}
.gs .pnav-in { display: flex; align-items: center; gap: 24px; height: 54px; }
.gs .pnav-name { font-weight: 600; font-size: 15px; letter-spacing: var(--track-heading); }
.gs .pnav-links { display: flex; gap: 4px; list-style: none; margin: 0 auto 0 8px; padding: 0; }
.gs .pnav-links a {
  display: inline-block; padding: 7px 12px; border-radius: var(--r-chip);
  font-size: 14px; font-weight: 500; color: var(--ink-soft); transition: .18s;
}
.gs .pnav-links a:hover { color: var(--ink); background: rgba(60, 64, 67, .07); }
.gs .pnav-cta { padding: 8px 18px; font-size: 14px; }
/* Anchor targets land under both bars rather than behind them. */
.gs .pp-section { scroll-margin-top: calc(var(--nav-h) + 54px + 12px); }

/* ---- hero ----
   Contained and light. The lineup page opens dark; this one is the
   machine on a white stage, which is what lets a cut-out PNG with the
   product's own shadow read as photography rather than clip-art. */
.gs .phero {
  display: grid;
  grid-template-columns: 0.92fr 1.08fr;
  grid-template-areas: 'copy art' 'stats stats';
  align-items: center;
  gap: clamp(20px, 3vw, 40px) clamp(24px, 4vw, 64px);
  padding: clamp(32px, 4.5vw, 64px) clamp(28px, 4.5vw, 64px) clamp(24px, 3vw, 40px);
  border-radius: var(--r-banner);
  background: var(--bg-alt);
  box-shadow: inset 0 0 0 1px var(--line-soft);
}
.gs .phero-copy { grid-area: copy; }
.gs .phero-copy h1 { margin: 14px 0 0; text-wrap: balance; }
/* The character line. Bigger than the lede, smaller than the name, and
   it says who the machine is for before any number does. */
.gs .phero-char {
  font-size: clamp(20px, 2.1vw, 27px); line-height: 1.25; font-weight: 500;
  letter-spacing: var(--track-heading); margin: 14px 0 0; color: var(--ink);
}
.gs .phero-copy .lede { margin: 14px 0 0; max-width: 30rem; }
.gs .phero-actions { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 30px; }
.gs .phero-art { grid-area: art; position: relative; display: grid; place-items: center; isolation: isolate; }
.gs .phero-art::before {
  content: ''; position: absolute; z-index: -1;
  width: min(120%, 640px); aspect-ratio: 1.6 / 1; border-radius: 50%;
  background: radial-gradient(ellipse at center, rgba(20, 24, 26, .16), transparent 68%);
  filter: blur(54px); pointer-events: none;
}
.gs .phero-art img {
  width: 100%; max-width: 620px; max-height: 460px; height: auto;
  object-fit: contain; filter: var(--shadow-product-lg);
}
.gs .phero-stats {
  grid-area: stats;
  display: grid; grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 0 clamp(16px, 2.4vw, 36px);
  margin: 0; padding-top: 22px; border-top: 1px solid var(--line);
}
.gs .phero-stats dd {
  margin: 0; font-size: clamp(24px, 2.6vw, 34px); font-weight: 700;
  line-height: 1; letter-spacing: var(--track-display); font-variant-numeric: tabular-nums;
}
.gs .phero-stats dt {
  margin-top: 8px; font-size: 12px; font-weight: 500;
  letter-spacing: var(--track-eyebrow); text-transform: uppercase; color: var(--ink-faint);
}

/* ---- claims ---- Same rule as the homepage pillars: statements, not cards. */
.gs .claims {
  list-style: none; margin: 0; padding: 0;
  display: grid; grid-template-columns: repeat(3, 1fr); gap: clamp(28px, 3.5vw, 56px);
}
.gs .claims li { border-top: 1px solid var(--accent); padding-top: 22px; }
.gs .claims h2 { font-size: clamp(21px, 2vw, 28px); line-height: 1.16; margin: 0 0 10px; text-wrap: balance; }
.gs .claims p { font-size: 15.5px; line-height: 1.62; color: var(--ink-soft); margin: 0; }

/* ---- feature essay ----
   Photo and copy, alternating. The photo is the larger column on
   purpose. `.essay .feat` deliberately outranks the `.gs .feat` used by
   the generic /product page, which is a different layout. */
.gs .essay { display: grid; gap: clamp(40px, 5vw, 80px); }
.gs .essay .feat {
  display: grid; grid-template-columns: 1.15fr 0.85fr;
  align-items: center; gap: clamp(24px, 4vw, 64px);
}
.gs .essay .feat.is-flip { direction: rtl; }
.gs .essay .feat.is-flip > * { direction: ltr; }
.gs .feat-copy h3 {
  font-size: clamp(24px, 2.8vw, 36px); line-height: 1.1;
  letter-spacing: var(--track-display); margin: 12px 0 14px; text-wrap: balance;
}
.gs .feat-copy p { font-size: clamp(15.5px, 1.2vw, 17px); line-height: 1.6; color: var(--ink-soft); margin: 0; max-width: 30rem; }

/* ---- detail grid ---- */
.gs .details {
  list-style: none; margin: 0; padding: 0;
  display: grid; grid-template-columns: repeat(3, 1fr); gap: clamp(16px, 2vw, 28px);
}
.gs .details li span { display: block; margin-top: 12px; font-size: 14px; font-weight: 500; color: var(--ink-soft); }

/* ---- specs ---- */
.gs .specs { display: grid; grid-template-columns: 1fr 1fr; gap: 0 clamp(32px, 5vw, 80px); max-width: 1000px; }

/* ---- pairs ----
   Every printer pairs with two shakers and two presses, so this is
   always four cards. On the shared 3-up mosaic that left one orphaned on
   a second row; four across keeps the set together. */
.gs .pairs { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: var(--gap); }
.gs .pairs .card { min-height: 0; }
.gs .pairs .card .card-media img { max-height: 170px; }
.gs .pairs .card .card-body h3 { font-size: 17px; }
.gs .pairs-more { margin: clamp(24px, 3vw, 36px) 0 0; }

@media (max-width: 1024px) {
  .gs .details { grid-template-columns: repeat(2, 1fr); }
  .gs .pairs { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 900px) {
  .gs .pnav-links { display: none; }
  .gs .pnav-cta { margin-left: auto; }
  .gs .phero {
    grid-template-columns: 1fr;
    grid-template-areas: 'art' 'copy' 'stats';
    padding: 24px 22px;
  }
  .gs .phero-art img { max-height: 260px; }
  .gs .phero-stats { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 18px clamp(14px, 4vw, 24px); }
  .gs .claims { grid-template-columns: 1fr; gap: 30px; }
  .gs .essay .feat, .gs .essay .feat.is-flip { grid-template-columns: 1fr; direction: ltr; gap: 18px; }
  .gs .specs { grid-template-columns: 1fr; }
}
@media (max-width: 640px) {
  .gs .details { grid-template-columns: 1fr; }
  .gs .pairs { grid-template-columns: 1fr; }
}


/* ===================================================================
   LEGAL PAGES — privacy and terms.

   Unlike every other page, the bodies are whole HTML blobs per language
   rather than data-en/data-vi text nodes, so viText() in build.mjs
   cannot swap them. Both ship and [data-lang] on <html> picks — which
   also means the in-page EN/VI toggle works here, not just the static
   /vi/ tree.
   =================================================================== */
.gs [data-legal="vi"] { display: none; }
html[data-lang="vi"] .gs [data-legal="en"] { display: none; }
html[data-lang="vi"] .gs [data-legal="vi"] { display: block; }

/* Long-form prose, same measure and rhythm as an article. */
.gs .legal-body { max-width: 44rem; margin: 0 auto; }
.gs .legal-body h2 {
  font-size: clamp(21px, 2.4vw, 27px); line-height: 1.2;
  letter-spacing: var(--track-heading); margin: 38px 0 12px;
}
.gs .legal-body h2:first-child { margin-top: 0; }
.gs .legal-body p { font-size: 16.5px; line-height: 1.7; color: var(--ink-soft); margin: 0 0 16px; }
.gs .legal-body ul { margin: 0 0 16px 22px; color: var(--ink-soft); font-size: 16.5px; line-height: 1.7; }
.gs .legal-body li { margin-bottom: 8px; }
.gs .legal-body strong { color: var(--ink); font-weight: 600; }
.gs .legal-body a { color: var(--link); text-decoration: underline; text-underline-offset: 2px; }


/* ===================================================================
   REVEAL ON SCROLL

   The original theme faded and lifted every section into view and that
   one effect is a large part of why it felt considered. Kept, and moved
   onto the US site's data attributes so both codebases mark up motion
   the same way.

   Why the easing matters more than the distance: cubic-bezier(.16,1,.3,1)
   is a heavy decelerate — it covers most of the travel immediately then
   settles. A linear or ease-in-out move over the same 26px reads as a
   slow web page rather than as something arriving. Do not "simplify"
   this to `ease`.

   THREE THINGS THIS MUST NOT DO, all of which are why it is written
   this way:

   1. Hide content that never comes back. The hidden state is gated on
      `html.js-motion`, set by an inline script in <head>. No JS, no
      hiding.
   2. Animate under prefers-reduced-motion. That same inline script
      checks the query, so a reduced-motion visitor never gets the class
      and sees a completely static page.
   3. Re-hide on the way back up. The observer unobserves after the
      first entry.
   =================================================================== */
.js-motion [data-reveal] { opacity: 0; transform: translateY(26px); }
.js-motion [data-reveal].is-in {
  opacity: 1; transform: none;
  transition: opacity .75s cubic-bezier(.16, 1, .3, 1), transform .75s cubic-bezier(.16, 1, .3, 1);
}

/* Staggering a group of siblings — three pillars, a row of cards. The
   delay rides on the TRANSITION, not on an animation, so a section
   already in view on load still resolves in order rather than at once. */
.js-motion [data-reveal-stagger] > * { opacity: 0; transform: translateY(22px); }
.js-motion [data-reveal-stagger].is-in > * {
  opacity: 1; transform: none;
  transition: opacity .7s cubic-bezier(.16, 1, .3, 1), transform .7s cubic-bezier(.16, 1, .3, 1);
}
.js-motion [data-reveal-stagger].is-in > *:nth-child(2) { transition-delay: .09s; }
.js-motion [data-reveal-stagger].is-in > *:nth-child(3) { transition-delay: .18s; }
.js-motion [data-reveal-stagger].is-in > *:nth-child(4) { transition-delay: .27s; }
.js-motion [data-reveal-stagger].is-in > *:nth-child(5) { transition-delay: .36s; }
.js-motion [data-reveal-stagger].is-in > *:nth-child(6) { transition-delay: .45s; }

@media (prefers-reduced-motion: reduce) {
  .js-motion [data-reveal],
  .js-motion [data-reveal].is-in,
  .js-motion [data-reveal-stagger] > *,
  .js-motion [data-reveal-stagger].is-in > * {
    opacity: 1; transform: none; transition: none;
  }
}

/* ============================================================================
   CONSENT BANNER + LEAD POP-UP                        (Vietnam-only components)
   ============================================================================
   Both follow the house rules from design.css: white surface, hairline edge,
   --r-card corners, and --accent used ONLY as a fill. The eyebrow and any tinted
   text take --accent-ink, which is the readable teal.
   ==========================================================================*/

/* ---------------- cookie consent ----------------
   Two purposes, two switches (PDPL Art. 9(4)(a)), and Accept and Decline given
   identical weight — a buried "no" is fineable under Decree 330 as a misleading
   consent instruction, quite apart from being bad manners. */
.gs .consent {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 9000;
  padding: 12px; pointer-events: none;
  transform: translateY(120%); transition: transform .32s cubic-bezier(.2, .7, .3, 1);
}
.gs .consent.is-in { transform: none; }
.gs .consent-in {
  pointer-events: auto;
  max-width: var(--maxw); margin: 0 auto;
  display: flex; align-items: flex-start; gap: 24px; flex-wrap: wrap;
  background: #fff; box-shadow: var(--edge), var(--shadow-lift);
  border-radius: 18px; padding: 18px 20px;
  max-height: calc(100dvh - 24px); overflow-y: auto;
}
.gs .consent-main { flex: 1 1 24rem; min-width: 0; }
.gs .consent-h { margin: 0 0 4px; font-size: 15px; font-weight: 600; color: var(--ink); }
.gs .consent-txt { margin: 0; font-size: 14px; color: var(--ink-soft); line-height: 1.5; }
.gs .consent-txt a { color: var(--link); text-decoration: underline; text-underline-offset: 2px; }

.gs .consent-toggle {
  margin-top: 8px; padding: 0; border: 0; background: none; cursor: pointer;
  font-family: var(--font); font-size: 14px; font-weight: 500; color: var(--link);
  text-decoration: underline; text-underline-offset: 2px;
}
.gs .consent-opts { margin-top: 14px; display: grid; gap: 10px; }
.gs .consent-opt {
  display: flex; gap: 10px; align-items: flex-start; cursor: pointer;
  padding: 10px 12px; border-radius: 12px; background: var(--bg-alt);
}
.gs .consent-opt input {
  margin: 2px 0 0; width: 17px; height: 17px; flex: 0 0 auto;
  accent-color: var(--accent-ink); cursor: pointer;
}
.gs .consent-opt span { font-size: 13px; line-height: 1.45; color: var(--ink-soft); }
.gs .consent-opt strong { display: block; color: var(--ink); font-weight: 600; margin-bottom: 1px; }

.gs .consent-btns { display: flex; gap: 8px; flex: 0 0 auto; flex-wrap: wrap; align-self: center; }
.gs .consent-btns button {
  font-family: var(--font); font-size: 14px; font-weight: 500;
  padding: 10px 20px; border-radius: var(--r-chip); cursor: pointer;
  border: 1px solid transparent; transition: background .2s, border-color .2s;
}
.gs .consent-no, .gs .consent-save { background: transparent; border-color: var(--line); color: var(--ink); }
.gs .consent-no:hover, .gs .consent-save:hover { background: rgba(60, 64, 67, .06); }
.gs .consent-yes { background: var(--accent); color: var(--ink); }
.gs .consent-yes:hover { background: var(--accent-hover); }
@media (max-width: 640px) {
  .gs .consent-in { padding: 16px; gap: 14px; }
  .gs .consent-btns { width: 100%; }
  .gs .consent-btns button { flex: 1 1 auto; }
}

/* ---------------- lead pop-up ---------------- */
html.pu-open, html.pu-open body { overflow: hidden; }

.gs .pu { position: fixed; inset: 0; z-index: 9500; display: grid; place-items: center; padding: 16px; }
.gs .pu-scrim {
  position: absolute; inset: 0; background: rgba(20, 24, 26, .5);
  opacity: 0; transition: opacity .25s;
}
.gs .pu.is-in .pu-scrim { opacity: 1; }

.gs .pu-card {
  position: relative; width: min(30rem, 100%);
  max-height: calc(100dvh - 32px); overflow-y: auto;
  background: #fff; border-radius: var(--r-card);
  box-shadow: var(--edge), var(--shadow-lift);
  padding: 34px 30px 30px;
  opacity: 0; transform: translateY(14px) scale(.98);
  transition: opacity .28s, transform .28s cubic-bezier(.2, .7, .3, 1);
}
.gs .pu.is-in .pu-card { opacity: 1; transform: none; }
.gs .pu-card:focus { outline: none; }
/* Reduced motion: appear, do not fly in. */
.gs .pu-still .pu-card, .gs .pu-still .pu-scrim { transition: none; }
@media (prefers-reduced-motion: reduce) {
  .gs .pu-card, .gs .pu-scrim { transition: none; }
  .gs .pu-card { transform: none; }
}

.gs .pu-x {
  position: absolute; top: 12px; right: 12px;
  width: 34px; height: 34px; border-radius: 50%;
  border: 0; background: transparent; cursor: pointer;
  font-size: 24px; line-height: 1; color: var(--ink-soft);
  transition: background .2s, color .2s;
}
.gs .pu-x:hover { background: rgba(60, 64, 67, .08); color: var(--ink); }

.gs .pu-eyebrow {
  margin: 0 0 10px; font-size: 12px; font-weight: 600;
  letter-spacing: var(--track-eyebrow); text-transform: uppercase;
  color: var(--accent-ink);
}
.gs .pu-head {
  margin: 0 0 10px; font-size: 25px; line-height: 1.22; font-weight: 600;
  letter-spacing: var(--track-heading); color: var(--ink);
}
.gs .pu-sub { margin: 0 0 22px; font-size: 15px; line-height: 1.55; color: var(--ink-soft); }

.gs .pu-form { display: grid; gap: 12px; }
.gs .pu-f { display: grid; gap: 5px; }
.gs .pu-f span { font-size: 13px; font-weight: 500; color: var(--ink-soft); }
.gs .pu-f input {
  font-family: var(--font); font-size: 15px; color: var(--ink);
  padding: 11px 14px; border-radius: 12px;
  border: 1px solid var(--line); background: #fff;
  transition: border-color .2s, box-shadow .2s;
}
.gs .pu-f input:focus {
  outline: none; border-color: var(--accent);
  box-shadow: 0 0 0 3px rgba(73, 191, 188, .25);
}
.gs .pu-err { margin: 0; font-size: 13px; color: #b3261e; }
.gs .pu-cta {
  margin-top: 6px; width: 100%;
  background: var(--accent); color: var(--ink);
  font-size: 15px; font-weight: 600; padding: 13px 24px;
  border-radius: var(--r-chip); border: 1px solid transparent;
  cursor: pointer; text-decoration: none;
  display: inline-flex; align-items: center; justify-content: center;
  font-family: var(--font); transition: background .2s, box-shadow .2s;
}
.gs .pu-cta:hover { background: var(--accent-hover); box-shadow: var(--shadow-lift); }
.gs .pu-cta[disabled] { opacity: .6; cursor: default; box-shadow: none; }

.gs .pu-done { text-align: center; padding: 18px 0 8px; }
.gs .pu-tick {
  width: 52px; height: 52px; margin: 0 auto 16px; border-radius: 50%;
  background: var(--accent); color: var(--ink);
  display: grid; place-items: center; font-size: 26px; font-weight: 700;
}

@media (max-width: 480px) {
  .gs .pu-card { padding: 30px 20px 24px; border-radius: 22px; }
  .gs .pu-head { font-size: 22px; }
}


/* ===================================================================
   VEGA S17 — additions to the product-page vocabulary
   =================================================================== */
/* Grouped spec table: a heading that spans both columns of .specs. */
.gs .specs-grouped .spec-group {
  grid-column: 1 / -1; margin: 28px 0 6px; font-size: 13px; font-weight: 600;
  letter-spacing: var(--track-heading); text-transform: uppercase; color: var(--ink-soft);
}
.gs .specs-grouped .spec-group:first-child { margin-top: 0; }
.gs .spec-note { margin: 22px 0 0; font-size: 14px; color: var(--ink-soft); }

/* FAQ: native <details>, styled as hairline rows. */
.gs .faq { max-width: 52rem; }
.gs .faq-item { border-top: 1px solid var(--line-soft); }
.gs .faq-item:last-child { border-bottom: 1px solid var(--line-soft); }
.gs .faq-item > summary {
  list-style: none; cursor: pointer; padding: 18px 40px 18px 0; position: relative;
  font-size: clamp(16px, 1.3vw, 18px); font-weight: 600; letter-spacing: var(--track-heading);
}
.gs .faq-item > summary::-webkit-details-marker { display: none; }
.gs .faq-item > summary::after {
  content: '+'; position: absolute; right: 6px; top: 50%; transform: translateY(-50%);
  font-size: 22px; font-weight: 400; color: var(--accent-ink); line-height: 1;
}
.gs .faq-item[open] > summary::after { content: '–'; }
.gs .faq-item > p { margin: 0 0 20px; font-size: 16px; line-height: 1.65; color: var(--ink-soft); max-width: 46rem; }

/* ===================================================================
   VEGA S17 REVEAL
   ⚠ The reveal deliberately does NOT reuse the site's own class names. It
   did at first, and `.gs .cta` (a light rounded card with a box-shadow edge)
   bled straight through into the dark footer block — the "white outline"
   Jason reported on 2026-09-07, plus its 88px padding crowding the phone
   layout. Anything generic here is prefixed vr-. — the one full-bleed dark page. A film of the
   machine: seven frame sets scrubbed by scroll position on a canvas.
   Ported from vega-s17/vega-s17-hero-mock; colours mapped onto the
   site's tokens (--accent is the teal), type inherits Be Vietnam Pro.
   Every rule is scoped to .vr so nothing leaks into the catalogue.
   =================================================================== */
.gs .vr {
  --vr-black: #050505; --vr-black-2: #0d0d0e; --vr-hair: rgba(255,255,255,.1);
  --vr-canvas: #f5f4f1; --vr-ink: #231F20;
  --vr-top: var(--nav-h);
  background: var(--vr-black); color: #fff; line-height: 1.6; overflow-x: clip;
}
.gs .vr h1, .gs .vr h2 { letter-spacing: -.02em; line-height: 1.06; font-weight: 700; text-wrap: balance; color: #fff; }
.gs .vr .eyebrow { font-size: 11.5px; font-weight: 600; letter-spacing: .14em; text-transform: uppercase; color: var(--accent); margin: 0; }

/* pinned stages */
.gs .vr .scroller { position: relative; }
.gs .vr .hero-scroller { height: 300vh; }
.gs .vr .beat-scroller { height: 230vh; }
.gs .vr .cure-scroller { height: 320vh; }

/* VERSION B (.vr-auto, SITE.vega.reveal === 'auto'). The film runs on its own
   clock instead of being dragged by the scroll wheel, so the tracks above --
   which exist only to give a scrubbed clip room to play -- become dead
   scrolling: the picture has already finished and the reader is still winding
   the wheel to get out of the section. Jason, 2026-09-11: "there's a huge delay
   in my scroll where I have to scroll a lot during one section before I can move
   to the next." So each section is exactly one screen and the page scrolls
   normally.
   --hold is the one dial: at 0 the sticky stage has no travel and behaves like a
   plain block; 40vh buys roughly half a second of pause on each section before
   the page moves on. These selectors carry one class more than the three above,
   so they win everywhere, the phone media query included. */
.gs .vr.vr-auto { --hold: 0vh; }
.gs .vr.vr-auto .hero-scroller,
.gs .vr.vr-auto .beat-scroller,
.gs .vr.vr-auto .cure-scroller { height: calc(100svh - var(--vr-top) + var(--hold)); }
.gs .vr .stage {
  position: sticky; top: var(--vr-top); height: calc(100svh - var(--vr-top)); min-height: 440px;
  overflow: hidden; background: var(--vr-black);
}
.gs .vr .art { position: absolute; inset: 0; z-index: 0; }
.gs .vr .art canvas { display: block; width: 100%; height: 100%; background: var(--vr-black); }
.gs .vr .scrim-l::before, .gs .vr .scrim-r::before, .gs .vr .scrim-t::before, .gs .vr .scrim-light::before { content: ""; position: absolute; inset: 0; z-index: 1; pointer-events: none; }
.gs .vr .scrim-b::after { content: ""; position: absolute; inset: 0; z-index: 1; pointer-events: none; }
.gs .vr .scrim-l::before { background: linear-gradient(100deg, rgba(5,5,5,.84) 0%, rgba(5,5,5,.5) 26%, rgba(5,5,5,0) 54%); }
.gs .vr .scrim-r::before { background: linear-gradient(260deg, rgba(5,5,5,.84) 0%, rgba(5,5,5,.5) 26%, rgba(5,5,5,0) 54%); }
.gs .vr .scrim-t::before { background: linear-gradient(180deg, rgba(5,5,5,.78) 0%, rgba(5,5,5,.22) 32%, rgba(5,5,5,0) 58%); }
.gs .vr .scrim-b::after { background: linear-gradient(0deg, rgba(5,5,5,.8) 0%, rgba(5,5,5,.26) 28%, rgba(5,5,5,0) 54%); }
.gs .vr .scrim-light::before { background: linear-gradient(100deg, rgba(245,244,241,.95) 0%, rgba(245,244,241,.72) 30%, rgba(245,244,241,0) 60%); }

/* copy inside the picture */
.gs .vr .copy { position: absolute; z-index: 2; display: grid; align-content: start; max-width: min(430px, 42vw); will-change: transform; }
.gs .vr .copy .eyebrow { margin-bottom: 14px; }
.gs .vr .copy h2 { font-size: clamp(26px, 3.5vw, 44px); margin: 0 0 14px; }
.gs .vr .copy .lede { font-size: 16px; font-weight: 300; color: rgba(255,255,255,.82); line-height: 1.5; margin: 0; max-width: none; }
.gs .vr .figure { display: flex; align-items: baseline; gap: 10px; margin-top: 26px; border-top: 1px solid var(--vr-hair); padding-top: 14px; transition: opacity .5s ease, transform .5s ease; }
.gs .vr .figure b { font-size: 32px; font-weight: 600; letter-spacing: -.02em; line-height: 1; color: var(--accent); font-variant-numeric: tabular-nums; }
.gs .vr .figure span { font-size: 12.5px; line-height: 1.35; max-width: 20ch; color: rgba(255,255,255,.62); }
.gs .vr .at-left { left: clamp(26px, 6vw, 86px); top: 50%; transform: translateY(-50%); }
.gs .vr .at-right { right: clamp(26px, 6vw, 86px); top: 50%; transform: translateY(-50%); text-align: right; justify-items: end; }
.gs .vr .at-right .figure { flex-direction: row-reverse; }
.gs .vr .at-right .figure span { text-align: right; }
.gs .vr .at-topcenter { left: 50%; top: clamp(40px, 9vh, 104px); transform: translateX(-50%); text-align: center; justify-items: center; max-width: min(860px, 80vw); }
.gs .vr .on-light .eyebrow { color: var(--accent-ink); }
.gs .vr .on-light h2 { color: var(--vr-ink); }
.gs .vr .on-light .lede { color: #3c4045; }
.gs .vr .on-light .figure { border-top-color: rgba(35,31,32,.22); }
.gs .vr .on-light .figure b { color: var(--accent-ink); }
.gs .vr .on-light .figure span { color: #4a4f54; }

/* hero */
.gs .vr .hero h1 { font-size: clamp(56px, 9vw, 124px); font-weight: 500; line-height: 1.04; letter-spacing: -.03em; margin: 10px 0 22px; }
.gs .vr .hero .tag { font-size: clamp(17px, 2vw, 26px); font-weight: 300; line-height: 1.3; color: rgba(255,255,255,.86); margin: 0; }
.gs .vr .scrollhint { position: absolute; left: 50%; bottom: 18px; transform: translateX(-50%); z-index: 2; font-size: 10.5px; letter-spacing: .18em; text-transform: uppercase; color: rgba(255,255,255,.55); display: flex; flex-direction: column; align-items: center; gap: 7px; transition: opacity .4s; }
.gs .vr .scrollhint i { display: block; width: 1px; height: 26px; background: linear-gradient(var(--accent), transparent); }

/* statements: one line, then the picture alone, then the number */
.gs .vr .statement h2 { font-size: clamp(28px, 4.8vw, 60px); line-height: 1.04; margin: 0; }
.gs .vr .statement .lede { margin-top: 16px; font-size: clamp(15px, 1.6vw, 19px); max-width: 44ch; }
.gs .vr .solo { position: absolute; left: 50%; bottom: clamp(34px, 7vh, 72px); transform: translateX(-50%); z-index: 2; display: flex; align-items: baseline; gap: 12px; justify-content: center; border-top: 1px solid var(--vr-hair); padding-top: 14px; min-width: min(430px, 74vw); opacity: 0; transition: opacity .5s ease, transform .5s ease; margin: 0; }
.gs .vr .solo b { font-size: clamp(30px, 4vw, 42px); font-weight: 600; color: var(--accent); letter-spacing: -.03em; line-height: 1; font-variant-numeric: tabular-nums; }
.gs .vr .solo span { font-size: 13px; color: rgba(255,255,255,.66); }

/* static bands */
.gs .vr .vband { padding: 56px 0; }
.gs .vr .stats { background: var(--vr-black-2); border-top: 1px solid var(--vr-hair); border-bottom: 1px solid var(--vr-hair); }
.gs .vr .statgrid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 30px; }
.gs .vr .stat b { display: block; font-size: clamp(38px, 5vw, 58px); font-weight: 600; letter-spacing: -.03em; line-height: 1; color: #fff; font-variant-numeric: tabular-nums; }
.gs .vr .stat u { display: block; text-decoration: none; font-size: 11.5px; font-weight: 600; letter-spacing: .13em; text-transform: uppercase; color: var(--accent); margin: 9px 0 8px; }
.gs .vr .stat p { font-size: 13.5px; color: rgba(255,255,255,.6); line-height: 1.5; margin: 0; }
.gs .vr .vr-cta { background: var(--vr-ink); text-align: center; padding: 40px 0; }
.gs .vr .ctarow { display: flex; gap: 13px; justify-content: center; flex-wrap: wrap; }
/* the same pair of buttons, sitting above the lockup in the hero so the
   reader never has to reach the foot of a very long page to act */
/* The same two actions above the lockup, so the reader never has to reach the
   foot of a very long page to act — but quiet. At full button weight they beat
   the product name they sit above (Jason, 2026-09-07: "way too dominant ...
   pressing against the other text"). No fill, hairline edge, small type, and
   real air beneath them. */
.gs .vr .ctarow-top { margin-bottom: 44px; gap: 10px; }
.gs .vr .ctarow-top .vbtn,
.gs .vr .ctarow-top .vbtn.ghost {
  background: transparent;
  border: 1px solid rgba(255, 255, 255, .28);
  color: rgba(255, 255, 255, .82);
  font-size: 12.5px; font-weight: 500;
  padding: 7px 16px;
}
.gs .vr .ctarow-top .vbtn:hover,
.gs .vr .ctarow-top .vbtn.ghost:hover {
  background: rgba(255, 255, 255, .1);
  border-color: rgba(255, 255, 255, .55);
  color: #fff;
}
@media (max-width: 820px) { .gs .vr .ctarow-top { margin-bottom: 30px; } }
.gs .vr .vbtn { display: inline-block; background: var(--accent); color: var(--vr-ink); text-decoration: none; font-weight: 600; font-size: 15px; padding: 13px 26px; border-radius: 999px; border: 2px solid var(--accent); transition: background .18s, border-color .18s; white-space: nowrap; }
.gs .vr .vbtn:hover { background: #5ad0cd; border-color: #5ad0cd; }
.gs .vr .vbtn.ghost { background: transparent; color: #fff; border-color: rgba(255,255,255,.42); }
.gs .vr .vbtn.ghost:hover { background: rgba(255,255,255,.1); border-color: #fff; }
.gs .vr .vfoot { background: var(--vr-ink); color: rgba(255,255,255,.44); font-size: 12px; margin: 0; padding: 0 0 44px; text-align: center; }
.gs .vr .vfoot .wrap { display: block; border-top: 1px solid var(--vr-hair); padding-top: 22px; max-width: 760px; line-height: 1.6; }

/* phones: picture on top, words underneath */
@media (max-width: 820px) {
  .gs .vr .hero-scroller { height: 230vh; }
  .gs .vr .beat-scroller { height: 190vh; }
  .gs .vr .cure-scroller { height: 250vh; }
  .gs .vr .hero h1 { font-size: clamp(44px, 13vw, 64px); }
  .gs .vr .stage { display: grid; grid-template-rows: minmax(0, 1fr) auto auto; align-content: stretch; }
  .gs .vr .art { position: relative; inset: auto; height: 100%; min-height: 0; }
  .gs .vr .scrim-l::before, .gs .vr .scrim-r::before, .gs .vr .scrim-light::before { display: none; }
  .gs .vr .scrim-t::before { height: 60%; bottom: auto; }
  .gs .vr .scrim-b::after { display: none; }
  .gs .vr .copy, .gs .vr .solo { position: relative; left: auto; right: auto; top: auto; bottom: auto; transform: none; max-width: none; width: auto; text-align: left; justify-items: start; padding: 24px clamp(20px, 5vw, 26px) 26px; background: var(--vr-black); }
  .gs .vr .at-right .figure { flex-direction: row; }
  .gs .vr .at-right .figure span { text-align: left; }
  .gs .vr .copy.on-light { background: var(--vr-canvas); }
  .gs .vr .copy h2 { font-size: clamp(24px, 6.2vw, 32px); }
  .gs .vr .statement h2 { font-size: clamp(26px, 7vw, 34px); }
  .gs .vr .solo { min-width: 0; justify-content: flex-start; border-top: none; padding-top: 0; opacity: 1; padding-bottom: 30px; }
  .gs .vr .scrollhint { display: none; }
  .gs .vr .statgrid { grid-template-columns: repeat(2, 1fr); gap: 26px; }
}
@media (prefers-reduced-motion: reduce) {
  .gs .vr .hero-scroller, .gs .vr .beat-scroller, .gs .vr .cure-scroller { height: auto; }
  .gs .vr .stage { position: static; height: auto; min-height: 0; display: grid; grid-template-rows: auto auto auto; }
  .gs .vr .art { position: relative; inset: auto; aspect-ratio: 16 / 9; height: auto; }
  .gs .vr .copy, .gs .vr .solo { position: relative; left: auto; right: auto; top: auto; bottom: auto; transform: none; max-width: none; padding: 26px 28px 34px; background: var(--vr-black); text-align: left; justify-items: start; opacity: 1; }
  .gs .vr .copy.on-light { background: var(--vr-canvas); }
  .gs .vr .scrim-l::before, .gs .vr .scrim-r::before, .gs .vr .scrim-t::before, .gs .vr .scrim-light::before, .gs .vr .scrim-b::after { display: none; }
  .gs .vr .scrollhint { display: none; }
  .gs .vr .figure { opacity: 1 !important; transform: none !important; }
  .gs .vr .solo { min-width: 0; border-top: none; padding-top: 0; justify-content: flex-start; }
}

/* ===================================================================
   VEGA S17 — INTERACTIVE 3D  (product page hero, full screen, AR)
   Markup from vega3dStage() in build.mjs, behaviour in assets/vega-3d.js.
   Three states on .v3d: the photograph (default) with one "See it in 3D"
   button; .is-3d, the live model with dots, chips and a toolbar; .is-full,
   the same model over the whole viewport. Everything is scoped under .v3d
   so the Prestige pages, which still carry a photograph, are untouched.
   =================================================================== */
.gs .phero-art-3d { display: block; place-items: unset; }
.gs .v3d {
  --v3d-h: clamp(300px, 34vw, 520px);
  position: relative; width: 100%; max-width: 680px; margin: 0 auto;
  display: grid; grid-template-rows: 1fr auto; gap: 10px; font-family: var(--font);
}
.gs .v3d-stage {
  position: relative; height: var(--v3d-h); border-radius: 22px; overflow: hidden;
  isolation: isolate; touch-action: pan-y;
}
.gs .v3d-canvas { position: absolute; inset: 0; width: 100% !important; height: 100% !important; display: block; outline: none; cursor: grab; }
.gs .v3d-canvas:active { cursor: grabbing; }
.gs .v3d-poster {
  position: absolute; inset: 0; width: 100%; height: 100%; max-height: none; max-width: none; object-fit: contain;
  filter: var(--shadow-product-lg); transition: opacity .7s ease; z-index: 2; pointer-events: none;
}
.gs .v3d-poster.is-hidden { opacity: 0; }

/* the one button on the photograph */
.gs .v3d-open {
  position: absolute; right: 14px; bottom: 14px; z-index: 5;
  display: inline-flex; align-items: center; gap: 8px;
  font-family: inherit; font-size: 13.5px; font-weight: 500; color: var(--ink);
  background: rgba(255, 255, 255, .94); border: 1px solid var(--line-soft); border-radius: 999px;
  padding: 9px 16px 9px 12px; cursor: pointer; box-shadow: var(--shadow-card);
  transition: transform .18s ease, box-shadow .18s ease, border-color .18s ease;
}
.gs .v3d-open svg { width: 18px; height: 18px; fill: none; stroke: var(--accent-ink); stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; }
.gs .v3d-open:hover { transform: translateY(-1px); box-shadow: var(--shadow-lift); border-color: var(--accent); }
.gs .v3d-open:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 2px; }
.gs .v3d.is-3d .v3d-open, .gs .v3d-off .v3d-open { display: none; }

.gs .v3d-progress { position: absolute; left: 0; right: 0; top: 0; height: 2px; z-index: 6; background: var(--line-soft); --p: 0; }
.gs .v3d-progress::after { content: ''; position: absolute; inset: 0; transform-origin: left; transform: scaleX(var(--p)); background: var(--accent); transition: transform .2s linear; }

/* nothing but the photograph until the model is asked for */
.gs .v3d:not(.is-3d) .v3d-tools, .gs .v3d:not(.is-3d) .v3d-hs-layer, .gs .v3d:not(.is-3d) .v3d-chips,
.gs .v3d:not(.is-3d) .v3d-hint, .gs .v3d:not(.is-3d) .v3d-canvas, .gs .v3d:not(.is-3d) .v3d-progress { display: none; }
.gs .v3d:not(.v3d-ready) .v3d-hs, .gs .v3d:not(.v3d-ready) .v3d-hint { display: none; }
.gs .v3d:not(.v3d-ready) .v3d-chips { visibility: hidden; }

/* hotspot dots, projected onto the model each frame */
.gs .v3d-hs-layer { position: absolute; inset: 0; z-index: 4; pointer-events: none; }
.gs .v3d-hs {
  position: absolute; left: 0; top: 0; margin: -14px 0 0 -14px; width: 28px; height: 28px;
  border: 0; background: transparent; padding: 0; cursor: pointer; pointer-events: auto;
  opacity: 0; transition: opacity .25s ease; will-change: transform;
}
.gs .v3d-hs-dot {
  position: absolute; inset: 6px; border-radius: 50%; background: #fff;
  box-shadow: 0 0 0 2px var(--accent), 0 2px 10px rgba(20, 24, 26, .35); transition: transform .18s ease, background .18s ease;
}
.gs .v3d-hs::before { content: ''; position: absolute; inset: 0; border-radius: 50%; border: 1.5px solid rgba(73, 191, 188, .7); animation: v3d-pulse 2.4s ease-out infinite; }
@keyframes v3d-pulse { 0% { transform: scale(.55); opacity: .9; } 70% { transform: scale(1.35); opacity: 0; } 100% { opacity: 0; } }
.gs .v3d-hs:hover .v3d-hs-dot, .gs .v3d-hs.is-on .v3d-hs-dot { background: var(--accent); transform: scale(1.15); }
.gs .v3d-hs.is-on::before { animation: none; opacity: 0; }
.gs .v3d-hs:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 2px; border-radius: 50%; }
.gs .v3d-hs-label {
  position: absolute; left: 34px; top: 50%; transform: translateY(-50%); white-space: nowrap;
  font-size: 12px; font-weight: 500; letter-spacing: .01em; color: var(--ink);
  background: rgba(255, 255, 255, .92); padding: 4px 9px; border-radius: 999px; box-shadow: var(--edge), 0 2px 8px rgba(20, 24, 26, .12);
  opacity: 0; transition: opacity .18s ease; pointer-events: none;
}
.gs .v3d-hs:hover .v3d-hs-label, .gs .v3d-hs.is-on .v3d-hs-label, .gs .v3d-hs:focus-visible .v3d-hs-label { opacity: 1; }
.gs .has-active .v3d-hs:not(.is-on)::before { animation: none; opacity: 0; }
.gs .has-active .v3d-hs:not(.is-on) .v3d-hs-dot { opacity: .55; }

/* toolbar: reset (when a part is open) · AR · full screen · close */
.gs .v3d-tools { position: absolute; top: 10px; right: 10px; z-index: 5; display: flex; gap: 6px; align-items: center; }
.gs .v3d-tools button, .gs .v3d-ar-end {
  display: inline-flex; align-items: center; gap: 7px; height: 34px; padding: 0 12px 0 10px;
  font-family: inherit; font-size: 12.5px; font-weight: 500; letter-spacing: .01em; color: var(--ink);
  background: rgba(255, 255, 255, .92); border: 1px solid var(--line-soft); border-radius: 999px; cursor: pointer;
  backdrop-filter: blur(6px); transition: background .15s, border-color .15s;
}
.gs .v3d-tools button svg { width: 16px; height: 16px; flex: none; fill: none; stroke: currentColor; stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; }
.gs .v3d-tools button:hover { border-color: var(--accent); background: #fff; }
.gs .v3d-tools button:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 2px; }
.gs .v3d-tools .v3d-close { width: 34px; padding: 0; justify-content: center; }
.gs .v3d-tools .v3d-home { display: none; }
.gs .has-active .v3d-tools .v3d-home { display: inline-flex; }
.gs .v3d-ar-end { display: none; }
.gs .v3d-hint {
  position: absolute; left: 50%; bottom: 10px; transform: translateX(-50%); z-index: 5; margin: 0;
  font-size: 11.5px; letter-spacing: .04em; color: var(--ink-faint); background: rgba(255, 255, 255, .8);
  padding: 4px 10px; border-radius: 999px; pointer-events: none; white-space: nowrap;
}
.gs .has-active .v3d-hint { display: none; }

/* the chip legend under the stage — every part, without hunting for a dot */
.gs .v3d-chips { display: flex; gap: 6px; flex-wrap: wrap; justify-content: center; min-height: 34px; }
.gs .v3d-chips button {
  font-family: inherit; font-size: 12.5px; font-weight: 500; color: var(--ink-soft);
  background: transparent; border: 1px solid var(--line); border-radius: 999px; padding: 6px 12px; cursor: pointer;
  transition: color .15s, border-color .15s, background .15s;
}
.gs .v3d-chips button:hover { color: var(--ink); border-color: #c4c7c5; }
.gs .v3d-chips button[aria-pressed="true"] { color: var(--accent-ink); border-color: var(--accent); background: var(--accent-tint); }
.gs .v3d-chips button:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 2px; }

/* the card for the open part — a strip along the foot of the stage, so the
   part stays in view above it; a floating card when full screen */
.gs .v3d-card {
  position: absolute; left: 0; right: 0; bottom: 0; z-index: 6;
  background: rgba(255, 255, 255, .80);
  backdrop-filter: blur(22px) saturate(1.3); -webkit-backdrop-filter: blur(22px) saturate(1.3);
  border-top: 1px solid rgba(255, 255, 255, .5); padding: 12px 44px 12px 18px; animation: v3d-in .32s ease both;
  transition: transform .34s cubic-bezier(.4, 0, .6, 1), opacity .3s ease;
}
/* leaving: slides down out of the stage and fades, then hidden */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .gs .v3d-card { background: rgba(255, 255, 255, .96); }
}
.gs .v3d-card.is-leaving { animation: none; transform: translateY(110%); opacity: 0; pointer-events: none; }
@keyframes v3d-in { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }
.gs .v3d-card .eyebrow { font-size: 10.5px; display: block; }
.gs .v3d-card h3 { font-size: 15.5px; line-height: 1.25; margin: 3px 0 5px; letter-spacing: var(--track-heading); text-wrap: balance; }
.gs .v3d-card p { font-size: 12.5px; line-height: 1.5; color: var(--ink-soft); margin: 0; }
.gs .v3d-more { display: inline-block; margin-top: 6px; font-size: 12.5px; font-weight: 500; color: var(--link); text-decoration: none; }
.gs .v3d-more::after { content: ' ↓'; }
.gs .v3d-more:hover { text-decoration: underline; }
.gs .v3d-card-close, .gs .v3d-qr-close {
  position: absolute; top: 8px; right: 8px; width: 30px; height: 30px; border-radius: 50%; border: 0;
  background: var(--bg-alt); color: var(--ink-soft); font-size: 20px; line-height: 1; cursor: pointer;
}
.gs .v3d-card-close:hover, .gs .v3d-qr-close:hover { background: var(--line-soft); color: var(--ink); }
.gs .v3d-qr[hidden], .gs .v3d-card[hidden], .gs .v3d-card [hidden] { display: none; }

/* desktop AR: the QR popover */
.gs .v3d-qr {
  position: absolute; inset: auto 12px 12px auto; z-index: 7; width: min(420px, calc(100% - 24px));
  display: grid; grid-template-columns: 132px 1fr; gap: 16px; align-items: center;
  background: #fff; border-radius: 18px; padding: 18px; box-shadow: var(--shadow-lift), var(--edge); animation: v3d-in .32s ease both;
}
.gs .v3d-qr-img { width: 132px; height: 132px; }
.gs .v3d-qr-img svg { width: 100%; height: 100%; display: block; }
.gs .v3d-qr h3 { font-size: 15.5px; margin: 0 0 6px; line-height: 1.25; }
.gs .v3d-qr p { font-size: 13px; line-height: 1.5; color: var(--ink-soft); margin: 0; padding-right: 20px; }

/* full screen */
.gs .v3d.is-full {
  position: fixed; inset: 0; z-index: 1200; max-width: none; margin: 0; gap: 0;
  grid-template-rows: 1fr auto; background: var(--bg); background-image: var(--brand-wash); --v3d-h: 100%;
}
.gs .v3d.is-full .v3d-stage { height: auto; border-radius: 0; min-height: 0; }
.gs .v3d.is-full .v3d-chips {
  padding: 12px 16px calc(12px + env(safe-area-inset-bottom, 0px)); background: rgba(255, 255, 255, .82);
  border-top: 1px solid var(--line-soft); backdrop-filter: blur(8px);
}
.gs .v3d.is-full .v3d-card {
  left: 16px; right: auto; bottom: 16px; width: min(380px, calc(100% - 32px));
  border: 0; border-radius: 18px; padding: 18px 20px 16px; box-shadow: var(--shadow-lift), var(--edge);
}
.gs .v3d.is-full .v3d-card, .gs .v3d.is-full .v3d-chips { background: rgba(255, 255, 255, .82); }
.gs .v3d.is-full .v3d-card h3 { font-size: 17px; }
.gs .v3d.is-full .v3d-card p { font-size: 13.5px; }
.gs .v3d.is-full .v3d-tools { top: max(12px, env(safe-area-inset-top, 0px)); right: 12px; }
.gs .v3d.is-full .v3d-hint { bottom: 14px; }
.v3d-lock { overflow: hidden; }

/* in-page AR (Android): the canvas is the camera view, the chrome hides */
.gs .v3d.is-ar .v3d-tools, .gs .v3d.is-ar .v3d-hs-layer, .gs .v3d.is-ar .v3d-card, .gs .v3d.is-ar .v3d-chips, .gs .v3d.is-ar .v3d-hint { display: none; }
.gs .v3d.is-ar .v3d-ar-end { display: inline-flex; position: fixed; left: 50%; bottom: 28px; transform: translateX(-50%); z-index: 2000; }

.gs .pnav-3d::before { content: '◈ '; color: var(--accent-ink); }

@media (max-width: 900px) {
  .gs .v3d { --v3d-h: min(64vw, 380px); max-width: none; }
  .gs .v3d-open { right: 10px; bottom: 10px; font-size: 13px; padding: 8px 14px 8px 11px; }
  .gs .v3d-tools { top: 8px; right: 8px; gap: 5px; }
  .gs .v3d-tools button { width: 34px; padding: 0; justify-content: center; }
  .gs .v3d-tools button span { display: none; }
  .gs .v3d-hs { width: 34px; height: 34px; margin: -17px 0 0 -17px; }
  .gs .v3d-hs-dot { inset: 8px; }
  .gs .v3d-hs-label { display: none; }
  /* phones: the stage is small, so the card is a bottom sheet over the page */
  .gs .v3d-card {
    position: fixed; left: 0; right: 0; bottom: 0; z-index: 1300; border-radius: 18px 18px 0 0;
    padding: 14px 44px calc(16px + env(safe-area-inset-bottom, 0px)) 18px; box-shadow: 0 -12px 40px rgba(20, 24, 26, .18);
  }
  .gs .v3d-card p { font-size: 13px; }
  .gs .v3d-card.is-leaving { transform: translateY(110%); }
  /* full screen on a phone: the card lives inside the stage, above the chip
     row, and scrolls if the text is longer than the space */
  .gs .v3d.is-full .v3d-card {
    position: absolute; left: 10px; right: 10px; bottom: 10px; width: auto; z-index: 6;
    border-radius: 16px; max-height: 46%; overflow-y: auto; -webkit-overflow-scrolling: touch;
    padding: 14px 40px 14px 16px; box-shadow: var(--shadow-lift), var(--edge);
  }
  .gs .v3d.is-full .v3d-card-close { position: sticky; float: right; top: 0; }
  .gs .v3d-qr { grid-template-columns: 1fr; inset: auto 8px 8px 8px; width: auto; }
  .gs .v3d-chips { justify-content: flex-start; overflow-x: auto; flex-wrap: nowrap; padding-bottom: 2px; scrollbar-width: none; }
  .gs .v3d-chips::-webkit-scrollbar { display: none; }
  .gs .v3d-chips button { flex: 0 0 auto; }
  .gs .v3d.is-full .v3d-chips { flex-wrap: nowrap; }
  .gs .v3d-hint { display: none; }
  .gs .v3d.is-full .v3d-hint { display: block; }
}
@media (prefers-reduced-motion: reduce) {
  .gs .v3d-hs::before { animation: none; opacity: 0; }
  .gs .v3d-card, .gs .v3d-qr { animation: none; }
}
