/* ==========================================================================
   Vivopage UI · v1 · motion.css  —  OPTIONAL motion layer
   Pairs with motion.js. Both are opt-in: if this file (and motion.js) is not
   loaded, or an element carries none of the data-vp-* motion attributes, the
   page looks EXACTLY as it does without motion. Nothing here is loaded by the
   six demos, /s/*, /work or /ui — the golden rule holds.

   Rules (non-negotiable): only transform/opacity are animated (never a property
   that reflows); will-change lives only while an element is animating; every
   effect goes still under prefers-reduced-motion and under the test switch
   [data-vp-motion="off"]; UI micro-motion stays <=300ms, section entrances a
   little longer (framer does the same); curves are custom ease-out, never
   ease-in. This file NEVER hides content on its own: the pre-reveal state only
   applies once motion.js has vouched for the page with [data-vp-motion-ready]
   on <html>, so a page whose JS never runs shows everything in its final state.

   Sections
     01 Vars & the ready gate      08 Scrim (legibility over a moving picture)
     02 Reveal + stagger           09 Split (word / letter / mask)
     03 Parallax                   10 Marquee
     04 Count / tilt (JS-driven)   11 Reveal-mask (clip-path wipe)
     05 Gradient + sticky          12 Sticky-stack
     06 Reduced motion, 01-05      13 Reduced motion, 07-12
     07 Background parallax
   ========================================================================== */

/* ==========================================================================
   01 · VARS & THE READY GATE
   Own tokens with fallbacks so this file stands alone even without ui.css.
   ========================================================================== */
:root {
  --vpm-ease: var(--vp-ease-out, cubic-bezier(.23, 1, .32, 1));
  --vpm-appear-dur: 560ms;   /* section entrance — deliberately > UI 300ms */
  --vpm-appear-dist: 12px;   /* travel of a reveal */
  --vpm-tilt-dur: 220ms;     /* pointer tilt — UI speed, <=300ms */
  --vpm-gradient-dur: 16s;   /* the slow breath */
  --vpm-gradient-a: rgba(120, 130, 255, .22);
  --vpm-gradient-b: rgba(255, 140, 190, .16);
  --vpm-sticky-top: 88px;
}

/* Everything below the gate only bites once motion.js adds the attribute.
   No gate → no hiding → graceful degradation. */

/* ==========================================================================
   02 · REVEAL  (data-vp-appear)  +  STAGGER  (data-vp-stagger)
   `appear` is our reveal — ui.css already owns [data-vp-reveal], so the motion
   layer uses its own name and never fights that rule. Direction via the value.
   ========================================================================== */
[data-vp-motion-ready] [data-vp-appear] {
  opacity: 0;
  transition:
    opacity var(--vpm-d, var(--vpm-appear-dur)) var(--vpm-ease),
    transform var(--vpm-d, var(--vpm-appear-dur)) var(--vpm-ease);
  transition-delay: var(--vpm-delay, 0ms);
}
[data-vp-motion-ready] [data-vp-appear="up"],
[data-vp-motion-ready] [data-vp-appear=""]      { transform: translateY(var(--vpm-dist, var(--vpm-appear-dist))); }
[data-vp-motion-ready] [data-vp-appear="down"]  { transform: translateY(calc(-1 * var(--vpm-dist, var(--vpm-appear-dist)))); }
[data-vp-motion-ready] [data-vp-appear="left"]  { transform: translateX(var(--vpm-dist, var(--vpm-appear-dist))); }
[data-vp-motion-ready] [data-vp-appear="right"] { transform: translateX(calc(-1 * var(--vpm-dist, var(--vpm-appear-dist)))); }
[data-vp-motion-ready] [data-vp-appear="scale"] { transform: scale(.94); }
[data-vp-motion-ready] [data-vp-appear="fade"]  { transform: none; }

/* will-change ONLY while pending — dropped the moment it lands */
[data-vp-motion-ready] [data-vp-appear]:not([data-vp-in]) { will-change: transform, opacity; }

