/* ==========================================================================
   QLive — Motion System
   Layered on top of style.css. Everything here is transform/opacity/filter
   only, so it composites on the GPU. Motion is decorative only:
   content and controls never depend on it.
   --------------------------------------------------------------------------
   1.  Cross-document view transitions
   2.  Page load choreography
   3.  Split-text reveals
   4.  Scroll progress + scroll-linked reveals
   5.  Aurora / mesh backdrops
   6.  Spotlight, tilt, magnetic
   7.  Odometer digits
   8.  SVG draw-in + connectors
   9.  Ambient loops (shimmer, breathe, drift, orbit)
   10. Scrollytelling
   11. Ripple + micro-interactions
   12. Motion
   ========================================================================== */

/* ==========================================================================
   1. CROSS-DOCUMENT VIEW TRANSITIONS
   Chrome/Edge morph between pages instead of flashing white. Other browsers
   simply navigate normally — this is pure progressive enhancement.
   ========================================================================== */

@view-transition { navigation: auto; }

::view-transition-old(root) {
  animation: vtOut .32s cubic-bezier(.4, 0, 1, 1) both;
}
::view-transition-new(root) {
  animation: vtIn .42s cubic-bezier(.16, 1, .3, 1) both;
}
@keyframes vtOut {
  to { opacity: 0; transform: translateY(-10px) scale(.995); filter: blur(3px); }
}
@keyframes vtIn {
  from { opacity: 0; transform: translateY(14px) scale(.995); filter: blur(4px); }
}

/* Elements that should persist across the navigation rather than cross-fade */
.site-header  { view-transition-name: qlive-header; }
.brand        { view-transition-name: qlive-brand; }
.wa-fab       { view-transition-name: qlive-fab; }
.site-footer  { view-transition-name: qlive-footer; }

::view-transition-group(qlive-header),
::view-transition-group(qlive-brand),
::view-transition-group(qlive-fab),
::view-transition-group(qlive-footer) {
  animation-duration: .42s;
  animation-timing-function: cubic-bezier(.16, 1, .3, 1);
}

/* The drawer must not be captured mid-navigation */
.drawer, .scrim { view-transition-name: none; }

/* ==========================================================================
   2. PAGE LOAD CHOREOGRAPHY
   The header, hero copy and hero visual arrive in a deliberate sequence
   rather than all at once.
   ========================================================================== */

html.js-motion .site-header {
  animation: dropIn .8s var(--ease) both;
  animation-delay: .05s;
}
@keyframes dropIn {
  from { opacity: 0; transform: translateY(-100%); }
}

html.js-motion [data-enter] {
  opacity: 0;
  animation: enterUp .95s var(--ease) both;
  animation-delay: calc(var(--enter-delay, 0ms) + 120ms);
}
@keyframes enterUp {
  from { opacity: 0; transform: translateY(26px); filter: blur(6px); }
  to   { opacity: 1; transform: none; filter: blur(0); }
}

html.js-motion [data-enter="scale"] {
  animation-name: enterScale;
  animation-duration: 1.15s;
}
@keyframes enterScale {
  from { opacity: 0; transform: translateY(34px) scale(.94) rotateX(6deg); filter: blur(10px); }
  to   { opacity: 1; transform: none; filter: blur(0); }
}

/* ==========================================================================
   3. SPLIT-TEXT REVEALS
   motion.js wraps headings in .sp-line > .sp-word. Each word rises out of a
   clipped line box, so the text unmasks rather than just fading.
   ========================================================================== */

.sp-line {
  display: block;
  overflow: hidden;
  padding-bottom: .08em;
  margin-bottom: -.08em;
}
.sp-word {
  display: inline-block;
  will-change: transform, opacity;
  transform: translateY(115%) rotate(3deg);
  opacity: 0;
}
.sp-ready .sp-word {
  transition:
    transform 1s cubic-bezier(.16, 1, .3, 1),
    opacity .7s cubic-bezier(.16, 1, .3, 1);
  transition-delay: var(--w, 0ms);
}
.sp-in .sp-word {
  transform: none;
  opacity: 1;
}

