




From idea…
… to Concept
From Design
… to Production & Compliance
TO
script for magic scroll
document.addEventListener('DOMContentLoaded', function() {
const items = 4; // Total number of items
const scrollHeight = document.querySelector('.scroll-spacer').offsetHeight;
window.addEventListener('scroll', function() {
const scrolled = window.pageYOffset;
const maxScroll = scrollHeight - window.innerHeight;
const scrollProgress = scrolled / maxScroll;
// Calculate which item should show (1 to items)
let currentIndex = Math.ceil(scrollProgress * items);
if (currentIndex items) currentIndex = items;
// Update all items at once
document.querySelectorAll('.fade-item').forEach(item => {
if (item.dataset.index == currentIndex) {
item.classList.add('active');
} else {
item.classList.remove('active');
}
});
});
});
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);
}
});
});
}
svg anim
document.addEventListener('DOMContentLoaded', function() {
// ===== CONFIGURATION - Customize these values =====
const config = {
svgSelector: '.my-animated-svg',
containerSelector: '.svg-container',
targetImageSelector: '.target-image', // Selector for the target image
relativePosition: {
// Position in pixels from top-left corner of target image
fromTop: 70, // Pixels from top edge of target image
fromLeft: 100// Pixels from left edge of target image
},
fadeInDuration: 1000,
speedEffectDuration: 2000,
scaleAndMoveDuration: 2000,
finalScale: 0.35
};// Get elements
const svg = document.querySelector(config.svgSelector);
const container = document.querySelector(config.containerSelector);
const targetImage = document.querySelector(config.targetImageSelector);
if (!svg) {
console.error('SVG element not found. Check your svgSelector:', config.svgSelector);
return;
}
if (!container) {
console.error('Container element not found. Check your containerSelector:', config.containerSelector);
return;
}
if (!targetImage) {
console.error('Target image not found. Check your targetImageSelector:', config.targetImageSelector);
return;
}// Store original dimensions
const originalWidth = svg.offsetWidth;
const originalHeight = svg.offsetHeight;
// Flag to prevent animation overlap
let isAnimating = false;// Function to calculate final position relative to target image
function calculateFinalPosition() {
const targetRect = targetImage.getBoundingClientRect();
const containerRect = container.getBoundingClientRect();
// Calculate target image position relative to container
const targetRelativeTop = targetRect.top - containerRect.top;
const targetRelativeLeft = targetRect.left - containerRect.left;
// Calculate final position from top-left corner of target image
// The SVG center will be positioned at these coordinates
const finalLeft = targetRelativeLeft + config.relativePosition.fromLeft;
const finalTop = targetRelativeTop + config.relativePosition.fromTop;
return { top: finalTop, left: finalLeft };
}// Main animation sequence
async function animateSequence() {
if (isAnimating) return;
isAnimating = true;
console.log('Starting animation sequence...');
// Phase 1: Fade in
console.log('Phase 1: Fading in...');
svg.style.transition = `opacity ${config.fadeInDuration}ms ease-in`;
svg.style.opacity = '1';
await delay(config.fadeInDuration);// Phase 2: Speed effect (standing still)
console.log('Phase 2: Speed effect while standing still...');
svg.style.animation = 'vibrate 0.1s infinite, speedEffect 0.2s infinite';
await delay(config.speedEffectDuration);// Phase 3: Progressive scale down and move to position
console.log('Phase 3: Scaling down and moving...');
// Clear the vibrate animation
svg.style.animation = 'speedEffect 0.2s infinite';
// Calculate positions
const finalPosition = calculateFinalPosition();
const finalWidth = originalWidth * config.finalScale;
const finalHeight = originalHeight * config.finalScale;
// Starting position (center of container)
const containerRect = container.getBoundingClientRect();
const startTop = containerRect.height / 2;
const startLeft = containerRect.width / 2;
// Ending position
const endTop = finalPosition.top;
const endLeft = finalPosition.left;
console.log('Moving to position relative to target image:', { endTop, endLeft });
const startTime = Date.now();
const duration = config.scaleAndMoveDuration;
// Animate position and scale progressively
const animateFrame = () => {
const elapsed = Date.now() - startTime;
const progress = Math.min(elapsed / duration, 1);
// Easing function (ease-in-out)
const easeProgress = progress < 0.5
? 2 * progress * progress
: 1 - Math.pow(-2 * progress + 2, 2) / 2;
// Calculate current values
const currentScale = 1 + (config.finalScale - 1) * easeProgress;
const currentTop = startTop + (endTop - startTop) * easeProgress;
const currentLeft = startLeft + (endLeft - startLeft) * easeProgress;
// Apply transformation
svg.style.width = (originalWidth * currentScale) + 'px';
svg.style.height = (originalHeight * currentScale) + 'px';
svg.style.top = currentTop + 'px';
svg.style.left = currentLeft + 'px';
// Add slight vibration during movement
const vibrationOffset = Math.sin(elapsed * 0.05) * 2;
svg.style.transform = `translate(calc(-50% + ${vibrationOffset}px), -50%)`;
if (progress setTimeout(resolve, ms));
}// Start animation on page load
animateSequence();// Restart animation on click
container.addEventListener('click', function() {
if (isAnimating) return;
console.log('Restarting animation...');
// Reset all styles
svg.style.transition = 'none';
svg.style.opacity = '0';
svg.style.top = '50%';
svg.style.left = '50%';
svg.style.width = originalWidth + 'px';
svg.style.height = originalHeight + 'px';
svg.style.transform = 'translate(-50%, -50%)';
svg.style.animation = 'none';
// Force browser to recalculate styles
svg.offsetHeight;
// Restart animation after brief delay
setTimeout(animateSequence, 100);
});
// Recalculate position on window resize
window.addEventListener('resize', function() {
if (!isAnimating) {
// Update final position if animation is complete
const finalPosition = calculateFinalPosition();
if (parseFloat(svg.style.opacity) === 1 && !svg.style.animation) {
svg.style.top = finalPosition.top + 'px';
svg.style.left = finalPosition.left + 'px';
}
}
});
});