/* the landed state — shared by reveal and every staggered child */
[data-vp-motion-ready] [data-vp-appear][data-vp-in] {
  opacity: 1;
  transform: none;
}

/* ==========================================================================
   03 · PARALLAX  (data-vp-parallax)
   motion.js writes the clamped offset into --vpm-px on each rAF tick.
   ========================================================================== */
[data-vp-motion-ready] [data-vp-parallax] { transform: translate3d(0, var(--vpm-px, 0px), 0); }
[data-vp-motion-ready] [data-vp-parallax][data-vp-px] { will-change: transform; }

/* ==========================================================================
   04 · COUNT (data-vp-count) & TILT (data-vp-tilt)
   Count is pure text work in JS — no CSS needed beyond tabular digits so the
   width does not jitter as it ticks.
   ========================================================================== */
[data-vp-count] { font-variant-numeric: tabular-nums; }

[data-vp-tilt] { transition: transform var(--vpm-tilt-dur) var(--vpm-ease); transform-style: preserve-3d; }
[data-vp-tilt][data-vp-tilting] { will-change: transform; transition-duration: 60ms; }

/* ==========================================================================
   05 · GRADIENT (data-vp-gradient)  +  STICKY-SCROLL (data-vp-sticky)
   Both are CSS-only, so they hold even if motion.js is skipped — but they still
   fall still under §06. Gradient breathes a soft blob via transform/opacity on
   a pseudo-element (never background-position, which we treat as off-limits).
   ========================================================================== */
/* overflow:clip keeps the oversized, breathing blob (inset:-20% then scale 1.14)
   from spilling past a full-bleed host and forcing a page-wide horizontal scroll.
   clip (not hidden) adds no scroll container, so no sticky/anchor side effects. */
[data-vp-gradient] { position: relative; isolation: isolate; overflow: clip; }
[data-vp-gradient]::before {
  content: ""; position: absolute; inset: -20%; z-index: -1; pointer-events: none;
  background:
    radial-gradient(42% 42% at 30% 30%, var(--vpm-gradient-a) 0%, transparent 70%),
    radial-gradient(46% 46% at 72% 66%, var(--vpm-gradient-b) 0%, transparent 72%);
  animation: vpm-breathe var(--vpm-gradient-dur) var(--vp-ease-in-out, cubic-bezier(.77, 0, .175, 1)) infinite alternate;
  will-change: transform, opacity;
}
@keyframes vpm-breathe {
  from { transform: scale(1)    translate3d(0, 0, 0); opacity: .75; }
  to   { transform: scale(1.14) translate3d(2%, -3%, 0); opacity: 1; }
}

[data-vp-sticky] { position: -webkit-sticky; position: sticky; top: var(--vpm-sticky-top); align-self: start; }

/* ==========================================================================
   06 · REDUCED MOTION + TEST SWITCH — one calm end-state, nothing left hidden
   Whoever asks for less motion (OS setting) or forces stillness for a test
   ([data-vp-motion="off"] on any ancestor) gets every element in its FINAL
   visible state, no transitions, no loops.
   ========================================================================== */
@media (prefers-reduced-motion: reduce) {
  [data-vp-appear] { opacity: 1 !important; transform: none !important; transition: none !important; }
  [data-vp-parallax] { transform: none !important; }
  [data-vp-tilt] { transition: none !important; }
  [data-vp-gradient]::before { animation: none !important; }
}
[data-vp-motion="off"] [data-vp-appear],
[data-vp-motion="off"][data-vp-appear] { opacity: 1 !important; transform: none !important; transition: none !important; }
[data-vp-motion="off"] [data-vp-parallax] { transform: none !important; }
[data-vp-motion="off"] [data-vp-gradient]::before { animation: none !important; }