/* Emphasised words inside a split heading keep their underline flourish */
.sp-word em { font-style: normal; }

/* The brand-coloured underline sweeps in after the word lands */
.hero h1 em::after {
  transform-origin: left center;
  transform: scaleX(0);
  transition: transform .9s cubic-bezier(.16, 1, .3, 1) .55s;
}
.sp-in .hero h1 em::after,
.hero h1.sp-in em::after { transform: scaleX(1); }

/* ==========================================================================
   4. SCROLL PROGRESS + SCROLL-LINKED REVEALS
   ========================================================================== */

.scroll-progress {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 999;
  height: 2px;
  width: 100%;
  transform-origin: 0 50%;
  transform: scaleX(0);
  background: linear-gradient(90deg, var(--brand), var(--violet));
  box-shadow: 0 0 12px color-mix(in srgb, var(--brand) 60%, transparent);
  pointer-events: none;
}

/* Native scroll-driven animation where supported — no JS involved at all */
@supports (animation-timeline: scroll()) {
  html.js-motion .scroll-progress {
    animation: progressGrow linear both;
    animation-timeline: scroll(root block);
    transform: none;
  }
  @keyframes progressGrow {
    from { transform: scaleX(0); }
    to   { transform: scaleX(1); }
  }
}

/* View-timeline reveals: richer than the IntersectionObserver fallback */
@supports (animation-timeline: view()) {
  html.js-motion [data-reveal]:not(.is-in) {
    opacity: 1;
    transform: none;
    animation: revealUp linear both;
    animation-timeline: view(block);
    animation-range: entry 4% cover 26%;
  }
  @keyframes revealUp {
    from { opacity: 0; transform: translateY(38px) scale(.985); filter: blur(5px); }
    to   { opacity: 1; transform: none; filter: blur(0); }
  }

  /* Cards lift and settle as they cross the viewport */
  html.js-motion [data-scroll-lift] {
    animation: scrollLift linear both;
    animation-timeline: view(block);
    animation-range: entry 0% cover 40%;
  }
  @keyframes scrollLift {
    from { transform: translateY(56px) rotateX(9deg); opacity: 0; }
    to   { transform: none; opacity: 1; }
  }

  /* Section headers drift slightly slower than the page — cheap parallax */
  html.js-motion [data-scroll-parallax] {
    animation: scrollDrift linear both;
    animation-timeline: view(block);
    animation-range: cover;
  }
  @keyframes scrollDrift {
    from { transform: translateY(var(--drift, 40px)); }
    to   { transform: translateY(calc(var(--drift, 40px) * -1)); }
  }
}

/* JS-driven parallax fallback target */
[data-parallax] { will-change: transform; }

/* ==========================================================================
   5. AURORA / MESH BACKDROPS
   Replaces the two static blur circles with slow-drifting coloured light.
   ========================================================================== */

.bg-glow::before,
.bg-glow::after {
  will-change: transform;
}
html.js-motion .bg-glow::before {
  animation: auroraA 22s cubic-bezier(.45, 0, .55, 1) infinite alternate;
}
html.js-motion .bg-glow::after {
  animation: auroraB 27s cubic-bezier(.45, 0, .55, 1) infinite alternate;
}
@keyframes auroraA {
  0%   { transform: translate3d(0, 0, 0) scale(1); }
  50%  { transform: translate3d(14vw, 5vh, 0) scale(1.18); }
  100% { transform: translate3d(4vw, -4vh, 0) scale(.92); }
}
@keyframes auroraB {
  0%   { transform: translate3d(0, 0, 0) scale(1.05); }
  50%  { transform: translate3d(-12vw, 7vh, 0) scale(.88); }
  100% { transform: translate3d(-3vw, -5vh, 0) scale(1.2); }
}

/* A third light source that follows the pointer across the hero */
.bg-glow .glow-cursor {
  position: absolute;
  top: 0;
  left: 0;
  width: 460px;
  height: 460px;
  margin: -230px 0 0 -230px;
  border-radius: 50%;
  background: radial-gradient(circle, var(--glow-a), transparent 68%);
  filter: blur(70px);
  opacity: 0;
  transition: opacity .9s var(--ease);
  will-change: transform;
  pointer-events: none;
}
.bg-glow .glow-cursor.is-on { opacity: .85; }

