Initial import of HarborSmith website
Some checks failed
build-website / build (push) Failing after 1m2s
Some checks failed
build-website / build (push) Failing after 1m2s
This commit is contained in:
349
website-mockups/js-backup-20250918-135225/animations.js
Normal file
349
website-mockups/js-backup-20250918-135225/animations.js
Normal file
@@ -0,0 +1,349 @@
|
||||
// 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 = `
|
||||
<div class="loader-content">
|
||||
<div class="loader-ship">
|
||||
<svg width="60" height="60" viewBox="0 0 60 60">
|
||||
<path d="M30 10 L45 40 L15 40 Z" fill="none" stroke="currentColor" stroke-width="2"/>
|
||||
<path d="M30 40 L30 50" stroke="currentColor" stroke-width="2"/>
|
||||
<path d="M20 50 L40 50" stroke="currentColor" stroke-width="2"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="loader-text">Setting Sail...</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
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 = `
|
||||
<style>
|
||||
.page-loader {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: linear-gradient(135deg, var(--primary) 0%, var(--accent) 100%);
|
||||
z-index: 9999;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: opacity 0.5s;
|
||||
}
|
||||
|
||||
.page-loader.fade-out {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.loader-content {
|
||||
text-align: center;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.loader-ship {
|
||||
animation: float 2s ease-in-out infinite;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.loader-text {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 600;
|
||||
animation: pulse 2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.custom-cursor {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
background: var(--primary);
|
||||
border-radius: 50%;
|
||||
position: fixed;
|
||||
pointer-events: none;
|
||||
z-index: 9998;
|
||||
transform: translate(-50%, -50%);
|
||||
transition: transform 0.2s, background 0.2s;
|
||||
}
|
||||
|
||||
.cursor-follower {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border: 2px solid var(--primary);
|
||||
border-radius: 50%;
|
||||
position: fixed;
|
||||
pointer-events: none;
|
||||
z-index: 9997;
|
||||
transform: translate(-50%, -50%);
|
||||
transition: transform 0.3s, border-color 0.2s;
|
||||
}
|
||||
|
||||
.custom-cursor.hover,
|
||||
.cursor-follower.hover {
|
||||
transform: translate(-50%, -50%) scale(1.5);
|
||||
}
|
||||
|
||||
.ripple-effect {
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
background: rgba(255, 255, 255, 0.5);
|
||||
animation: ripple 0.6s ease-out;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.char-animate {
|
||||
display: inline-block;
|
||||
opacity: 0;
|
||||
animation: fadeInUp 0.5s ease-out forwards;
|
||||
}
|
||||
|
||||
.page-transition {
|
||||
animation: fadeOut 0.5s ease-out;
|
||||
}
|
||||
|
||||
@keyframes fadeOut {
|
||||
to {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
`;
|
||||
|
||||
// Inject loader styles
|
||||
document.head.insertAdjacentHTML('beforeend', loaderStyles);
|
||||
Reference in New Issue
Block a user