/* ==========================================================================
   07 · BACKGROUND PARALLAX  (data-vp-parallax-bg)
   The reference (fuel.framer.website) moves its hero picture at 1/10 of the
   scroll — measured, -50px per 500px of page. §03 can only move an ELEMENT;
   this moves the PICTURE BEHIND a block, which is the shot that reads as
   cinema. There are two of those pictures and therefore two paths, and
   motion.js decides which one a node is on:
     A · the block paints its photograph with a REAL element inside a clipping
         layer — every section with a background, both full-bleed heroes. This
         is the common case and the one that matters; it is the second block
         of rules below.
     B · the picture is the node's own CSS background-image, which in this
         library means the hero's veil. Unchanged since it was written: the
         image is lifted onto an ::before over-inset by exactly the clamp
         (--vpm-bgover, from data-vp-parallax-bg-max), so travel never
         uncovers an edge, and the host clips. `clip`, not `hidden`: it adds
         no scroll container, so a sticky child or an anchor jump inside keeps
         working. A page can also hand an image in with --vpm-bg-image.
   Neither path asks a block for new markup.
   ========================================================================== */
[data-vp-motion-ready] [data-vp-parallax-bg]:not([data-vpm-bg-media]) { isolation: isolate; overflow: clip; }
[data-vp-motion-ready] [data-vp-parallax-bg]:not([data-vpm-bg-media])::before {
  content: ""; position: absolute; z-index: -1; pointer-events: none;
  left: 0; right: 0;
  top: calc(-1 * var(--vpm-bgover, 120px));
  bottom: calc(-1 * var(--vpm-bgover, 120px));
  background-image: var(--vpm-bg-image, none);
  background-size: var(--vpm-bg-size, cover);
  background-position: var(--vpm-bg-position, center);
  background-repeat: no-repeat;
  transform: translate3d(0, var(--vpm-bgpx, 0px), 0);
}
[data-vp-motion-ready] [data-vp-parallax-bg][data-vp-bgpx]:not([data-vpm-bg-media])::before { will-change: transform; }

/* THE PICTURE THE BLOCK ITSELF PAINTS — the path that makes this premium.
   A section photograph in this library is a real element inside an absolutely
   positioned, clipping layer (`.vp-b-bg` on any section, the figure on the two
   full-bleed heroes), never a CSS `background-image`. So over exactly the
   sections this effect exists for, the gradient layer above had nothing to
   carry and the control was dead. motion.js marks the layer's media
   (`data-vpm-bg-shot`) and its host (`data-vpm-bg-media`), and the picture is
   what travels. Three consequences worth stating:
     · the veil that keeps the copy readable does NOT move — it is measured
       against the section, so legibility cannot drift with the shot;
     · the overhang is a ZOOM, not an inset, so nothing here rewrites the
       sizing a block already wrote for its own media. motion.js keeps
       --vpm-bgzoom at exactly the travel the factor can produce at this size,
       so the shot never runs short of picture;
     · the pseudo-element is untouched in this mode, which settles the
       documented clash: `gradient` and `bgParallax` can now share a section.
   The host is not clipped again either: the layer already clips, and a second
   clip would catch a sticky or fixed child that has nothing to do with the
   photograph. */
[data-vp-motion-ready] [data-vpm-bg-shot] {
  transform: translate3d(0, var(--vpm-bgpx, 0px), 0) scale(var(--vpm-bgzoom, 1));
  will-change: transform;
}

/* ==========================================================================
   08 · SCRIM  (data-vp-scrim)  — legibility, not decoration
   Text over a moving picture has to stay readable at EVERY frame, and the
   picture is the one thing that changes. The scrim is a gradient laid on the
   host, above the moving layer (both pseudo-elements sit in the negative
   layer, so tree order decides: ::before is the picture, ::after the scrim)
   and below the words. Pure CSS: it holds even where motion.js never runs.
   ========================================================================== */