/* The dotted grid breathes very slightly so the backdrop is never fully static */
html.js-motion .bg-grid {
  animation: gridBreathe 14s ease-in-out infinite;
}
@keyframes gridBreathe {
  0%, 100% { opacity: 1; }
  50%      { opacity: .55; }
}

/* ==========================================================================
   6. SPOTLIGHT, TILT, MAGNETIC
   ========================================================================== */

/* Cursor-tracked highlight on cards */
[data-spotlight] { position: relative; isolation: isolate; }
[data-spotlight]::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  border-radius: inherit;
  opacity: 0;
  transition: opacity .45s var(--ease);
  background: radial-gradient(
    360px circle at var(--mx, 50%) var(--my, 50%),
    color-mix(in srgb, var(--brand) 14%, transparent),
    transparent 62%
  );
  pointer-events: none;
}
[data-spotlight]:hover::before { opacity: 1; }
[data-spotlight] > * { position: relative; z-index: 1; }

/* A brand-coloured border that lights up under the cursor */
[data-spotlight]::after {
  content: "";
  position: absolute;
  inset: -1px;
  z-index: 2;
  border-radius: inherit;
  padding: 1px;
  opacity: 0;
  transition: opacity .45s var(--ease);
  background: radial-gradient(
    240px circle at var(--mx, 50%) var(--my, 50%),
    color-mix(in srgb, var(--brand) 65%, transparent),
    transparent 60%
  );
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask-composite: exclude;
  pointer-events: none;
}
[data-spotlight]:hover::after { opacity: 1; }

/* 3D tilt */
[data-tilt] {
  transform-style: preserve-3d;
  transition: transform .5s var(--ease);
  will-change: transform;
}
[data-tilt].is-tilting { transition: transform .12s linear; }
[data-tilt] > * { transform: translateZ(0); }
[data-tilt-layer] {
  transform: translateZ(38px);
  transition: transform .5s var(--ease);
}

/* Magnetic buttons */
[data-magnetic] {
  will-change: transform;
  transition: transform .55s var(--ease);
}
[data-magnetic].is-pulled { transition: transform .18s cubic-bezier(.2, .8, .3, 1); }
[data-magnetic] .btn-label {
  display: inline-block;
  transition: transform .55s var(--ease);
}

/* Sheen that sweeps across primary buttons on hover */
.btn-primary, .btn-ink, .btn-wa { position: relative; overflow: hidden; }
.btn-primary::after, .btn-ink::after, .btn-wa::after {
  content: "";
  position: absolute;
  top: 0;
  left: -140%;
  width: 60%;
  height: 100%;
  background: linear-gradient(100deg, transparent, rgba(255, 255, 255, .38), transparent);
  transform: skewX(-18deg);
  pointer-events: none;
}
html.js-motion .btn-primary:hover::after,
html.js-motion .btn-ink:hover::after,
html.js-motion .btn-wa:hover::after {
  animation: sheen .9s cubic-bezier(.4, 0, .2, 1);
}
@keyframes sheen {
  to { left: 160%; }
}

/* Nav links get an underline that wipes in from the left */
.nav-link { position: relative; }
.nav-link::before {
  content: "";
  position: absolute;
  left: 13px;
  right: 13px;
  bottom: 1px;
  height: 2px;
  border-radius: 2px;
  background: var(--brand);
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform .45s var(--ease);
}
.nav-link:hover::before { transform: scaleX(1); }
.nav-link[aria-current="page"]::before { display: none; }

/* Mega-menu items stagger in */
html.js-motion .has-menu.is-open .mega-item {
  animation: megaIn .5s var(--ease) both;
  animation-delay: calc(var(--i, 0) * 45ms + 60ms);
}
@keyframes megaIn {
  from { opacity: 0; transform: translateY(10px); }
}

/* ==========================================================================
   7. ODOMETER DIGITS
   Token numbers roll like a departure board instead of snapping.
   ========================================================================== */

