/* Initial hidden states - only applied once JS confirms it's ready to animate
   AND the user hasn't asked for reduced motion. No-JS / reduced-motion users
   always see the final, fully visible state (see the two overrides below). */
html.vn-js:not(.vn-reduced-motion) [data-anim="fade-up"] {
  opacity: 0;
  transform: translateY(28px);
}

html.vn-js:not(.vn-reduced-motion) [data-anim="fade"] {
  opacity: 0;
}

html.vn-js:not(.vn-reduced-motion) [data-anim="scale-in"] {
  opacity: 0;
  transform: scale(0.92);
}

html.vn-js:not(.vn-reduced-motion) [data-anim="scale-in-subtle"] {
  opacity: 0;
  transform: scale(0.96);
}

html.vn-js:not(.vn-reduced-motion) [data-anim-item] {
  opacity: 0;
  transform: translateY(24px);
}

/* `vn-anim-ready` is set by the animation script once it has actually taken
   charge of these elements. Keying the hidden state on it means a script that
   fails to load, throws, or is blocked leaves the page fully visible instead of
   full of holes — the failure mode becomes "no animation" rather than "no
   content", which is the only acceptable one for a shop. */
html.vn-js.vn-anim-ready:not(.vn-reduced-motion) [data-anim-icon] {
  transform: scale(0);
}

html.vn-js:not(.vn-reduced-motion) [data-anim-line-inner] {
  opacity: 0;
  transform: translateY(10px);
}

html.vn-js:not(.vn-reduced-motion) [data-anim-signature] {
  opacity: 0;
  transform: scale(0.9) rotate(-3deg);
}

/* Lines are clipped at the wrapper so translateY reveals read as a clean
   "rise into place" instead of overflowing into the line above. */
[data-anim-line] {
  display: block;
  overflow: hidden;
}

[data-anim-line-inner] {
  display: block;
}

/* Hard safety net: no matter what JS did or failed to do, reduced-motion
   users always get the finished layout with zero animation. */
@media (prefers-reduced-motion: reduce) {
  [data-anim],
  [data-anim-item],
  [data-anim-icon],
  [data-anim-line-inner],
  [data-anim-signature] {
    opacity: 1 !important;
    transform: none !important;
  }

  .vn-pulse {
    animation: none !important;
  }
}

/* will-change is added only while an element is actively animating (GSAP
   onStart/onComplete toggle this class) so the browser doesn't keep whole
   sections promoted to their own GPU layer permanently. */
.vn-will-animate {
  will-change: transform, opacity;
}

/* Generic micro-interaction: a soft "press" on anything that can be pressed.

   This used to be opt-in through `[data-vn-interactive]`, and in the whole
   shop exactly one element had ever opted in — so every other button in the
   place changed state with no motion at all, which is what a tap that "just
   snaps" is. Opt-out is the honest default: a control that moves under the
   finger is the baseline a phone user expects, and the handful of places where
   it would be wrong can say so.

   Element selectors deliberately, at the lowest specificity there is, so any
   component that has opinions about its own transform or transition still
   wins outright. `:active` is one class up, which is enough to land the press
   on components that only ever declared colours.

   `scale`, not `transform: scale()`. Several buttons in the shop are placed
   with a transform — the lightbox arrows sit on `translateY(-50%)`, the
   carousel controls likewise — and a `transform` here replaces theirs whole,
   so pressing an arrow would fling it half its own height down the page. The
   independent `scale` property composes with whatever transform is already
   there instead of overwriting it, which is the only version of this rule
   that is safe to apply to every button in the shop at once.

   `[data-vn-nopress]` is the way out for a control where scaling reads badly —
   something already being scaled, or drawn at the edge of its own frame. */
button,
[role='button'],
summary,
.ub-button,
[data-vn-interactive] {
  transition: scale 160ms cubic-bezier(0.2, 0.7, 0.3, 1);
}

button:active,
[role='button']:active,
summary:active,
.ub-button:active,
[data-vn-interactive]:active {
  scale: 0.97;
}

button[data-vn-nopress]:active,
[role='button'][data-vn-nopress]:active,
[data-vn-nopress] button:active {
  scale: 1;
}

/* A control that is disabled is not being pressed, whatever the finger did. */
button:disabled:active,
button[aria-disabled='true']:active {
  scale: 1;
}

@media (prefers-reduced-motion: reduce) {
  button,
  [role='button'],
  summary,
  .ub-button,
  [data-vn-interactive] {
    transition: none;
  }

  button:active,
  [role='button']:active,
  summary:active,
  .ub-button:active,
  [data-vn-interactive]:active {
    scale: 1;
  }
}

/* Star pop-in, replayed on every carousel slide change (not just once). */
@keyframes vn-star-pop {
  0% {
    transform: scale(0.4);
    opacity: 0.4;
  }
  60% {
    transform: scale(1.15);
    opacity: 1;
  }
  100% {
    transform: scale(1);
    opacity: 1;
  }
}

.vn-star-pop {
  animation: vn-star-pop 320ms ease-out backwards;
}

@media (prefers-reduced-motion: reduce) {
  .vn-star-pop {
    animation: none !important;
  }
}