[data-vp-scrim] { position: relative; isolation: isolate; }
[data-vp-scrim]::after {
  content: ""; position: absolute; inset: 0; z-index: -1; pointer-events: none;
  background: var(--vpm-scrim-image, linear-gradient(var(--vpm-scrim-angle, 180deg),
    rgba(0, 0, 0, calc(var(--vpm-scrim-a, .55) * var(--vpm-scrim-from, 1))) 0%,
    rgba(0, 0, 0, calc(var(--vpm-scrim-a, .55) * var(--vpm-scrim-to, 1))) 100%));
}
/* the ramps never reach zero: a scrim that fades all the way out stops being a
   scrim exactly where a bright corner of the photo can walk under the words —
   measured on this very lab page, 2.93:1 with a ramp from 0, 6.9:1 with this */
[data-vp-scrim="bottom"] { --vpm-scrim-a: .72; --vpm-scrim-from: .28; --vpm-scrim-to: 1; }
[data-vp-scrim="top"]    { --vpm-scrim-a: .72; --vpm-scrim-from: 1; --vpm-scrim-to: .28; }
[data-vp-scrim="light"]  { --vpm-scrim-image: linear-gradient(180deg,
    rgba(255, 255, 255, var(--vpm-scrim-a, .55)) 0%, rgba(255, 255, 255, var(--vpm-scrim-a, .55)) 100%); }

/* ==========================================================================
   09 · SPLIT  (data-vp-split)  — the headline arrives word by word
   motion.js rewrites the text into <span> units and NOTHING else: the words
   stay in the same order, separated by the same real spaces, so a selection
   copies the sentence back exactly as written and a screen reader reads one
   phrase. Under reduced motion the text is not split at all.
     words  each word rises and fades in
     chars  same, per letter (words still never break across lines)
     mask   each word rises from behind its own line — no fade, the premium one
   ========================================================================== */
[data-vp-motion-ready] [data-vp-split] .vpm-wd { display: inline-block; }
/* The whole word, for a screen reader only: off screen, out of any selection,
   and the letters beside it are aria-hidden — see motion.js §07. Deliberately
   NOT behind the ready gate: this box only exists because motion.js built it,
   and it must stay hidden whatever happens to the gate afterwards. */
[data-vp-split] .vpm-sr {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
  user-select: none; -webkit-user-select: none;
}
[data-vp-motion-ready] [data-vp-split] .vpm-u {
  display: inline-block;
  opacity: 0;
  transform: translateY(var(--vpm-split-dist, .44em));
  transition:
    opacity var(--vpm-split-dur, 620ms) var(--vpm-ease),
    transform var(--vpm-split-dur, 620ms) var(--vpm-ease);
  transition-delay: calc(var(--vpm-i, 0) * var(--vpm-split-step, 42ms) + var(--vpm-delay, 0ms));
}
[data-vp-motion-ready] [data-vp-split]:not([data-vp-in]) .vpm-u { will-change: transform, opacity; }
[data-vp-motion-ready] [data-vp-split][data-vp-in] .vpm-u { opacity: 1; transform: none; }

/* mask: the unit is the window, the inner span is what travels. The padding /
   negative-margin pair gives descenders (g, y, p) room inside the window so
   `clip` does not shave them; the baseline is untouched. */
[data-vp-motion-ready] [data-vp-split="mask"] .vpm-u {
  overflow: clip; opacity: 1; transform: none; transition: none;
  padding-bottom: .18em; margin-bottom: -.18em;
}
[data-vp-motion-ready] [data-vp-split="mask"] .vpm-in {
  display: inline-block;
  transform: translateY(110%);
  transition: transform var(--vpm-split-dur, 620ms) var(--vpm-ease);
  transition-delay: calc(var(--vpm-i, 0) * var(--vpm-split-step, 42ms) + var(--vpm-delay, 0ms));
}
[data-vp-motion-ready] [data-vp-split="mask"][data-vp-in] .vpm-in { transform: none; }
/* too many words to split one by one — the whole line fades in instead */
[data-vp-motion-ready] [data-vp-split][data-vpm-split="whole"] {
  opacity: 0; transition: opacity var(--vpm-split-dur, 620ms) var(--vpm-ease);
}
[data-vp-motion-ready] [data-vp-split][data-vpm-split="whole"][data-vp-in] { opacity: 1; }