.odo {
  display: inline-flex;
  line-height: 1;
  font-variant-numeric: tabular-nums;
}
.odo-digit {
  position: relative;
  display: inline-block;
  overflow: hidden;
  height: 1em;
  width: .62em;
  user-select: none;
}
/* The real value sits in an .sr-only span; keep it out of the flex row */
.odo-real { position: absolute; }
.odo-strip {
  display: block;
  will-change: transform;
  transition: transform .78s cubic-bezier(.16, 1, .3, 1);
}
.odo-strip span {
  display: block;
  height: 1em;
  text-align: center;
}

/* Pulse the token panel when it advances */
@keyframes tokenPulse {
  0%   { box-shadow: 0 0 0 0 color-mix(in srgb, var(--brand) 45%, transparent); }
  70%  { box-shadow: 0 0 0 18px transparent; }
  100% { box-shadow: 0 0 0 0 transparent; }
}
html.js-motion .now-serving.is-advancing { animation: tokenPulse 1s var(--ease-soft); }

/* Queue rows slide up as the queue advances */
@keyframes queueShift {
  from { opacity: 0; transform: translateY(14px) scale(.97); }
}
html.js-motion .queue-row { animation: queueShift .5s var(--ease) both; }
html.js-motion .queue-row:nth-child(1) { animation-delay: 0ms; }
html.js-motion .queue-row:nth-child(2) { animation-delay: 50ms; }
html.js-motion .queue-row:nth-child(3) { animation-delay: 100ms; }
html.js-motion .queue-row:nth-child(4) { animation-delay: 150ms; }

/* The TV token gets a glow flash on change */
@keyframes tvFlash {
  0%   { text-shadow: 0 0 34px rgba(75, 123, 251, .55); }
  35%  { text-shadow: 0 0 70px rgba(75, 123, 251, 1), 0 0 120px rgba(75, 123, 251, .7); }
  100% { text-shadow: 0 0 34px rgba(75, 123, 251, .55); }
}
html.js-motion .tv-token.is-changing { animation: tvFlash .9s var(--ease-soft); }

/* Scanline sweep across the TV panel */
.sim-tv .scanline {
  position: absolute;
  left: 0;
  right: 0;
  height: 34%;
  background: linear-gradient(180deg, transparent, rgba(75, 123, 251, .07), transparent);
  pointer-events: none;
}
html.js-motion .sim-tv .scanline { animation: scan 6s linear infinite; }
@keyframes scan {
  from { transform: translateY(-120%); }
  to   { transform: translateY(420%); }
}

/* ==========================================================================
   8. SVG DRAW-IN + CONNECTORS
   ========================================================================== */

[data-draw] path,
[data-draw] circle,
[data-draw] line {
  stroke-dasharray: var(--len, 200);
  stroke-dashoffset: var(--len, 200);
}
[data-draw].is-in path,
[data-draw].is-in circle,
[data-draw].is-in line {
  animation: draw 1.5s cubic-bezier(.16, 1, .3, 1) both;
  animation-delay: calc(var(--i, 0) * 120ms);
}
@keyframes draw {
  to { stroke-dashoffset: 0; }
}

/* The step connector line grows downward as you scroll past it */
.step::after {
  transform-origin: top center;
  transform: scaleY(0);
  transition: transform .9s var(--ease);
}
.step.is-in::after { transform: scaleY(1); }

@supports (animation-timeline: view()) {
  html.js-motion .step::after {
    transform: none;
    animation: growLine linear both;
    animation-timeline: view(block);
    animation-range: entry 20% cover 55%;
  }
  @keyframes growLine {
    from { transform: scaleY(0); }
    to   { transform: scaleY(1); }
  }
}

/* Step number badges pop */
html.js-motion .step.is-in::before {
  animation: badgePop .7s cubic-bezier(.34, 1.56, .64, 1) both;
}
@keyframes badgePop {
  from { opacity: 0; transform: scale(.5) rotate(-12deg); }
  to   { opacity: 1; transform: none; }
}

