2354
This commit is contained in:
@@ -0,0 +1,480 @@
|
||||
/* =====================================================================
|
||||
ONAPB — Kinetic FX
|
||||
Capa de interacciones del frontend.
|
||||
Vanilla JS, sin dependencias. Cargar al final de <body> con defer.
|
||||
===================================================================== */
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
var prefersReduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
||||
var hasFinePointer = window.matchMedia('(hover: hover) and (pointer: fine)').matches;
|
||||
var isTouch = !hasFinePointer || ('ontouchstart' in window) || (navigator.maxTouchPoints > 0);
|
||||
|
||||
function ready(fn) {
|
||||
if (document.readyState !== 'loading') fn();
|
||||
else document.addEventListener('DOMContentLoaded', fn);
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────────
|
||||
1. Reveal on scroll (.kfx-reveal, [data-stagger], .kfx-section-tag)
|
||||
───────────────────────────────────────────────────────────────── */
|
||||
function initReveal() {
|
||||
if (!('IntersectionObserver' in window)) {
|
||||
document.querySelectorAll('.kfx-reveal, [data-stagger], .kfx-section-tag, .hero-title, .kinetic-reveal-text, .kinetic-highlight')
|
||||
.forEach(function (el) { el.classList.add('is-in'); });
|
||||
return;
|
||||
}
|
||||
var io = new IntersectionObserver(function (entries) {
|
||||
entries.forEach(function (e) {
|
||||
if (e.isIntersecting) {
|
||||
e.target.classList.add('is-in');
|
||||
io.unobserve(e.target);
|
||||
}
|
||||
});
|
||||
}, { threshold: 0.12, rootMargin: '0px 0px -60px 0px' });
|
||||
|
||||
document.querySelectorAll('.kfx-reveal, [data-stagger], .kfx-section-tag, .hero-title, .kinetic-reveal-text, .kinetic-highlight')
|
||||
.forEach(function (el) { io.observe(el); });
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────────
|
||||
2. Word splitter para reveal de títulos (hero-title, kinetic-reveal-text)
|
||||
───────────────────────────────────────────────────────────────── */
|
||||
function splitWords() {
|
||||
document.querySelectorAll('.hero-title:not([data-split]), .kinetic-reveal-text:not([data-split])').forEach(function (el) {
|
||||
if (el.children.length > 0 && el.querySelector('.word')) return;
|
||||
var text = el.textContent.trim();
|
||||
if (!text) return;
|
||||
var words = text.split(/\s+/);
|
||||
el.innerHTML = words.map(function (w) {
|
||||
return '<span class="word"><span>' + w + '</span></span>';
|
||||
}).join(' ');
|
||||
el.setAttribute('data-split', 'true');
|
||||
});
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────────
|
||||
3. Reading progress bar (top)
|
||||
───────────────────────────────────────────────────────────────── */
|
||||
function initReadingBar() {
|
||||
if (prefersReduced) return;
|
||||
var bar = document.getElementById('kinetic-reading-bar');
|
||||
if (!bar) {
|
||||
bar = document.createElement('div');
|
||||
bar.id = 'kinetic-reading-bar';
|
||||
document.body.appendChild(bar);
|
||||
}
|
||||
var ticking = false;
|
||||
function update() {
|
||||
var scrollTop = window.scrollY || document.documentElement.scrollTop;
|
||||
var docHeight = document.documentElement.scrollHeight - window.innerHeight;
|
||||
var pct = docHeight > 0 ? Math.min(100, (scrollTop / docHeight) * 100) : 0;
|
||||
bar.style.width = pct + '%';
|
||||
ticking = false;
|
||||
}
|
||||
window.addEventListener('scroll', function () {
|
||||
if (!ticking) {
|
||||
window.requestAnimationFrame(update);
|
||||
ticking = true;
|
||||
}
|
||||
}, { passive: true });
|
||||
update();
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────────
|
||||
4. Navbar scroll state
|
||||
───────────────────────────────────────────────────────────────── */
|
||||
function initNavbarScroll() {
|
||||
var nav = document.querySelector('.custom-navbar');
|
||||
if (!nav) return;
|
||||
function update() {
|
||||
if (window.scrollY > 30) nav.classList.add('is-scrolled');
|
||||
else nav.classList.remove('is-scrolled');
|
||||
}
|
||||
window.addEventListener('scroll', update, { passive: true });
|
||||
update();
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────────
|
||||
5. Spotlight para .kinetic-card y .kfx-img-spotlight
|
||||
───────────────────────────────────────────────────────────────── */
|
||||
function initSpotlight() {
|
||||
if (isTouch) return;
|
||||
var els = document.querySelectorAll('.kinetic-card, .kfx-img-spotlight');
|
||||
els.forEach(function (el) {
|
||||
el.addEventListener('mousemove', function (e) {
|
||||
var rect = el.getBoundingClientRect();
|
||||
var x = ((e.clientX - rect.left) / rect.width) * 100;
|
||||
var y = ((e.clientY - rect.top) / rect.height) * 100;
|
||||
el.style.setProperty('--mx', x + '%');
|
||||
el.style.setProperty('--my', y + '%');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────────
|
||||
6. Tilt 3D (data-tilt)
|
||||
───────────────────────────────────────────────────────────────── */
|
||||
function initTilt() {
|
||||
if (isTouch || prefersReduced) return;
|
||||
document.querySelectorAll('[data-tilt]').forEach(function (el) {
|
||||
var max = parseFloat(el.getAttribute('data-tilt-max') || '8');
|
||||
el.addEventListener('mousemove', function (e) {
|
||||
var rect = el.getBoundingClientRect();
|
||||
var x = (e.clientX - rect.left) / rect.width - 0.5;
|
||||
var y = (e.clientY - rect.top) / rect.height - 0.5;
|
||||
el.classList.add('is-tilting');
|
||||
el.style.setProperty('--ry', (x * max) + 'deg');
|
||||
el.style.setProperty('--rx', (-y * max) + 'deg');
|
||||
});
|
||||
el.addEventListener('mouseleave', function () {
|
||||
el.classList.remove('is-tilting');
|
||||
el.style.setProperty('--rx', '0deg');
|
||||
el.style.setProperty('--ry', '0deg');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────────
|
||||
7. Magnetic buttons (data-magnetic)
|
||||
───────────────────────────────────────────────────────────────── */
|
||||
function initMagnetic() {
|
||||
if (isTouch || prefersReduced) return;
|
||||
document.querySelectorAll('[data-magnetic]').forEach(function (el) {
|
||||
var strength = parseFloat(el.getAttribute('data-magnetic-strength') || '0.25');
|
||||
el.addEventListener('mousemove', function (e) {
|
||||
var rect = el.getBoundingClientRect();
|
||||
var x = e.clientX - rect.left - rect.width / 2;
|
||||
var y = e.clientY - rect.top - rect.height / 2;
|
||||
el.style.transform = 'translate(' + (x * strength) + 'px, ' + (y * strength) + 'px)';
|
||||
});
|
||||
el.addEventListener('mouseleave', function () {
|
||||
el.style.transform = '';
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────────
|
||||
8. Ripple en clicks (cualquier .btn, .btn-kinetic-primary)
|
||||
───────────────────────────────────────────────────────────────── */
|
||||
function initRipple() {
|
||||
document.addEventListener('click', function (e) {
|
||||
var btn = e.target.closest('.btn, .btn-kinetic-primary, .btn-admin-primary, .sidebar-link');
|
||||
if (!btn || btn.classList.contains('no-ripple')) return;
|
||||
var rect = btn.getBoundingClientRect();
|
||||
var size = Math.max(rect.width, rect.height);
|
||||
var wave = document.createElement('span');
|
||||
wave.className = 'kinetic-ripple-wave';
|
||||
wave.style.width = wave.style.height = size + 'px';
|
||||
wave.style.left = (e.clientX - rect.left) + 'px';
|
||||
wave.style.top = (e.clientY - rect.top) + 'px';
|
||||
// Posición relativa para contener el ripple
|
||||
var pos = window.getComputedStyle(btn).position;
|
||||
if (pos === 'static') btn.style.position = 'relative';
|
||||
btn.style.overflow = 'hidden';
|
||||
btn.appendChild(wave);
|
||||
setTimeout(function () { wave.remove(); }, 650);
|
||||
});
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────────
|
||||
9. Counter animado (.kfx-counter con data-target)
|
||||
───────────────────────────────────────────────────────────────── */
|
||||
function initCounters() {
|
||||
if (!('IntersectionObserver' in window)) return;
|
||||
var counters = document.querySelectorAll('.kfx-counter[data-target]');
|
||||
if (!counters.length) return;
|
||||
var io = new IntersectionObserver(function (entries) {
|
||||
entries.forEach(function (entry) {
|
||||
if (!entry.isIntersecting) return;
|
||||
var el = entry.target;
|
||||
var target = parseFloat(el.getAttribute('data-target')) || 0;
|
||||
var duration = parseInt(el.getAttribute('data-duration') || '1400', 10);
|
||||
var decimals = parseInt(el.getAttribute('data-decimals') || '0', 10);
|
||||
var prefix = el.getAttribute('data-prefix') || '';
|
||||
var suffix = el.getAttribute('data-suffix') || '';
|
||||
var start = 0;
|
||||
var startTime = null;
|
||||
if (prefersReduced) {
|
||||
el.textContent = prefix + target.toFixed(decimals) + suffix;
|
||||
io.unobserve(el);
|
||||
return;
|
||||
}
|
||||
function tick(ts) {
|
||||
if (!startTime) startTime = ts;
|
||||
var progress = Math.min(1, (ts - startTime) / duration);
|
||||
var eased = 1 - Math.pow(1 - progress, 3); // easeOutCubic
|
||||
var current = start + (target - start) * eased;
|
||||
el.textContent = prefix + current.toFixed(decimals) + suffix;
|
||||
if (progress < 1) requestAnimationFrame(tick);
|
||||
}
|
||||
requestAnimationFrame(tick);
|
||||
io.unobserve(el);
|
||||
});
|
||||
}, { threshold: 0.4 });
|
||||
counters.forEach(function (c) { io.observe(c); });
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────────
|
||||
10. Parallax suave (data-parallax="speed")
|
||||
───────────────────────────────────────────────────────────────── */
|
||||
function initParallax() {
|
||||
if (prefersReduced) return;
|
||||
var els = Array.prototype.slice.call(document.querySelectorAll('[data-parallax]'));
|
||||
if (!els.length) return;
|
||||
var ticking = false;
|
||||
function update() {
|
||||
var vh = window.innerHeight;
|
||||
els.forEach(function (el) {
|
||||
var rect = el.getBoundingClientRect();
|
||||
if (rect.bottom < 0 || rect.top > vh) return;
|
||||
var speed = parseFloat(el.getAttribute('data-parallax')) || 0.2;
|
||||
var offset = (rect.top - vh / 2) * speed * -1;
|
||||
el.style.transform = 'translate3d(0, ' + offset.toFixed(1) + 'px, 0)';
|
||||
});
|
||||
ticking = false;
|
||||
}
|
||||
window.addEventListener('scroll', function () {
|
||||
if (!ticking) {
|
||||
requestAnimationFrame(update);
|
||||
ticking = true;
|
||||
}
|
||||
}, { passive: true });
|
||||
update();
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────────
|
||||
11. Marquee duplicador (.kfx-marquee con un solo track)
|
||||
───────────────────────────────────────────────────────────────── */
|
||||
function initMarquee() {
|
||||
document.querySelectorAll('.kfx-marquee').forEach(function (el) {
|
||||
var track = el.querySelector('.kfx-marquee__track');
|
||||
if (!track || track.dataset.cloned === '1') return;
|
||||
track.dataset.cloned = '1';
|
||||
track.innerHTML += track.innerHTML; // duplicar para loop seamless
|
||||
});
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────────
|
||||
12. Cursor personalizado (sólo desktop hover-fino)
|
||||
───────────────────────────────────────────────────────────────── */
|
||||
function initCustomCursor() {
|
||||
if (!hasFinePointer || isTouch || prefersReduced) return;
|
||||
if (document.querySelector('.kinetic-cursor-dot')) return;
|
||||
|
||||
var dot = document.createElement('div');
|
||||
var ring = document.createElement('div');
|
||||
dot.className = 'kinetic-cursor-dot';
|
||||
ring.className = 'kinetic-cursor-ring';
|
||||
document.body.appendChild(dot);
|
||||
document.body.appendChild(ring);
|
||||
document.body.classList.add('kinetic-cursor-on');
|
||||
|
||||
var mx = window.innerWidth / 2, my = window.innerHeight / 2;
|
||||
var rx = mx, ry = my;
|
||||
|
||||
document.addEventListener('mousemove', function (e) {
|
||||
mx = e.clientX; my = e.clientY;
|
||||
dot.style.transform = 'translate(' + mx + 'px, ' + my + 'px) translate(-50%, -50%)';
|
||||
}, { passive: true });
|
||||
|
||||
function loop() {
|
||||
rx += (mx - rx) * 0.18;
|
||||
ry += (my - ry) * 0.18;
|
||||
ring.style.transform = 'translate(' + rx + 'px, ' + ry + 'px) translate(-50%, -50%)';
|
||||
requestAnimationFrame(loop);
|
||||
}
|
||||
loop();
|
||||
|
||||
// Hover en interactivos
|
||||
var hoverSel = 'a, button, .btn, .kinetic-card, [role="button"], [data-tilt], [data-magnetic], .nav-link, input[type="submit"]';
|
||||
document.addEventListener('mouseover', function (e) {
|
||||
if (e.target.closest(hoverSel)) {
|
||||
ring.classList.add('is-hover');
|
||||
dot.classList.add('is-hover');
|
||||
}
|
||||
});
|
||||
document.addEventListener('mouseout', function (e) {
|
||||
if (e.target.closest(hoverSel)) {
|
||||
ring.classList.remove('is-hover');
|
||||
dot.classList.remove('is-hover');
|
||||
}
|
||||
});
|
||||
document.addEventListener('mouseleave', function () {
|
||||
ring.style.opacity = '0';
|
||||
dot.style.opacity = '0';
|
||||
});
|
||||
document.addEventListener('mouseenter', function () {
|
||||
ring.style.opacity = '1';
|
||||
dot.style.opacity = '1';
|
||||
});
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────────
|
||||
13. Auto-aplicar reveal a tarjetas / encabezados (best-effort)
|
||||
───────────────────────────────────────────────────────────────── */
|
||||
function autoTagReveal() {
|
||||
// Tarjetas comunes en home/eventos
|
||||
document.querySelectorAll('.kinetic-card:not(.kfx-reveal)').forEach(function (el) {
|
||||
el.classList.add('kfx-reveal');
|
||||
if (!el.hasAttribute('data-fx')) el.setAttribute('data-fx', 'up');
|
||||
});
|
||||
|
||||
// Display headings (h1.display-* / h2.display-*)
|
||||
document.querySelectorAll('h1.display-1, h1.display-2, h1.display-3, h1.display-4, h2.display-3, h2.display-4, h2.display-5')
|
||||
.forEach(function (el) {
|
||||
if (!el.classList.contains('kfx-reveal') && !el.classList.contains('hero-title')) {
|
||||
el.classList.add('kfx-reveal');
|
||||
el.setAttribute('data-fx', 'up');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────────
|
||||
14. Smooth anchor scroll
|
||||
───────────────────────────────────────────────────────────────── */
|
||||
function initAnchorSmooth() {
|
||||
document.addEventListener('click', function (e) {
|
||||
var a = e.target.closest('a[href^="#"]');
|
||||
if (!a) return;
|
||||
var href = a.getAttribute('href');
|
||||
if (!href || href === '#' || href.length < 2) return;
|
||||
var target = document.querySelector(href);
|
||||
if (!target) return;
|
||||
e.preventDefault();
|
||||
target.scrollIntoView({ behavior: prefersReduced ? 'auto' : 'smooth', block: 'start' });
|
||||
});
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────────
|
||||
15. SweetAlert2 patcher (a prueba de cache)
|
||||
Detecta cuando aparece un .swal2-popup y fuerza inline styles en
|
||||
los botones para garantizar fondo rojo en confirm y gris en cancel,
|
||||
sin importar el orden/cache del CSS o el confirmButtonColor del JS.
|
||||
───────────────────────────────────────────────────────────────── */
|
||||
function initSwalPatcher() {
|
||||
if (!('MutationObserver' in window)) return;
|
||||
|
||||
var BRAND_RED = '#c20000';
|
||||
var BRAND_RED_DEEP = '#8a0000';
|
||||
var BRAND_GRAY = '#6c757d';
|
||||
|
||||
// Inyectar reglas críticas como <style> en runtime para evitar
|
||||
// problemas de caché del archivo .css (LiteSpeed/Cloudflare).
|
||||
if (!document.getElementById('kfx-swal-runtime-style')) {
|
||||
var s = document.createElement('style');
|
||||
s.id = 'kfx-swal-runtime-style';
|
||||
s.textContent = [
|
||||
/* Selección visible dentro del modal SweetAlert (rojo + texto blanco). */
|
||||
'.swal2-popup ::selection,.swal2-popup *::selection{background-color:' + BRAND_RED + ' !important;color:#fff !important;}',
|
||||
'.swal2-popup ::-moz-selection,.swal2-popup *::-moz-selection{background-color:' + BRAND_RED + ' !important;color:#fff !important;}',
|
||||
/* Selección global (failsafe por si el CSS principal cachea viejo). */
|
||||
'::selection{background-color:' + BRAND_RED + ' !important;color:#fff !important;}',
|
||||
'::-moz-selection{background-color:' + BRAND_RED + ' !important;color:#fff !important;}',
|
||||
/* Botones de SweetAlert (failsafe). */
|
||||
'body .swal2-popup .swal2-actions .swal2-styled.swal2-confirm{' +
|
||||
'background-color:' + BRAND_RED + ' !important;' +
|
||||
'background-image:linear-gradient(135deg,' + BRAND_RED + ',' + BRAND_RED_DEEP + ') !important;' +
|
||||
'color:#fff !important;border:none !important;' +
|
||||
'}',
|
||||
'body .swal2-popup .swal2-actions .swal2-styled.swal2-cancel{' +
|
||||
'background-color:' + BRAND_GRAY + ' !important;' +
|
||||
'background-image:none !important;' +
|
||||
'color:#fff !important;border:none !important;' +
|
||||
'}'
|
||||
].join('\n');
|
||||
document.head.appendChild(s);
|
||||
}
|
||||
|
||||
function styleConfirm(btn) {
|
||||
if (!btn) return;
|
||||
btn.style.setProperty('background-color', BRAND_RED, 'important');
|
||||
btn.style.setProperty('background-image',
|
||||
'linear-gradient(135deg, ' + BRAND_RED + ', ' + BRAND_RED_DEEP + ')', 'important');
|
||||
btn.style.setProperty('color', '#ffffff', 'important');
|
||||
btn.style.setProperty('border', 'none', 'important');
|
||||
btn.style.setProperty('text-shadow', 'none', 'important');
|
||||
btn.style.setProperty('font-weight', '700', 'important');
|
||||
btn.style.setProperty('text-transform', 'uppercase', 'important');
|
||||
btn.style.setProperty('letter-spacing', '0.08em', 'important');
|
||||
}
|
||||
function styleCancel(btn) {
|
||||
if (!btn) return;
|
||||
btn.style.setProperty('background-color', BRAND_GRAY, 'important');
|
||||
btn.style.setProperty('background-image', 'none', 'important');
|
||||
btn.style.setProperty('color', '#ffffff', 'important');
|
||||
btn.style.setProperty('border', 'none', 'important');
|
||||
btn.style.setProperty('font-weight', '700', 'important');
|
||||
btn.style.setProperty('text-transform', 'uppercase', 'important');
|
||||
btn.style.setProperty('letter-spacing', '0.08em', 'important');
|
||||
}
|
||||
function styleDeny(btn) {
|
||||
if (!btn) return;
|
||||
btn.style.setProperty('background-color', '#b91c1c', 'important');
|
||||
btn.style.setProperty('color', '#ffffff', 'important');
|
||||
}
|
||||
|
||||
function patch(popup) {
|
||||
if (!popup || popup.dataset.kfxPatched === '1') return;
|
||||
popup.dataset.kfxPatched = '1';
|
||||
styleConfirm(popup.querySelector('.swal2-confirm'));
|
||||
styleCancel(popup.querySelector('.swal2-cancel'));
|
||||
styleDeny(popup.querySelector('.swal2-deny'));
|
||||
}
|
||||
|
||||
// Patch popups que ya estén en el DOM al cargar
|
||||
document.querySelectorAll('.swal2-popup').forEach(patch);
|
||||
|
||||
// Observa nuevos popups (SWAL los agrega/quita dinámicamente)
|
||||
var obs = new MutationObserver(function (mutations) {
|
||||
mutations.forEach(function (m) {
|
||||
m.addedNodes && m.addedNodes.forEach(function (n) {
|
||||
if (!(n instanceof HTMLElement)) return;
|
||||
if (n.classList && n.classList.contains('swal2-popup')) {
|
||||
patch(n);
|
||||
} else {
|
||||
// Por si el contenedor agrega un wrapper antes del popup
|
||||
var inner = n.querySelector && n.querySelector('.swal2-popup');
|
||||
if (inner) patch(inner);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
obs.observe(document.body, { childList: true, subtree: true });
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────────
|
||||
Init
|
||||
───────────────────────────────────────────────────────────────── */
|
||||
ready(function () {
|
||||
try { splitWords(); } catch (e) { console.warn('kinetic-fx splitWords:', e); }
|
||||
try { autoTagReveal(); } catch (e) { console.warn('kinetic-fx autoTagReveal:', e); }
|
||||
try { initReveal(); } catch (e) { console.warn('kinetic-fx reveal:', e); }
|
||||
try { initReadingBar(); } catch (e) { console.warn('kinetic-fx readingBar:', e); }
|
||||
try { initNavbarScroll(); } catch (e) { console.warn('kinetic-fx navbarScroll:', e); }
|
||||
try { initSpotlight(); } catch (e) { console.warn('kinetic-fx spotlight:', e); }
|
||||
try { initTilt(); } catch (e) { console.warn('kinetic-fx tilt:', e); }
|
||||
try { initMagnetic(); } catch (e) { console.warn('kinetic-fx magnetic:', e); }
|
||||
try { initRipple(); } catch (e) { console.warn('kinetic-fx ripple:', e); }
|
||||
try { initCounters(); } catch (e) { console.warn('kinetic-fx counters:', e); }
|
||||
try { initParallax(); } catch (e) { console.warn('kinetic-fx parallax:', e); }
|
||||
try { initMarquee(); } catch (e) { console.warn('kinetic-fx marquee:', e); }
|
||||
try { initCustomCursor(); } catch (e) { console.warn('kinetic-fx cursor:', e); }
|
||||
try { initAnchorSmooth(); } catch (e) { console.warn('kinetic-fx anchor:', e); }
|
||||
try { initSwalPatcher(); } catch (e) { console.warn('kinetic-fx swalPatcher:', e); }
|
||||
});
|
||||
|
||||
// API pública mínima (por si una vista necesita re-inicializar tras AJAX)
|
||||
window.KineticFX = {
|
||||
rescan: function () {
|
||||
splitWords();
|
||||
autoTagReveal();
|
||||
initReveal();
|
||||
initSpotlight();
|
||||
initTilt();
|
||||
initMagnetic();
|
||||
initCounters();
|
||||
initMarquee();
|
||||
}
|
||||
};
|
||||
})();
|
||||
Reference in New Issue
Block a user