/* ==========================================================================
   10 · MARQUEE  (data-vp-marquee)  — a line of big text, looping
   motion.js wraps the content in a run of TWO identical parts and the loop
   travels exactly one part (-50%), so the seam never shows. The second part
   is a copy: it is aria-hidden and unselectable, so the sentence is still
   read once and copied once. Only transform moves. It pauses under the
   pointer, and it pauses while off screen — a loop nobody is watching is
   pure battery.
   ========================================================================== */
[data-vp-marquee] { display: block; overflow: clip; }
[data-vp-marquee] .vpm-mq-run {
  display: flex; width: max-content;
  animation: vpm-marquee var(--vpm-mq-dur, 24s) linear infinite;
  animation-direction: var(--vpm-mq-dir, normal);
  will-change: transform;
}
[data-vp-marquee] .vpm-mq-part {
  display: flex; align-items: center; flex: none;
  gap: var(--vpm-mq-gap, 3rem); padding-right: var(--vpm-mq-gap, 3rem);
}
[data-vp-marquee] .vpm-mq-copy { user-select: none; -webkit-user-select: none; }
[data-vp-marquee]:not([data-vp-marquee-pause="off"]):hover .vpm-mq-run,
[data-vp-marquee][data-vpm-mq-off] .vpm-mq-run { animation-play-state: paused; }
@keyframes vpm-marquee { from { transform: translate3d(0, 0, 0); } to { transform: translate3d(-50%, 0, 0); } }

/* ==========================================================================
   11 · REVEAL-MASK  (data-vp-reveal-mask)  — a wipe, not a fade
   clip-path from a collapsed edge to the full box. The landed state only
   zeroes the four variables, so the `circle` shape keeps its own pair of
   rules and nothing has to be repeated per direction.
   ========================================================================== */
[data-vp-motion-ready] [data-vp-reveal-mask] {
  clip-path: inset(var(--vpm-mt, 0%) var(--vpm-mr, 0%) var(--vpm-mb, 0%) var(--vpm-ml, 0%));
  transition: clip-path var(--vpm-mask-dur, 900ms) var(--vpm-ease);
  transition-delay: var(--vpm-delay, 0ms);
}
[data-vp-motion-ready] [data-vp-reveal-mask]:not([data-vp-in]) { will-change: clip-path; }
[data-vp-motion-ready] [data-vp-reveal-mask="right"], [data-vp-motion-ready] [data-vp-reveal-mask=""] { --vpm-mr: 100%; }
[data-vp-motion-ready] [data-vp-reveal-mask="left"]  { --vpm-ml: 100%; }
[data-vp-motion-ready] [data-vp-reveal-mask="up"]    { --vpm-mt: 100%; }
[data-vp-motion-ready] [data-vp-reveal-mask="down"]  { --vpm-mb: 100%; }
[data-vp-motion-ready] [data-vp-reveal-mask][data-vp-in] { --vpm-mt: 0%; --vpm-mr: 0%; --vpm-mb: 0%; --vpm-ml: 0%; }
[data-vp-motion-ready] [data-vp-reveal-mask="circle"] { clip-path: circle(0% at 50% 50%); }
[data-vp-motion-ready] [data-vp-reveal-mask="circle"][data-vp-in] { clip-path: circle(78% at 50% 50%); }

/* ==========================================================================
   12 · STICKY-STACK  (data-vp-sticky-stack)  — sections that take turns
   Each direct child pins a little lower than the one before and paints over
   it, so the page deals them like cards. CSS alone cannot count children, so
   motion.js writes --vpm-i and the mark; with no JS nothing pins and the
   sections simply flow, which is the honest fallback.
   ========================================================================== */
[data-vpm-stacked] {
  position: -webkit-sticky; position: sticky;
  top: calc(var(--vpm-stack-top, 0px) + var(--vpm-i, 0) * var(--vpm-stack-step, 16px));
  z-index: calc(1 + var(--vpm-i, 0));
}