/* Bars in the mini charts grow from the baseline */
.mini-bars i {
  transform-origin: bottom center;
  transform: scaleY(0);
  transition: transform .95s cubic-bezier(.16, 1, .3, 1);
  transition-delay: calc(var(--i, 0) * 70ms);
}
.mini-bars.is-in i { transform: scaleY(1); }

/* ==========================================================================
   9. AMBIENT LOOPS
   ========================================================================== */

/* The live dot already pings; give the pill a soft breathing border */
html.js-motion .pill-live {
  animation: breathe 4.5s ease-in-out infinite;
}
@keyframes breathe {
  0%, 100% { border-color: var(--line); }
  50%      { border-color: color-mix(in srgb, var(--brand) 40%, transparent); }
}

/* Floating KPI chip drifts and rotates a touch */
html.js-motion .sim-float {
  animation: floatDrift 9s ease-in-out infinite;
}
@keyframes floatDrift {
  0%, 100% { transform: translate3d(0, 0, 0) rotate(0deg); }
  33%      { transform: translate3d(-5px, -14px, 0) rotate(-1.1deg); }
  66%      { transform: translate3d(5px, -7px, 0) rotate(.9deg); }
}

/* The whole simulation breathes very slightly in 3D */
html.js-motion .sim {
  animation: simBreathe 16s ease-in-out infinite;
}
@keyframes simBreathe {
  0%, 100% { transform: rotateY(0deg) rotateX(0deg); }
  50%      { transform: rotateY(-1.4deg) rotateX(1deg); }
}

/* Brand mark ring rotates slowly forever, faster on hover */
html.js-motion .brand-mark .ring {
  animation: ringSpin 26s linear infinite;
}
@keyframes ringSpin {
  to { transform: rotate(360deg); }
}
html.js-motion .brand:hover .brand-mark .ring {
  animation-duration: 2.2s;
}

/* WhatsApp FAB gets a slow attention pulse */
html.js-motion .wa-fab::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  border: 2px solid var(--whatsapp);
  animation: fabPing 3.4s cubic-bezier(0, 0, .2, 1) infinite;
  pointer-events: none;
}
@keyframes fabPing {
  0%   { opacity: .7; transform: scale(1); }
  60%  { opacity: 0;  transform: scale(1.4); }
  100% { opacity: 0;  transform: scale(1.4); }
}

/* Price card featured glow slowly rotates its gradient */
html.js-motion .price-card.is-featured::before {
  background: conic-gradient(
    from var(--angle, 0deg),
    var(--brand), transparent 28%, transparent 72%, var(--brand)
  );
  animation: spinBorder 7s linear infinite;
}
@property --angle {
  syntax: "<angle>";
  initial-value: 0deg;
  inherits: false;
}
@keyframes spinBorder {
  to { --angle: 360deg; }
}

/* Cards rise on hover with a springier curve than the base stylesheet */
html.js-motion .card-hover {
  transition:
    transform .55s cubic-bezier(.34, 1.3, .64, 1),
    box-shadow .55s var(--ease),
    border-color .35s;
}

/* Icon tiles react to their card being hovered */
.card-hover .card-icon,
.info-card .card-icon {
  transition: transform .55s cubic-bezier(.34, 1.4, .64, 1), background-color .4s;
}
html.js-motion .card-hover:hover .card-icon,
html.js-motion .info-card:hover .card-icon {
  transform: translateY(-3px) rotate(-6deg) scale(1.08);
}

/* Table rows highlight with a sliding tint */
html.js-motion .cmp tbody tr th,
html.js-motion .cmp tbody tr td {
  transition: background-color .3s var(--ease-soft);
}

/* ==========================================================================
   10. SCROLLYTELLING
   A sticky visual that swaps as you scroll through the accompanying steps.
   ========================================================================== */

