feat(shell): site header, footer, router with view stubs
SiteHeader renders a sticky bilingual nav with click-to-open dropdowns for Concept / What's On / Collaborate / Reservations groups plus a flat Marketplace link and an FR/EN toggle. Outside-click and Esc close the open group; route changes reset both desktop and mobile state. SiteFooter pins contact, address and social links to every page. Router declares all eleven content routes plus a 404. Each view is a "coming soon" stub so per-section content can land independently without breaking the build. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
71b3ea477d
commit
2b675aed85
18 changed files with 516 additions and 41 deletions
10
src/App.vue
10
src/App.vue
|
|
@ -1,7 +1,15 @@
|
|||
<script setup lang="ts">
|
||||
import { RouterView } from 'vue-router'
|
||||
import SiteHeader from '@/components/SiteHeader.vue'
|
||||
import SiteFooter from '@/components/SiteFooter.vue'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<RouterView />
|
||||
<div class="flex min-h-screen flex-col">
|
||||
<SiteHeader />
|
||||
<main class="flex-1">
|
||||
<RouterView />
|
||||
</main>
|
||||
<SiteFooter />
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
66
src/components/SiteFooter.vue
Normal file
66
src/components/SiteFooter.vue
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
<script setup lang="ts">
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const { t } = useI18n()
|
||||
const year = new Date().getFullYear()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<footer class="mt-16 border-t border-border bg-secondary/40">
|
||||
<div class="mx-auto grid max-w-7xl gap-8 px-4 py-10 lg:px-6 md:grid-cols-3">
|
||||
<div>
|
||||
<div class="mb-3 flex items-center gap-3">
|
||||
<img src="/cosmic-stag.png" alt="" class="h-10 w-10" />
|
||||
<span class="font-serif text-lg">Château du Faune</span>
|
||||
</div>
|
||||
<p class="text-sm text-muted-foreground">{{ t('footer.tagline') }}</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 class="mb-3 text-sm font-semibold uppercase tracking-wider">
|
||||
{{ t('footer.contact') }}
|
||||
</h3>
|
||||
<address class="not-italic text-sm space-y-1 text-muted-foreground">
|
||||
<div>456 Grand Rue de Bellissen</div>
|
||||
<div>Château de Bénac, 09000 France</div>
|
||||
<div>
|
||||
<a href="mailto:chateaudufaune@ariege.io" class="hover:text-primary">
|
||||
chateaudufaune@ariege.io
|
||||
</a>
|
||||
</div>
|
||||
</address>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 class="mb-3 text-sm font-semibold uppercase tracking-wider">
|
||||
{{ t('footer.social') }}
|
||||
</h3>
|
||||
<ul class="space-y-1 text-sm">
|
||||
<li>
|
||||
<a
|
||||
href="https://www.instagram.com/chateaudufaune"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
class="text-muted-foreground hover:text-primary"
|
||||
>
|
||||
Instagram @chateaudufaune
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="https://www.linkedin.com/company/chateau-du-faune"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
class="text-muted-foreground hover:text-primary"
|
||||
>
|
||||
LinkedIn
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="border-t border-border py-4 text-center text-xs text-muted-foreground">
|
||||
© {{ year }} Château du Faune · Ariège · {{ t('footer.rights') }}
|
||||
</div>
|
||||
</footer>
|
||||
</template>
|
||||
195
src/components/SiteHeader.vue
Normal file
195
src/components/SiteHeader.vue
Normal file
|
|
@ -0,0 +1,195 @@
|
|||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted, onUnmounted, watch } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { RouterLink, useRoute } from 'vue-router'
|
||||
|
||||
const { t, locale } = useI18n()
|
||||
const route = useRoute()
|
||||
|
||||
const openGroup = ref<string | null>(null)
|
||||
const mobileOpen = ref(false)
|
||||
const headerEl = ref<HTMLElement | null>(null)
|
||||
|
||||
interface NavItem {
|
||||
to: string
|
||||
label: string
|
||||
}
|
||||
interface NavGroup {
|
||||
id: string
|
||||
label: string
|
||||
items: NavItem[]
|
||||
}
|
||||
|
||||
const groups = computed<NavGroup[]>(() => [
|
||||
{
|
||||
id: 'concept',
|
||||
label: t('nav.concept'),
|
||||
items: [
|
||||
{ to: '/concept', label: t('nav.artEcology') },
|
||||
{ to: '/vision-values', label: t('nav.visionValues') },
|
||||
{ to: '/gallery', label: t('nav.gallery') },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'whatsOn',
|
||||
label: t('nav.whatsOn'),
|
||||
items: [
|
||||
{ to: '/events', label: t('nav.events') },
|
||||
{ to: '/symposium', label: t('nav.symposium') },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'collaborate',
|
||||
label: t('nav.collaborate'),
|
||||
items: [
|
||||
{ to: '/long-stays', label: t('nav.longStays') },
|
||||
{ to: '/opportunities', label: t('nav.opportunities') },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'reservations',
|
||||
label: t('nav.reservations'),
|
||||
items: [
|
||||
{ to: '/reservations', label: t('nav.planYourVisit') },
|
||||
{ to: '/accommodation', label: t('nav.accommodation') },
|
||||
],
|
||||
},
|
||||
])
|
||||
|
||||
function toggle(id: string) {
|
||||
openGroup.value = openGroup.value === id ? null : id
|
||||
}
|
||||
|
||||
function closeAll() {
|
||||
openGroup.value = null
|
||||
mobileOpen.value = false
|
||||
}
|
||||
|
||||
function toggleLocale() {
|
||||
locale.value = locale.value === 'fr' ? 'en' : 'fr'
|
||||
}
|
||||
|
||||
function onClickOutside(e: MouseEvent) {
|
||||
if (!headerEl.value) return
|
||||
if (!headerEl.value.contains(e.target as Node)) openGroup.value = null
|
||||
}
|
||||
|
||||
function onKeydown(e: KeyboardEvent) {
|
||||
if (e.key === 'Escape') closeAll()
|
||||
}
|
||||
|
||||
watch(() => route.path, closeAll)
|
||||
|
||||
onMounted(() => {
|
||||
document.addEventListener('click', onClickOutside)
|
||||
document.addEventListener('keydown', onKeydown)
|
||||
})
|
||||
onUnmounted(() => {
|
||||
document.removeEventListener('click', onClickOutside)
|
||||
document.removeEventListener('keydown', onKeydown)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<header
|
||||
ref="headerEl"
|
||||
class="sticky top-0 z-40 border-b border-border bg-background/95 backdrop-blur"
|
||||
>
|
||||
<div class="mx-auto max-w-7xl px-4 lg:px-6">
|
||||
<div class="flex h-16 items-center justify-between gap-4">
|
||||
<RouterLink to="/" class="flex items-center gap-3" @click="closeAll">
|
||||
<img src="/cosmic-stag.png" alt="" class="h-9 w-9 shrink-0" />
|
||||
<span class="leading-tight">
|
||||
<span class="block font-serif text-base tracking-tight text-foreground">
|
||||
Château du Faune
|
||||
</span>
|
||||
<span class="block text-[11px] uppercase tracking-wider text-muted-foreground">
|
||||
{{ t('common.tagline') }}
|
||||
</span>
|
||||
</span>
|
||||
</RouterLink>
|
||||
|
||||
<nav class="hidden items-center gap-1 lg:flex">
|
||||
<div v-for="g in groups" :key="g.id" class="relative">
|
||||
<button
|
||||
type="button"
|
||||
class="rounded-md px-3 py-2 text-sm hover:bg-muted hover:text-primary"
|
||||
:aria-expanded="openGroup === g.id"
|
||||
@click.stop="toggle(g.id)"
|
||||
>
|
||||
{{ g.label }}
|
||||
<span class="ml-1 inline-block text-xs">▾</span>
|
||||
</button>
|
||||
<ul
|
||||
v-show="openGroup === g.id"
|
||||
class="absolute right-0 top-full z-50 mt-1 min-w-52 rounded-md border border-border bg-popover py-1 shadow-md"
|
||||
>
|
||||
<li v-for="i in g.items" :key="i.to">
|
||||
<RouterLink
|
||||
:to="i.to"
|
||||
class="block px-4 py-2 text-sm hover:bg-muted"
|
||||
@click="closeAll"
|
||||
>
|
||||
{{ i.label }}
|
||||
</RouterLink>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<RouterLink
|
||||
to="/marketplace"
|
||||
class="rounded-md px-3 py-2 text-sm hover:bg-muted hover:text-primary"
|
||||
>
|
||||
{{ t('nav.marketplace') }}
|
||||
</RouterLink>
|
||||
<button
|
||||
type="button"
|
||||
class="ml-2 rounded-md border border-border px-2 py-1 text-xs uppercase tracking-wider text-muted-foreground hover:bg-muted"
|
||||
@click="toggleLocale"
|
||||
>
|
||||
{{ locale === 'fr' ? 'EN' : 'FR' }}
|
||||
</button>
|
||||
</nav>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="rounded-md p-2 lg:hidden"
|
||||
:aria-label="t('nav.menu')"
|
||||
:aria-expanded="mobileOpen"
|
||||
@click="mobileOpen = !mobileOpen"
|
||||
>
|
||||
<span class="mb-1.5 block h-0.5 w-6 bg-foreground"></span>
|
||||
<span class="mb-1.5 block h-0.5 w-6 bg-foreground"></span>
|
||||
<span class="block h-0.5 w-6 bg-foreground"></span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div v-show="mobileOpen" class="border-t border-border py-3 lg:hidden">
|
||||
<div v-for="g in groups" :key="g.id" class="py-2">
|
||||
<div
|
||||
class="px-2 pb-1 text-xs font-semibold uppercase tracking-wider text-muted-foreground"
|
||||
>
|
||||
{{ g.label }}
|
||||
</div>
|
||||
<RouterLink
|
||||
v-for="i in g.items"
|
||||
:key="i.to"
|
||||
:to="i.to"
|
||||
class="block rounded-md px-4 py-2 hover:bg-muted"
|
||||
>
|
||||
{{ i.label }}
|
||||
</RouterLink>
|
||||
</div>
|
||||
<RouterLink to="/marketplace" class="block rounded-md px-4 py-2 hover:bg-muted">
|
||||
{{ t('nav.marketplace') }}
|
||||
</RouterLink>
|
||||
<button
|
||||
type="button"
|
||||
class="mt-2 block w-full rounded-md border border-border px-4 py-2 text-left text-sm text-muted-foreground hover:bg-muted"
|
||||
@click="toggleLocale"
|
||||
>
|
||||
{{ locale === 'fr' ? 'Switch to English' : 'Passer en français' }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
</template>
|
||||
|
|
@ -1,11 +1,37 @@
|
|||
{
|
||||
"app": {
|
||||
"title": "Boilerplate Website"
|
||||
"title": "Château du Faune"
|
||||
},
|
||||
"home": {
|
||||
"heading": "Welcome",
|
||||
"intro": "Edit src/views/HomeView.vue to begin.",
|
||||
"counter": "Count: {n}",
|
||||
"increment": "Increment"
|
||||
"common": {
|
||||
"tagline": "Center for Art & Ecology",
|
||||
"siteName": "Château du Faune",
|
||||
"comingSoon": "Coming soon.",
|
||||
"learnMore": "Learn more",
|
||||
"seeAll": "See all",
|
||||
"contactUs": "Contact us"
|
||||
},
|
||||
"nav": {
|
||||
"concept": "Concept",
|
||||
"artEcology": "Art & Ecology",
|
||||
"visionValues": "Vision & Values",
|
||||
"gallery": "Gallery",
|
||||
"whatsOn": "What's On",
|
||||
"events": "Events & Programs",
|
||||
"symposium": "Symposium II.0",
|
||||
"collaborate": "Collaborate",
|
||||
"longStays": "Long Stays",
|
||||
"opportunities": "Opportunities",
|
||||
"reservations": "Reservations",
|
||||
"planYourVisit": "Plan Your Visit",
|
||||
"accommodation": "Accommodation",
|
||||
"marketplace": "Marketplace",
|
||||
"menu": "Menu"
|
||||
},
|
||||
"footer": {
|
||||
"tagline": "A farm, a residency and a refuge at the meeting point of art and ecology, in the Pyrenean foothills of Ariège.",
|
||||
"contact": "Contact",
|
||||
"social": "Follow us",
|
||||
"address": "456 Grand Rue de Bellissen, Château de Bénac, 09000 France",
|
||||
"rights": "All rights reserved."
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,37 @@
|
|||
{
|
||||
"app": {
|
||||
"title": "Modèle de site"
|
||||
"title": "Château du Faune"
|
||||
},
|
||||
"home": {
|
||||
"heading": "Bienvenue",
|
||||
"intro": "Modifiez src/views/HomeView.vue pour commencer.",
|
||||
"counter": "Compteur : {n}",
|
||||
"increment": "Incrémenter"
|
||||
"common": {
|
||||
"tagline": "Centre pour l'art et l'écologie",
|
||||
"siteName": "Château du Faune",
|
||||
"comingSoon": "Bientôt disponible.",
|
||||
"learnMore": "En savoir plus",
|
||||
"seeAll": "Voir tout",
|
||||
"contactUs": "Nous contacter"
|
||||
},
|
||||
"nav": {
|
||||
"concept": "Concept",
|
||||
"artEcology": "Art & Écologie",
|
||||
"visionValues": "Vision & Valeurs",
|
||||
"gallery": "Galerie",
|
||||
"whatsOn": "À l'affiche",
|
||||
"events": "Événements & Programmes",
|
||||
"symposium": "Symposium II.0",
|
||||
"collaborate": "Collaborer",
|
||||
"longStays": "Séjours longue durée",
|
||||
"opportunities": "Opportunités",
|
||||
"reservations": "Réservations",
|
||||
"planYourVisit": "Préparer votre visite",
|
||||
"accommodation": "Hébergement",
|
||||
"marketplace": "Boutique",
|
||||
"menu": "Menu"
|
||||
},
|
||||
"footer": {
|
||||
"tagline": "Une fermette, une résidence d'artistes et un refuge à la croisée de l'art et de l'écologie, au pied des Pyrénées ariégeoises.",
|
||||
"contact": "Contact",
|
||||
"social": "Suivez-nous",
|
||||
"address": "456 Grand Rue de Bellissen, Château de Bénac, 09000 France",
|
||||
"rights": "Tous droits réservés."
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,54 @@
|
|||
import { createRouter, createWebHistory, type RouteRecordRaw } from 'vue-router'
|
||||
|
||||
const routes: RouteRecordRaw[] = [
|
||||
{ path: '/', name: 'home', component: () => import('@/views/HomeView.vue') },
|
||||
{ path: '/concept', name: 'concept', component: () => import('@/views/ConceptView.vue') },
|
||||
{
|
||||
path: '/',
|
||||
name: 'home',
|
||||
component: () => import('@/views/HomeView.vue'),
|
||||
path: '/vision-values',
|
||||
name: 'vision-values',
|
||||
component: () => import('@/views/VisionValuesView.vue'),
|
||||
},
|
||||
{ path: '/gallery', name: 'gallery', component: () => import('@/views/GalleryView.vue') },
|
||||
{ path: '/events', name: 'events', component: () => import('@/views/EventsView.vue') },
|
||||
{ path: '/symposium', name: 'symposium', component: () => import('@/views/SymposiumView.vue') },
|
||||
{
|
||||
path: '/long-stays',
|
||||
name: 'long-stays',
|
||||
component: () => import('@/views/LongStaysView.vue'),
|
||||
},
|
||||
{
|
||||
path: '/opportunities',
|
||||
name: 'opportunities',
|
||||
component: () => import('@/views/OpportunitiesView.vue'),
|
||||
},
|
||||
{
|
||||
path: '/reservations',
|
||||
name: 'reservations',
|
||||
component: () => import('@/views/ReservationsView.vue'),
|
||||
},
|
||||
{
|
||||
path: '/accommodation',
|
||||
name: 'accommodation',
|
||||
component: () => import('@/views/AccommodationView.vue'),
|
||||
},
|
||||
{
|
||||
path: '/marketplace',
|
||||
name: 'marketplace',
|
||||
component: () => import('@/views/MarketplaceView.vue'),
|
||||
},
|
||||
{
|
||||
path: '/:pathMatch(.*)*',
|
||||
name: 'not-found',
|
||||
component: () => import('@/views/NotFoundView.vue'),
|
||||
},
|
||||
]
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(import.meta.env.BASE_URL),
|
||||
routes,
|
||||
scrollBehavior() {
|
||||
return { top: 0 }
|
||||
},
|
||||
})
|
||||
|
||||
export default router
|
||||
|
|
|
|||
12
src/views/AccommodationView.vue
Normal file
12
src/views/AccommodationView.vue
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<script setup lang="ts">
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const { t } = useI18n()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<article class="mx-auto max-w-4xl px-4 py-16">
|
||||
<h1 class="font-serif text-4xl font-semibold tracking-tight">{{ t('nav.accommodation') }}</h1>
|
||||
<p class="mt-4 text-muted-foreground">{{ t('common.comingSoon') }}</p>
|
||||
</article>
|
||||
</template>
|
||||
12
src/views/ConceptView.vue
Normal file
12
src/views/ConceptView.vue
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<script setup lang="ts">
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const { t } = useI18n()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<article class="mx-auto max-w-4xl px-4 py-16">
|
||||
<h1 class="font-serif text-4xl font-semibold tracking-tight">{{ t('nav.artEcology') }}</h1>
|
||||
<p class="mt-4 text-muted-foreground">{{ t('common.comingSoon') }}</p>
|
||||
</article>
|
||||
</template>
|
||||
12
src/views/EventsView.vue
Normal file
12
src/views/EventsView.vue
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<script setup lang="ts">
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const { t } = useI18n()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<article class="mx-auto max-w-4xl px-4 py-16">
|
||||
<h1 class="font-serif text-4xl font-semibold tracking-tight">{{ t('nav.events') }}</h1>
|
||||
<p class="mt-4 text-muted-foreground">{{ t('common.comingSoon') }}</p>
|
||||
</article>
|
||||
</template>
|
||||
12
src/views/GalleryView.vue
Normal file
12
src/views/GalleryView.vue
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<script setup lang="ts">
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const { t } = useI18n()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<article class="mx-auto max-w-4xl px-4 py-16">
|
||||
<h1 class="font-serif text-4xl font-semibold tracking-tight">{{ t('nav.gallery') }}</h1>
|
||||
<p class="mt-4 text-muted-foreground">{{ t('common.comingSoon') }}</p>
|
||||
</article>
|
||||
</template>
|
||||
|
|
@ -1,32 +1,15 @@
|
|||
<script setup lang="ts">
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useCounterStore } from '@/stores/counter'
|
||||
|
||||
const { t, locale } = useI18n()
|
||||
const counter = useCounterStore()
|
||||
|
||||
function toggleLocale() {
|
||||
locale.value = locale.value === 'en' ? 'fr' : 'en'
|
||||
}
|
||||
const { t } = useI18n()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main class="mx-auto max-w-2xl space-y-6 p-8">
|
||||
<h1 class="text-3xl font-bold">{{ t('home.heading') }}</h1>
|
||||
<p class="text-muted-foreground">{{ t('home.intro') }}</p>
|
||||
|
||||
<div class="space-y-2">
|
||||
<p>{{ t('home.counter', { n: counter.count }) }}</p>
|
||||
<button
|
||||
class="rounded-md bg-primary px-4 py-2 text-primary-foreground hover:opacity-90"
|
||||
@click="counter.increment"
|
||||
>
|
||||
{{ t('home.increment') }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<button class="text-sm underline" @click="toggleLocale">
|
||||
Switch to {{ locale === 'en' ? 'Français' : 'English' }}
|
||||
</button>
|
||||
</main>
|
||||
<article class="mx-auto max-w-4xl px-4 py-16">
|
||||
<h1 class="font-serif text-4xl font-semibold tracking-tight md:text-5xl">
|
||||
{{ t('common.siteName') }}
|
||||
</h1>
|
||||
<p class="mt-3 text-lg text-muted-foreground">{{ t('common.tagline') }}</p>
|
||||
<p class="mt-8 text-base">{{ t('common.comingSoon') }}</p>
|
||||
</article>
|
||||
</template>
|
||||
|
|
|
|||
12
src/views/LongStaysView.vue
Normal file
12
src/views/LongStaysView.vue
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<script setup lang="ts">
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const { t } = useI18n()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<article class="mx-auto max-w-4xl px-4 py-16">
|
||||
<h1 class="font-serif text-4xl font-semibold tracking-tight">{{ t('nav.longStays') }}</h1>
|
||||
<p class="mt-4 text-muted-foreground">{{ t('common.comingSoon') }}</p>
|
||||
</article>
|
||||
</template>
|
||||
12
src/views/MarketplaceView.vue
Normal file
12
src/views/MarketplaceView.vue
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<script setup lang="ts">
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const { t } = useI18n()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<article class="mx-auto max-w-4xl px-4 py-16">
|
||||
<h1 class="font-serif text-4xl font-semibold tracking-tight">{{ t('nav.marketplace') }}</h1>
|
||||
<p class="mt-4 text-muted-foreground">{{ t('common.comingSoon') }}</p>
|
||||
</article>
|
||||
</template>
|
||||
13
src/views/NotFoundView.vue
Normal file
13
src/views/NotFoundView.vue
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<script setup lang="ts">
|
||||
import { RouterLink } from 'vue-router'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<article class="mx-auto max-w-3xl px-4 py-24 text-center">
|
||||
<h1 class="font-serif text-5xl font-semibold tracking-tight">404</h1>
|
||||
<p class="mt-4 text-muted-foreground">Page introuvable / Page not found.</p>
|
||||
<RouterLink to="/" class="mt-8 inline-block text-sm underline hover:text-primary">
|
||||
← Château du Faune
|
||||
</RouterLink>
|
||||
</article>
|
||||
</template>
|
||||
12
src/views/OpportunitiesView.vue
Normal file
12
src/views/OpportunitiesView.vue
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<script setup lang="ts">
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const { t } = useI18n()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<article class="mx-auto max-w-4xl px-4 py-16">
|
||||
<h1 class="font-serif text-4xl font-semibold tracking-tight">{{ t('nav.opportunities') }}</h1>
|
||||
<p class="mt-4 text-muted-foreground">{{ t('common.comingSoon') }}</p>
|
||||
</article>
|
||||
</template>
|
||||
12
src/views/ReservationsView.vue
Normal file
12
src/views/ReservationsView.vue
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<script setup lang="ts">
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const { t } = useI18n()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<article class="mx-auto max-w-4xl px-4 py-16">
|
||||
<h1 class="font-serif text-4xl font-semibold tracking-tight">{{ t('nav.planYourVisit') }}</h1>
|
||||
<p class="mt-4 text-muted-foreground">{{ t('common.comingSoon') }}</p>
|
||||
</article>
|
||||
</template>
|
||||
12
src/views/SymposiumView.vue
Normal file
12
src/views/SymposiumView.vue
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<script setup lang="ts">
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const { t } = useI18n()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<article class="mx-auto max-w-4xl px-4 py-16">
|
||||
<h1 class="font-serif text-4xl font-semibold tracking-tight">{{ t('nav.symposium') }}</h1>
|
||||
<p class="mt-4 text-muted-foreground">{{ t('common.comingSoon') }}</p>
|
||||
</article>
|
||||
</template>
|
||||
12
src/views/VisionValuesView.vue
Normal file
12
src/views/VisionValuesView.vue
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<script setup lang="ts">
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const { t } = useI18n()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<article class="mx-auto max-w-4xl px-4 py-16">
|
||||
<h1 class="font-serif text-4xl font-semibold tracking-tight">{{ t('nav.visionValues') }}</h1>
|
||||
<p class="mt-4 text-muted-foreground">{{ t('common.comingSoon') }}</p>
|
||||
</article>
|
||||
</template>
|
||||
Loading…
Add table
Add a link
Reference in a new issue