Files
leuschke-site/src/components/Header.vue
Homér 04d9ea35f4
Some checks failed
Build and Deploy / build-and-deploy (push) Has been cancelled
feat: build leuschke.site with pixel design system, vue 3, tailwind
2026-05-13 12:48:05 +00:00

107 lines
3.8 KiB
Vue

<template>
<header :class="themeClass">
<div class="max-w-[960px] mx-auto px-6 py-5 flex justify-between items-center flex-wrap gap-3">
<router-link to="/" class="text-olive font-pixel text-xs tracking-tight hover:text-orange transition-colors">
LEUSCHKE<span class="text-orange">.</span>DIGITAL
</router-link>
<!-- Desktop Nav -->
<nav class="hidden md:flex items-center gap-3">
<router-link v-for="item in navItems" :key="item.path" :to="item.path"
class="font-pixel text-[8px] text-olive hover:text-orange transition-colors"
:class="{ 'text-orange border-b-2 border-orange': route.path === item.path }">
{{ t(item.label) }}
</router-link>
</nav>
<!-- Controls -->
<div class="flex items-center gap-2">
<!-- Mobile menu button -->
<button @click="mobileOpen = !mobileOpen" class="md:hidden pixel-btn text-[8px] px-3 py-2">
</button>
<button @click="toggleTheme" class="hidden md:inline-flex pixel-btn text-[8px] px-3 py-2">
{{ isDark ? '☀️' : '🌙' }} {{ isDark ? t('nav.light') : t('nav.dark') }}
</button>
<button @click="toggleLang" class="hidden md:inline-flex pixel-btn text-[8px] px-3 py-2">
{{ lang === 'de' ? '🇩🇪 DE' : '🇬🇧 EN' }}
</button>
</div>
</div>
<!-- Mobile menu -->
<div v-if="mobileOpen" class="md:hidden pixel-border mx-3 mb-3 p-3 flex flex-col gap-3">
<router-link v-for="item in navItems" :key="item.path" :to="item.path"
class="font-pixel text-[8px] text-olive hover:text-orange block px-2 py-1"
@click="mobileOpen = false">
{{ t(item.label) }}
</router-link>
<div class="flex gap-2 pt-2">
<button @click="toggleTheme" class="pixel-btn text-[8px] px-3 py-2">
{{ isDark ? '☀️' : '🌙' }} {{ isDark ? t('nav.light') : t('nav.dark') }}
</button>
<button @click="toggleLang" class="pixel-btn text-[8px] px-3 py-2">
{{ lang === 'de' ? '🇩🇪 DE' : '🇬🇧 EN' }}
</button>
</div>
</div>
</header>
</template>
<script setup lang="ts">
import { ref, computed, watch } from 'vue'
import { useRoute } from 'vue-router'
import { t, currentLang, type Lang } from '../i18n'
const route = useRoute()
const isDark = ref<boolean>(false)
const lang = ref<Lang>('de')
const mobileOpen = ref(false)
const navItems = [
{ path: '/', label: 'nav.home' },
{ path: '/about', label: 'nav.about' },
{ path: '/projects', label: 'nav.projects' },
{ path: '/blog', label: 'nav.blog' },
{ path: '/contact', label: 'nav.contact' },
{ path: '/impressum', label: 'nav.impressum' },
]
const toggleTheme = () => { isDark.value = !isDark.value; applyTheme() }
const toggleLang = () => {
lang.value = lang.value === 'de' ? 'en' : 'de'
currentLang.value = lang.value
}
const applyTheme = () => {
document.documentElement.setAttribute('data-theme', isDark.value ? 'dark' : 'light')
document.documentElement.setAttribute('data-lang', lang.value)
}
const themeClass = computed(() => ({
'bg-beige-light border-b-4 border-olive': true,
'[&[data-theme=dark]]:bg-beige-dark [&[data-theme=dark]]:text-[#d4ccb8] [&[data-theme=dark]]:border-olive-dark': isDark.value,
}))
// Init from localStorage or defaults
if (typeof localStorage !== 'undefined') {
isDark.value = localStorage.getItem('theme') === 'dark'
}
lang.value = (localStorage.getItem('lang') as Lang) || 'de'
currentLang.value = lang.value
applyTheme()
watch(isDark, (v) => {
localStorage.setItem('theme', v ? 'dark' : 'light')
applyTheme()
})
watch(lang, (v) => {
localStorage.setItem('lang', v)
currentLang.value = v
applyTheme()
})
</script>
<style scoped>
header { position: fixed; top: 0; left: 0; right: 0; z-index: 50; }
</style>