LLM-test/test/code/1/deepseel-cc/js/animate.js

456 lines
12 KiB
JavaScript

/* ==========================================================================
SYSTEM AWAKENING - Animation Coordinator Module
Manages and coordinates all animations across the system
========================================================================== */
import {
ANIMATION,
NAV_CONFIG
} from './config.js';
/**
* Animation Coordinator Class
* Manages all animations and their synchronization
*/
export class AnimationCoordinator {
/**
* Create a new animation coordinator
* @param {Object} components - All animation components
*/
constructor(components = {}) {
// Component references
this.particleSystem = components.particleSystem;
this.progressCircles = components.progressCircles;
this.terminal = components.terminal;
this.typewriter = components.typewriter;
// Animation state
this.lastFrameTime = 0;
this.animationId = null;
this.isRunning = false;
// Scroll tracking
this.currentSection = 'hero';
this.scrollObserver = null;
// Performance monitoring
this.frameCount = 0;
this.lastFpsUpdate = 0;
this.currentFps = 60;
// Initialize
this.init();
}
/**
* Initialize the animation coordinator
*/
init() {
// Start animation loop
this.start();
// Set up scroll animations
this.initScrollAnimations();
// Set up navigation tracking
this.initNavigationTracking();
// Start performance monitoring
this.startPerformanceMonitoring();
console.log('Animation coordinator initialized');
}
/**
* Start the main animation loop
*/
start() {
if (this.isRunning) return;
this.isRunning = true;
this.lastFrameTime = performance.now();
const animate = (timestamp) => {
if (!this.isRunning) return;
// Calculate delta time
const deltaTime = (timestamp - this.lastFrameTime) / 1000; // Convert to seconds
this.lastFrameTime = timestamp;
// Update frame count for FPS calculation
this.frameCount++;
// Update all animations
this.updateAnimations(deltaTime);
// Render all components
this.renderAll();
// Continue animation loop
this.animationId = requestAnimationFrame(animate);
};
// Start animation loop
this.animationId = requestAnimationFrame(animate);
}
/**
* Stop the animation loop
*/
stop() {
this.isRunning = false;
if (this.animationId) {
cancelAnimationFrame(this.animationId);
this.animationId = null;
}
}
/**
* Update all animations
* @param {number} deltaTime - Time since last frame in seconds
*/
updateAnimations(deltaTime) {
// Update particle system
if (this.particleSystem && this.particleSystem.update) {
this.particleSystem.update(deltaTime);
}
// Update typewriter animation is handled internally by its class
// Update terminal animation is handled internally by its class
// Update progress circles animation is handled internally by its class
}
/**
* Render all components
*/
renderAll() {
// Render particle system
if (this.particleSystem && this.particleSystem.render) {
this.particleSystem.render();
}
// Other components handle their own rendering internally
}
/**
* Initialize scroll-triggered animations
*/
initScrollAnimations() {
// Set up Intersection Observer for card animations
this.setupCardAnimations();
// Set up parallax effects
this.setupParallaxEffects();
// Update navigation on scroll
this.setupScrollTracking();
}
/**
* Set up card reveal animations using Intersection Observer
*/
setupCardAnimations() {
const cards = document.querySelectorAll('.card');
if (!cards.length) return;
// Create observer with options
const observerOptions = {
root: null, // Use viewport as root
rootMargin: '50px', // Trigger slightly before entering viewport
threshold: 0.1 // Trigger when 10% of element is visible
};
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
// Get delay from data attribute or calculate
const delay = entry.target.dataset.delay || 0;
// Animate card in after delay
setTimeout(() => {
entry.target.classList.add('visible');
// Add slight stagger for visual interest
const index = Array.from(cards).indexOf(entry.target);
const staggerDelay = index * 0.05; // 50ms per card
setTimeout(() => {
// Add hover-ready class after animation completes
entry.target.classList.add('animation-complete');
}, staggerDelay * 1000);
}, delay * 1000);
// Unobserve after animation starts
observer.unobserve(entry.target);
}
});
}, observerOptions);
// Observe all cards
cards.forEach(card => observer.observe(card));
}
/**
* Set up parallax effects for depth
*/
setupParallaxEffects() {
// Simple parallax for hero section
const heroSection = document.getElementById('hero');
if (!heroSection) return;
// Use scroll event with throttling
let lastScrollY = window.scrollY;
let ticking = false;
const updateParallax = () => {
const scrollY = window.scrollY;
// Calculate parallax offset (subtle effect)
const parallaxOffset = scrollY * ANIMATION.PARALLAX_SPEED;
// Apply transform to hero section
heroSection.style.transform = `translateY(${parallaxOffset}px)`;
// Update last scroll position
lastScrollY = scrollY;
ticking = false;
};
const onScroll = () => {
if (!ticking) {
requestAnimationFrame(updateParallax);
ticking = true;
}
};
// Add scroll listener
window.addEventListener('scroll', onScroll, { passive: true });
}
/**
* Set up scroll tracking for section navigation
*/
setupScrollTracking() {
// Throttle scroll events for performance
let ticking = false;
const updateCurrentSection = () => {
// Get all sections
const sections = NAV_CONFIG.SECTIONS.map(s => s.id);
const scrollPosition = window.scrollY + 100; // Offset for nav
// Find current section
for (const sectionId of sections) {
const section = document.getElementById(sectionId);
if (section) {
const sectionTop = section.offsetTop;
const sectionHeight = section.offsetHeight;
if (scrollPosition >= sectionTop &&
scrollPosition < sectionTop + sectionHeight) {
if (this.currentSection !== sectionId) {
this.currentSection = sectionId;
this.updateNavigation();
}
break;
}
}
}
ticking = false;
};
const onScroll = () => {
if (!ticking) {
requestAnimationFrame(updateCurrentSection);
ticking = true;
}
};
// Add scroll listener
window.addEventListener('scroll', onScroll, { passive: true });
}
/**
* Initialize navigation section tracking
*/
initNavigationTracking() {
// Update navigation on page load
this.updateNavigation();
// Set up click handlers for navigation
this.setupNavigationClickHandlers();
}
/**
* Update navigation UI based on current section
*/
updateNavigation() {
const navLinks = document.querySelectorAll('.nav-link');
navLinks.forEach(link => {
const sectionId = link.getAttribute('href').substring(1);
if (sectionId === this.currentSection) {
link.classList.add('active');
} else {
link.classList.remove('active');
}
});
}
/**
* Set up click handlers for navigation links
*/
setupNavigationClickHandlers() {
const navLinks = document.querySelectorAll('.nav-link');
navLinks.forEach(link => {
link.addEventListener('click', (e) => {
e.preventDefault();
const targetId = link.getAttribute('href').substring(1);
const targetSection = document.getElementById(targetId);
if (targetSection) {
// Smooth scroll to section
targetSection.scrollIntoView({
behavior: 'smooth',
block: 'start'
});
// Update current section
this.currentSection = targetId;
this.updateNavigation();
}
});
});
}
/**
* Start performance monitoring
*/
startPerformanceMonitoring() {
// Update FPS every second
setInterval(() => {
this.updateFps();
}, 1000);
}
/**
* Update FPS calculation
*/
updateFps() {
const now = performance.now();
const elapsed = now - this.lastFpsUpdate;
if (elapsed >= 1000) {
this.currentFps = Math.round((this.frameCount * 1000) / elapsed);
this.frameCount = 0;
this.lastFpsUpdate = now;
// Adaptive quality based on FPS
this.adjustQualityForPerformance();
}
}
/**
* Adjust animation quality based on performance
*/
adjustQualityForPerformance() {
// Only adjust if adaptive quality is enabled
// This is a placeholder for more sophisticated quality adjustment
if (this.currentFps < 30) {
console.warn(`Low FPS detected: ${this.currentFps}. Consider reducing particle count.`);
}
}
/**
* Get current FPS
* @returns {number} Current frames per second
*/
getFps() {
return this.currentFps;
}
/**
* Get current section ID
* @returns {string} Current section ID
*/
getCurrentSection() {
return this.currentSection;
}
/**
* Check if animation loop is running
* @returns {boolean} True if running
*/
isAnimationRunning() {
return this.isRunning;
}
/**
* Pause all animations
*/
pause() {
this.stop();
// Pause individual components
if (this.typewriter && this.typewriter.pause) {
this.typewriter.pause();
}
if (this.terminal) {
// Terminal auto-logging is handled by interval
}
console.log('Animations paused');
}
/**
* Resume all animations
*/
resume() {
this.start();
// Resume individual components
if (this.typewriter && this.typewriter.resume) {
this.typewriter.resume();
}
console.log('Animations resumed');
}
/**
* Clean up all resources
*/
dispose() {
// Stop animation loop
this.stop();
// Dispose individual components
if (this.particleSystem && this.particleSystem.dispose) {
this.particleSystem.dispose();
}
if (this.progressCircles && this.progressCircles.dispose) {
this.progressCircles.dispose();
}
if (this.terminal && this.terminal.dispose) {
this.terminal.dispose();
}
if (this.typewriter && this.typewriter.dispose) {
this.typewriter.dispose();
}
// Remove event listeners
window.removeEventListener('scroll', this.handleScroll);
console.log('Animation coordinator disposed');
}
}