UNPKG

@laubloch/scrolly-motion

Version:

Advanced scroll animation library with breakpoint support, timeline presets.

1,540 lines 62.2 kB
var N = Object.defineProperty; var I = (u, e, t) => e in u ? N(u, e, { enumerable: !0, configurable: !0, writable: !0, value: t }) : u[e] = t; var d = (u, e, t) => (I(u, typeof e != "symbol" ? e + "" : e, t), t); const j = { selector: "[data-scroll], [data-animation]", defaultEnter: "50vh", breakpoints: { default: "(min-width: 1px)", mobile: "(max-width: 767px)", tablet: "(min-width: 768px) and (max-width: 1023px)", desktop: "(min-width: 1024px)" }, presets: {} }, C = { enterClassNames: [], leaveClassNames: [] }, W = { enter: "10vh", exit: "40vh", once: !1, damping: 0, friction: 0.95 }, A = [ "large", "desktop", "tablet", "mobile", "default" ]; function $(u, e) { let t; return function(...s) { clearTimeout(t), t = setTimeout(() => u.apply(this, s), e); }; } function E(u, e) { return u ? u.trim().replace(/^['"]|['"]$/g, "").split(/\s+/).filter(Boolean) : e; } function M(u, e) { if (typeof u == "number") return u > 0 && u < 1 ? u * e : u; if (typeof u == "string") { const t = u.match(/^(-?[\d.]+)(vh|px|%)$/); if (t) { const s = parseFloat(t[1]), r = t[2]; if (r === "%" || r === "vh") return e * (s / 100); if (r === "px") return s; } } return 0; } function L(u) { const e = /* @__PURE__ */ new Map(); return Object.entries(u).forEach(([t, s]) => { const r = window.matchMedia(s); e.set(t, r); }), e; } function Y(u) { const e = [], t = [], s = { ...u }; if (u.selector && typeof u.selector != "string" && e.push("`selector` must be a string."), u.defaultEnter && typeof u.defaultEnter != "string" && e.push("`defaultEnter` must be a string."), u.breakpoints) if (typeof u.breakpoints != "object" || u.breakpoints === null) e.push("`breakpoints` must be an object."); else for (const r in u.breakpoints) typeof u.breakpoints[r] != "string" && t.push( `Breakpoint \`${r}\` has an invalid value. It must be a string.` ); if (u.presets) if (typeof u.presets != "object" || u.presets === null) e.push("`presets` must be an object."); else for (const r in u.presets) Array.isArray(u.presets[r]) || t.push( `Preset \`${r}\` has an invalid value. It must be an array of TimelineStep objects.` ); return { isValid: e.length === 0, errors: e, warnings: t, sanitizedConfig: s }; } class P { constructor(e) { d(this, "valueParser"); this.valueParser = e; } parse(e) { try { const t = []; return e.split(";").forEach((r) => { const n = r.trim(), o = n.indexOf(":"); if (o === -1) return; const a = n.substring(0, o), i = n.substring(o + 1); let l; if (a === "from") l = 0; else if (a === "to") l = 1; else if (a.startsWith("via-") && a.endsWith("%")) { const h = a.substring(4, a.length - 1), p = parseFloat(h); if (isNaN(p)) return; l = p / 100; } else if (l = parseFloat(a), isNaN(l)) return; const c = {}; i.split("|").forEach((h) => { if (h.startsWith("letter-spacing")) { const f = h.substring(15); c["letter-spacing"] = this.valueParser.parse( "letter-spacing", f ); return; } const p = h.indexOf("-"); if (p > 0) { const f = h.substring(0, p); let g = h.substring(p + 1); g.startsWith("-") && (g = g), f && g !== void 0 && (c[f] = this.valueParser.parse(f, g)); } }), t.push({ at: l, properties: c }); }), t.sort((r, n) => r.at - n.at); } catch (t) { return console.error( `ScrollyMotion: Failed to parse timeline: "${e}"`, t ), []; } } } class V { constructor(e) { d(this, "pluginManager"); this.pluginManager = e; } parse(e, t) { try { const s = this.pluginManager.parse(e, t); return s !== void 0 ? s : t.startsWith("[") && t.endsWith("]") ? this.parseArbitraryValue(e, t.slice(1, -1)) : this.parseTailwindValue(e, t); } catch (s) { return console.error( `ScrollyMotion: Failed to parse value for property "${e}": "${t}"`, s ), t; } } parseArbitraryValue(e, t) { if (e.includes("translate") || e.includes("scale") || e.includes("rotate") || e.includes("blur") || e.includes("grayscale") || e.includes("skew") || e.includes("perspective")) { if (/^-?[\d.]+[a-z%]+$/i.test(t)) return t; if (/^-?[\d.]+$/.test(t)) { const s = parseFloat(t); return e.includes("rotate") ? `${s}deg` : e.includes("scale") ? s : `${s}px`; } } return e === "opacity" ? parseFloat(t) : t; } parseTailwindValue(e, t) { if (e.includes("translate")) return this.parseTailwindSpacing(t); if (e === "opacity") return this.parseTailwindOpacity(t); if (e.includes("scale")) return this.parseTailwindScale(t); if (e.includes("rotate")) return this.parseTailwindRotate(t); if (e.includes("blur")) return this.parseTailwindBlur(t); if (e.includes("grayscale")) return this.parseTailwindGrayscale(t); if (e.includes("skew")) return this.parseTailwindSkew(t); if (e.includes("perspective")) return this.parseTailwindPerspective(t); const s = parseFloat(t); return isNaN(s) ? t : s; } parseTailwindSpacing(e) { const t = parseFloat(e); return isNaN(t) ? 0 : t * 4; } parseTailwindOpacity(e) { const t = parseFloat(e); return isNaN(t) ? 0 : t * 0.01; } parseTailwindScale(e) { const t = parseFloat(e); return isNaN(t) ? 1 : t * 0.01; } parseTailwindRotate(e) { const t = parseFloat(e); return isNaN(t) ? 0 : t; } parseTailwindBlur(e) { const t = parseFloat(e); return isNaN(t) ? 0 : t * 4; } parseTailwindGrayscale(e) { const t = parseFloat(e); return isNaN(t) ? 0 : t * 0.01; } parseTailwindSkew(e) { const t = parseFloat(e); return isNaN(t) ? 0 : t; } parseTailwindPerspective(e) { const t = parseFloat(e); return isNaN(t) ? 0 : t; } } class D { constructor(e, t, s) { d(this, "timelinePresets"); d(this, "mediaQueries"); d(this, "timelineParser"); d(this, "valueParser"); this.timelinePresets = e, this.mediaQueries = t, this.valueParser = new V(s), this.timelineParser = new P(this.valueParser); } parse(e) { if (!e) return null; const t = this.parseBreakpoints(e), s = this.getActiveConfig(t); if (!s) return null; const r = e.match( /transition-duration-(\d+)/ ); r && (s.transitionDuration = parseInt( r[1], 10 )); const n = e.match( /transition-easing-([a-zA-Z-]+)/ ); return n && (s.transitionEasing = n[1]), s; } parseBreakpoints(e) { const t = {}; return e.trim().split(/(?=@)/).forEach((r) => { const n = r.trim(); if (!n) return; let o = "default", a = n; if (n.startsWith("@")) { const i = n.indexOf(":"); i > -1 && (o = n.substring(1, i), a = n.substring(i + 1)); } t[o] || (t[o] = { from: {}, to: {}, breakpoint: o }), this.processConfig(o, a, t); }), t; } processConfig(e, t, s) { if (s[e] || (s[e] = { from: {}, to: {}, breakpoint: e }), t.includes(";") && (t.includes("from:") || t.includes("via-") || t.includes("to:"))) { s[e].timeline = this.timelineParser.parse(t); return; } (t.includes(";") ? t.split(";").map((o) => o.trim()) : [t.trim()]).forEach((o) => { o && this.parsePart(o, s[e]); }); } parsePart(e, t) { try { e.split(/\s+(?=(?:from|to|timeline|preset):)/).forEach((r) => { const n = r.trim(); if (n.startsWith("from:")) { const o = n.substring(5); this.parseProps(o, t.from); } else if (n.startsWith("to:")) { const o = n.substring(3); this.parseProps(o, t.to); } else if (n.startsWith("timeline:")) { const o = n.substring(9); t.timeline = this.timelineParser.parse(o); } else if (n.startsWith("preset:")) { const o = n.substring(7).trim(), a = this.timelinePresets.get(o); a ? t.timeline = a : console.warn(`ScrollyMotion: Unknown preset: ${o}`); } }); } catch (s) { console.error( `ScrollyMotion: Failed to parse animation part: "${e}"`, s ); } } parseProps(e, t) { try { e.split("|").forEach((r) => { if (r.startsWith("letter-spacing")) { const o = r.substring(15); t["letter-spacing"] = this.valueParser.parse( "letter-spacing", o ); return; } const n = r.indexOf("-"); if (n > 0) { const o = r.substring(0, n); let a = r.substring(n + 1); a.startsWith("-") && (a = a), o && a !== void 0 && (t[o] = this.valueParser.parse(o, a)); } }); } catch (s) { console.error( `ScrollyMotion: Failed to parse properties: "${e}"`, s ); } } getActiveConfig(e) { for (const t of A) if (e[t]) { const s = this.mediaQueries.get(t); if (s && s.matches) return e[t]; } return e.default || null; } } class B { constructor(e) { d(this, "timelineParser"); d(this, "valueParser"); this.valueParser = new V(e), this.timelineParser = new P(this.valueParser); } parse(e) { if (!e) return null; try { const t = e.match( /transition-duration-(\d+)/ ), s = e.match( /transition-easing-([a-zA-Z-]+)/ ), r = e.match(/^\[([^\]]+)\]:/); if (!r) return null; let n = r[1]; n.startsWith("&") && (n = n.substring(1)); const o = e.substring(r[0].length), a = { selector: n, from: {}, to: {}, staggerDelay: 0.1, breakpoint: "default", transitionDuration: t ? parseInt(t[1], 10) : void 0, transitionEasing: s ? s[1] : void 0 }; if (o.includes(";") && (o.includes("from:") || o.includes("via-") || o.includes("to:")) || o.includes("via-")) this.processSection("timeline", o, a); else if (o.startsWith("timeline:")) { const l = o.substring(9); this.processSection("timeline", l, a); } else { const l = o.split(":"); let c = "", m = ""; l.forEach((h) => { h === "from" || h === "to" ? (c && m && this.processSection(c, m, a), c = h, m = "") : (m && (m += ":"), m += h); }), c && m && this.processSection(c, m, a); } return a; } catch (t) { return console.error( `ScrollyMotion: Failed to parse stagger animation: "${e}"`, t ), null; } } parseMultiBreakpoint(e) { var t; try { const s = {}, r = /(@[^:]+:)/g, n = e.split(r).filter(Boolean); let o = "default"; for (let a = 0; a < n.length; a++) { const i = n[a].trim(); if (i.startsWith("@") && i.endsWith(":")) { o = i.slice(1, -1); const l = (t = n[a + 1]) == null ? void 0 : t.trim(); if (l) { const c = this.parse(l); c && (s[o] = c), a++; } } else { const l = this.parse(i); l && (s.default = l); } } return s; } catch (s) { return console.error( `ScrollyMotion: Failed to parse multi-breakpoint stagger animation: "${e}"`, s ), {}; } } processSection(e, t, s) { if (e === "from" || e === "to") t.split("|").forEach((n) => { if (n.startsWith("stagger-")) s.staggerDelay = parseFloat(n.substring(8)) || 0.1; else { const o = n.indexOf("-"); if (o > 0) { const a = n.substring(0, o), i = n.substring(o + 1); if (a && i !== void 0) { const l = e === "from" ? s.from : s.to; l[a] = this.valueParser.parse(a, i); } } } }); else if (e === "timeline") { let r = t, n = 0.1, o; t.includes(";") ? o = t.split(";") : o = t.split(/\s+(?=(?:from:|via-|to:))/); const a = []; o.forEach((i) => { const l = i.trim(); if (l.includes("|stagger-")) { const c = l.split("|"), m = c.find((p) => p.startsWith("stagger-")); m && (n = parseFloat(m.substring(8)) || 0.1); const h = c.filter( (p) => !p.startsWith("stagger-") ); a.push(h.join("|")); } else a.push(l); }), s.staggerDelay = n, r = a.join("; "), s.timeline = this.timelineParser.parse(r); } } } class R { constructor(e, t, s) { d(this, "animationParser"); d(this, "staggerParser"); this.animationParser = new D( e, t, s ), this.staggerParser = new B(s); } parseAnimation(e) { try { return this.animationParser.parse(e); } catch (t) { return console.error("Failed to parse animation:", t), null; } } parseStaggerAnimation(e, t = "default") { try { return this.staggerParser.parse(e); } catch (s) { return console.error("Failed to parse stagger animation:", s), null; } } parseMultiBreakpointStagger(e) { try { return this.staggerParser.parseMultiBreakpoint(e); } catch (t) { return console.error("Failed to parse multi-breakpoint stagger animation:", t), {}; } } } class X { /** * Update regular animation based on scroll progress */ updateAnimation(e, t) { if (!e._animationConfig) return; const { from: s, to: r, timeline: n } = e._animationConfig; if (n && n.length > 0) { const i = this.calculateTimelineValues(n, t); this.applyAnimationValues(e, i); return; } const o = {}; (/* @__PURE__ */ new Set([...Object.keys(s), ...Object.keys(r)])).forEach((i) => { const l = s[i], c = r[i]; if (l !== void 0 && c !== void 0) if (typeof l == "number" && typeof c == "number") o[i] = l + (c - l) * t; else if (typeof l == "string" && typeof c == "string") { const m = l.match(/^(-?[\d.]+)([a-z%]+)$/i), h = c.match(/^(-?[\d.]+)([a-z%]+)$/i); if (m && h && m[2] === h[2]) { const p = parseFloat(m[1]), f = parseFloat(h[1]), g = p + (f - p) * t; o[i] = g + m[2]; } else o[i] = t < 0.5 ? l : c; } else o[i] = t < 0.5 ? l : c; else if (l !== void 0 && c === void 0) { const m = i === "opacity" ? 1 : 0; typeof l == "number" ? o[i] = l + (m - l) * t : o[i] = l; } else l === void 0 && c !== void 0 && (typeof c == "number" ? o[i] = 0 + (c - 0) * t : o[i] = t > 0 ? c : void 0); }), this.applyAnimationValues(e, o); } /** * Calculate timeline values for a given progress */ calculateTimelineValues(e, t) { const s = {}; let r = null, n = null; for (let o = 0; o < e.length; o++) { const a = e[o]; if (t >= a.at) r = a, n = e[o + 1] || null; else break; } if (r || (r = e[0], n = e[1] || null), n && t > r.at) { const o = (t - r.at) / (n.at - r.at); (/* @__PURE__ */ new Set([ ...Object.keys(r.properties), ...Object.keys(n.properties) ])).forEach((i) => { const l = r.properties[i], c = n.properties[i]; if (l !== void 0 && c !== void 0) if (typeof l == "number" && typeof c == "number") s[i] = l + (c - l) * o; else if (typeof l == "string" && typeof c == "string") { const m = l.match(/^(-?[\d.]+)([a-z%]+)$/i), h = c.match(/^(-?[\d.]+)([a-z%]+)$/i); if (m && h && m[2] === h[2]) { const p = parseFloat(m[1]), f = parseFloat(h[1]), g = p + (f - p) * o; s[i] = g + m[2]; } else s[i] = o < 0.5 ? l : c; } else s[i] = o < 0.5 ? l : c; else l !== void 0 ? s[i] = l : c !== void 0 && (s[i] = o > 0 ? c : void 0); }); } else Object.assign(s, r.properties); return s; } /** * Update stagger animation for multiple children */ updateStaggerAnimation(e, t) { if (!e._staggerConfig || e._staggerChildren.length === 0) return; const { from: s, to: r, staggerDelay: n, timeline: o } = e._staggerConfig, a = e._staggerChildren.length, i = (a - 1) * n; e._staggerChildren.forEach((l, c) => { let m = 0; if (t > 0) { const p = c * n / (1 + i), f = p + 1 / (1 + i); t >= p && (m = Math.min( 1, (t - p) / (f - p) )); } else if (t < 0 && !e._once) { const f = (a - 1 - c) * n / (1 + i), g = f + 1 / (1 + i), y = Math.abs(t); y >= f ? m = 1 - Math.min( 1, (y - f) / (g - f) ) : m = 1; } let h = {}; o && o.length > 0 ? h = this.calculateTimelineValues(o, m) : (/* @__PURE__ */ new Set([...Object.keys(s), ...Object.keys(r)])).forEach((f) => { const g = s[f], y = r[f]; if (g !== void 0 && y !== void 0) typeof g == "number" && typeof y == "number" ? h[f] = g + (y - g) * m : h[f] = m < 0.5 ? g : y; else if (g !== void 0) { const b = f === "opacity" ? 1 : 0; typeof g == "number" ? h[f] = g + (b - g) * m : h[f] = g; } else y !== void 0 && (typeof y == "number" ? h[f] = 0 + (y - 0) * m : h[f] = m > 0 ? y : void 0); }), this.applyAnimationValues(l, h); }); } /** * Apply animation values to an element */ applyAnimationValues(e, t) { const s = [], r = []; let n = !1, o = !1; if (Object.keys(t).forEach((a) => { const i = t[a]; if (i != null) if (a === "opacity") e.style.opacity = i.toString(); else if (a === "translateX") { const l = typeof i == "string" && i.match(/[a-z%]/i) ? i : `${i}px`; s.push(`translateX(${l})`), n = !0; } else if (a === "translateY") { const l = typeof i == "string" && i.match(/[a-z%]/i) ? i : `${i}px`; s.push(`translateY(${l})`), n = !0; } else if (a === "translateZ") { const l = typeof i == "string" && i.match(/[a-z%]/i) ? i : `${i}px`; s.push(`translateZ(${l})`), n = !0; } else if (a === "scale") s.push(`scale(${i})`), n = !0; else if (a === "scaleX") s.push(`scaleX(${i})`), n = !0; else if (a === "scaleY") s.push(`scaleY(${i})`), n = !0; else if (a === "rotate") { const l = typeof i == "string" && i.match(/[a-z%]/i) ? i : `${i}deg`; s.push(`rotate(${l})`), n = !0; } else if (a === "rotateX") { const l = typeof i == "string" && i.match(/[a-z%]/i) ? i : `${i}deg`; s.push(`rotateX(${l})`), n = !0; } else if (a === "rotateY") { const l = typeof i == "string" && i.match(/[a-z%]/i) ? i : `${i}deg`; s.push(`rotateY(${l})`), n = !0; } else if (a === "rotateZ") { const l = typeof i == "string" && i.match(/[a-z%]/i) ? i : `${i}deg`; s.push(`rotateZ(${l})`), n = !0; } else if (a === "skewX") { const l = typeof i == "string" && i.match(/[a-z%]/i) ? i : `${i}deg`; s.push(`skewX(${l})`), n = !0; } else if (a === "skewY") { const l = typeof i == "string" && i.match(/[a-z%]/i) ? i : `${i}deg`; s.push(`skewY(${l})`), n = !0; } else if (a === "blur") { const l = typeof i == "string" && i.match(/[a-z%]/i) ? i : `${i}px`; r.push(`blur(${l})`), o = !0; } else if (a === "grayscale") r.push(`grayscale(${i})`), o = !0; else if (a === "perspective") { const l = typeof i == "string" && i.match(/[a-z%]/i) ? i : `${i}px`; e.style.perspective = l; } else if (a === "letter-spacing") { const l = typeof i == "string" && i.match(/[a-z%]/i) ? i : `${i}px`; e.style.letterSpacing = l; } else try { e.style[a] = i; } catch (l) { console.error(`Failed to apply style ${a}: ${i}`, l); } }), n) { const a = s.join(" "); e.style.transform = a; } if (o) { const a = r.join(" "); e.style.filter = a; } } } class Q { constructor() { d(this, "themedElements", /* @__PURE__ */ new Map()); d(this, "body", document.body); } registerElement(e, t) { this.themedElements.set(e, { theme: t, inView: !1 }); } updateElementViewStatus(e, t) { this.themedElements.has(e) && (this.themedElements.get(e).inView = t), this.updateBodyTheme(); } updateBodyTheme() { const e = Array.from(this.themedElements.values()).filter((t) => t.inView).map((t) => t.theme); e.length > 0 ? this.body.setAttribute( "data-theme", e[e.length - 1] ) : this.body.removeAttribute("data-theme"); } } const Z = /* @__PURE__ */ new Set([ "translateX", "translateY", "translateZ", "scale", "scaleX", "scaleY", "rotate", "rotateX", "rotateY", "rotateZ", "skewX", "skewY", "perspective" ]), U = /* @__PURE__ */ new Set(["blur", "grayscale"]); function q(u) { const e = /* @__PURE__ */ new Set(); return u.timeline ? u.timeline.forEach((t) => { Object.keys(t.properties).forEach((s) => e.add(s)); }) : ("from" in u && Object.keys(u.from).forEach((t) => e.add(t)), "to" in u && Object.keys(u.to).forEach((t) => e.add(t))), e; } function _(u, e = 200, t = "linear") { const s = q(u), r = /* @__PURE__ */ new Set(); let n = !1, o = !1; return s.forEach((a) => { Z.has(a) ? n = !0 : U.has(a) ? o = !0 : r.add( `${a.replace(/([A-Z])/g, "-$1").toLowerCase()} ${e}ms ${t}` ); }), n && r.add(`transform ${e}ms ${t}`), o && r.add(`filter ${e}ms ${t}`), Array.from(r).join(", "); } class K { constructor(e, t, s, r, n) { d(this, "elements"); d(this, "selector"); d(this, "config"); d(this, "parser"); d(this, "animation"); d(this, "themeManager"); d(this, "mediaQueries"); d(this, "intersectionObserver"); d(this, "activeElements", /* @__PURE__ */ new Set()); this.elements = /* @__PURE__ */ new Map(), this.selector = e.selector || "[data-scroll], [data-animation]", this.config = e, this.parser = t, this.animation = s, this.themeManager = r, this.mediaQueries = n, this.intersectionObserver = new IntersectionObserver( this.handleIntersection.bind(this), { rootMargin: "0px", threshold: 0 } ); } handleIntersection(e) { e.forEach((t) => { const s = this.elements.get(t.target); s && (t.isIntersecting ? this.activeElements.add(s) : this.activeElements.delete(s)); }); } discoverElements() { document.querySelectorAll(this.selector).forEach((t) => { this.elements.has(t) || (this.elements.set(t, t), this.intersectionObserver.observe(t)); }); } measureElements() { const e = window.innerHeight; this.elements.forEach((t) => { this._parseElementConfig(t, e), this._setupAnimationConfig(t); }); } _applyInitialVisualState(e) { if (e._animationConfig) { let t = {}; if (e._animationConfig.timeline && e._animationConfig.timeline.length > 0) { const s = e._animationConfig.timeline[0]; s.at === 0 && (t = s.properties); } else e._animationConfig.from && Object.keys(e._animationConfig.from).length > 0 && (t = e._animationConfig.from); Object.keys(t).length > 0 && (this.animation.applyAnimationValues(e, t), e.style.setProperty("--element-progress", "0.000")); } if (e._staggerConfig) { let t = {}; if (e._staggerConfig.timeline && e._staggerConfig.timeline.length > 0) { const s = e._staggerConfig.timeline[0]; s.at === 0 && (t = s.properties); } else e._staggerConfig.from && Object.keys(e._staggerConfig.from).length > 0 && (t = e._staggerConfig.from); if (Object.keys(t).length > 0) { let s = []; if (e._staggerConfig.selector.startsWith(">")) { const r = e._staggerConfig.selector.substring(1); s = Array.from(e.children).filter( (n) => n.matches(r) ); } else s = Array.from( e.querySelectorAll(e._staggerConfig.selector) ); s.forEach((r) => { this.animation.applyAnimationValues(r, t); }), e.style.setProperty("--element-progress", "0.000"); } } } _parseElementConfig(e, t) { const s = window.scrollY || window.pageYOffset, r = e.getBoundingClientRect(), n = s + r.top, o = r.height, a = e.hasAttribute("data-scroll"), i = e.hasAttribute("data-animation"), c = (!a && i ? Object.entries(W).map(([g, y]) => `${g}:${y}`).join(";") : e.getAttribute("data-scroll") || "").split(/\s*;\s*/).reduce((g, y) => { const [b, z] = y.split(/\s*:\s*/); if (!b) return g; let w = z; return w === "true" || w === "false" ? w = w === "true" : isNaN(w) || (w = parseFloat(w)), g[b] = w, g; }, {}), m = M( c.enter != null ? c.enter : this.config.defaultEnter, t ), h = M(c.exit != null ? c.exit : 0, t); let p; if (c.exit != null && c.distance == null) { const g = n - (t - m); p = n + o - h - g; } else p = M(c.distance != null ? c.distance : o, t); if (e._enterAt = n - (t - m), e._distance = p, e._exitAt = n + o - h, e._theme = c.theme, c.theme && this.themeManager.registerElement(e, c.theme), e._once = c.once, e._enterClassNames = E( c.enterClass || c.class, C.enterClassNames ), e._leaveClassNames = E( c.leaveClass, C.leaveClassNames ), e._hasEnteredOnce = !1, !e._wcElements) { const g = E(c.wc, []); e._wcElements = g.flatMap( (y) => Array.from(e.querySelectorAll(y)) ); } e._targetProgress = 0, e._currentProgress = 0; const f = e.getAttribute("data-animation"); if (f) { const g = f.includes(":["); f.includes("]:") ? (e._multiStaggerConfig = this.parser.parseMultiBreakpointStagger(f), e._staggerConfig = this.getActiveStaggerConfig(e._multiStaggerConfig), e._animationConfig = null) : g ? (e._staggerConfig = this.parser.parseStaggerAnimation(f), e._animationConfig = null) : (e._animationConfig = this.parser.parseAnimation(f), e._staggerConfig = null); } else e._animationConfig = null, e._staggerConfig = null; e._animeInstance = null, e._staggerChildren = [], this._applyInitialVisualState(e); } _setupAnimationConfig(e) { if (e._staggerConfig) { const t = e._staggerConfig; if (!e._staggerChildren || e._staggerChildren.length === 0) { let s = []; if (t.selector.startsWith(">")) { const r = t.selector.substring(1); s = Array.from(e.children).filter( (n) => n.matches(r) ); } else s = Array.from( e.querySelectorAll(t.selector) ); e._staggerChildren = s, s.forEach((r) => { const n = _( t, t.transitionDuration, t.transitionEasing ); n && (r.style.transition = n); }); } } if (e._animationConfig) { const t = e._animationConfig, s = _( t, t.transitionDuration, t.transitionEasing ); s && (e.style.transition = s); } } getElements() { return Array.from(this.elements.values()); } getActiveElements() { return Array.from(this.activeElements); } destroy() { this.intersectionObserver.disconnect(), this.elements.clear(); } getActiveStaggerConfig(e) { if (!e) return null; for (const t of A) if (e[t]) { const s = this.mediaQueries.get(t); if (s && s.matches) return e[t]; } return e.default || null; } } class T { constructor() { d(this, "events"); this.events = /* @__PURE__ */ new Map(); } on(e, t) { this.events.has(e) || this.events.set(e, /* @__PURE__ */ new Set()), this.events.get(e).add(t); } off(e, t) { this.events.has(e) && this.events.get(e).delete(t); } emit(e, ...t) { this.events.has(e) && this.events.get(e).forEach((s) => { try { s(...t); } catch (r) { console.error( `ScrollyMotion: Error in event handler for "${e}"`, r ); } }); } destroy() { this.events.clear(); } } class G { constructor() { d(this, "plugins"); this.plugins = /* @__PURE__ */ new Map(); } register(e) { if (this.plugins.has(e.name)) { console.warn( `ScrollyMotion: Plugin "${e.name}" is already registered.` ); return; } this.plugins.set(e.name, e); } getPlugin(e) { return this.plugins.get(e); } parse(e, t) { for (const s of this.plugins.values()) { const r = s.parse(e, t); if (r !== void 0) return r; } } destroy() { this.plugins.clear(); } } class H { constructor(...e) { d(this, "prevScrollY"); d(this, "ticking"); d(this, "body"); d(this, "onResize"); d(this, "mediaQueries"); d(this, "modules"); d(this, "parser"); d(this, "animation"); d(this, "themeManager"); d(this, "elementManager"); d(this, "eventManager"); d(this, "pluginManager"); const { modules: t, config: s } = this.parseConstructorArgs(e); this.modules = t, this.eventManager = new T(), this.pluginManager = new G(); const { isValid: r, errors: n, warnings: o, sanitizedConfig: a } = Y(s); if (o.length > 0 && o.forEach((l) => console.warn(`ScrollyMotion: ${l}`)), !r) throw n.forEach((l) => console.error(`ScrollyMotion: ${l}`)), new Error("ScrollyMotion: Invalid configuration."); this.prevScrollY = 0, this.ticking = !1, this.updateScroll = this.updateScroll.bind(this), this.onScroll = this.onScroll.bind(this), this.onResize = $(() => { this.elementManager.measureElements(), this.updateScroll(); }, 200), this.body = document.body, this.mediaQueries = L( a.breakpoints || j.breakpoints ); const i = this.initializePresets(a.presets); this.parser = new R( i, this.mediaQueries, this.pluginManager ), this.animation = new X(), this.themeManager = new Q(), this.elementManager = new K( a, this.parser, this.animation, this.themeManager, this.mediaQueries ), this.initializeModules(), this.init(); } parseConstructorArgs(e) { const t = []; let s = {}; if (e.length === 0) return { modules: t, config: s }; const r = e[e.length - 1]; if (r && typeof r == "object" && !r.name && // modules have a name property !Array.isArray(r) && typeof r != "function") { s = r; for (let o = 0; o < e.length - 1; o++) { const a = e[o]; if (typeof a == "function") { const i = a(); i && i.name && t.push(i); } else a && typeof a == "object" && a.name && t.push(a); } } else if (e.length === 1 && typeof e[0] == "object" && !e[0].name && typeof e[0] != "function") s = e[0]; else for (const o of e) if (typeof o == "function") { const a = o(); a && a.name && t.push(a); } else o && typeof o == "object" && o.name && t.push(o); return console.log( "🔧 ScrollyMotion: Parsed modules:", t.map((o) => o.name) ), { modules: t, config: s }; } initializeModules() { this.modules.forEach((e) => { e.init && e.init(this); }); } hasModule(e) { return this.modules.some((t) => t.name === e); } destroy() { this.modules.forEach((e) => { e.destroy && e.destroy(); }), window.removeEventListener("resize", this.onResize), window.removeEventListener("scroll", this.onScroll), this.elementManager.destroy(), this.eventManager.destroy(), this.pluginManager.destroy(); } init() { const e = () => { this.elementManager.discoverElements(); const t = this.elementManager.getElements(); t.length > 0 && (this.elementManager.measureElements(), t.forEach((s) => { s._currentProgress = 0, s._targetProgress = 0, s._hasStartedAnimating = !1, s.style.setProperty("--element-progress", "0.000"); }), this.updateScroll(), setTimeout(() => { this.updateScroll(); }, 50)); }; document.readyState === "loading" ? window.addEventListener("DOMContentLoaded", e) : e(), window.addEventListener("resize", this.onResize), window.addEventListener("scroll", this.onScroll, { passive: !0 }); } onScroll() { this.ticking || (window.requestAnimationFrame(this.updateScroll), this.ticking = !0); } updateScroll() { const e = window.scrollY || window.pageYOffset; let t = "up"; e >= 300 && (t = e > this.prevScrollY ? "down" : "up"), this.body.setAttribute("data-scroll-direction", t), this.prevScrollY = e, this.elementManager.getActiveElements().forEach((s) => { const { _enterAt: r, _distance: n, _exitAt: o, _once: a } = s, i = Math.min( 1, Math.max(0, (e - r) / n) ); s._targetProgress = i, s._currentProgress = i, s.style.setProperty("--element-progress", i.toFixed(3)); const l = i > 0, c = e > o, m = l && !c; s._theme && this.hasModule("themes") && this.themeManager.updateElementViewStatus(s, m), m ? (s._hasEnteredOnce || this.eventManager.emit("elementEnter", s), s._hasEnteredOnce = !0, s._enterClassNames.forEach((h) => s.classList.add(h)), s._leaveClassNames.forEach((h) => s.classList.remove(h))) : (s._hasEnteredOnce && this.eventManager.emit("elementLeave", s), a && s._hasEnteredOnce || (s._enterClassNames.forEach((h) => s.classList.remove(h)), s._leaveClassNames.forEach((h) => s.classList.add(h)))), this.hasModule("webcomponents") && s._wcElements.forEach((h) => { const p = h; if (typeof p.progress == "function" && p._lastProgress !== i && (p.progress(i), p._lastProgress = i), typeof p.enter == "function") { const f = m; p._lastInView !== f && (f && typeof p.enter == "function" && p.enter(), !f && typeof p.leave == "function" && p.leave(), p._lastInView = f); } }), this.modules.forEach((h) => { h.updateElement && h.updateElement(s, i); }); }), this.ticking = !1; } on(e, t) { this.eventManager.on(e, t); } registerPlugin(e) { this.pluginManager.register(e); } initializePresets(e = {}) { const t = /* @__PURE__ */ new Map(); return Object.entries(e).forEach(([s, r]) => { t.set(s, r); }), t; } getMetrics() { const e = this.elementManager.getElements(), t = e.filter( (n) => n._animationConfig || n._staggerConfig ).length, s = e.length * 0.5 + t * 1.2, r = "transform" in document.createElement("div").style; return { fps: 60, activeElements: t, memoryUsage: s, gpuAccelerated: r }; } // Public API for getting loaded modules getModules() { return [...this.modules]; } // Public API for checking if a module is loaded hasModuleLoaded(e) { return this.hasModule(e); } // ScrollyMotionCore interface methods required by modules getElements() { return this.elementManager.getElements(); } getActiveElements() { return this.elementManager.getActiveElements(); } emit(e, ...t) { this.eventManager.emit(e, ...t); } } class J { constructor(e) { d(this, "name", "timeline"); } init(e) { } destroy() { } updateElement(e, t) { e._animationConfig && this.updateAnimation(e, t), e._staggerConfig && e._staggerChildren.length > 0 && this.updateStaggerAnimation(e, t); } /** * Update regular animation based on scroll progress */ updateAnimation(e, t) { if (!e._animationConfig) return; const { from: s, to: r, timeline: n } = e._animationConfig; if (n && n.length > 0) { const i = this.calculateTimelineValues(n, t); this.applyAnimationValues(e, i); return; } const o = {}; (/* @__PURE__ */ new Set([...Object.keys(s), ...Object.keys(r)])).forEach((i) => { const l = s[i], c = r[i]; if (l !== void 0 && c !== void 0) if (typeof l == "number" && typeof c == "number") o[i] = l + (c - l) * t; else if (typeof l == "string" && typeof c == "string") { const m = l.match(/^(-?[\d.]+)([a-z%]+)$/i), h = c.match(/^(-?[\d.]+)([a-z%]+)$/i); if (m && h && m[2] === h[2]) { const p = parseFloat(m[1]), f = parseFloat(h[1]), g = p + (f - p) * t; o[i] = g + m[2]; } else o[i] = t < 0.5 ? l : c; } else o[i] = t < 0.5 ? l : c; else if (l !== void 0 && c === void 0) { const m = i === "opacity" ? 1 : 0; typeof l == "number" ? o[i] = l + (m - l) * t : o[i] = l; } else l === void 0 && c !== void 0 && (typeof c == "number" ? o[i] = 0 + (c - 0) * t : o[i] = t > 0 ? c : void 0); }), this.applyAnimationValues(e, o); } /** * Calculate timeline values for a given progress */ calculateTimelineValues(e, t) { const s = {}; let r = null, n = null; for (let o = 0; o < e.length; o++) { const a = e[o]; if (t >= a.at) r = a, n = e[o + 1] || null; else break; } if (r || (r = e[0], n = e[1] || null), n && t > r.at) { const o = (t - r.at) / (n.at - r.at); (/* @__PURE__ */ new Set([ ...Object.keys(r.properties), ...Object.keys(n.properties) ])).forEach((i) => { const l = r.properties[i], c = n.properties[i]; if (l !== void 0 && c !== void 0) if (typeof l == "number" && typeof c == "number") s[i] = l + (c - l) * o; else if (typeof l == "string" && typeof c == "string") { const m = l.match(/^(-?[\d.]+)([a-z%]+)$/i), h = c.match(/^(-?[\d.]+)([a-z%]+)$/i); if (m && h && m[2] === h[2]) { const p = parseFloat(m[1]), f = parseFloat(h[1]), g = p + (f - p) * o; s[i] = g + m[2]; } else s[i] = o < 0.5 ? l : c; } else s[i] = o < 0.5 ? l : c; else l !== void 0 ? s[i] = l : c !== void 0 && (s[i] = o > 0 ? c : void 0); }); } else Object.assign(s, r.properties); return s; } /** * Update stagger animation for multiple children */ updateStaggerAnimation(e, t) { if (!e._staggerConfig || e._staggerChildren.length === 0) return; const { from: s, to: r, staggerDelay: n, timeline: o } = e._staggerConfig, a = e._staggerChildren.length, i = (a - 1) * n; e._staggerChildren.forEach((l, c) => { let m = 0; if (t > 0) { const p = c * n / (1 + i), f = p + 1 / (1 + i); t >= p && (m = Math.min( 1, (t - p) / (f - p) )); } else if (t < 0 && !e._once) { const f = (a - 1 - c) * n / (1 + i), g = f + 1 / (1 + i), y = Math.abs(t); y >= f ? m = 1 - Math.min( 1, (y - f) / (g - f) ) : m = 1; } let h = {}; o && o.length > 0 ? h = this.calculateTimelineValues(o, m) : (/* @__PURE__ */ new Set([...Object.keys(s), ...Object.keys(r)])).forEach((f) => { const g = s[f], y = r[f]; if (g !== void 0 && y !== void 0) typeof g == "number" && typeof y == "number" ? h[f] = g + (y - g) * m : h[f] = m < 0.5 ? g : y; else if (g !== void 0) { const b = f === "opacity" ? 1 : 0; typeof g == "number" ? h[f] = g + (b - g) * m : h[f] = g; } else y !== void 0 && (typeof y == "number" ? h[f] = 0 + (y - 0) * m : h[f] = m > 0 ? y : void 0); }), this.applyAnimationValues(l, h); }); } /** * Apply animation values to an element */ applyAnimationValues(e, t) { const s = [], r = []; let n = !1, o = !1; if (Object.keys(t).forEach((a) => { const i = t[a]; if (i != null) if (a === "opacity") e.style.opacity = i.toString(); else if (a === "translateX") { const l = typeof i == "string" && i.match(/[a-z%]/i) ? i : `${i}px`; s.push(`translateX(${l})`), n = !0; } else if (a === "translateY") { const l = typeof i == "string" && i.match(/[a-z%]/i) ? i : `${i}px`; s.push(`translateY(${l})`), n = !0; } else if (a === "translateZ") { const l = typeof i == "string" && i.match(/[a-z%]/i) ? i : `${i}px`; s.push(`translateZ(${l})`), n = !0; } else if (a === "scale") s.push(`scale(${i})`), n = !0; else if (a === "scaleX") s.push(`scaleX(${i})`), n = !0; else if (a === "scaleY") s.push(`scaleY(${i})`), n = !0; else if (a === "rotate") { const l = typeof i == "string" && i.match(/[a-z%]/i) ? i : `${i}deg`; s.push(`rotate(${l})`), n = !0; } else if (a === "rotateX") { const l = typeof i == "string" && i.match(/[a-z%]/i) ? i : `${i}deg`; s.push(`rotateX(${l})`), n = !0; } else if (a === "rotateY") { const l = typeof i == "string" && i.match(/[a-z%]/i) ? i : `${i}deg`; s.push(`rotateY(${l})`), n = !0; } else if (a === "rotateZ") { const l = typeof i == "string" && i.match(/[a-z%]/i) ? i : `${i}deg`; s.push(`rotateZ(${l})`), n = !0; } else if (a === "skewX") { const l = typeof i == "string" && i.match(/[a-z%]/i) ? i : `${i}deg`; s.push(`skewX(${l})`), n = !0; } else if (a === "skewY") { const l = typeof i == "string" && i.match(/[a-z%]/i) ? i : `${i}deg`; s.push(`skewY(${l})`), n = !0; } else if (a === "blur") { const l = typeof i == "string" && i.match(/[a-z%]/i) ? i : `${i}px`; r.push(`blur(${l})`), o = !0; } else if (a === "grayscale") r.push(`grayscale(${i})`), o = !0; else if (a === "perspective") { const l = typeof i == "string" && i.match(/[a-z%]/i) ? i : `${i}px`; e.style.perspective = l; } else if (a === "letter-spacing") { const l = typeof i == "string" && i.match(/[a-z%]/i) ? i : `${i}px`; e.style.letterSpacing = l; } else try { e.style[a] = i; } catch (l) { console.error(`Failed to apply style ${a}: ${i}`, l); } }), n) { const a = s.join(" "); e.style.transform = a; } if (o) { const a = r.join(" "); e.style.filter = a; } } } const S = (u) => new J(u); class k { constructor() { d(this, "name", "stagger"); } init(e) { } destroy() { } parseElement(e) { const t = e.getAttribute("data-animation"); if (!t) return; const s = t.match(/\[([^\]]+)\]:/); if (!s) return; const r = s[1], n = Array.from( e.querySelectorAll(r) ); if (n.length === 0) return; const o = this.parseStaggerAnimation(t, r); if (!o) return; const a = e; a._staggerConfig = o, a._staggerChildren = n; } updateElement(e, t) { } /** * Parse stagger animation configuration from data-animation attribute */ parseStaggerAnimation(e, t) { try { const r = e.replace(`[${t}]:`, "").split(/\s*;\s*/), n = { selector: t, from: {}, to: {}, staggerDelay: 0.1 // default }; for (const o of r) { const a = o.trim(); if (a) if (a.startsWith("from:")) n.from = this.parseAnimationProperties(a.substring(5)); else if (a.startsWith("to:")) { const i = this.parseAnimationProperties( a.substring(3) ); n.to = i.properties, n.staggerDelay = i.staggerDelay || n.staggerDelay; } else a.startsWith("timeline:") && (n.timeline = this.parseTimelineProperties( a.substring(9) )); } return n; } catch (s) { return console.warn("Failed to parse stagger animation:", s), null; } } /** * Parse animation properties from a property string */ parseAnimationProperties(e) { const t = {}; let s; const r = e.split("|"); for (const n of r) { const o = n.trim(); if (!o) continue; if (o.startsWith("stagger-")) { s = parseFloat(o.substring(8)); continue; } const a = o.match(/^([a-zA-Z-]+)-(.+)$/); if (!a) continue; const [, i, l] = a; t[i] = this.parseValue(l); } return { properties: t, staggerDelay: s }; } /** * Parse timeline properties (simplified version) */ parseTimelineProperties(e) { const t = e.split(";"), s = []; for (const r of t) { const n = r.trim(); if (!n) continue; let o = 0, a = ""; if (n.startsWith("from:")) o = 0, a = n.substring(5); else if (n.startsWith("to:")) o = 1, a = n.substring(3); else if (n.includes("via-")) { const i = n.match(/via-(\d+)%:(.+)/); i && (o = parseInt(i[1]) / 100, a = i[2]); } if (a) { const { properties: i } = this.parseAnimationProperties(a); s.push({ at: o, properties: i }); } } return s; } /** * Parse a single value (number, string with units, etc.) */ parseValue(e) { const t = e.match(/^\[(.+)\]$/); if (t) { const r = t[1], n = parseFloat(r); return !isNaN(n) && r === n.toString() ? n : r; } if (e.startsWith("-")) { const r = e.substring(1), n = this.parseValue(r); return typeof n == "number" ? -n : `-${n}`; } const s = parseFloat(e); return isNaN(s) ? e : s; } /** * Get stagger children for an element */ getStaggerChildren(e, t) { return Array.from(e.querySelectorAll(t)); } /** * Update stagger children for an element (useful for dynamic content) */ updateStaggerChildren(e) { if (!e._staggerConfig) return; const t = e._staggerConfig.selector, s = this.getStaggerChildren(e, t); e._staggerChildren = s; } } const x = () => new k(); class ee { constructor() { d(this, "name", "themes"); d(this, "scrollyMotion", null); d(this, "currentTheme", null); d(this, "themeElements", /* @__PURE__ */ new Set()); } init(e) { this.scrollyMotion = e; } destroy() { this.themeElements.clear(), this.scrollyMotion = null; } parseElement(e) { const t = e.getAttribute("data-scroll"); if (!t) return; const s = t.match(/theme:\s*([^;]+)/); if (!s) return; const r = s[1].trim(), n = e; n._theme = r, this.themeElements.add(n); } updateElement(e, t) { if (!e._theme) return; t > 0 && t <= 1 && this.setTheme(e._theme); } /** * Set the current theme on the body element */ setTheme(e) { if (this.currentTheme === e) return; const t = document.body; this.currentTheme && t.removeAttribute(`data-theme-${this.currentTheme}`), t.setAttribute(`data-theme-${e}`, ""), this.currentTheme = e, this.scrollyMotion && this.scrollyMotion.emit("themeChange", e); } /** * Get the current active theme */ getCurrentTheme() { return this.currentTheme; } /** * Manually set a theme */ setManualTheme(e) { this.setTheme(e); } /** * Clear the current theme */ clearTheme() { this.currentTheme && (document.body.removeAttribute(`data-theme-${this.currentTheme}`), this.currentTheme = null, this.scrollyMotion && this.scrollyMotion.emit("themeChange", null)); } /** * Get all elements with theme configuration */ getThemeElements() { return Array.from(this.themeElements); } /** * Remove an element from theme tracking */ removeElement(e) { this.themeElements.delete(e); } /** * Update theme based on currently visible elements * This method can be called to recalculate theme based on all visible elements */ updateThemeFromVisibleElements() { if (!this.scrollyMotion) return; const e = this.scrollyMotion.getActiveElements(); for (const t of e) if (t._theme) { this.setTheme(t._theme); return; } this.clearTheme(); } } const F = () => new ee(); class te { constructor() { d(this, "name", "webcomponents"); d(this, "webComponentElements", /* @__PURE__ */ new Map()); } init(e) { } destroy() { this.webComponentElements.clear(); } parseElement(e) { const t = e.getAttribute("data-scroll"); if (!t) return; const s = t.match(/wc:\s*([^;]+)/); if (!s) return; const r = s[1].trim(), n = Array.from( e.querySelectorAll(r) ); if (n.length === 0) return; const o = e; o._webComponents = r, this.webComponentElements.set(o, n); } updateElement(e, t) { const s = this.webComponentElements.get(e); !s || s.length === 0 || s.forEach((r) => { if (typeof r.progress == "function") try { r.progress(t); } catch (n) { console.warn( "Error calling progress method on web component:", n ); } }); } /** * Handle element enter event */ onElementEnter(e) { const t = this.webComponentElements.get(e); !t || t.length === 0 || t.forEach((s) => { if (typeof s.enter == "function") try { s.en