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);
|
||||
375
website-mockups/js-backup-20250918-135225/bento-layout.js
Normal file
375
website-mockups/js-backup-20250918-135225/bento-layout.js
Normal file
@@ -0,0 +1,375 @@
|
||||
// Bento Grid Layout JavaScript
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Menu Toggle
|
||||
const menuToggle = document.getElementById('menuToggle');
|
||||
const menuOverlay = document.getElementById('menuOverlay');
|
||||
const menuClose = document.getElementById('menuClose');
|
||||
|
||||
menuToggle.addEventListener('click', () => {
|
||||
menuOverlay.classList.add('active');
|
||||
});
|
||||
|
||||
menuClose.addEventListener('click', () => {
|
||||
menuOverlay.classList.remove('active');
|
||||
});
|
||||
|
||||
// Filter Toggle
|
||||
const filterToggle = document.getElementById('filterToggle');
|
||||
const filterBar = document.getElementById('filterBar');
|
||||
|
||||
filterToggle.addEventListener('click', () => {
|
||||
filterBar.classList.toggle('active');
|
||||
});
|
||||
|
||||
// Filter Functionality
|
||||
const filterChips = document.querySelectorAll('.filter-chip');
|
||||
const bentoTiles = document.querySelectorAll('.bento-tile');
|
||||
|
||||
filterChips.forEach(chip => {
|
||||
chip.addEventListener('click', () => {
|
||||
// Update active state
|
||||
filterChips.forEach(c => c.classList.remove('active'));
|
||||
chip.classList.add('active');
|
||||
|
||||
const filter = chip.dataset.filter;
|
||||
|
||||
// Filter tiles
|
||||
bentoTiles.forEach(tile => {
|
||||
if (filter === 'all' || tile.dataset.category === filter) {
|
||||
tile.classList.remove('hiding');
|
||||
tile.classList.add('showing');
|
||||
} else {
|
||||
tile.classList.add('hiding');
|
||||
tile.classList.remove('showing');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// FAB Menu
|
||||
const fabBtn = document.getElementById('fabBtn');
|
||||
const fabMenu = document.getElementById('fabMenu');
|
||||
|
||||
fabBtn.addEventListener('click', () => {
|
||||
fabMenu.classList.toggle('active');
|
||||
});
|
||||
|
||||
// FAB Options
|
||||
const fabOptions = document.querySelectorAll('.fab-option');
|
||||
|
||||
fabOptions.forEach(option => {
|
||||
option.addEventListener('click', () => {
|
||||
const action = option.dataset.action;
|
||||
|
||||
switch(action) {
|
||||
case 'chat':
|
||||
alert('Opening chat...');
|
||||
break;
|
||||
case 'call':
|
||||
window.location.href = 'tel:4155550123';
|
||||
break;
|
||||
case 'book':
|
||||
window.location.href = 'charter.html';
|
||||
break;
|
||||
}
|
||||
|
||||
fabMenu.classList.remove('active');
|
||||
});
|
||||
});
|
||||
|
||||
// Quick View for Yachts
|
||||
const quickViewBtns = document.querySelectorAll('.quick-view-btn');
|
||||
|
||||
quickViewBtns.forEach(btn => {
|
||||
btn.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
const yachtTile = btn.closest('.tile-yacht');
|
||||
const yachtName = yachtTile.querySelector('.yacht-name').textContent;
|
||||
|
||||
// Create modal
|
||||
const modal = document.createElement('div');
|
||||
modal.className = 'yacht-modal';
|
||||
modal.innerHTML = `
|
||||
<div class="modal-content">
|
||||
<button class="modal-close">×</button>
|
||||
<h2>${yachtName}</h2>
|
||||
<div class="modal-gallery">
|
||||
<img src="${yachtTile.querySelector('img').src}" alt="${yachtName}">
|
||||
</div>
|
||||
<div class="modal-details">
|
||||
<h3>Specifications</h3>
|
||||
<ul>
|
||||
<li>Length: 120ft</li>
|
||||
<li>Capacity: 12 Guests</li>
|
||||
<li>Cabins: 5</li>
|
||||
<li>Crew: Full Professional Crew</li>
|
||||
<li>Amenities: Jacuzzi, Water Toys, Full Bar</li>
|
||||
</ul>
|
||||
<div class="modal-actions">
|
||||
<button class="btn-primary">Book Now</button>
|
||||
<button class="btn-secondary">Request Info</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
document.body.appendChild(modal);
|
||||
|
||||
// Add styles
|
||||
const modalStyles = `
|
||||
.yacht-modal {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0,0,0,0.8);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 300;
|
||||
animation: fadeIn 0.3s ease;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
background: white;
|
||||
border-radius: 20px;
|
||||
padding: 2rem;
|
||||
max-width: 600px;
|
||||
width: 90%;
|
||||
max-height: 90vh;
|
||||
overflow-y: auto;
|
||||
position: relative;
|
||||
animation: slideUp 0.3s ease;
|
||||
}
|
||||
|
||||
.modal-close {
|
||||
position: absolute;
|
||||
top: 1rem;
|
||||
right: 1rem;
|
||||
background: transparent;
|
||||
border: none;
|
||||
font-size: 2rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.modal-gallery img {
|
||||
width: 100%;
|
||||
border-radius: 10px;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
.modal-details h3 {
|
||||
margin: 1.5rem 0 1rem;
|
||||
}
|
||||
|
||||
.modal-details ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.modal-details li {
|
||||
padding: 0.5rem 0;
|
||||
border-bottom: 1px solid #e2e8f0;
|
||||
}
|
||||
|
||||
.modal-actions {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
.modal-actions button {
|
||||
flex: 1;
|
||||
padding: 0.75rem;
|
||||
border-radius: 8px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
|
||||
@keyframes slideUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(30px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
if (!document.getElementById('modal-styles')) {
|
||||
const styleSheet = document.createElement('style');
|
||||
styleSheet.id = 'modal-styles';
|
||||
styleSheet.textContent = modalStyles;
|
||||
document.head.appendChild(styleSheet);
|
||||
}
|
||||
|
||||
// Close modal
|
||||
modal.querySelector('.modal-close').addEventListener('click', () => {
|
||||
modal.remove();
|
||||
});
|
||||
|
||||
modal.addEventListener('click', (e) => {
|
||||
if (e.target === modal) {
|
||||
modal.remove();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Wishlist functionality
|
||||
let wishlist = [];
|
||||
const wishlistSidebar = document.getElementById('wishlistSidebar');
|
||||
const wishlistContent = document.getElementById('wishlistContent');
|
||||
|
||||
// Add to wishlist buttons (would be added to tiles)
|
||||
bentoTiles.forEach(tile => {
|
||||
if (tile.classList.contains('tile-yacht') || tile.classList.contains('tile-experience')) {
|
||||
const wishlistBtn = document.createElement('button');
|
||||
wishlistBtn.className = 'wishlist-add';
|
||||
wishlistBtn.innerHTML = '♡';
|
||||
wishlistBtn.style.cssText = `
|
||||
position: absolute;
|
||||
top: 1rem;
|
||||
left: 1rem;
|
||||
background: white;
|
||||
border: none;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
font-size: 1.5rem;
|
||||
cursor: pointer;
|
||||
z-index: 10;
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||
`;
|
||||
|
||||
tile.appendChild(wishlistBtn);
|
||||
|
||||
wishlistBtn.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
const itemName = tile.querySelector('h3').textContent;
|
||||
|
||||
if (wishlistBtn.innerHTML === '♡') {
|
||||
wishlistBtn.innerHTML = '❤️';
|
||||
wishlist.push(itemName);
|
||||
updateWishlist();
|
||||
} else {
|
||||
wishlistBtn.innerHTML = '♡';
|
||||
wishlist = wishlist.filter(item => item !== itemName);
|
||||
updateWishlist();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
function updateWishlist() {
|
||||
if (wishlist.length === 0) {
|
||||
wishlistContent.innerHTML = '<p class="wishlist-empty">Select yachts and experiences to build your dream charter</p>';
|
||||
} else {
|
||||
wishlistContent.innerHTML = `
|
||||
<h4>Selected Items (${wishlist.length})</h4>
|
||||
<ul style="list-style: none; padding: 0;">
|
||||
${wishlist.map(item => `
|
||||
<li style="padding: 0.75rem; background: #f8fafc; margin-bottom: 0.5rem; border-radius: 8px;">
|
||||
${item}
|
||||
</li>
|
||||
`).join('')}
|
||||
</ul>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
// Show wishlist
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'w' && e.ctrlKey) {
|
||||
e.preventDefault();
|
||||
wishlistSidebar.classList.toggle('active');
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('wishlistClose').addEventListener('click', () => {
|
||||
wishlistSidebar.classList.remove('active');
|
||||
});
|
||||
|
||||
// Parallax effect on scroll
|
||||
let ticking = false;
|
||||
|
||||
function updateParallax() {
|
||||
const scrolled = window.pageYOffset;
|
||||
const heroTile = document.querySelector('.tile-hero');
|
||||
|
||||
if (heroTile) {
|
||||
const heroVisual = heroTile.querySelector('.hero-visual');
|
||||
if (heroVisual) {
|
||||
heroVisual.style.transform = `translateY(${scrolled * 0.3}px)`;
|
||||
}
|
||||
}
|
||||
|
||||
ticking = false;
|
||||
}
|
||||
|
||||
function requestTick() {
|
||||
if (!ticking) {
|
||||
window.requestAnimationFrame(updateParallax);
|
||||
ticking = true;
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('scroll', requestTick);
|
||||
|
||||
// Auto-play/pause videos on visibility
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
entries.forEach(entry => {
|
||||
const video = entry.target.querySelector('video');
|
||||
if (video) {
|
||||
if (entry.isIntersecting) {
|
||||
video.play();
|
||||
} else {
|
||||
video.pause();
|
||||
}
|
||||
}
|
||||
});
|
||||
}, { threshold: 0.5 });
|
||||
|
||||
document.querySelectorAll('.tile-video, .tile-hero').forEach(tile => {
|
||||
observer.observe(tile);
|
||||
});
|
||||
|
||||
// Tile click navigation
|
||||
bentoTiles.forEach(tile => {
|
||||
if (!tile.classList.contains('tile-yacht')) {
|
||||
tile.addEventListener('click', () => {
|
||||
if (tile.classList.contains('tile-service')) {
|
||||
window.location.href = 'maintenance.html';
|
||||
} else if (tile.classList.contains('tile-experience')) {
|
||||
window.location.href = 'charter.html';
|
||||
} else if (tile.classList.contains('tile-cta')) {
|
||||
window.location.href = 'charter.html';
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Smooth hover effects
|
||||
bentoTiles.forEach(tile => {
|
||||
tile.addEventListener('mouseenter', () => {
|
||||
tile.style.zIndex = '10';
|
||||
});
|
||||
|
||||
tile.addEventListener('mouseleave', () => {
|
||||
setTimeout(() => {
|
||||
tile.style.zIndex = '';
|
||||
}, 300);
|
||||
});
|
||||
});
|
||||
});
|
||||
89
website-mockups/js-backup-20250918-135225/booking.js
Normal file
89
website-mockups/js-backup-20250918-135225/booking.js
Normal file
@@ -0,0 +1,89 @@
|
||||
// HarborSmith - Booking JavaScript
|
||||
// ================================
|
||||
|
||||
// Calendar functionality
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// Calendar day selection
|
||||
const calendarDays = document.querySelectorAll('.calendar-day.available');
|
||||
calendarDays.forEach(day => {
|
||||
day.addEventListener('click', function() {
|
||||
// Remove previous selection
|
||||
document.querySelector('.calendar-day.selected')?.classList.remove('selected');
|
||||
// Add selection to clicked day
|
||||
this.classList.add('selected');
|
||||
// Update summary
|
||||
updateBookingSummary();
|
||||
});
|
||||
});
|
||||
|
||||
// Duration selection
|
||||
const durationOptions = document.querySelectorAll('input[name="duration"]');
|
||||
durationOptions.forEach(option => {
|
||||
option.addEventListener('change', updateBookingSummary);
|
||||
});
|
||||
|
||||
// Guest number input
|
||||
const guestInput = document.querySelector('input[type="number"]');
|
||||
if (guestInput) {
|
||||
guestInput.addEventListener('change', updateBookingSummary);
|
||||
}
|
||||
|
||||
// Yacht selection (for booking page 2)
|
||||
const yachtCards = document.querySelectorAll('.yacht-select-card');
|
||||
yachtCards.forEach(card => {
|
||||
card.addEventListener('click', function() {
|
||||
// Remove previous selection
|
||||
document.querySelector('.yacht-select-card.selected')?.classList.remove('selected');
|
||||
// Add selection
|
||||
this.classList.add('selected');
|
||||
updateBookingSummary();
|
||||
});
|
||||
});
|
||||
|
||||
// Addon selection (for booking page 3)
|
||||
const addonCards = document.querySelectorAll('.addon-card');
|
||||
addonCards.forEach(card => {
|
||||
card.addEventListener('click', function() {
|
||||
this.classList.toggle('selected');
|
||||
updateBookingSummary();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Update booking summary
|
||||
function updateBookingSummary() {
|
||||
// This would normally update the summary sidebar with selected options
|
||||
console.log('Updating booking summary...');
|
||||
}
|
||||
|
||||
// Form validation for final step
|
||||
function validateBookingForm() {
|
||||
const form = document.getElementById('bookingForm');
|
||||
if (!form) return true;
|
||||
|
||||
const inputs = form.querySelectorAll('[required]');
|
||||
let isValid = true;
|
||||
|
||||
inputs.forEach(input => {
|
||||
if (!input.value.trim()) {
|
||||
input.classList.add('error');
|
||||
isValid = false;
|
||||
} else {
|
||||
input.classList.remove('error');
|
||||
}
|
||||
});
|
||||
|
||||
return isValid;
|
||||
}
|
||||
|
||||
// Handle form submission
|
||||
const bookingForm = document.getElementById('bookingForm');
|
||||
if (bookingForm) {
|
||||
bookingForm.addEventListener('submit', (e) => {
|
||||
e.preventDefault();
|
||||
if (validateBookingForm()) {
|
||||
alert('Thank you for your booking request! We will contact you shortly to confirm your charter.');
|
||||
// In production, this would submit to a server
|
||||
}
|
||||
});
|
||||
}
|
||||
339
website-mockups/js-backup-20250918-135225/main.js
Normal file
339
website-mockups/js-backup-20250918-135225/main.js
Normal file
@@ -0,0 +1,339 @@
|
||||
// HarborSmith - Main JavaScript
|
||||
// ========================
|
||||
|
||||
// Theme Management
|
||||
const themeManager = {
|
||||
init() {
|
||||
this.themeToggle = document.getElementById('themeToggle');
|
||||
this.themeDropdown = document.getElementById('themeDropdown');
|
||||
this.themeOptions = document.querySelectorAll('.theme-option');
|
||||
|
||||
// Load saved theme or default to nautical
|
||||
const savedTheme = localStorage.getItem('harborsmith-theme') || 'nautical';
|
||||
this.setTheme(savedTheme);
|
||||
|
||||
// Event listeners
|
||||
this.themeToggle?.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
this.themeDropdown.classList.toggle('active');
|
||||
});
|
||||
|
||||
this.themeOptions.forEach(option => {
|
||||
option.addEventListener('click', (e) => {
|
||||
const theme = e.currentTarget.dataset.theme;
|
||||
this.setTheme(theme);
|
||||
this.themeDropdown.classList.remove('active');
|
||||
});
|
||||
});
|
||||
|
||||
// Close dropdown when clicking outside
|
||||
document.addEventListener('click', () => {
|
||||
this.themeDropdown?.classList.remove('active');
|
||||
});
|
||||
},
|
||||
|
||||
setTheme(theme) {
|
||||
document.documentElement.setAttribute('data-theme', theme);
|
||||
localStorage.setItem('harborsmith-theme', theme);
|
||||
|
||||
// Update active state
|
||||
this.themeOptions.forEach(option => {
|
||||
option.classList.toggle('active', option.dataset.theme === theme);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Navigation
|
||||
const navigation = {
|
||||
init() {
|
||||
this.navbar = document.getElementById('navbar');
|
||||
this.navToggle = document.getElementById('navToggle');
|
||||
this.navMenu = document.getElementById('navMenu');
|
||||
this.navLinks = document.querySelectorAll('.nav-link');
|
||||
|
||||
// Mobile menu toggle
|
||||
this.navToggle?.addEventListener('click', () => {
|
||||
this.navToggle.classList.toggle('active');
|
||||
this.navMenu.classList.toggle('active');
|
||||
});
|
||||
|
||||
// Close mobile menu on link click
|
||||
this.navLinks.forEach(link => {
|
||||
link.addEventListener('click', () => {
|
||||
this.navToggle?.classList.remove('active');
|
||||
this.navMenu?.classList.remove('active');
|
||||
});
|
||||
});
|
||||
|
||||
// Scroll behavior
|
||||
window.addEventListener('scroll', () => {
|
||||
if (window.scrollY > 50) {
|
||||
this.navbar?.classList.add('scrolled');
|
||||
} else {
|
||||
this.navbar?.classList.remove('scrolled');
|
||||
}
|
||||
});
|
||||
|
||||
// Active link highlighting
|
||||
this.updateActiveLink();
|
||||
},
|
||||
|
||||
updateActiveLink() {
|
||||
const currentPath = window.location.pathname.split('/').pop() || 'index.html';
|
||||
this.navLinks.forEach(link => {
|
||||
const href = link.getAttribute('href');
|
||||
if (href === currentPath) {
|
||||
link.classList.add('active');
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Smooth Scrolling
|
||||
const smoothScroll = {
|
||||
init() {
|
||||
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'
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Testimonial Slider
|
||||
const testimonialSlider = {
|
||||
init() {
|
||||
this.cards = document.querySelectorAll('.testimonial-card');
|
||||
this.dots = document.querySelectorAll('.dot');
|
||||
this.currentIndex = 0;
|
||||
|
||||
if (this.cards.length === 0) return;
|
||||
|
||||
// Auto-play
|
||||
this.startAutoPlay();
|
||||
|
||||
// Dot navigation
|
||||
this.dots.forEach((dot, index) => {
|
||||
dot.addEventListener('click', () => {
|
||||
this.goToSlide(index);
|
||||
this.resetAutoPlay();
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
goToSlide(index) {
|
||||
this.cards[this.currentIndex]?.classList.remove('active');
|
||||
this.dots[this.currentIndex]?.classList.remove('active');
|
||||
|
||||
this.currentIndex = index;
|
||||
|
||||
this.cards[this.currentIndex]?.classList.add('active');
|
||||
this.dots[this.currentIndex]?.classList.add('active');
|
||||
},
|
||||
|
||||
nextSlide() {
|
||||
const nextIndex = (this.currentIndex + 1) % this.cards.length;
|
||||
this.goToSlide(nextIndex);
|
||||
},
|
||||
|
||||
startAutoPlay() {
|
||||
this.autoPlayInterval = setInterval(() => {
|
||||
this.nextSlide();
|
||||
}, 5000);
|
||||
},
|
||||
|
||||
resetAutoPlay() {
|
||||
clearInterval(this.autoPlayInterval);
|
||||
this.startAutoPlay();
|
||||
}
|
||||
};
|
||||
|
||||
// Counter Animation
|
||||
const counterAnimation = {
|
||||
init() {
|
||||
this.counters = document.querySelectorAll('.stat-number');
|
||||
this.animated = false;
|
||||
|
||||
if (this.counters.length === 0) return;
|
||||
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting && !this.animated) {
|
||||
this.animateCounters();
|
||||
this.animated = true;
|
||||
}
|
||||
});
|
||||
}, { threshold: 0.5 });
|
||||
|
||||
const statsSection = document.querySelector('.stats-section');
|
||||
if (statsSection) {
|
||||
observer.observe(statsSection);
|
||||
}
|
||||
},
|
||||
|
||||
animateCounters() {
|
||||
this.counters.forEach(counter => {
|
||||
const target = parseInt(counter.dataset.count);
|
||||
const duration = 2000;
|
||||
const increment = target / (duration / 16);
|
||||
let current = 0;
|
||||
|
||||
const updateCounter = () => {
|
||||
current += increment;
|
||||
if (current < target) {
|
||||
counter.textContent = Math.floor(current);
|
||||
requestAnimationFrame(updateCounter);
|
||||
} else {
|
||||
counter.textContent = target;
|
||||
}
|
||||
};
|
||||
|
||||
updateCounter();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Form Validation
|
||||
const formValidation = {
|
||||
init() {
|
||||
const forms = document.querySelectorAll('form');
|
||||
forms.forEach(form => {
|
||||
form.addEventListener('submit', (e) => {
|
||||
if (!this.validateForm(form)) {
|
||||
e.preventDefault();
|
||||
}
|
||||
});
|
||||
|
||||
// Real-time validation
|
||||
const inputs = form.querySelectorAll('input, textarea, select');
|
||||
inputs.forEach(input => {
|
||||
input.addEventListener('blur', () => {
|
||||
this.validateField(input);
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
validateForm(form) {
|
||||
const inputs = form.querySelectorAll('[required]');
|
||||
let isValid = true;
|
||||
|
||||
inputs.forEach(input => {
|
||||
if (!this.validateField(input)) {
|
||||
isValid = false;
|
||||
}
|
||||
});
|
||||
|
||||
return isValid;
|
||||
},
|
||||
|
||||
validateField(field) {
|
||||
const value = field.value.trim();
|
||||
const type = field.type;
|
||||
let isValid = true;
|
||||
|
||||
// Remove previous error
|
||||
field.classList.remove('error');
|
||||
const errorMsg = field.parentElement.querySelector('.error-message');
|
||||
if (errorMsg) {
|
||||
errorMsg.remove();
|
||||
}
|
||||
|
||||
// Required field
|
||||
if (field.hasAttribute('required') && !value) {
|
||||
this.showError(field, 'This field is required');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Email validation
|
||||
if (type === 'email' && value) {
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
if (!emailRegex.test(value)) {
|
||||
this.showError(field, 'Please enter a valid email address');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Phone validation
|
||||
if (type === 'tel' && value) {
|
||||
const phoneRegex = /^[\d\s\-\+\(\)]+$/;
|
||||
if (!phoneRegex.test(value)) {
|
||||
this.showError(field, 'Please enter a valid phone number');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return isValid;
|
||||
},
|
||||
|
||||
showError(field, message) {
|
||||
field.classList.add('error');
|
||||
const errorElement = document.createElement('div');
|
||||
errorElement.className = 'error-message';
|
||||
errorElement.textContent = message;
|
||||
field.parentElement.appendChild(errorElement);
|
||||
}
|
||||
};
|
||||
|
||||
// Yacht Quick View Modal
|
||||
const yachtModal = {
|
||||
init() {
|
||||
const viewButtons = document.querySelectorAll('.yacht-view-btn');
|
||||
viewButtons.forEach(btn => {
|
||||
btn.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
this.openModal();
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
openModal() {
|
||||
// For now, just alert - in production, would open a modal
|
||||
alert('Quick view modal would open here with yacht details and 360° view');
|
||||
}
|
||||
};
|
||||
|
||||
// Parallax Effects
|
||||
const parallaxEffects = {
|
||||
init() {
|
||||
this.elements = document.querySelectorAll('.parallax');
|
||||
|
||||
if (this.elements.length === 0) return;
|
||||
|
||||
window.addEventListener('scroll', () => {
|
||||
this.updateParallax();
|
||||
});
|
||||
},
|
||||
|
||||
updateParallax() {
|
||||
const scrolled = window.pageYOffset;
|
||||
|
||||
this.elements.forEach(element => {
|
||||
const rate = element.dataset.rate || 0.5;
|
||||
const yPos = -(scrolled * rate);
|
||||
element.style.transform = `translateY(${yPos}px)`;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Initialize everything when DOM is ready
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
themeManager.init();
|
||||
navigation.init();
|
||||
smoothScroll.init();
|
||||
testimonialSlider.init();
|
||||
counterAnimation.init();
|
||||
formValidation.init();
|
||||
yachtModal.init();
|
||||
parallaxEffects.init();
|
||||
|
||||
// Log successful initialization
|
||||
console.log('HarborSmith website initialized successfully!');
|
||||
});
|
||||
355
website-mockups/js-backup-20250918-135225/voyage-layout.js
Normal file
355
website-mockups/js-backup-20250918-135225/voyage-layout.js
Normal file
@@ -0,0 +1,355 @@
|
||||
// Voyage Layout JavaScript - Smooth, Interactive, Engaging
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Theme Switcher
|
||||
const themeToggle = document.getElementById('themeToggle');
|
||||
const themeDropdown = document.getElementById('themeDropdown');
|
||||
const themeOptions = document.querySelectorAll('.theme-option');
|
||||
|
||||
if (themeToggle && themeDropdown) {
|
||||
themeToggle.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
themeDropdown.classList.toggle('active');
|
||||
});
|
||||
|
||||
themeOptions.forEach(option => {
|
||||
option.addEventListener('click', () => {
|
||||
const theme = option.dataset.theme;
|
||||
document.body.className = theme === 'nautical' ? '' : `theme-${theme}`;
|
||||
localStorage.setItem('selectedTheme', theme);
|
||||
themeDropdown.classList.remove('active');
|
||||
});
|
||||
});
|
||||
|
||||
// Close dropdown when clicking outside
|
||||
document.addEventListener('click', () => {
|
||||
themeDropdown.classList.remove('active');
|
||||
});
|
||||
|
||||
// Load saved theme
|
||||
const savedTheme = localStorage.getItem('selectedTheme');
|
||||
if (savedTheme && savedTheme !== 'nautical') {
|
||||
document.body.className = `theme-${savedTheme}`;
|
||||
}
|
||||
}
|
||||
|
||||
// Navigation scroll effect
|
||||
const nav = document.getElementById('voyageNav');
|
||||
let lastScroll = 0;
|
||||
|
||||
window.addEventListener('scroll', () => {
|
||||
const currentScroll = window.pageYOffset;
|
||||
|
||||
if (currentScroll > 50) {
|
||||
nav.classList.add('scrolled');
|
||||
} else {
|
||||
nav.classList.remove('scrolled');
|
||||
}
|
||||
|
||||
lastScroll = currentScroll;
|
||||
});
|
||||
|
||||
// Smooth scroll for navigation links
|
||||
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
|
||||
anchor.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
const target = document.querySelector(this.getAttribute('href'));
|
||||
if (target) {
|
||||
target.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'start'
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Parallax effect for hero video
|
||||
const heroSection = document.getElementById('heroSection');
|
||||
const heroVideo = document.querySelector('.hero-video-container');
|
||||
|
||||
window.addEventListener('scroll', () => {
|
||||
const scrolled = window.pageYOffset;
|
||||
const rate = scrolled * 0.5;
|
||||
|
||||
if (heroVideo) {
|
||||
heroVideo.style.transform = `translateY(${rate}px)`;
|
||||
}
|
||||
});
|
||||
|
||||
// Fleet Carousel
|
||||
const yachtCards = document.querySelectorAll('.yacht-card');
|
||||
const dots = document.querySelectorAll('.dot');
|
||||
const prevBtn = document.querySelector('.fleet-prev');
|
||||
const nextBtn = document.querySelector('.fleet-next');
|
||||
let currentYacht = 0;
|
||||
|
||||
function showYacht(index) {
|
||||
yachtCards.forEach(card => card.classList.remove('active'));
|
||||
dots.forEach(dot => dot.classList.remove('active'));
|
||||
|
||||
yachtCards[index].classList.add('active');
|
||||
dots[index].classList.add('active');
|
||||
}
|
||||
|
||||
if (prevBtn && nextBtn) {
|
||||
prevBtn.addEventListener('click', () => {
|
||||
currentYacht = (currentYacht - 1 + yachtCards.length) % yachtCards.length;
|
||||
showYacht(currentYacht);
|
||||
});
|
||||
|
||||
nextBtn.addEventListener('click', () => {
|
||||
currentYacht = (currentYacht + 1) % yachtCards.length;
|
||||
showYacht(currentYacht);
|
||||
});
|
||||
}
|
||||
|
||||
dots.forEach((dot, index) => {
|
||||
dot.addEventListener('click', () => {
|
||||
currentYacht = index;
|
||||
showYacht(currentYacht);
|
||||
});
|
||||
});
|
||||
|
||||
// Auto-rotate fleet carousel
|
||||
setInterval(() => {
|
||||
if (document.visibilityState === 'visible') {
|
||||
currentYacht = (currentYacht + 1) % yachtCards.length;
|
||||
showYacht(currentYacht);
|
||||
}
|
||||
}, 5000);
|
||||
|
||||
// CTA Button interactions
|
||||
document.querySelectorAll('.btn-primary-warm, .btn-secondary-warm').forEach(btn => {
|
||||
btn.addEventListener('click', function(e) {
|
||||
// Create ripple effect
|
||||
const ripple = document.createElement('span');
|
||||
ripple.classList.add('ripple');
|
||||
this.appendChild(ripple);
|
||||
|
||||
const rect = this.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';
|
||||
|
||||
setTimeout(() => ripple.remove(), 600);
|
||||
});
|
||||
});
|
||||
|
||||
// Add ripple styles
|
||||
const style = document.createElement('style');
|
||||
style.textContent = `
|
||||
.ripple {
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
background: rgba(255, 255, 255, 0.6);
|
||||
transform: scale(0);
|
||||
animation: ripple 0.6s ease-out;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@keyframes ripple {
|
||||
to {
|
||||
transform: scale(4);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-primary-warm,
|
||||
.btn-secondary-warm {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(style);
|
||||
|
||||
// Animate elements on scroll
|
||||
const observerOptions = {
|
||||
threshold: 0.1,
|
||||
rootMargin: '0px 0px -100px 0px'
|
||||
};
|
||||
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
entry.target.style.opacity = '1';
|
||||
entry.target.style.transform = 'translateY(0)';
|
||||
}
|
||||
});
|
||||
}, observerOptions);
|
||||
|
||||
// Observe elements for animation
|
||||
document.querySelectorAll('.welcome-content, .story-card, .booking-card').forEach(el => {
|
||||
el.style.opacity = '0';
|
||||
el.style.transform = 'translateY(30px)';
|
||||
el.style.transition = 'all 0.6s ease';
|
||||
observer.observe(el);
|
||||
});
|
||||
|
||||
// Interactive booking cards
|
||||
document.querySelectorAll('.booking-card').forEach(card => {
|
||||
card.addEventListener('mouseenter', function() {
|
||||
this.style.transform = 'translateY(-10px) scale(1.02)';
|
||||
});
|
||||
|
||||
card.addEventListener('mouseleave', function() {
|
||||
if (this.classList.contains('featured')) {
|
||||
this.style.transform = 'scale(1.05)';
|
||||
} else {
|
||||
this.style.transform = 'translateY(0) scale(1)';
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Phone number click handler
|
||||
document.querySelectorAll('.btn-booking.primary').forEach(btn => {
|
||||
if (btn.textContent.includes('415')) {
|
||||
btn.addEventListener('click', () => {
|
||||
window.location.href = 'tel:4155550123';
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Video optimization - pause when not visible
|
||||
const video = document.querySelector('.hero-video');
|
||||
if (video) {
|
||||
const videoObserver = new IntersectionObserver((entries) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
video.play();
|
||||
} else {
|
||||
video.pause();
|
||||
}
|
||||
});
|
||||
}, { threshold: 0.25 });
|
||||
|
||||
videoObserver.observe(video);
|
||||
}
|
||||
|
||||
// Story cards hover effect
|
||||
document.querySelectorAll('.story-card').forEach(card => {
|
||||
const img = card.querySelector('img');
|
||||
|
||||
card.addEventListener('mouseenter', () => {
|
||||
if (img) {
|
||||
img.style.transform = 'scale(1.1)';
|
||||
img.style.transition = 'transform 0.6s ease';
|
||||
}
|
||||
});
|
||||
|
||||
card.addEventListener('mouseleave', () => {
|
||||
if (img) {
|
||||
img.style.transform = 'scale(1)';
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Dynamic trust badge counter - starts from 0
|
||||
// Target the white text span specifically (the second span in trust-badge)
|
||||
const trustBadge = document.querySelector('.trust-badge > span:last-child');
|
||||
if (trustBadge) {
|
||||
let count = 0;
|
||||
const targetCount = 500;
|
||||
const duration = 2000; // 2 seconds
|
||||
const steps = 50;
|
||||
const increment = targetCount / steps;
|
||||
const stepDuration = duration / steps;
|
||||
|
||||
// Start with 0
|
||||
trustBadge.textContent = `Trusted by 0+ adventurers`;
|
||||
|
||||
const counter = setInterval(() => {
|
||||
count += increment;
|
||||
if (count >= targetCount) {
|
||||
count = targetCount;
|
||||
clearInterval(counter);
|
||||
trustBadge.textContent = `Trusted by 500+ adventurers`;
|
||||
} else {
|
||||
trustBadge.textContent = `Trusted by ${Math.floor(count)}+ adventurers`;
|
||||
}
|
||||
}, stepDuration);
|
||||
}
|
||||
|
||||
// Remove any duplicate red text that might exist
|
||||
const allSpans = document.querySelectorAll('.hero-content span');
|
||||
allSpans.forEach(span => {
|
||||
// Check if this is the red duplicate text (not inside trust-badge)
|
||||
if (span.textContent.includes('Trusted by') &&
|
||||
span.textContent.includes('adventurers') &&
|
||||
!span.closest('.trust-badge')) {
|
||||
span.style.display = 'none';
|
||||
}
|
||||
});
|
||||
|
||||
// Smooth hover for navigation links
|
||||
document.querySelectorAll('.nav-link').forEach(link => {
|
||||
link.addEventListener('mouseenter', function() {
|
||||
this.style.transform = 'translateY(-2px)';
|
||||
});
|
||||
|
||||
link.addEventListener('mouseleave', function() {
|
||||
this.style.transform = 'translateY(0)';
|
||||
});
|
||||
});
|
||||
|
||||
// Mobile menu (would need to add hamburger menu for production)
|
||||
const mobileMenuBtn = document.createElement('button');
|
||||
mobileMenuBtn.className = 'mobile-menu-btn';
|
||||
mobileMenuBtn.innerHTML = '<span></span><span></span><span></span>';
|
||||
mobileMenuBtn.style.cssText = `
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
background: transparent;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
padding: 0.5rem;
|
||||
`;
|
||||
|
||||
// Add mobile menu styles
|
||||
const mobileStyles = document.createElement('style');
|
||||
mobileStyles.textContent = `
|
||||
@media (max-width: 768px) {
|
||||
.mobile-menu-btn {
|
||||
display: flex !important;
|
||||
}
|
||||
|
||||
.mobile-menu-btn span {
|
||||
width: 24px;
|
||||
height: 2px;
|
||||
background: white;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.voyage-nav.scrolled .mobile-menu-btn span {
|
||||
background: var(--primary-blue);
|
||||
}
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(mobileStyles);
|
||||
|
||||
// Add mobile menu button to nav
|
||||
const navContainer = document.querySelector('.nav-container');
|
||||
if (navContainer && window.innerWidth <= 768) {
|
||||
navContainer.appendChild(mobileMenuBtn);
|
||||
}
|
||||
|
||||
// Loading animation for images
|
||||
document.querySelectorAll('img').forEach(img => {
|
||||
// Check if image is already loaded
|
||||
if (img.complete && img.naturalHeight !== 0) {
|
||||
img.style.opacity = '1';
|
||||
} else {
|
||||
img.style.opacity = '0';
|
||||
img.addEventListener('load', function() {
|
||||
this.style.opacity = '1';
|
||||
});
|
||||
}
|
||||
img.style.transition = 'opacity 0.6s ease';
|
||||
});
|
||||
|
||||
console.log('Voyage layout initialized successfully!');
|
||||
});
|
||||
Reference in New Issue
Block a user