Populated Content

This commit is contained in:
2025-09-15 11:44:26 +10:00
parent 5ad54abff2
commit f0e7ce30fc
177 changed files with 1859 additions and 5463 deletions

239
resources/js/pages/Home.vue Normal file
View File

@@ -0,0 +1,239 @@
<template>
<Hero />
<!-- About Section -->
<About />
<!-- Featured Tours Section -->
<FeaturedTours />
</template>
<style>
</style>
<script setup lang="ts">
import { onMounted } from 'vue';
import AppLayout from '../layouts/AppLayout.vue'
import Hero from "@/components/home/Hero.vue";
import About from "@/components/home/About.vue";
import FeaturedTours from "@/components/home/FeaturedTours.vue";
defineOptions({
layout: AppLayout
})
// Types
interface IntersectionObserverOptions {
threshold?: number;
rootMargin?: string;
}
// Intersection Observer for animations
const createIntersectionObserver = (
callback: IntersectionObserverCallback,
options: IntersectionObserverOptions = {}
): IntersectionObserver => {
const defaultOptions: IntersectionObserverOptions = {
threshold: 0.3,
rootMargin: '0px 0px -50px 0px'
};
return new IntersectionObserver(callback, { ...defaultOptions, ...options });
};
// About section animation
const initAboutAnimation = (): void => {
const aboutContent = document.querySelector('.about-content') as HTMLElement | null;
if (!aboutContent) return;
const observer = createIntersectionObserver((entries: IntersectionObserverEntry[]) => {
entries.forEach((entry: IntersectionObserverEntry) => {
if (entry.isIntersecting) {
entry.target.classList.add('visible');
} else {
entry.target.classList.remove('visible');
}
});
});
observer.observe(aboutContent);
};
// Featured tours animation
const initToursAnimation = (): void => {
const toursHeader = document.querySelector('.tours-header') as HTMLElement | null;
const tourCards = document.querySelectorAll('.tour-card') as NodeListOf<HTMLElement>;
if (!toursHeader || !tourCards.length) return;
// Header animation
const headerObserver = createIntersectionObserver((entries: IntersectionObserverEntry[]) => {
entries.forEach((entry: IntersectionObserverEntry) => {
if (entry.isIntersecting) {
entry.target.classList.add('visible');
}
});
});
headerObserver.observe(toursHeader);
// Tour cards animation with staggered effect
const cardsObserver = createIntersectionObserver((entries: IntersectionObserverEntry[]) => {
entries.forEach((entry: IntersectionObserverEntry) => {
const tourCard = entry.target as HTMLElement;
const index = parseInt(tourCard.dataset.index || '0');
if (entry.isIntersecting) {
// Staggered animation when scrolling down
setTimeout(() => {
tourCard.classList.add('visible');
}, index * 150);
} else {
// Reverse staggered animation when scrolling up
const reverseIndex = (tourCards.length - 1) - index;
setTimeout(() => {
tourCard.classList.remove('visible');
}, reverseIndex * 100);
}
});
}, { threshold: 0.2 });
tourCards.forEach((card: HTMLElement) => {
cardsObserver.observe(card);
});
};
// Smooth scrolling for navigation links
const initSmoothScrolling = (): void => {
const navLinks = document.querySelectorAll('a[href^="#"]') as NodeListOf<HTMLAnchorElement>;
navLinks.forEach((link: HTMLAnchorElement) => {
link.addEventListener('click', (e: Event) => {
e.preventDefault();
const targetId = link.getAttribute('href');
const targetElement = targetId ? document.querySelector(targetId) as HTMLElement | null : null;
if (targetElement) {
const header = document.querySelector('.header') as HTMLElement | null;
const headerHeight = header ? header.offsetHeight : 0;
const targetPosition = targetElement.offsetTop - headerHeight;
window.scrollTo({
top: targetPosition,
behavior: 'smooth'
});
// Close dropdown if open
const dropdown = document.querySelector('.dropdown') as HTMLElement | null;
if (dropdown) {
dropdown.classList.remove('open');
}
}
});
});
};
// Parallax effect for hero background
const initParallax = (): void => {
const heroBackground = document.querySelector('.hero-bg') as HTMLElement | null;
const heroSection = document.querySelector('.hero') as HTMLElement | null;
if (!heroBackground || !heroSection) return;
window.addEventListener('scroll', () => {
const scrolled = window.pageYOffset;
const heroRect = heroSection.getBoundingClientRect();
const heroHeight = heroSection.offsetHeight;
if (heroRect.bottom > 0) {
const rate = scrolled * -0.15;
const maxMovement = heroHeight * 0.1;
const constrainedRate = Math.max(rate, -maxMovement);
heroBackground.style.transform = `translateY(${constrainedRate}px)`;
}
});
};
// Add loading animations
const initLoadingAnimations = (): void => {
// Add class to trigger CSS animations
document.body.classList.add('loaded');
// Animate hero content
const heroTitle = document.querySelector('.hero-title') as HTMLElement | null;
const heroSubtitle = document.querySelector('.hero-subtitle') as HTMLElement | null;
if (heroTitle) {
setTimeout(() => {
heroTitle.style.opacity = '1';
heroTitle.style.transform = 'translateY(0)';
}, 300);
}
if (heroSubtitle) {
setTimeout(() => {
heroSubtitle.style.opacity = '1';
heroSubtitle.style.transform = 'translateY(0)';
}, 600);
}
};
// Tour card interactions
const initTourCardInteractions = (): void => {
const tourCards = document.querySelectorAll('.tour-card') as NodeListOf<HTMLElement>;
tourCards.forEach((card: HTMLElement) => {
const bookButton = card.querySelector('.btn') as HTMLElement | null;
if (bookButton) {
bookButton.addEventListener('click', () => {
// Add your booking logic here
alert('Booking functionality would be implemented here!');
});
}
// Add hover effects
card.addEventListener('mouseenter', () => {
card.style.transform = 'translateY(-8px) scale(1.02)';
});
card.addEventListener('mouseleave', () => {
if (card.classList.contains('visible')) {
card.style.transform = 'translateY(0) rotate(0deg) scale(1)';
}
});
});
};
// Initialize all functionality when DOM is loaded
onMounted(() => {
initAboutAnimation();
initToursAnimation();
initSmoothScrolling();
initParallax();
initLoadingAnimations();
initTourCardInteractions();
});
// Add performance optimization
const debounce = <T extends (...args: any[]) => void>(func: T, wait: number): T => {
let timeout: number | undefined;
return ((...args: Parameters<T>) => {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
}) as T;
};
// Optimize scroll events
window.addEventListener('scroll', debounce(() => {
// Scroll-based animations can be added here
}, 16)); // ~60fps
</script>