diff --git a/app/composables/useHeroScroll.ts b/app/composables/useHeroScroll.ts new file mode 100644 index 0000000..e544c66 --- /dev/null +++ b/app/composables/useHeroScroll.ts @@ -0,0 +1,75 @@ +/** + * Composable for home page hero scroll behavior + * Starts with full-screen hero, collapses to fixed header on scroll + */ +export const useHeroScroll = () => { + const scrollY = ref(0) + + // Hero height = viewport height minus some space (e.g., 80px for visual balance) + const heroBaseHeight = ref(0) + const collapsedHeight = 100 // Fixed header height when collapsed + + // Calculate hero height based on viewport + const updateHeroHeight = () => { + if (import.meta.client) { + heroBaseHeight.value = window.innerHeight - 80 // Nearly full screen + } + } + + // How much to collapse (0 = full hero, 1 = fully collapsed) + const collapseProgress = computed(() => { + const maxScroll = heroBaseHeight.value - collapsedHeight + if (maxScroll <= 0) return 1 + return Math.min(1, Math.max(0, scrollY.value / maxScroll)) + }) + + // Current hero height (animated) + const heroHeight = computed(() => { + const maxScroll = heroBaseHeight.value - collapsedHeight + return Math.max(collapsedHeight, heroBaseHeight.value - scrollY.value) + }) + + // Is fully collapsed to fixed header? + const isCollapsed = computed(() => collapseProgress.value >= 1) + + // Padding for content below hero + const contentPaddingTop = computed(() => { + return heroHeight.value + }) + + const onScroll = () => { + if (import.meta.client) { + scrollY.value = window.scrollY + } + } + + const onResize = () => { + updateHeroHeight() + } + + onMounted(() => { + if (import.meta.client) { + updateHeroHeight() + window.addEventListener('scroll', onScroll, { passive: true }) + window.addEventListener('resize', onResize, { passive: true }) + onScroll() + } + }) + + onUnmounted(() => { + if (import.meta.client) { + window.removeEventListener('scroll', onScroll) + window.removeEventListener('resize', onResize) + } + }) + + return { + scrollY: readonly(scrollY), + heroBaseHeight: readonly(heroBaseHeight), + heroHeight, + collapseProgress, + isCollapsed, + contentPaddingTop, + collapsedHeight + } +} diff --git a/app/layouts/topnav.vue b/app/layouts/topnav.vue index c3f59e8..4512f1c 100644 --- a/app/layouts/topnav.vue +++ b/app/layouts/topnav.vue @@ -1,49 +1,200 @@