// Advanced animations and interactions for Freezone website class FreezoneAnimations { constructor() { this.init(); this.setupInteractiveElements(); } init() { // Initialize on DOM load if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => this.onReady()); } else { this.onReady(); } } onReady() { this.setupNavbarEffects(); this.setupSmoothScrolling(); this.setupMarketplaceFlow(); this.setupOrderbookAnimation(); this.setupLiquidityPoolAnimation(); this.setupWireframeAnimations(); this.setupChartAnimations(); this.setupCounterAnimations(); } setupCounterAnimations() { // Simple counter animation for statistics const counters = document.querySelectorAll('.counter'); counters.forEach(counter => { const target = parseInt(counter.textContent.replace(/[^\d]/g, '')); const suffix = counter.textContent.replace(/[\d,]/g, ''); let current = 0; const increment = target / 100; const timer = setInterval(() => { current += increment; if (current >= target) { current = target; clearInterval(timer); } counter.textContent = Math.floor(current).toLocaleString() + suffix; }, 20); }); } setupObservers() { // Intersection Observer for scroll animations this.observerOptions = { threshold: 0.1, rootMargin: '0px 0px -100px 0px' }; this.observer = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { entry.target.classList.add('visible'); // Trigger specific animations based on element type if (entry.target.classList.contains('counter')) { this.animateCounter(entry.target); } if (entry.target.classList.contains('chart')) { this.animateChart(entry.target); } if (entry.target.classList.contains('wireframe')) { this.animateWireframe(entry.target); } } }); }, this.observerOptions); // Observe all animated elements this.observeElements(); } observeElements() { const animatedElements = document.querySelectorAll( '.fade-in, .slide-in-left, .slide-in-right, .scale-in, .counter, .chart, .wireframe' ); animatedElements.forEach(el => { el.classList.add('animate'); this.observer.observe(el); }); } setupScrollAnimations() { // Add stagger effect to grouped elements const groups = document.querySelectorAll('.row'); groups.forEach(group => { const children = group.querySelectorAll('.fade-in, .slide-in-left, .slide-in-right'); children.forEach((child, index) => { child.style.transitionDelay = `${index * 0.1}s`; }); }); } setupNavbarEffects() { const navbar = document.querySelector('.navbar'); if (!navbar) return; let lastScrollY = window.scrollY; let ticking = false; const updateNavbar = () => { const scrollY = window.scrollY; if (scrollY > 100) { navbar.classList.add('scrolled'); } else { navbar.classList.remove('scrolled'); } // Hide/show navbar on scroll if (scrollY > lastScrollY && scrollY > 200) { navbar.style.transform = 'translateY(-100%)'; } else { navbar.style.transform = 'translateY(0)'; } lastScrollY = scrollY; ticking = false; }; window.addEventListener('scroll', () => { if (!ticking) { requestAnimationFrame(updateNavbar); ticking = true; } }); } setupSmoothScrolling() { document.querySelectorAll('a[href^="#"]').forEach(anchor => { anchor.addEventListener('click', (e) => { e.preventDefault(); const target = document.querySelector(anchor.getAttribute('href')); if (target) { target.scrollIntoView({ behavior: 'smooth', block: 'start' }); } }); }); } setupMarketplaceFlow() { const containers = document.querySelectorAll('.marketplace-flow-container'); containers.forEach(container => { const items = container.querySelector('.marketplace-items'); if (!items) return; // Clone items for seamless loop const itemsClone = items.cloneNode(true); container.appendChild(itemsClone); // Add hover pause effect container.addEventListener('mouseenter', () => { items.style.animationPlayState = 'paused'; itemsClone.style.animationPlayState = 'paused'; }); container.addEventListener('mouseleave', () => { items.style.animationPlayState = 'running'; itemsClone.style.animationPlayState = 'running'; }); }); } setupOrderbookAnimation() { const orderbooks = document.querySelectorAll('.orderbook'); orderbooks.forEach(orderbook => { this.animateOrderbook(orderbook); }); } animateOrderbook(orderbook) { const orders = [ { price: '42,150.00', amount: '0.5432', type: 'buy' }, { price: '42,145.50', amount: '1.2100', type: 'buy' }, { price: '42,140.25', amount: '0.8750', type: 'buy' }, { price: '42,155.75', amount: '0.3200', type: 'sell' }, { price: '42,160.00', amount: '0.9800', type: 'sell' }, { price: '42,165.25', amount: '1.5000', type: 'sell' } ]; let currentIndex = 0; const addOrder = () => { if (currentIndex >= orders.length) { currentIndex = 0; orderbook.innerHTML = '
Live Orderbook
'; } const order = orders[currentIndex]; const orderRow = document.createElement('div'); orderRow.className = `order-row order-${order.type}`; orderRow.innerHTML = ` $${order.price} ${order.amount} BTC `; orderbook.appendChild(orderRow); currentIndex++; // Remove old orders to keep it manageable const rows = orderbook.querySelectorAll('.order-row'); if (rows.length > 8) { rows[1].remove(); // Keep header } }; // Add orders periodically setInterval(addOrder, 2000); addOrder(); // Initial order } setupLiquidityPoolAnimation() { const pools = document.querySelectorAll('.liquidity-pool'); pools.forEach(pool => { this.animateLiquidityPool(pool); }); } animateLiquidityPool(pool) { const updatePool = () => { const ethAmount = (Math.random() * 1000 + 500).toFixed(2); const usdcAmount = (Math.random() * 2000000 + 1000000).toFixed(0); const apr = (Math.random() * 20 + 5).toFixed(1); pool.innerHTML = `
ETH: ${ethAmount}
${apr}% APR
USDC: ${Number(usdcAmount).toLocaleString()}
`; }; updatePool(); setInterval(updatePool, 5000); } setupWireframeAnimations() { const wireframes = document.querySelectorAll('.platform-wireframe'); wireframes.forEach(wireframe => { this.createWireframeContent(wireframe); }); } createWireframeContent(wireframe) { wireframe.innerHTML = `
`; } animateWireframe(wireframe) { const blocks = wireframe.querySelectorAll('.wireframe-block'); blocks.forEach((block, index) => { setTimeout(() => { block.style.background = 'var(--pastel-blue)'; setTimeout(() => { block.style.background = 'var(--border-color)'; }, 500); }, index * 200); }); } setupChartAnimations() { const charts = document.querySelectorAll('.defi-chart'); charts.forEach(chart => { this.createChart(chart); }); } createChart(chart) { const canvas = document.createElement('canvas'); canvas.width = chart.offsetWidth; canvas.height = chart.offsetHeight; chart.appendChild(canvas); const ctx = canvas.getContext('2d'); this.animateChart(chart, ctx, canvas); } animateChart(chart, ctx, canvas) { if (!ctx || !canvas) return; const points = []; const numPoints = 50; // Generate random chart data for (let i = 0; i < numPoints; i++) { points.push({ x: (i / numPoints) * canvas.width, y: Math.random() * canvas.height * 0.6 + canvas.height * 0.2 }); } let animationProgress = 0; const animate = () => { ctx.clearRect(0, 0, canvas.width, canvas.height); // Draw grid ctx.strokeStyle = 'rgba(255, 255, 255, 0.05)'; ctx.lineWidth = 1; for (let i = 0; i < 10; i++) { const y = (i / 10) * canvas.height; ctx.beginPath(); ctx.moveTo(0, y); ctx.lineTo(canvas.width, y); ctx.stroke(); } // Draw chart line const gradient = ctx.createLinearGradient(0, 0, canvas.width, 0); gradient.addColorStop(0, '#a7f3d0'); gradient.addColorStop(0.5, '#c4b5fd'); gradient.addColorStop(1, '#fbb6ce'); ctx.strokeStyle = gradient; ctx.lineWidth = 3; ctx.beginPath(); const visiblePoints = Math.floor(animationProgress * points.length); for (let i = 0; i < visiblePoints; i++) { if (i === 0) { ctx.moveTo(points[i].x, points[i].y); } else { ctx.lineTo(points[i].x, points[i].y); } } ctx.stroke(); // Fill area under curve ctx.fillStyle = 'rgba(167, 243, 208, 0.1)'; ctx.beginPath(); ctx.moveTo(points[0].x, canvas.height); for (let i = 0; i < visiblePoints; i++) { ctx.lineTo(points[i].x, points[i].y); } ctx.lineTo(points[visiblePoints - 1]?.x || 0, canvas.height); ctx.closePath(); ctx.fill(); animationProgress += 0.02; if (animationProgress < 1) { requestAnimationFrame(animate); } }; animate(); } setupCounterAnimations() { // Counter animation will be triggered by intersection observer } animateCounter(element) { const target = parseInt(element.textContent.replace(/[^\d]/g, '')); const duration = 2000; const start = performance.now(); const suffix = element.textContent.replace(/[\d,]/g, ''); const animate = (currentTime) => { const elapsed = currentTime - start; const progress = Math.min(elapsed / duration, 1); // Easing function const easeOut = 1 - Math.pow(1 - progress, 3); const current = Math.floor(target * easeOut); element.textContent = current.toLocaleString() + suffix; if (progress < 1) { requestAnimationFrame(animate); } }; requestAnimationFrame(animate); } setupParallaxEffects() { const parallaxElements = document.querySelectorAll('.hero-section'); window.addEventListener('scroll', () => { const scrolled = window.pageYOffset; parallaxElements.forEach(element => { const speed = 0.3; element.style.transform = `translateY(${scrolled * speed}px)`; }); }); } setupInteractiveElements() { // Add hover effects to cards const cards = document.querySelectorAll('.feature-card'); cards.forEach(card => { card.addEventListener('mouseenter', () => { card.style.transform = 'translateY(-15px) scale(1.02)'; }); card.addEventListener('mouseleave', () => { card.style.transform = 'translateY(0) scale(1)'; }); }); // Add click ripple effect to buttons const buttons = document.querySelectorAll('.btn'); buttons.forEach(button => { button.addEventListener('click', (e) => { const ripple = document.createElement('span'); const rect = button.getBoundingClientRect(); const size = Math.max(rect.width, rect.height); const x = e.clientX - rect.left - size / 2; const y = e.clientY - rect.top - size / 2; ripple.style.cssText = ` position: absolute; width: ${size}px; height: ${size}px; left: ${x}px; top: ${y}px; background: rgba(255, 255, 255, 0.3); border-radius: 50%; transform: scale(0); animation: ripple 0.6s ease-out; pointer-events: none; `; button.style.position = 'relative'; button.style.overflow = 'hidden'; button.appendChild(ripple); setTimeout(() => ripple.remove(), 600); }); }); } setupParticleSystem() { const heroSections = document.querySelectorAll('.hero-section'); heroSections.forEach(section => { this.createParticles(section); }); } createParticles(container) { const canvas = document.createElement('canvas'); canvas.style.position = 'absolute'; canvas.style.top = '0'; canvas.style.left = '0'; canvas.style.width = '100%'; canvas.style.height = '100%'; canvas.style.pointerEvents = 'none'; canvas.style.zIndex = '1'; container.appendChild(canvas); const ctx = canvas.getContext('2d'); const particles = []; const resizeCanvas = () => { canvas.width = container.offsetWidth; canvas.height = container.offsetHeight; }; resizeCanvas(); window.addEventListener('resize', resizeCanvas); // Create particles for (let i = 0; i < 50; i++) { particles.push({ x: Math.random() * canvas.width, y: Math.random() * canvas.height, vx: (Math.random() - 0.5) * 0.5, vy: (Math.random() - 0.5) * 0.5, size: Math.random() * 2 + 1, opacity: Math.random() * 0.5 + 0.1 }); } const animate = () => { ctx.clearRect(0, 0, canvas.width, canvas.height); particles.forEach(particle => { particle.x += particle.vx; particle.y += particle.vy; // Wrap around edges if (particle.x < 0) particle.x = canvas.width; if (particle.x > canvas.width) particle.x = 0; if (particle.y < 0) particle.y = canvas.height; if (particle.y > canvas.height) particle.y = 0; // Draw particle ctx.beginPath(); ctx.arc(particle.x, particle.y, particle.size, 0, Math.PI * 2); ctx.fillStyle = `rgba(167, 243, 208, ${particle.opacity})`; ctx.fill(); }); requestAnimationFrame(animate); }; animate(); } // Public method to reinitialize animations (for SPA navigation) reinitialize() { this.observeElements(); this.setupMarketplaceFlow(); this.setupOrderbookAnimation(); this.setupLiquidityPoolAnimation(); this.setupWireframeAnimations(); this.setupChartAnimations(); this.setupParticleSystem(); } } // Add CSS for ripple animation const style = document.createElement('style'); style.textContent = ` @keyframes ripple { to { transform: scale(2); opacity: 0; } } `; document.head.appendChild(style); // Initialize animations const freezoneAnimations = new FreezoneAnimations(); // Export for use in Yew components window.freezoneAnimations = freezoneAnimations; window.reinitializeAnimations = () => freezoneAnimations.reinitialize();