557 lines
18 KiB
JavaScript
557 lines
18 KiB
JavaScript
// 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 = '<div class="text-center text-muted mb-2">Live Orderbook</div>';
|
|
}
|
|
|
|
const order = orders[currentIndex];
|
|
const orderRow = document.createElement('div');
|
|
orderRow.className = `order-row order-${order.type}`;
|
|
orderRow.innerHTML = `
|
|
<span>$${order.price}</span>
|
|
<span>${order.amount} BTC</span>
|
|
`;
|
|
|
|
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 = `
|
|
<div class="pool-token">
|
|
<div class="token-icon" style="background: var(--pastel-blue);"></div>
|
|
<span>ETH: ${ethAmount}</span>
|
|
</div>
|
|
<div class="text-center">
|
|
<div class="text-success">${apr}% APR</div>
|
|
</div>
|
|
<div class="pool-token">
|
|
<div class="token-icon" style="background: var(--pastel-green);"></div>
|
|
<span>USDC: ${Number(usdcAmount).toLocaleString()}</span>
|
|
</div>
|
|
`;
|
|
};
|
|
|
|
updatePool();
|
|
setInterval(updatePool, 5000);
|
|
}
|
|
|
|
setupWireframeAnimations() {
|
|
const wireframes = document.querySelectorAll('.platform-wireframe');
|
|
wireframes.forEach(wireframe => {
|
|
this.createWireframeContent(wireframe);
|
|
});
|
|
}
|
|
|
|
createWireframeContent(wireframe) {
|
|
wireframe.innerHTML = `
|
|
<div class="wireframe-header"></div>
|
|
<div class="wireframe-content">
|
|
<div class="wireframe-block"></div>
|
|
<div class="wireframe-block"></div>
|
|
<div class="wireframe-block"></div>
|
|
<div class="wireframe-block"></div>
|
|
</div>
|
|
<div class="wireframe-content">
|
|
<div class="wireframe-block" style="grid-column: 1 / -1; height: 40px;"></div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
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(); |