.scrolly {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: clamp(2rem, 5vw, 4.5rem);
  align-items: start;
}
.scrolly-sticky {
  position: sticky;
  top: calc(var(--header-h) + 40px);
}
.scrolly-step {
  min-height: 62vh;
  display: flex;
  flex-direction: column;
  justify-content: center;
  opacity: .32;
  transform: translateX(8px);
  transition: opacity .6s var(--ease), transform .6s var(--ease);
}
.scrolly-step.is-active {
  opacity: 1;
  transform: none;
}
.scrolly-frame { display: none; }
.scrolly-frame.is-active {
  display: block;
  animation: frameIn .65s var(--ease) both;
}
@keyframes frameIn {
  from { opacity: 0; transform: translateY(18px) scale(.97); filter: blur(6px); }
}

@media (max-width: 900px) {
  .scrolly { grid-template-columns: 1fr; }
  .scrolly-sticky { position: static; margin-bottom: 2rem; }
  .scrolly-step { min-height: 0; opacity: 1; transform: none; padding-block: 1.5rem; }
}

/* ==========================================================================
   11. RIPPLE + MICRO-INTERACTIONS
   ========================================================================== */

.ripple {
  position: absolute;
  border-radius: 50%;
  transform: scale(0);
  background: rgba(255, 255, 255, .45);
  pointer-events: none;
  animation: rippleOut .65s cubic-bezier(.4, 0, .2, 1) forwards;
}
.btn-ghost .ripple,
.btn-quiet .ripple { background: color-mix(in srgb, var(--brand) 30%, transparent); }
@keyframes rippleOut {
  to { transform: scale(2.6); opacity: 0; }
}

/* Accordion answers slide as well as expand */
.acc-panel > div > * {
  opacity: 0;
  transform: translateY(-8px);
  transition: opacity .45s var(--ease) .1s, transform .45s var(--ease) .1s;
}
.acc-item.is-open .acc-panel > div > * {
  opacity: 1;
  transform: none;
}

/* Tabs: the active pill scales in */
html.js-motion .tab {
  transition: background-color .35s var(--ease-soft), color .35s, border-color .35s,
              transform .35s cubic-bezier(.34, 1.4, .64, 1);
}
html.js-motion .tab[aria-selected="true"] { transform: scale(1.04); }

/* Form fields lift on focus */
.input, .select, .textarea {
  transition: border-color .25s, box-shadow .25s, transform .25s var(--ease);
}
html.js-motion .input:focus,
html.js-motion .select:focus,
html.js-motion .textarea:focus { transform: translateY(-1px); }

/* Choice tiles pop when selected */
html.js-motion .choice span {
  transition: all .3s var(--ease-soft), transform .4s cubic-bezier(.34, 1.5, .64, 1);
}
html.js-motion .choice input:checked + span { transform: translateY(-2px) scale(1.02); }

/* Range thumbs grow while dragging */
html.js-motion input[type="range"]:active::-webkit-slider-thumb { transform: scale(1.3); }

/* Calculator figures flash when they change */
@keyframes valueFlash {
  0%   { color: var(--brand-300); transform: scale(1.06); }
  100% { color: inherit; transform: none; }
}
html.js-motion .calc-metric b.is-changed {
  animation: valueFlash .55s var(--ease-soft);
  display: inline-block;
}

/* Link arrows */
.link-arrow svg { transition: transform .45s cubic-bezier(.34, 1.4, .64, 1); }

/* Drawer links stagger in when the drawer opens */
html.js-motion .drawer.is-open .drawer-link,
html.js-motion .drawer.is-open .drawer-label,
html.js-motion .drawer.is-open .drawer-cta,
html.js-motion .drawer.is-open .drawer-meta {
  animation: drawerItem .55s var(--ease) both;
  animation-delay: calc(var(--i, 0) * 32ms + 90ms);
}
@keyframes drawerItem {
  from { opacity: 0; transform: translateX(24px); }
}

/* Footer columns rise on entry */
@supports (animation-timeline: view()) {
  html.js-motion .footer-col,
  html.js-motion .footer-about {
    animation: enterUp .9s var(--ease) both;
    animation-timeline: view(block);
    animation-range: entry 0% entry 70%;
  }
}

/* ==========================================================================
   12. MOTION
   Animation is always on: it is part of how the brand presents itself, and the
   site is built so motion is decorative only — every piece of content is fully
   readable and every control fully usable with animation stripped out.
   ========================================================================== */
