Files
Portfolio-Game/frontend/app/components/layout/AppHeader.vue
skycel ca828d86b4 Add layouts, routing, transitions & pages (Story 1.4)
Default layout with sticky AppHeader (nav, LanguageSwitcher, mobile hamburger),
AppFooter with social links. Minimal layout for express mode. 7 placeholder pages
with localized EN routes. Page transitions (fade+slide), prefers-reduced-motion
support, custom scroll behavior, error.vue, useSeo composable, SVG favicon.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-05 18:35:31 +01:00

106 lines
3.1 KiB
Vue

<template>
<header
class="sticky top-0 z-50 transition-colors duration-300"
:class="scrolled ? 'bg-sky-dark/90 backdrop-blur-sm shadow-lg' : 'bg-transparent'"
>
<div class="max-w-7xl mx-auto px-4 py-3 flex items-center justify-between">
<NuxtLink :to="localePath('/')" class="text-xl font-narrative text-sky-accent font-bold">
Skycel
</NuxtLink>
<!-- Desktop nav -->
<nav class="hidden md:flex items-center gap-6" aria-label="Main navigation">
<NuxtLink
v-for="item in navItems"
:key="item.path"
:to="localePath(item.path)"
class="text-sm font-ui text-sky-text/70 hover:text-sky-accent transition-colors"
active-class="!text-sky-accent"
>
{{ $t(item.label) }}
</NuxtLink>
</nav>
<div class="flex items-center gap-3">
<!-- Placeholder barre progression (Epic 3) -->
<div class="hidden md:block w-24 h-1.5 bg-sky-text/10 rounded-full overflow-hidden">
<div class="h-full bg-sky-accent/40 rounded-full" style="width: 0%"></div>
</div>
<UiLanguageSwitcher />
<!-- Mobile hamburger -->
<button
class="md:hidden text-sky-text p-1"
aria-label="Menu"
@click="mobileOpen = !mobileOpen"
>
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
v-if="!mobileOpen"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M4 6h16M4 12h16M4 18h16"
/>
<path
v-else
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M6 18L18 6M6 6l12 12"
/>
</svg>
</button>
</div>
</div>
<!-- Mobile menu -->
<Transition name="slide-down">
<nav
v-if="mobileOpen"
class="md:hidden bg-sky-dark/95 backdrop-blur-sm border-t border-sky-text/10 px-4 py-4"
aria-label="Mobile navigation"
>
<NuxtLink
v-for="item in navItems"
:key="item.path"
:to="localePath(item.path)"
class="block py-2 text-sm font-ui text-sky-text/70 hover:text-sky-accent transition-colors"
active-class="!text-sky-accent"
@click="mobileOpen = false"
>
{{ $t(item.label) }}
</NuxtLink>
</nav>
</Transition>
</header>
</template>
<script setup lang="ts">
const localePath = useLocalePath()
const mobileOpen = ref(false)
const scrolled = ref(false)
const navItems = [
{ path: '/projets', label: 'nav.projects' },
{ path: '/competences', label: 'nav.skills' },
{ path: '/parcours', label: 'nav.journey' },
{ path: '/temoignages', label: 'nav.testimonials' },
{ path: '/contact', label: 'nav.contact' },
]
onMounted(() => {
window.addEventListener('scroll', handleScroll, { passive: true })
})
onUnmounted(() => {
window.removeEventListener('scroll', handleScroll)
})
function handleScroll() {
scrolled.value = window.scrollY > 20
}
</script>