/* ==========================================================================
   13 · REDUCED MOTION + TEST SWITCH, for §07–§12
   Same promise as §06: the calm end state is the FINAL one — nothing clipped
   away, nothing left at opacity 0, nothing looping. motion.js also refuses to
   split, wrap or pin anything while the request is on, so these rules are the
   belt to that pair of braces.
   ========================================================================== */
@media (prefers-reduced-motion: reduce) {
  [data-vp-parallax-bg]::before { transform: none !important; }
  [data-vpm-bg-shot] { transform: none !important; }
  [data-vp-split] .vpm-u, [data-vp-split] .vpm-in,
  [data-vp-split][data-vpm-split="whole"] { opacity: 1 !important; transform: none !important; transition: none !important; }
  [data-vp-marquee] .vpm-mq-run { animation: none !important; }
  [data-vp-reveal-mask] { clip-path: none !important; transition: none !important; }
  [data-vpm-stacked] { position: static !important; }
}
[data-vp-motion="off"] [data-vp-parallax-bg]::before { transform: none !important; }
[data-vp-motion="off"] [data-vpm-bg-shot] { transform: none !important; }
[data-vp-motion="off"] [data-vp-split] .vpm-u,
[data-vp-motion="off"] [data-vp-split] .vpm-in,
[data-vp-motion="off"] [data-vp-split][data-vpm-split="whole"] { opacity: 1 !important; transform: none !important; transition: none !important; }
[data-vp-motion="off"] [data-vp-marquee] .vpm-mq-run { animation: none !important; }
[data-vp-motion="off"] [data-vp-reveal-mask] { clip-path: none !important; transition: none !important; }
[data-vp-motion="off"] [data-vpm-stacked] { position: static !important; }

/* ==========================================================================
   14 · THE PRO LAYER (Ola K) — data-vp-draw / morph / path / drag / inertia /
   split-lines / stagger-grid / pin / smooth / flip, run by motion-pro.js and
   the vendored libraries in /ui/1/vendor/ (anime.js 4.5, GSAP 3.15).
   Everything visual about these effects is driven by the libraries at run
   time — this section only carries the affordances CSS is better at, and it
   is gated on [data-vp-motion-pro] (set by motion-pro.js once it is live),
   so a page whose adapter never loads shows no trace of it. Nothing here
   hides content: the pre-states are set by JS after the adapter vouched.
   ========================================================================== */
[data-vp-motion-pro] [data-vp-drag],
[data-vp-motion-pro] [data-vp-inertia] {
  cursor: grab;
  touch-action: none;          /* pointer dragging needs the gesture for itself */
  user-select: none;
  -webkit-user-select: none;
}
[data-vp-motion-pro] [data-vpm-pro-drag] { cursor: grabbing; }
/* the wrapper pair ScrollSmoother scrolls inside of (data-vp-smooth) */
#vpm-smooth-wrapper { position: fixed; inset: 0; overflow: hidden; }
#vpm-smooth-content { min-height: 100%; }

/* Reduced motion + the test switch: motion-pro.js refuses to wire anything at
   all while either is on (no split, no pin, no wrapper, no library fetched),
   so the final state IS the markup — these are the belt to those braces for
   the two attributes that leave a static trace. */
@media (prefers-reduced-motion: reduce) {
  [data-vp-drag], [data-vp-inertia] { cursor: auto; touch-action: auto; }
}
[data-vp-motion="off"] [data-vp-drag],
[data-vp-motion="off"] [data-vp-inertia] { cursor: auto; touch-action: auto; }

/* ==========================================================================
   15 · NAV ON SCROLL  (data-vp-nav-scroll)  — the header reacts to the page
   moving: `solidify` keeps the bar transparent while the page is at the top
   and gives it its full surface (background, hairline, blur) once it scrolls;
   `shrink` lowers the bar's height by 16px once it scrolls; `both` does both.
   No new JS: the STATE is the `data-scrolled` attribute vp-site-nav's own
   IntersectionObserver has always written on `.vp-nav-bar` (blocks.js §05),
   so the effect only exists where the style prop `navScroll` (group `shape`,
   style-hooks.json) puts this attribute on the section — an unstyled page
   cannot match a single rule here. The scrolled surface repeats the DEFAULT
   bar values from blocks.css §05 on purpose: a compositions like `ghost`
   declares "no surface, ever" at the same strength, and the explicit scrolled
   rule (later file, so it wins) is what lets ghost+solidify read as the
   transparent-over-hero header that hardens when the page moves.
   ========================================================================== */
