



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);
}
});
});
}