Componentalization

This commit is contained in:
2025-09-16 22:01:42 +10:00
parent e965cc2780
commit 91771e9573
19 changed files with 1683 additions and 223 deletions

View File

@@ -0,0 +1,17 @@
<script setup lang="ts">
import AppLayout from "@/layouts/AppLayout.vue";
defineOptions({
layout: AppLayout
})
</script>
<template>
<section style="position: relative;min-height: 100vh;display:flex;align-items: center;justify-content: center;">
<v-date-picker show-adjacent-months></v-date-picker>
</section>
</template>
<style scoped>
</style>

View File

@@ -29,87 +29,6 @@ defineOptions({
})
// 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>;
@@ -140,87 +59,9 @@ const initSmoothScrolling = (): void => {
});
};
// 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