// HarborSmith - Animation Scripts // ================================ // Scroll Animation Observer const scrollAnimations = { init() { this.observeElements(); this.initProgressBars(); this.initTextAnimations(); }, observeElements() { const options = { threshold: 0.1, rootMargin: '0px 0px -100px 0px' }; const observer = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { entry.target.classList.add('visible'); // Trigger specific animations if (entry.target.classList.contains('counter-animate')) { this.animateValue(entry.target); } // Only animate once if (!entry.target.classList.contains('repeat-animation')) { observer.unobserve(entry.target); } } }); }, options); // Observe all animated elements const animatedElements = document.querySelectorAll('.scroll-animate, .scroll-animate-left, .scroll-animate-right, .scroll-animate-scale, .counter-animate'); animatedElements.forEach(el => observer.observe(el)); }, animateValue(element) { const target = parseInt(element.dataset.target); const duration = parseInt(element.dataset.duration) || 2000; const start = 0; const increment = target / (duration / 16); let current = start; const updateValue = () => { current += increment; if (current < target) { element.textContent = Math.floor(current); requestAnimationFrame(updateValue); } else { element.textContent = target; } }; updateValue(); }, initProgressBars() { const progressBars = document.querySelectorAll('.progress-bar-fill'); const observer = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { const progressBar = entry.target; const targetWidth = progressBar.dataset.progress || '100'; progressBar.style.setProperty('--progress', targetWidth + '%'); progressBar.classList.add('animate'); observer.unobserve(progressBar); } }); }, { threshold: 0.5 }); progressBars.forEach(bar => observer.observe(bar)); }, initTextAnimations() { const textElements = document.querySelectorAll('.text-animate'); textElements.forEach(element => { const text = element.textContent; element.textContent = ''; [...text].forEach((char, index) => { const span = document.createElement('span'); span.textContent = char === ' ' ? '\u00A0' : char; span.style.animationDelay = `${index * 0.05}s`; span.classList.add('char-animate'); element.appendChild(span); }); }); } }; // Cursor Effects const cursorEffects = { init() { this.cursor = document.createElement('div'); this.cursor.className = 'custom-cursor'; document.body.appendChild(this.cursor); this.follower = document.createElement('div'); this.follower.className = 'cursor-follower'; document.body.appendChild(this.follower); this.initEventListeners(); }, initEventListeners() { document.addEventListener('mousemove', (e) => { this.cursor.style.left = e.clientX + 'px'; this.cursor.style.top = e.clientY + 'px'; setTimeout(() => { this.follower.style.left = e.clientX + 'px'; this.follower.style.top = e.clientY + 'px'; }, 100); }); // Add hover effects const interactiveElements = document.querySelectorAll('a, button, .clickable'); interactiveElements.forEach(el => { el.addEventListener('mouseenter', () => { this.cursor.classList.add('hover'); this.follower.classList.add('hover'); }); el.addEventListener('mouseleave', () => { this.cursor.classList.remove('hover'); this.follower.classList.remove('hover'); }); }); } }; // Ripple Effect const rippleEffect = { init() { const buttons = document.querySelectorAll('.btn, .ripple'); buttons.forEach(button => { button.addEventListener('click', (e) => { const ripple = document.createElement('span'); ripple.className = 'ripple-effect'; 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.width = ripple.style.height = size + 'px'; ripple.style.left = x + 'px'; ripple.style.top = y + 'px'; button.appendChild(ripple); setTimeout(() => { ripple.remove(); }, 600); }); }); } }; // Magnetic Buttons const magneticButtons = { init() { const magneticElements = document.querySelectorAll('.magnetic'); magneticElements.forEach(element => { element.addEventListener('mousemove', (e) => { const rect = element.getBoundingClientRect(); const x = e.clientX - rect.left - rect.width / 2; const y = e.clientY - rect.top - rect.height / 2; element.style.transform = `translate(${x * 0.2}px, ${y * 0.2}px)`; }); element.addEventListener('mouseleave', () => { element.style.transform = 'translate(0, 0)'; }); }); } }; // Page Loader const pageLoader = { init() { const loader = document.createElement('div'); loader.className = 'page-loader'; loader.innerHTML = `
Setting Sail...
`; document.body.appendChild(loader); window.addEventListener('load', () => { setTimeout(() => { loader.classList.add('fade-out'); setTimeout(() => { loader.remove(); }, 500); }, 1000); }); } }; // Smooth Page Transitions const pageTransitions = { init() { const links = document.querySelectorAll('a[href^="/"], a[href^="./"], a[href$=".html"]'); links.forEach(link => { link.addEventListener('click', (e) => { const href = link.getAttribute('href'); // Skip if it's an anchor link or external link if (href.startsWith('#') || href.startsWith('http')) return; e.preventDefault(); document.body.classList.add('page-transition'); setTimeout(() => { window.location.href = href; }, 500); }); }); } }; // Initialize animations document.addEventListener('DOMContentLoaded', () => { scrollAnimations.init(); rippleEffect.init(); magneticButtons.init(); // Uncomment these for production // cursorEffects.init(); // pageLoader.init(); // pageTransitions.init(); }); // Add some CSS for the loader (normally would be in a separate file) const loaderStyles = ` `; // Inject loader styles document.head.insertAdjacentHTML('beforeend', loaderStyles);