/* Any on-scroll behaviour first makes the bar PERSIST: `position: sticky` on a
   bar whose section is exactly bar-height has zero travel (measured on volt:
   the bar leaves with the page), so a header that is asked to react to scroll
   pins itself to the viewport instead, and the section keeps a placeholder of
   the bar's height so the page does not jump. `rail` is excluded — its bar is
   already fixed to the edge and must not be re-pinned to the top. */
[data-vp-nav-scroll]:not([data-variant="rail"]) .vp-nav-bar[data-sticky] { position: fixed; top: 0; left: 0; right: 0; z-index: 50; }
.vp-b-site-nav[data-vp-nav-scroll]:not([data-variant="rail"]) { min-height: var(--vp-nav-h); }
.vp-b-site-nav[data-variant="double"][data-vp-nav-scroll] { min-height: calc(var(--vp-nav-h) + 35px); }  /* + the utility strip */
[data-vp-nav-scroll="solidify"] .vp-nav-bar,
[data-vp-nav-scroll="both"] .vp-nav-bar { transition: background-color .28s ease, border-color .28s ease, box-shadow .28s ease; }
[data-vp-nav-scroll="solidify"] .vp-nav-bar:not([data-scrolled="1"]),
[data-vp-nav-scroll="both"] .vp-nav-bar:not([data-scrolled="1"]) { background: transparent; border-bottom-color: transparent; box-shadow: none; -webkit-backdrop-filter: none; backdrop-filter: none; }
[data-vp-nav-scroll="solidify"] .vp-nav-bar[data-scrolled="1"],
[data-vp-nav-scroll="both"] .vp-nav-bar[data-scrolled="1"] { background: var(--vps-bar-bg, var(--vp-nav-bg, color-mix(in srgb, var(--vp-bg) 80%, transparent))); border-bottom-color: var(--vps-bar-border, var(--vp-nav-line, var(--vp-line))); -webkit-backdrop-filter: var(--vp-nav-blur); backdrop-filter: var(--vp-nav-blur); }
/* the island compositions (pill · rail) paint their surface on .vp-nav-in */
[data-vp-nav-scroll="solidify"] .vp-nav-bar:not([data-scrolled="1"]) .vp-nav-in,
[data-vp-nav-scroll="both"] .vp-nav-bar:not([data-scrolled="1"]) .vp-nav-in { background: transparent; border-color: transparent; box-shadow: none; -webkit-backdrop-filter: none; backdrop-filter: none; }
[data-vp-nav-scroll="shrink"] .vp-nav-in,
[data-vp-nav-scroll="both"] .vp-nav-in { transition: height .28s ease; }
[data-vp-nav-scroll="shrink"] .vp-nav-bar[data-scrolled="1"] .vp-nav-in,
[data-vp-nav-scroll="both"] .vp-nav-bar[data-scrolled="1"] .vp-nav-in { height: calc(var(--vp-nav-h) - 16px); }

/* Reduced motion + the test switch: the STATES stay (a header that never
   hardens over a photograph is a legibility bug, not a calm one) — only the
   easing between them goes, so every comparison sees a settled bar. */
@media (prefers-reduced-motion: reduce) {
  [data-vp-nav-scroll] .vp-nav-bar, [data-vp-nav-scroll] .vp-nav-in { transition: none !important; }
}
[data-vp-motion="off"] [data-vp-nav-scroll] .vp-nav-bar,
[data-vp-motion="off"] [data-vp-nav-scroll] .vp-nav-in { transition: none !important; }
