freezone/website/static/js/animations.js
2025-06-27 12:05:38 +03:00

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