`;
const cards = wrap.querySelectorAll('[data-bcard]');
if (this.reduce) { cards.forEach((c) => { c.style.opacity = '1'; c.style.transform = 'none'; }); return; }
cards.forEach((c, idx) => { c.style.transform = 'translateY(16px)'; this._tweenNode(c, idx * 70); });
}
_tweenNode(el, delay) {
const dur = 460; let s = null;
const run = (t) => {
if (s === null) s = t;
const p = Math.min(1, (t - s) / dur);
const e = 1 - Math.pow(1 - p, 3);
el.style.opacity = e.toFixed(3);
el.style.transform = `translateY(${(16 * (1 - e)).toFixed(1)}px)`;
if (p < 1) requestAnimationFrame(run);
else { el.style.opacity = '1'; el.style.transform = 'none'; }
};
setTimeout(() => requestAnimationFrame(run), delay);
setTimeout(() => { el.style.opacity = '1'; el.style.transform = 'none'; }, delay + dur + 220);
}
// rAF crossfade for the active product panel (CSS transitions don't commit reliably here)
_fadePanel(p, to) {
const from = parseFloat(p.style.opacity || '0');
const dur = 300; let s = null;
const run = (t) => {
if (s === null) s = t;
const k = Math.min(1, (t - s) / dur);
p.style.opacity = (from + (to - from) * k).toFixed(3);
if (k < 1) requestAnimationFrame(run);
else p.style.opacity = String(to);
};
requestAnimationFrame(run);
}
_buildSearchRows() {
const wrap = document.querySelector('[data-rowwrap]');
if (!wrap) return;
const rows = [
{ n: 'Léonie Bassard', t: ['Clean beauty', 'Lifestyle'], m: 99, p: this.AV.women44 },
{ n: 'Aurélie Malik', t: ['Skincare', 'Luxury'], m: 97, p: this.AV.women68 },
{ n: 'Sophie Nguyen', t: ['Wellness', 'Fashion'], m: 94, p: this.AV.women79 },
];
wrap.innerHTML = rows.map((r, i) => `
${r.t.map((tag,j)=>`${tag}`).join('')}
`).join('');
wrap._targets = rows.map(r => r.m);
}
_animateSearchRows() {
const wrap = document.querySelector('[data-rowwrap]');
if (!wrap) return;
const rows = wrap.querySelectorAll('[data-srow]');
rows.forEach((r, i) => {
r.style.opacity = '0'; r.style.animation = '';
r.querySelector('[data-srow-bar]').style.width = '0';
setTimeout(() => {
r.style.opacity = '1';
r.style.animation = 'upfRise .5s var(--ease-out) both';
r.querySelector('[data-srow-bar]').style.width = wrap._targets[i] + '%';
r.querySelector('[data-srow-bar]').style.transition = 'width .9s var(--ease-out)';
setTimeout(() => { r.style.animation = ''; r.style.opacity = '1'; r.style.transform = 'none'; }, 560);
}, 150 + i * 170);
});
return;
requestAnimationFrame(() => {
rows.forEach((r, i) => {
r.style.opacity = '1'; r.style.transform = 'translateY(0)';
r.querySelector('[data-srow-bar]').style.width = wrap._targets[i] + '%';
});
});
}
_buildOrbAvatars() {
const pics = [this.AV.women44, this.AV.men32, this.AV.women68, this.AV.men45, this.AV.women65];
let picIdx = 0;
document.querySelectorAll('[data-orb]').forEach((o, i) => {
const delay = (i * 0.4).toFixed(2);
picIdx = (picIdx + 1) % pics.length;
o.innerHTML = `
`;
});
}
// ---- mobile hero carousel: clickable tabs + swipe, synced both ways ----
_initMobileHero() {
const car = document.querySelector('[data-mcarousel]');
const tabsWrap = document.querySelector('[data-mtabs]');
if (!car || !tabsWrap) return;
const tabs = Array.from(document.querySelectorAll('[data-mtab]'));
const dots = Array.from(document.querySelectorAll('[data-mdot]'));
const setActive = (i) => {
if (this._mSlide === i) return;
this._mSlide = i;
tabs.forEach((t, k) => {
const on = k === i;
t.style.color = on ? 'var(--brand-indigo)' : 'var(--text-muted)';
t.style.background = on ? 'linear-gradient(#fff,#fff) padding-box, linear-gradient(135deg,#D0D9FF,#F9C6FF) border-box' : '#fff';
t.style.border = '1px solid transparent';
t.style.fontWeight = on ? '700' : '600';
});
dots.forEach((d, k) => {
const on = k === i;
d.style.width = on ? '22px' : '6px';
d.style.background = on ? 'var(--brand-primary)' : 'var(--surface-tint)';
});
// keep the active tab visible in its own scroller
const t = tabs[i];
if (t) tabsWrap.scrollTo({ left: Math.max(0, t.offsetLeft - 12), behavior: this.reduce ? 'auto' : 'smooth' });
};
// tap a tab -> slide the carousel
tabs.forEach((t) => {
t.addEventListener('click', () => {
const i = parseInt(t.getAttribute('data-mtab'), 10);
car.scrollTo({ left: i * car.clientWidth, behavior: this.reduce ? 'auto' : 'smooth' });
setActive(i);
});
});
// swipe the carousel -> update tabs + dots
let raf = false;
car.addEventListener('scroll', () => {
if (raf) return;
raf = true;
requestAnimationFrame(() => {
raf = false;
const w = car.clientWidth || 1;
setActive(Math.max(0, Math.min(tabs.length - 1, Math.round(car.scrollLeft / w))));
});
}, { passive: true });
setActive(0);
}
// ---- shared motion: unified scroll pipeline + cursor followers ----
_wireMotion() {
let ticking = false;
const onScroll = () => {
if (!ticking) { ticking = true; requestAnimationFrame(() => { this._onScroll(); ticking = false; }); }
};
window.addEventListener('scroll', onScroll, { passive: true });
window.addEventListener('resize', () => this._onScroll(), { passive: true });
if (this.reduce) return;
document.addEventListener('mousemove', (e) => {
document.querySelectorAll('[data-magnetic]').forEach((el) => {
if (!el.offsetParent) return;
const r = el.getBoundingClientRect();
const cx = r.left + r.width / 2, cy = r.top + r.height / 2;
const dx = e.clientX - cx, dy = e.clientY - cy;
const dist = Math.hypot(dx, dy);
const reach = 260, strength = parseFloat(el.getAttribute('data-mag-strength') || '14');
if (dist < reach) {
el.style.transition = 'transform .18s var(--ease-out)';
el.style.transform = `translate(${(dx / reach) * strength}px, ${(dy / reach) * strength}px)`;
} else {
el.style.transform = 'translate(0,0)';
}
});
document.querySelectorAll('[data-tilt]').forEach((el) => {
if (!el.offsetParent) return;
const rx = (e.clientY / window.innerHeight - 0.5) * -8;
const ry = (e.clientX / window.innerWidth - 0.5) * 8;
el.style.transform = `perspective(900px) rotateX(${rx}deg) rotateY(${ry}deg)`;
});
});
}
_onScroll() {
this._updateProgress();
if (!this.reduce) { this._updateParallax(); this._updateScrubX(); if (!this.mobile) this._heroScrub(); }
this._updateRail();
this._revealInView();
}
_updateProgress() {
const bar = document.querySelector('[data-progress-bar]');
if (!bar) return;
const h = document.documentElement.scrollHeight - window.innerHeight;
const p = h > 0 ? Math.min(1, Math.max(0, window.scrollY / h)) : 0;
bar.style.width = (p * 100).toFixed(2) + '%';
}
_updateParallax() {
const y = window.scrollY;
document.querySelectorAll('[data-parallax]').forEach((el) => {
if (!el.offsetParent) return;
const sp = parseFloat(el.getAttribute('data-parallax')) || 0.1;
const ty = Math.min(y * sp, 260);
el.style.transform = `translate(-50%, ${ty}px)`;
});
}
_updateScrubX() {
const vh = window.innerHeight;
document.querySelectorAll('[data-scrub-x]').forEach((el) => {
if (!el.offsetParent) return;
const amt = parseFloat(el.getAttribute('data-scrub-x')) || 80;
const r = el.getBoundingClientRect();
const p = Math.min(1, Math.max(0, 1 - (r.top + r.height / 2) / (vh + r.height)));
el.style.transform = `translateX(${(0.5 - p) * amt}px)`;
});
}
// pinned hero: scroll position selects the Jaice stage, dots fill as you go
_heroScrub() {
const scrub = document.querySelector('[data-hero-scrub]');
if (!scrub) return;
const stickyTop = 64;
const r = scrub.getBoundingClientRect();
const range = r.height - (window.innerHeight - stickyTop);
const p = range > 0 ? Math.min(1, Math.max(0, (stickyTop - r.top) / range)) : 0;
const stage = Math.min(3, Math.floor(p / 0.25));
if (stage !== this._scrubStage) { this._scrubStage = stage; this._setHeroTab(stage); }
document.querySelectorAll('[data-dotfill]').forEach((f, i) => {
const fill = Math.min(1, Math.max(0, (p - i * 0.25) / 0.25));
f.style.width = (fill * 100).toFixed(1) + '%';
});
}
_updateRail() {
if (!this._sections) return;
const rail = document.querySelector('[data-rail]');
if (!rail) return;
const center = window.innerHeight / 2;
let best = 0, bestD = Infinity;
this._sections.forEach((s, i) => {
const r = s.getBoundingClientRect();
const c = r.top + r.height / 2;
const d = Math.abs(c - center);
if (d < bestD) { bestD = d; best = i; }
});
rail.querySelectorAll('[data-raildot] span').forEach((sp, i) => {
const on = i === best;
sp.style.background = on ? 'var(--brand-primary)' : 'var(--surface-tint)';
sp.style.transform = on ? 'scale(1.5)' : 'scale(1)';
sp.style.boxShadow = on ? '0 0 0 4px rgba(13,13,230,0.12)' : 'inset 0 0 0 1px rgba(13,13,230,0.15)';
});
}
_resetReveals(scope) {
scope.querySelectorAll('[data-reveal]').forEach((el) => {
el.dataset.shown = '';
el.style.animation = '';
el.style.opacity = '0';
});
scope.querySelectorAll('[data-counter]').forEach((c) => {
c.dataset.shown = '';
c.textContent = (c.getAttribute('data-prefix') || '') + '0' + (c.getAttribute('data-suffix') || '');
});
scope.querySelectorAll('[data-bars]').forEach((b) => { b.dataset.shown = ''; });
scope.querySelectorAll('[data-bar]').forEach((b) => { b.style.height = '0'; });
}
// Prepare base hidden state (directional). Reveals are driven by a manual rAF
// tween (CSS transitions/animations don't commit reliably in every render context).
_scanReveals() {
const active = document.querySelector('[data-variant="a"]');
if (!active) return;
active.querySelectorAll('[data-reveal]').forEach((el) => {
if (el.dataset.shown === '1') return;
if (this.reduce) { el.style.opacity = '1'; el.style.transform = 'none'; el.dataset.shown = '1'; return; }
const dir = el.getAttribute('data-reveal') || '';
el.style.transition = 'none';
el.style.opacity = '0';
el.style.transform = dir === 'left' ? 'translateX(-46px)' : dir === 'right' ? 'translateX(46px)' : dir === 'scale' ? 'scale(.92)' : 'translateY(28px)';
});
this._revealInView();
}
_revealInView() {
const active = document.querySelector('[data-variant="a"]');
if (!active) return;
const trigger = window.innerHeight * 0.9;
let n = 0;
active.querySelectorAll('[data-reveal],[data-counter],[data-bars]').forEach((el) => {
if (el.dataset.shown === '1') return;
const r = el.getBoundingClientRect();
if (r.top < trigger && r.bottom > -60) {
el.dataset.shown = '1';
if (el.hasAttribute('data-counter')) { this._runCounter(el); return; }
if (el.hasAttribute('data-bars')) { this._runBars(el); return; }
if (this.reduce) { el.style.opacity = '1'; el.style.transform = 'none'; return; }
this._tweenReveal(el, Math.min(n, 4) * 80); n++;
}
});
}
_tweenReveal(el, delay) {
const dir = el.getAttribute('data-reveal') || '';
let ax = 0, ay = 0, sc = 1;
if (dir === 'left') ax = -46; else if (dir === 'right') ax = 46; else if (dir === 'scale') sc = 0.92; else ay = 28;
const dur = 640; let started = null;
const run = (t) => {
if (started === null) started = t;
const p = Math.min(1, (t - started) / dur);
const e = 1 - Math.pow(1 - p, 3);
el.style.opacity = e.toFixed(3);
el.style.transform = dir === 'scale'
? `scale(${(sc + (1 - sc) * e).toFixed(4)})`
: `translate(${(ax * (1 - e)).toFixed(1)}px, ${(ay * (1 - e)).toFixed(1)}px)`;
if (p < 1) requestAnimationFrame(run);
else { el.style.opacity = '1'; el.style.transform = 'none'; }
};
setTimeout(() => requestAnimationFrame(run), delay);
setTimeout(() => { el.style.opacity = '1'; el.style.transform = 'none'; }, delay + dur + 260);
}
_runBars(wrap) {
wrap.querySelectorAll('[data-bar]').forEach((b) => {
b.style.height = (parseFloat(b.getAttribute('data-bar')) || 0) + '%';
});
}
_runCounter(el) {
const to = parseFloat(el.getAttribute('data-counter'));
const dec = parseInt(el.getAttribute('data-dec') || '0', 10);
const prefix = el.getAttribute('data-prefix') || '';
const suffix = el.getAttribute('data-suffix') || '';
const dur = 1500; const t0 = performance.now();
const fmt = (v) => {
if (dec > 0) return v.toFixed(dec);
return Math.round(v).toLocaleString('en-US');
};
const step = (t) => {
const p = Math.min(1, (t - t0) / dur);
const e = 1 - Math.pow(1 - p, 3);
el.textContent = prefix + fmt(to * e) + suffix;
if (p < 1) requestAnimationFrame(step);
};
requestAnimationFrame(step);
}
_wireGlow() {
document.querySelectorAll('[data-glow]').forEach((w) => {
if (w._glowWired) return; w._glowWired = true;
const ring = w.querySelector('[data-glow-ring]');
if (ring) { ring.style.opacity = '0.001'; requestAnimationFrame(() => requestAnimationFrame(() => { ring.style.opacity = '0'; })); }
w.addEventListener('mouseenter', () => {
if (this.reduce) return;
w.style.boxShadow = '0 4px 15px rgba(0,0,0,0.2)';
if (ring) { ring.style.animation = 'none'; void ring.offsetWidth; ring.style.animation = 'ctaSpin 1.4s cubic-bezier(.22,.61,.36,1) forwards'; }
w.style.animation = 'none'; void w.offsetWidth; w.style.animation = 'ctaPad 1.4s cubic-bezier(.22,.61,.36,1) forwards';
});
w.addEventListener('mouseleave', () => { w.style.boxShadow = 'none'; });
});
}
renderVals() { return { promoCode: '{{HANDLE}}20' }; }
}
document.addEventListener('DOMContentLoaded', function(){ try { var c = new Component(); if (c.componentDidMount) c.componentDidMount(); } catch(e){ console.error(e); } });