From idea…

… to Concept

From Design

… to Production & Compliance

magic scroll
document.addEventListener('DOMContentLoaded', function() { const totalItems = 2; // Change this if you add more items // Set initial state - show first items document.querySelectorAll('.fade-item').forEach(item => { if (item.dataset.index == "1") { item.classList.add('active'); } else { item.classList.remove('active'); } }); // Handle scrolling window.addEventListener('scroll', function() { const scrolled = window.pageYOffset; const windowHeight = window.innerHeight; const transitionPoint = windowHeight * 0.8; // Adjust for timing let index; // If at the very top, force index to 1 if (scrolled totalItems) index = totalItems; if (index { if (item.dataset.index == index) { item.classList.add('active'); } else { item.classList.remove('active'); } }); }); // Trigger once on load to set correct initial state window.dispatchEvent(new Event('scroll')); });
text scramble
class TextScramble { constructor(el) { this.el = el; this.chars = '!-_\\/[]{}—=+*^?#________'; this.update = this.update.bind(this); } setText(newText) { const oldText = this.el.innerText; const length = Math.max(oldText.length, newText.length); const promise = new Promise((resolve) => this.resolve = resolve); this.queue = []; for (let i = 0; i < length; i++) { const from = oldText[i] || ''; const to = newText[i] || ''; const start = Math.floor(Math.random() * 40); const end = start + Math.floor(Math.random() * 40); this.queue.push({ from, to, start, end }); } cancelAnimationFrame(this.frameRequest); this.frame = 0; this.update(); return promise; } update() { let output = ''; let complete = 0; for (let i = 0, n = this.queue.length; i = end) { complete++; output += to; } else if (this.frame >= start) { if (!char || Math.random() { // Skip if already initialized if (element.hasAttribute('data-scramble-init')) return; // Get the original text - prioritize data-text attribute if it exists // Otherwise use the actual text content let originalText = element.getAttribute('data-text') || element.textContent || element.innerText; // Clean up the text (remove extra whitespace) originalText = originalText.trim(); // Mark as initialized element.setAttribute('data-scramble-init', 'true'); // Store original text element.setAttribute('data-text', originalText); // Clear text initially element.textContent = ''; // Find the parent fade-item element const fadeItem = element.closest('.fade-item'); if (fadeItem) { // Track if currently animating to prevent overlapping animations let isAnimating = false; // Create MutationObserver to watch for class changes const observer = new MutationObserver((mutations) => { mutations.forEach((mutation) => { if (mutation.type === 'attributes' && mutation.attributeName === 'class') { // Check if 'active' class was added if (fadeItem.classList.contains('active') && !isAnimating) { // Set animating flag isAnimating = true; // Create scramble effect instance const fx = new TextScramble(element); // Small delay to sync with fade-in setTimeout(() => { element.classList.add('visible'); fx.setText(originalText).then(() => { // Optional: Add glow effect after animation completes element.classList.add('glowing'); setTimeout(() => { element.classList.remove('glowing'); }, 500); }); }, 100); // Adjust delay as needed } // Reset when active class is removed else if (!fadeItem.classList.contains('active') && isAnimating) { // Reset for re-animation isAnimating = false; element.textContent = ''; element.classList.remove('visible'); element.classList.remove('glowing'); } } }); }); // Start observing the fade-item for class changes observer.observe(fadeItem, { attributes: true, attributeFilter: ['class'] }); // Check if already active on page load if (fadeItem.classList.contains('active')) { setTimeout(() => { isAnimating = true; const fx = new TextScramble(element); element.classList.add('visible'); fx.setText(originalText); }, 100); } } else { // If not inside a fade-item, use Intersection Observer as fallback const observerOptions = { threshold: 0.2, rootMargin: '0px' }; const scrambleObserver = new IntersectionObserver((entries) => { entries.forEach((entry) => { if (entry.isIntersecting && !element.hasAttribute('data-scramble-animated')) { element.setAttribute('data-scramble-animated', 'true'); const fx = new TextScramble(element); element.classList.add('visible'); fx.setText(originalText).then(() => { element.classList.add('glowing'); setTimeout(() => { element.classList.remove('glowing'); }, 500); }); scrambleObserver.unobserve(element); } }); }, observerOptions); scrambleObserver.observe(element); } }); } // Initialize on DOMContentLoaded document.addEventListener('DOMContentLoaded', function() { // Small delay to ensure your scroll script runs first setTimeout(initScrambleEffect, 100); }); // Also initialize when Elementor loads new content (for popup, tabs, etc.) if (typeof jQuery !== 'undefined' && window.elementorFrontend) { jQuery(window).on('elementor/frontend/init', function() { elementorFrontend.hooks.addAction('frontend/element_ready/widget', initScrambleEffect); }); }// For Elementor: Re-run on Elementor preview refresh if (window.elementor) { window.elementor.on('preview:loaded', function() { // Re-initialize for preview mode const elements = document.querySelectorAll('.scramble-text'); elements.forEach((element) => { const fx = new TextScramble(element); const originalText = element.getAttribute('data-text'); if (originalText) { element.innerText = ''; element.classList.remove('visible'); setTimeout(() => { element.classList.add('visible'); fx.setText(originalText); }, 500); } }); }); }