From cecbed99b5f28e4cb52940750b422484752dfc24 Mon Sep 17 00:00:00 2001 From: Ruslan Bakiev Date: Mon, 26 Jan 2026 16:12:00 +0700 Subject: [PATCH] Add hero section to home page with scroll collapse - Full-screen dark gradient hero on home page - Search input centered vertically - Smooth collapse to fixed header on scroll - Added hero translations (ru/en) --- app/composables/useHeroScroll.ts | 75 +++++++++ app/layouts/topnav.vue | 277 +++++++++++++++++++++++++------ i18n/locales/en/hero.json | 6 + i18n/locales/ru/hero.json | 6 + nuxt.config.ts | 4 +- 5 files changed, 320 insertions(+), 48 deletions(-) create mode 100644 app/composables/useHeroScroll.ts create mode 100644 i18n/locales/en/hero.json create mode 100644 i18n/locales/ru/hero.json 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 @@