- Vue 3 + Nuxt 3 Framework - Tailwind CSS Styling - Responsive Design mit schwarz-roten Vereinsfarben - Dynamische Galerie mit Lightbox - Event-Management über CSV-Dateien - Mannschaftsübersicht mit dynamischen Seiten - SMTP-Kontaktformular - Google Maps Integration - Mobile-optimierte Navigation mit Submenus - Trainer-Übersicht - Vereinsmeisterschaften, Spielsysteme, TT-Regeln - Impressum mit Datenschutzerklärung
415 lines
20 KiB
Vue
415 lines
20 KiB
Vue
<template>
|
|
<nav
|
|
class="fixed top-0 left-0 right-0 z-50 bg-gradient-to-r from-gray-900 via-primary-900 to-gray-900 shadow-xl h-20">
|
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 h-full">
|
|
<div class="flex flex-col justify-between h-full py-2">
|
|
<!-- Hauptmenü -->
|
|
<div class="flex justify-between items-center">
|
|
<!-- Logo -->
|
|
<NuxtLink to="/" class="flex items-center space-x-3 hover:scale-105 transition-transform">
|
|
<img
|
|
src="~/assets/images/logos/Harheimer TC.svg"
|
|
alt="Harheimer TC Logo"
|
|
class="w-12 h-12"
|
|
/>
|
|
<div class="hidden sm:block">
|
|
<span class="text-xl font-display font-bold text-white">Harheimer <span
|
|
class="text-primary-400">TC</span></span>
|
|
</div>
|
|
</NuxtLink>
|
|
|
|
<div style="display:flex;flex-direction:column;">
|
|
<!-- Desktop Navigation -->
|
|
<div class="hidden lg:flex items-center space-x-1">
|
|
<NuxtLink to="/"
|
|
class="px-4 py-2 text-gray-300 hover:text-white font-medium transition-all rounded-lg hover:bg-primary-700/50"
|
|
active-class="text-white bg-primary-600">
|
|
Start
|
|
</NuxtLink>
|
|
|
|
<button @click="toggleSubmenu('verein')"
|
|
class="px-4 py-2 text-gray-300 hover:text-white font-medium transition-all rounded-lg hover:bg-primary-700/50"
|
|
:class="(route.path.startsWith('/ueber-uns') || route.path.startsWith('/vorstand') || route.path.startsWith('/geschichte') || route.path.startsWith('/satzung') || route.path.startsWith('/vereinsmeisterschaften') || currentSubmenu === 'verein') ? 'text-white bg-primary-600' : ''">
|
|
Verein
|
|
</button>
|
|
|
|
<button @click="toggleSubmenu('mannschaften')"
|
|
class="px-4 py-2 text-gray-300 hover:text-white font-medium transition-all rounded-lg hover:bg-primary-700/50"
|
|
:class="(route.path.startsWith('/mannschaften') || route.path.startsWith('/spielsysteme') || currentSubmenu === 'mannschaften') ? 'text-white bg-primary-600' : ''">
|
|
Mannschaften
|
|
</button>
|
|
|
|
<button @click="toggleSubmenu('training')"
|
|
class="px-4 py-2 text-gray-300 hover:text-white font-medium transition-all rounded-lg hover:bg-primary-700/50"
|
|
:class="(route.path.startsWith('/training') || route.path.startsWith('/tt-regeln') || currentSubmenu === 'training') ? 'text-white bg-primary-600' : ''">
|
|
Training
|
|
</button>
|
|
|
|
<NuxtLink to="/mitgliedschaft"
|
|
class="px-4 py-2 text-gray-300 hover:text-white font-medium transition-all rounded-lg hover:bg-primary-700/50"
|
|
active-class="text-white bg-primary-600">
|
|
Mitgliedschaft
|
|
</NuxtLink>
|
|
|
|
<NuxtLink to="/termine" @click="currentSubmenu = null"
|
|
class="px-4 py-2 text-gray-300 hover:text-white font-medium transition-all rounded-lg hover:bg-primary-700/50"
|
|
active-class="text-white bg-primary-600">
|
|
Termine
|
|
</NuxtLink>
|
|
|
|
<NuxtLink
|
|
v-if="hasGalleryImages"
|
|
to="/galerie"
|
|
@click="currentSubmenu = null"
|
|
class="px-4 py-2 text-gray-300 hover:text-white font-medium transition-all rounded-lg hover:bg-primary-700/50"
|
|
active-class="text-white bg-primary-600">
|
|
Galerie
|
|
</NuxtLink>
|
|
|
|
<NuxtLink to="/kontakt" @click="currentSubmenu = null"
|
|
class="px-4 py-2 bg-primary-600 hover:bg-primary-700 text-white font-semibold transition-all rounded-lg shadow-lg">
|
|
Kontakt
|
|
</NuxtLink>
|
|
</div>
|
|
|
|
<div class="hidden lg:flex items-center h-6 border-t border-primary-700/20">
|
|
<div v-if="currentSubmenu" class="flex items-center space-x-1">
|
|
<!-- Verein Submenu -->
|
|
<template v-if="currentSubmenu === 'verein'">
|
|
<NuxtLink to="/ueber-uns"
|
|
class="px-2.5 py-1 text-xs text-gray-300 hover:text-white hover:bg-primary-700/50 rounded transition-all"
|
|
active-class="text-white bg-primary-600">
|
|
Über uns
|
|
</NuxtLink>
|
|
<NuxtLink to="/vorstand"
|
|
class="px-2.5 py-1 text-xs text-gray-300 hover:text-white hover:bg-primary-700/50 rounded transition-all"
|
|
active-class="text-white bg-primary-600">
|
|
Vorstand
|
|
</NuxtLink>
|
|
<NuxtLink to="/geschichte"
|
|
class="px-2.5 py-1 text-xs text-gray-300 hover:text-white hover:bg-primary-700/50 rounded transition-all"
|
|
active-class="text-white bg-primary-600">
|
|
Geschichte
|
|
</NuxtLink>
|
|
<NuxtLink to="/satzung"
|
|
class="px-2.5 py-1 text-xs text-gray-300 hover:text-white hover:bg-primary-700/50 rounded transition-all"
|
|
active-class="text-white bg-primary-600">
|
|
Satzung
|
|
</NuxtLink>
|
|
<NuxtLink to="/vereinsmeisterschaften"
|
|
class="px-2.5 py-1 text-xs text-gray-300 hover:text-white hover:bg-primary-700/50 rounded transition-all"
|
|
active-class="text-white bg-primary-600">
|
|
Vereinsmeisterschaften
|
|
</NuxtLink>
|
|
</template>
|
|
|
|
<!-- Mannschaften Submenu -->
|
|
<template v-if="currentSubmenu === 'mannschaften'">
|
|
<NuxtLink to="/mannschaften"
|
|
class="px-2.5 py-1 text-xs font-semibold text-white hover:bg-primary-700/50 rounded transition-all"
|
|
active-class="bg-primary-600">
|
|
Übersicht
|
|
</NuxtLink>
|
|
<div class="h-3 w-px bg-primary-700" />
|
|
<template v-for="mannschaft in mannschaften" :key="mannschaft.slug">
|
|
<NuxtLink
|
|
:to="`/mannschaften/${mannschaft.slug}`"
|
|
class="px-2.5 py-1 text-xs text-gray-300 hover:text-white hover:bg-primary-700/50 rounded transition-all"
|
|
active-class="text-white bg-primary-600">
|
|
{{ mannschaft.mannschaft }}
|
|
</NuxtLink>
|
|
</template>
|
|
<div class="h-3 w-px bg-primary-700" />
|
|
<NuxtLink to="/mannschaften/spielplaene"
|
|
class="px-2.5 py-1 text-xs text-gray-300 hover:text-white hover:bg-primary-700/50 rounded transition-all"
|
|
active-class="text-white bg-primary-600">
|
|
Spielpläne
|
|
</NuxtLink>
|
|
<NuxtLink to="/spielsysteme"
|
|
class="px-2.5 py-1 text-xs text-gray-300 hover:text-white hover:bg-primary-700/50 rounded transition-all"
|
|
active-class="text-white bg-primary-600">
|
|
Spielsysteme
|
|
</NuxtLink>
|
|
</template>
|
|
|
|
<!-- Training Submenu -->
|
|
<template v-if="currentSubmenu === 'training'">
|
|
<NuxtLink to="/training"
|
|
class="px-2.5 py-1 text-xs text-gray-300 hover:text-white hover:bg-primary-700/50 rounded transition-all"
|
|
active-class="text-white bg-primary-600">
|
|
Trainingszeiten
|
|
</NuxtLink>
|
|
<NuxtLink to="/training/trainer"
|
|
class="px-2.5 py-1 text-xs text-gray-300 hover:text-white hover:bg-primary-700/50 rounded transition-all"
|
|
active-class="text-white bg-primary-600">
|
|
Trainer
|
|
</NuxtLink>
|
|
<NuxtLink to="/training/anfaenger"
|
|
class="px-2.5 py-1 text-xs text-gray-300 hover:text-white hover:bg-primary-700/50 rounded transition-all"
|
|
active-class="text-white bg-primary-600">
|
|
Anfänger
|
|
</NuxtLink>
|
|
<NuxtLink to="/tt-regeln"
|
|
class="px-2.5 py-1 text-xs text-gray-300 hover:text-white hover:bg-primary-700/50 rounded transition-all"
|
|
active-class="text-white bg-primary-600">
|
|
TT-Regeln
|
|
</NuxtLink>
|
|
</template>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Mobile Menu Button -->
|
|
<button @click="isMobileMenuOpen = !isMobileMenuOpen"
|
|
class="lg:hidden p-2 rounded-lg hover:bg-primary-700/50 transition-colors" aria-label="Toggle menu">
|
|
<X v-if="isMobileMenuOpen" :size="24" class="text-white" />
|
|
<Menu v-else :size="24" class="text-white" />
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Untermenü (Desktop) - im gleichen Block, immer gleiche Höhe -->
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Mobile Menu -->
|
|
<Transition enter-active-class="transition duration-200 ease-out"
|
|
enter-from-class="opacity-0 transform -translate-y-2" enter-to-class="opacity-100 transform translate-y-0"
|
|
leave-active-class="transition duration-150 ease-in" leave-from-class="opacity-100 transform translate-y-0"
|
|
leave-to-class="opacity-0 transform -translate-y-2">
|
|
<div v-if="isMobileMenuOpen"
|
|
class="lg:hidden bg-gray-800 border-t border-primary-700/30 max-h-[80vh] overflow-y-auto">
|
|
<div class="px-4 py-4 space-y-2">
|
|
<NuxtLink to="/" @click="isMobileMenuOpen = false"
|
|
class="block px-4 py-3 text-gray-300 hover:text-white hover:bg-primary-700/50 rounded-lg font-medium transition-colors">
|
|
Start
|
|
</NuxtLink>
|
|
|
|
<!-- Verein Mobile -->
|
|
<div>
|
|
<button @click="toggleMobileSubmenu('verein')"
|
|
class="w-full flex items-center justify-between px-4 py-3 text-gray-300 hover:text-white hover:bg-primary-700/50 rounded-lg font-medium transition-colors">
|
|
Verein
|
|
<ChevronDown :size="16"
|
|
:class="['transition-transform', mobileSubmenu === 'verein' ? 'rotate-180' : '']" />
|
|
</button>
|
|
<div v-if="mobileSubmenu === 'verein'" class="pl-4 space-y-1 mt-1 bg-primary-900/30 rounded-lg p-2">
|
|
<NuxtLink to="/ueber-uns" @click="isMobileMenuOpen = false"
|
|
class="block px-4 py-2 text-sm text-gray-400 hover:text-white hover:bg-primary-700/50 rounded-lg transition-colors">
|
|
Über uns
|
|
</NuxtLink>
|
|
<NuxtLink to="/vorstand" @click="isMobileMenuOpen = false"
|
|
class="block px-4 py-2 text-sm text-gray-400 hover:text-white hover:bg-primary-700/50 rounded-lg transition-colors">
|
|
Vorstand
|
|
</NuxtLink>
|
|
<NuxtLink to="/geschichte" @click="isMobileMenuOpen = false"
|
|
class="block px-4 py-2 text-sm text-gray-400 hover:text-white hover:bg-primary-700/50 rounded-lg transition-colors">
|
|
Geschichte
|
|
</NuxtLink>
|
|
<NuxtLink to="/satzung" @click="isMobileMenuOpen = false"
|
|
class="block px-4 py-2 text-sm text-gray-400 hover:text-white hover:bg-primary-700/50 rounded-lg transition-colors">
|
|
Satzung
|
|
</NuxtLink>
|
|
<NuxtLink to="/vereinsmeisterschaften" @click="isMobileMenuOpen = false"
|
|
class="block px-4 py-2 text-sm text-gray-400 hover:text-white hover:bg-primary-700/50 rounded-lg transition-colors">
|
|
Vereinsmeisterschaften
|
|
</NuxtLink>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Mannschaften Mobile -->
|
|
<div>
|
|
<button @click="toggleMobileSubmenu('mannschaften')"
|
|
class="w-full flex items-center justify-between px-4 py-3 text-gray-300 hover:text-white hover:bg-primary-700/50 rounded-lg font-medium transition-colors">
|
|
Mannschaften
|
|
<ChevronDown :size="16"
|
|
:class="['transition-transform', mobileSubmenu === 'mannschaften' ? 'rotate-180' : '']" />
|
|
</button>
|
|
<div v-if="mobileSubmenu === 'mannschaften'" class="pl-4 space-y-1 mt-1 bg-primary-900/30 rounded-lg p-2">
|
|
<NuxtLink to="/mannschaften" @click="isMobileMenuOpen = false"
|
|
class="block px-4 py-2 text-sm font-semibold text-gray-300 hover:text-white hover:bg-primary-700/50 rounded-lg transition-colors">
|
|
Übersicht
|
|
</NuxtLink>
|
|
<template v-for="mannschaft in mannschaften" :key="mannschaft.slug">
|
|
<NuxtLink
|
|
:to="`/mannschaften/${mannschaft.slug}`"
|
|
@click="isMobileMenuOpen = false"
|
|
class="block px-4 py-2 text-sm text-gray-400 hover:text-white hover:bg-primary-700/50 rounded-lg transition-colors">
|
|
{{ mannschaft.mannschaft }}
|
|
</NuxtLink>
|
|
</template>
|
|
<div class="border-t border-primary-700/20 my-2" />
|
|
<NuxtLink to="/mannschaften/spielplaene" @click="isMobileMenuOpen = false"
|
|
class="block px-4 py-2 text-sm text-gray-400 hover:text-white hover:bg-primary-700/50 rounded-lg transition-colors">
|
|
Spielpläne
|
|
</NuxtLink>
|
|
<NuxtLink to="/spielsysteme" @click="isMobileMenuOpen = false"
|
|
class="block px-4 py-2 text-sm text-gray-400 hover:text-white hover:bg-primary-700/50 rounded-lg transition-colors">
|
|
Spielsysteme
|
|
</NuxtLink>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Training Mobile -->
|
|
<div>
|
|
<button @click="toggleMobileSubmenu('training')"
|
|
class="w-full flex items-center justify-between px-4 py-3 text-gray-300 hover:text-white hover:bg-primary-700/50 rounded-lg font-medium transition-colors">
|
|
Training
|
|
<ChevronDown :size="16"
|
|
:class="['transition-transform', mobileSubmenu === 'training' ? 'rotate-180' : '']" />
|
|
</button>
|
|
<div v-if="mobileSubmenu === 'training'" class="pl-4 space-y-1 mt-1 bg-primary-900/30 rounded-lg p-2">
|
|
<NuxtLink to="/training" @click="isMobileMenuOpen = false"
|
|
class="block px-4 py-2 text-sm text-gray-400 hover:text-white hover:bg-primary-700/50 rounded-lg transition-colors">
|
|
Trainingszeiten
|
|
</NuxtLink>
|
|
<NuxtLink to="/training/trainer" @click="isMobileMenuOpen = false"
|
|
class="block px-4 py-2 text-sm text-gray-400 hover:text-white hover:bg-primary-700/50 rounded-lg transition-colors">
|
|
Trainer
|
|
</NuxtLink>
|
|
<NuxtLink to="/training/anfaenger" @click="isMobileMenuOpen = false"
|
|
class="block px-4 py-2 text-sm text-gray-400 hover:text-white hover:bg-primary-700/50 rounded-lg transition-colors">
|
|
Anfänger
|
|
</NuxtLink>
|
|
<NuxtLink to="/tt-regeln" @click="isMobileMenuOpen = false"
|
|
class="block px-4 py-2 text-sm text-gray-400 hover:text-white hover:bg-primary-700/50 rounded-lg transition-colors">
|
|
TT-Regeln
|
|
</NuxtLink>
|
|
</div>
|
|
</div>
|
|
|
|
<NuxtLink to="/mitgliedschaft" @click="isMobileMenuOpen = false"
|
|
class="block px-4 py-3 text-gray-300 hover:text-white hover:bg-primary-700/50 rounded-lg font-medium transition-colors">
|
|
Mitgliedschaft
|
|
</NuxtLink>
|
|
|
|
<NuxtLink to="/termine" @click="isMobileMenuOpen = false"
|
|
class="block px-4 py-3 text-gray-300 hover:text-white hover:bg-primary-700/50 rounded-lg font-medium transition-colors">
|
|
Termine
|
|
</NuxtLink>
|
|
|
|
<NuxtLink
|
|
v-if="hasGalleryImages"
|
|
to="/galerie"
|
|
@click="isMobileMenuOpen = false"
|
|
class="block px-4 py-3 text-gray-300 hover:text-white hover:bg-primary-700/50 rounded-lg font-medium transition-colors">
|
|
Galerie
|
|
</NuxtLink>
|
|
|
|
<NuxtLink to="/kontakt" @click="isMobileMenuOpen = false"
|
|
class="block px-4 py-3 bg-primary-600 hover:bg-primary-700 text-white rounded-lg font-semibold transition-colors">
|
|
Kontakt
|
|
</NuxtLink>
|
|
</div>
|
|
</div>
|
|
</Transition>
|
|
</nav>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref, onMounted, computed, watch } from 'vue'
|
|
import { useRoute } from 'vue-router'
|
|
import { Menu, X, ChevronDown } from 'lucide-vue-next'
|
|
|
|
const route = useRoute()
|
|
const isMobileMenuOpen = ref(false)
|
|
const mobileSubmenu = ref(null)
|
|
const mannschaften = ref([])
|
|
const hasGalleryImages = ref(false)
|
|
|
|
// Automatisches Setzen des Submenus basierend auf der Route
|
|
const currentSubmenu = computed(() => {
|
|
const path = route.path
|
|
if (path.startsWith('/ueber-uns') || path.startsWith('/vorstand') ||
|
|
path.startsWith('/geschichte') || path.startsWith('/satzung') ||
|
|
path.startsWith('/vereinsmeisterschaften')) {
|
|
return 'verein'
|
|
}
|
|
if (path.startsWith('/mannschaften') || path.startsWith('/spielsysteme')) {
|
|
return 'mannschaften'
|
|
}
|
|
if (path.startsWith('/training') || path.startsWith('/tt-regeln')) {
|
|
return 'training'
|
|
}
|
|
return null
|
|
})
|
|
|
|
// Manuelles Toggle für Click-Events
|
|
const manualSubmenu = ref(null)
|
|
|
|
const toggleMobileSubmenu = (menu) => {
|
|
mobileSubmenu.value = mobileSubmenu.value === menu ? null : menu
|
|
}
|
|
|
|
const loadMannschaften = async () => {
|
|
try {
|
|
const response = await fetch('/data/mannschaften.csv')
|
|
if (!response.ok) return
|
|
|
|
const csv = await response.text()
|
|
const lines = csv.split('\n').filter(line => line.trim() !== '')
|
|
|
|
if (lines.length < 2) return
|
|
|
|
mannschaften.value = lines.slice(1).map(line => {
|
|
// Besserer CSV-Parser: Respektiert Anführungszeichen
|
|
const values = []
|
|
let current = ''
|
|
let inQuotes = false
|
|
|
|
for (let i = 0; i < line.length; i++) {
|
|
const char = line[i]
|
|
|
|
if (char === '"') {
|
|
inQuotes = !inQuotes
|
|
} else if (char === ',' && !inQuotes) {
|
|
values.push(current.trim())
|
|
current = ''
|
|
} else {
|
|
current += char
|
|
}
|
|
}
|
|
values.push(current.trim())
|
|
|
|
if (values.length < 10) return null
|
|
|
|
return {
|
|
mannschaft: values[0].trim(),
|
|
slug: values[0].trim().toLowerCase().replace(/\s+/g, '-')
|
|
}
|
|
}).filter(mannschaft => mannschaft !== null)
|
|
} catch (error) {
|
|
console.error('Fehler beim Laden der Mannschaften:', error)
|
|
}
|
|
}
|
|
|
|
const checkGalleryImages = async () => {
|
|
try {
|
|
const response = await $fetch('/api/galerie')
|
|
hasGalleryImages.value = response && response.length > 0
|
|
} catch (error) {
|
|
console.error('Fehler beim Prüfen der Galerie-Bilder:', error)
|
|
hasGalleryImages.value = false
|
|
}
|
|
}
|
|
|
|
onMounted(() => {
|
|
loadMannschaften()
|
|
checkGalleryImages()
|
|
})
|
|
|
|
const toggleSubmenu = (menu) => {
|
|
// Wenn wir schon im richtigen Bereich sind, nichts tun (Submenu bleibt offen)
|
|
// Wenn nicht, zur Hauptseite navigieren
|
|
const path = route.path
|
|
|
|
if (menu === 'verein' && !path.startsWith('/ueber-uns') && !path.startsWith('/vorstand') &&
|
|
!path.startsWith('/geschichte') && !path.startsWith('/satzung') && !path.startsWith('/vereinsmeisterschaften')) {
|
|
navigateTo('/ueber-uns')
|
|
} else if (menu === 'mannschaften' && !path.startsWith('/mannschaften') && !path.startsWith('/spielsysteme')) {
|
|
navigateTo('/mannschaften')
|
|
} else if (menu === 'training' && !path.startsWith('/training') && !path.startsWith('/tt-regeln')) {
|
|
navigateTo('/training')
|
|
}
|
|
}
|
|
</script>
|