Compare commits

...

7 commits

Author SHA1 Message Date
35bec04fd2 feat(header): frosted-glass mobile sheet
Match the desktop nav's frosted aesthetic on the mobile menu:

- SheetContent in SiteHeader overrides the default bg-background with
  bg-background/65 + backdrop-blur-xl + backdrop-saturate-150 (same
  recipe the sticky <header> uses), plus a translucent white/10
  border so the slide-in panel reads as glass rather than a solid
  green block.
- SheetContent.vue (shared primitive): drop the overlay opacity from
  bg-black/80 to bg-black/30 so the page behind the sheet dims
  enough to indicate focus, but the pinned landscape still shows
  through.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-09 23:50:52 +02:00
ba009ed4b7 feat(events): shadcn Alert replaces hand-rolled callout aside
Install the shadcn-vue Alert primitives (Alert / AlertTitle /
AlertDescription + the alertVariants cva) at
src/components/ui/alert. Files mirrored from the registry JSON.

EventsView: the bouge.ariege.io heads-up at the top of the listing
was a hand-styled <aside> with a <p> kicker and a body paragraph;
swap to <Alert><AlertTitle>…</AlertTitle><AlertDescription>…
</AlertDescription></Alert>. Keep bg-card + text-foreground/85
overrides so it still reads as a quiet card on the green hero band
rather than the variant default.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-09 23:32:56 +02:00
b0a414972b feat(header): shadcn Sheet replaces hand-rolled mobile menu
Install the shadcn-vue Sheet primitives (Sheet / SheetTrigger /
SheetContent / SheetHeader / SheetTitle / SheetDescription /
SheetFooter / SheetClose + the sheetVariants cva) at
src/components/ui/sheet. Files mirrored from the registry JSON to
sidestep the same corepack-pnpm issue that blocks the CLI; X icon
import fixed to @lucide/vue.

SiteHeader: drop the hand-rolled hamburger that toggled a v-show
panel beneath the header, in favour of a Sheet with the hamburger
as SheetTrigger (as-child) and the full nav list as SheetContent
sliding from the right. Sheet handles open-state via v-model:open,
which the route watcher still drives so navigating from the menu
auto-closes the panel. SheetContent runs through a Portal at body
level so the slide animation isn't constrained by the frosted-glass
header.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-09 23:31:32 +02:00
0aeab92763 fix(header): drop nav triggers back to font-light
navigationMenuTriggerStyle ships with font-medium baked in. Our
pre-NavigationMenu nav had no weight class so it inherited Roboto 300
(font-light) from the body, which read more in keeping with the
restrained aesthetic of the rest of the page. Override the cva base
with font-light on each NavigationMenuTrigger and on the Marketplace
flat link.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-09 23:20:11 +02:00
262fb039a8 feat(header): shadcn NavigationMenu replaces hand-rolled dropdowns
Install the shadcn-vue NavigationMenu component family at
src/components/ui/navigation-menu (NavigationMenu / NavigationMenuList
/ NavigationMenuItem / NavigationMenuTrigger / NavigationMenuContent /
NavigationMenuLink / NavigationMenuIndicator / NavigationMenuViewport
+ navigationMenuTriggerStyle cva). Files came in via the registry JSON
(www.shadcn-vue.com/r/styles/default/navigation-menu.json) because the
CLI's corepack pnpm shim conflicts with our nix-managed pnpm — handle
that more cleanly later.

Adds @vueuse/core as a runtime dependency (reactiveOmit /
useForwardProps inside the navigation-menu primitives).

Two upstream fixes applied:

1. NavigationMenuTrigger imports ChevronDown from "@lucide/vue", not
   the deprecated "lucide-vue-next" the registry ships by default.

2. NavigationMenuViewport's height/width CSS variable references use
   Tailwind v3 syntax — h-[--reka-navigation-menu-viewport-height] —
   which renders as a literal string under Tailwind v4, collapsing
   the viewport to 2px. Wrap them in var() so v4 resolves them
   correctly: h-[var(--reka-navigation-menu-viewport-height)].

SiteHeader desktop nav: drop the custom openGroup ref, the
document-level click/keydown listeners and the v-show toggle in
favour of NavigationMenuTrigger + NavigationMenuContent for the four
group dropdowns (Concept / What's On / Collaborate / Reservations),
plus a flat Marketplace link styled with navigationMenuTriggerStyle.
Hover/focus/open states all use bg-muted + text-primary to preserve
the green-bg/gold-text feel from the hand-rolled nav.

Mobile menu stays hand-rolled for this commit; Sheet refactor lands
in a follow-up.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-09 23:16:27 +02:00
d56664abf9 feat(ui): install shadcn Badge; use for accommodation status pills
Add the Badge primitive at src/components/ui/badge with the standard
default / secondary / destructive / outline variants.

Replace the two hand-rolled status pill patterns in AccommodationView
(rooms + cabins) with <Badge>:

- Room "Open" pill → variant="default" (filled gold on green)
- Room/Cabin "Coming soon" pill → variant="outline" (border + muted)

Keeps the small-caps tracking and 10px size via class overrides so
the visual rhythm against the room/cabin Card titles is preserved.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-09 17:02:57 +02:00
4cb0fa14a2 feat(ui): install shadcn Card; sweep card patterns site-wide
Add the shadcn-vue Card primitive (Card / CardHeader / CardTitle /
CardDescription / CardContent / CardFooter) at src/components/ui/card.

Replace ~20 hand-rolled "rounded-lg border border-border bg-card …"
patterns across nine views with <Card>:

- ConceptView (slow-farming pillars)
- VisionValuesView (philosophy + pillars + team)
- GalleryView (image figure cards)
- EventsView (program cards)
- SymposiumView (included items + apply steps)
- LongStaysView (path cards)
- OpportunitiesView (group cards + apply explainers)
- AccommodationView (rooms + cabins + exterior items)
- ReservationsView (kind cards + contact card)
- MarketplaceView (category cards)
- HomeView (featured events)

For image-bearing cards (events / rooms), use Card + CardContent so
the image stays flush at the top of the card and the inner padding
lives on the content slot. For clickable cards, the RouterLink wraps
the Card so the whole card is the link target.

Variants where the card sits on a tinted section (philosophy items on
bg-card section, cabins on bg-card section) override Card's default
bg-card with bg-background via the class prop.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-09 17:01:51 +02:00
45 changed files with 910 additions and 304 deletions

View file

@ -14,6 +14,7 @@
"dependencies": { "dependencies": {
"@lucide/vue": "^1.16.0", "@lucide/vue": "^1.16.0",
"@vee-validate/zod": "^4.15.1", "@vee-validate/zod": "^4.15.1",
"@vueuse/core": "^14.3.0",
"class-variance-authority": "^0.7.1", "class-variance-authority": "^0.7.1",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"pinia": "^3.0.4", "pinia": "^3.0.4",

3
pnpm-lock.yaml generated
View file

@ -14,6 +14,9 @@ importers:
'@vee-validate/zod': '@vee-validate/zod':
specifier: ^4.15.1 specifier: ^4.15.1
version: 4.15.1(vue@3.5.34(typescript@6.0.3))(zod@3.25.76) version: 4.15.1(vue@3.5.34(typescript@6.0.3))(zod@3.25.76)
'@vueuse/core':
specifier: ^14.3.0
version: 14.3.0(vue@3.5.34(typescript@6.0.3))
class-variance-authority: class-variance-authority:
specifier: ^0.7.1 specifier: ^0.7.1
version: 0.7.1 version: 0.7.1

View file

@ -1,15 +1,30 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed, onMounted, onUnmounted, watch } from 'vue' import { ref, computed, watch } from 'vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { RouterLink, useRoute } from 'vue-router' import { RouterLink, useRoute } from 'vue-router'
import { cn } from '@/lib/utils'
import cosmicStag from '@/assets/cosmic-stag.avif' import cosmicStag from '@/assets/cosmic-stag.avif'
import {
NavigationMenu,
NavigationMenuContent,
NavigationMenuItem,
NavigationMenuLink,
NavigationMenuList,
NavigationMenuTrigger,
navigationMenuTriggerStyle,
} from '@/components/ui/navigation-menu'
import {
Sheet,
SheetContent,
SheetHeader,
SheetTitle,
SheetTrigger,
} from '@/components/ui/sheet'
const { t, locale } = useI18n() const { t, locale } = useI18n()
const route = useRoute() const route = useRoute()
const openGroup = ref<string | null>(null)
const mobileOpen = ref(false) const mobileOpen = ref(false)
const headerEl = ref<HTMLElement | null>(null)
interface NavItem { interface NavItem {
to: string to: string
@ -57,12 +72,7 @@ const groups = computed<NavGroup[]>(() => [
}, },
]) ])
function toggle(id: string) { function closeMobile() {
openGroup.value = openGroup.value === id ? null : id
}
function closeAll() {
openGroup.value = null
mobileOpen.value = false mobileOpen.value = false
} }
@ -70,35 +80,16 @@ function toggleLocale() {
locale.value = locale.value === 'fr' ? 'en' : 'fr' locale.value = locale.value === 'fr' ? 'en' : 'fr'
} }
function onClickOutside(e: MouseEvent) { watch(() => route.path, closeMobile)
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> </script>
<template> <template>
<header <header
ref="headerEl"
class="sticky top-0 z-40 border-b border-white/10 bg-background/65 backdrop-blur-xl backdrop-saturate-150" class="sticky top-0 z-40 border-b border-white/10 bg-background/65 backdrop-blur-xl backdrop-saturate-150"
> >
<div class="mx-auto max-w-7xl px-4 lg:px-6"> <div class="mx-auto max-w-7xl px-4 lg:px-6">
<div class="flex h-16 items-center justify-between gap-4"> <div class="flex h-16 items-center justify-between gap-4">
<RouterLink to="/" class="flex items-center gap-3" @click="closeAll"> <RouterLink to="/" class="flex items-center gap-3" @click="closeMobile">
<img :src="cosmicStag" alt="" class="h-9 w-9 shrink-0" /> <img :src="cosmicStag" alt="" class="h-9 w-9 shrink-0" />
<span class="leading-tight"> <span class="leading-tight">
<span class="block font-serif text-base tracking-tight text-foreground"> <span class="block font-serif text-base tracking-tight text-foreground">
@ -110,38 +101,47 @@ onUnmounted(() => {
</span> </span>
</RouterLink> </RouterLink>
<nav class="hidden items-center gap-1 lg:flex"> <div class="hidden items-center gap-1 lg:flex">
<div v-for="g in groups" :key="g.id" class="relative"> <NavigationMenu>
<button <NavigationMenuList>
type="button" <NavigationMenuItem v-for="g in groups" :key="g.id">
class="rounded-md px-3 py-2 text-sm hover:bg-muted hover:text-primary" <NavigationMenuTrigger
:aria-expanded="openGroup === g.id" class="bg-transparent font-light hover:bg-muted hover:text-primary focus:bg-muted focus:text-primary data-[state=open]:bg-muted data-[state=open]:text-primary"
@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 }} {{ g.label }}
</RouterLink> </NavigationMenuTrigger>
</li> <NavigationMenuContent>
</ul> <ul class="min-w-52 p-1">
</div> <li v-for="i in g.items" :key="i.to">
<RouterLink <NavigationMenuLink as-child>
to="/marketplace" <RouterLink
class="rounded-md px-3 py-2 text-sm hover:bg-muted hover:text-primary" :to="i.to"
> class="block rounded-sm px-4 py-2 text-sm hover:bg-muted hover:text-primary"
{{ t('nav.marketplace') }} >
</RouterLink> {{ i.label }}
</RouterLink>
</NavigationMenuLink>
</li>
</ul>
</NavigationMenuContent>
</NavigationMenuItem>
<NavigationMenuItem>
<NavigationMenuLink as-child>
<RouterLink
to="/marketplace"
:class="
cn(
navigationMenuTriggerStyle(),
'bg-transparent font-light hover:bg-muted hover:text-primary focus:bg-muted focus:text-primary',
)
"
>
{{ t('nav.marketplace') }}
</RouterLink>
</NavigationMenuLink>
</NavigationMenuItem>
</NavigationMenuList>
</NavigationMenu>
<button <button
type="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" class="ml-2 rounded-md border border-border px-2 py-1 text-xs uppercase tracking-wider text-muted-foreground hover:bg-muted"
@ -149,47 +149,58 @@ onUnmounted(() => {
> >
{{ locale === 'fr' ? 'EN' : 'FR' }} {{ locale === 'fr' ? 'EN' : 'FR' }}
</button> </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> </div>
<RouterLink to="/marketplace" class="block rounded-md px-4 py-2 hover:bg-muted">
{{ t('nav.marketplace') }} <Sheet v-model:open="mobileOpen">
</RouterLink> <SheetTrigger as-child>
<button <button
type="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" class="rounded-md p-2 lg:hidden"
@click="toggleLocale" :aria-label="t('nav.menu')"
> >
{{ locale === 'fr' ? 'Switch to English' : 'Passer en français' }} <span class="mb-1.5 block h-0.5 w-6 bg-foreground"></span>
</button> <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>
</SheetTrigger>
<SheetContent
side="right"
class="w-full overflow-y-auto border-white/10 bg-background/65 backdrop-blur-xl backdrop-saturate-150 sm:max-w-sm"
>
<SheetHeader>
<SheetTitle class="font-display uppercase tracking-wider">
{{ t('nav.menu') }}
</SheetTitle>
</SheetHeader>
<nav class="mt-6 space-y-4">
<div v-for="g in groups" :key="g.id" class="space-y-1">
<div
class="px-2 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>
</nav>
</SheetContent>
</Sheet>
</div> </div>
</div> </div>
</header> </header>

View file

@ -0,0 +1,17 @@
<script setup lang="ts">
import type { HTMLAttributes } from "vue"
import type { AlertVariants } from "."
import { cn } from "@/lib/utils"
import { alertVariants } from "."
const props = defineProps<{
class?: HTMLAttributes["class"]
variant?: AlertVariants["variant"]
}>()
</script>
<template>
<div :class="cn(alertVariants({ variant }), props.class)" role="alert">
<slot />
</div>
</template>

View file

@ -0,0 +1,14 @@
<script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { cn } from "@/lib/utils"
const props = defineProps<{
class?: HTMLAttributes["class"]
}>()
</script>
<template>
<div :class="cn('text-sm [&_p]:leading-relaxed', props.class)">
<slot />
</div>
</template>

View file

@ -0,0 +1,14 @@
<script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { cn } from "@/lib/utils"
const props = defineProps<{
class?: HTMLAttributes["class"]
}>()
</script>
<template>
<h5 :class="cn('mb-1 font-medium leading-none tracking-tight', props.class)">
<slot />
</h5>
</template>

View file

@ -0,0 +1,24 @@
import type { VariantProps } from "class-variance-authority"
import { cva } from "class-variance-authority"
export { default as Alert } from "./Alert.vue"
export { default as AlertDescription } from "./AlertDescription.vue"
export { default as AlertTitle } from "./AlertTitle.vue"
export const alertVariants = cva(
"relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground",
{
variants: {
variant: {
default: "bg-background text-foreground",
destructive:
"border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
},
},
defaultVariants: {
variant: "default",
},
},
)
export type AlertVariants = VariantProps<typeof alertVariants>

View file

@ -0,0 +1,17 @@
<script setup lang="ts">
import type { HTMLAttributes } from "vue"
import type { BadgeVariants } from "."
import { cn } from "@/lib/utils"
import { badgeVariants } from "."
const props = defineProps<{
variant?: BadgeVariants["variant"]
class?: HTMLAttributes["class"]
}>()
</script>
<template>
<div :class="cn(badgeVariants({ variant }), props.class)">
<slot />
</div>
</template>

View file

@ -0,0 +1,26 @@
import type { VariantProps } from "class-variance-authority"
import { cva } from "class-variance-authority"
export { default as Badge } from "./Badge.vue"
export const badgeVariants = cva(
"inline-flex gap-1 items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
{
variants: {
variant: {
default:
"border-transparent bg-primary text-primary-foreground hover:bg-primary/80",
secondary:
"border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
destructive:
"border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
outline: "text-foreground",
},
},
defaultVariants: {
variant: "default",
},
},
)
export type BadgeVariants = VariantProps<typeof badgeVariants>

View file

@ -0,0 +1,21 @@
<script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { cn } from "@/lib/utils"
const props = defineProps<{
class?: HTMLAttributes["class"]
}>()
</script>
<template>
<div
:class="
cn(
'rounded-lg border bg-card text-card-foreground shadow-sm',
props.class,
)
"
>
<slot />
</div>
</template>

View file

@ -0,0 +1,14 @@
<script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { cn } from "@/lib/utils"
const props = defineProps<{
class?: HTMLAttributes["class"]
}>()
</script>
<template>
<div :class="cn('p-6 pt-0', props.class)">
<slot />
</div>
</template>

View file

@ -0,0 +1,14 @@
<script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { cn } from "@/lib/utils"
const props = defineProps<{
class?: HTMLAttributes["class"]
}>()
</script>
<template>
<p :class="cn('text-sm text-muted-foreground', props.class)">
<slot />
</p>
</template>

View file

@ -0,0 +1,14 @@
<script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { cn } from "@/lib/utils"
const props = defineProps<{
class?: HTMLAttributes["class"]
}>()
</script>
<template>
<div :class="cn('flex items-center p-6 pt-0', props.class)">
<slot />
</div>
</template>

View file

@ -0,0 +1,14 @@
<script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { cn } from "@/lib/utils"
const props = defineProps<{
class?: HTMLAttributes["class"]
}>()
</script>
<template>
<div :class="cn('flex flex-col gap-y-1.5 p-6', props.class)">
<slot />
</div>
</template>

View file

@ -0,0 +1,18 @@
<script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { cn } from "@/lib/utils"
const props = defineProps<{
class?: HTMLAttributes["class"]
}>()
</script>
<template>
<h3
:class="
cn('text-2xl font-semibold leading-none tracking-tight', props.class)
"
>
<slot />
</h3>
</template>

View file

@ -0,0 +1,6 @@
export { default as Card } from "./Card.vue"
export { default as CardContent } from "./CardContent.vue"
export { default as CardDescription } from "./CardDescription.vue"
export { default as CardFooter } from "./CardFooter.vue"
export { default as CardHeader } from "./CardHeader.vue"
export { default as CardTitle } from "./CardTitle.vue"

View file

@ -0,0 +1,29 @@
<script setup lang="ts">
import type { NavigationMenuRootEmits, NavigationMenuRootProps } from "reka-ui"
import type { HTMLAttributes } from "vue"
import { reactiveOmit } from "@vueuse/core"
import {
NavigationMenuRoot,
useForwardPropsEmits,
} from "reka-ui"
import { cn } from "@/lib/utils"
import NavigationMenuViewport from "./NavigationMenuViewport.vue"
const props = defineProps<NavigationMenuRootProps & { class?: HTMLAttributes["class"] }>()
const emits = defineEmits<NavigationMenuRootEmits>()
const delegatedProps = reactiveOmit(props, "class")
const forwarded = useForwardPropsEmits(delegatedProps, emits)
</script>
<template>
<NavigationMenuRoot
v-bind="forwarded"
:class="cn('relative z-10 flex max-w-max flex-1 items-center justify-center', props.class)"
>
<slot />
<NavigationMenuViewport />
</NavigationMenuRoot>
</template>

View file

@ -0,0 +1,30 @@
<script setup lang="ts">
import type { NavigationMenuContentEmits, NavigationMenuContentProps } from "reka-ui"
import type { HTMLAttributes } from "vue"
import { reactiveOmit } from "@vueuse/core"
import {
NavigationMenuContent,
useForwardPropsEmits,
} from "reka-ui"
import { cn } from "@/lib/utils"
const props = defineProps<NavigationMenuContentProps & { class?: HTMLAttributes["class"] }>()
const emits = defineEmits<NavigationMenuContentEmits>()
const delegatedProps = reactiveOmit(props, "class")
const forwarded = useForwardPropsEmits(delegatedProps, emits)
</script>
<template>
<NavigationMenuContent
v-bind="forwarded"
:class="cn(
'left-0 top-0 w-full data-[motion^=from-]:animate-in data-[motion^=to-]:animate-out data-[motion^=from-]:fade-in data-[motion^=to-]:fade-out data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 md:absolute md:w-auto',
props.class,
)"
>
<slot />
</NavigationMenuContent>
</template>

View file

@ -0,0 +1,22 @@
<script setup lang="ts">
import type { NavigationMenuIndicatorProps } from "reka-ui"
import type { HTMLAttributes } from "vue"
import { reactiveOmit } from "@vueuse/core"
import { NavigationMenuIndicator, useForwardProps } from "reka-ui"
import { cn } from "@/lib/utils"
const props = defineProps<NavigationMenuIndicatorProps & { class?: HTMLAttributes["class"] }>()
const delegatedProps = reactiveOmit(props, "class")
const forwardedProps = useForwardProps(delegatedProps)
</script>
<template>
<NavigationMenuIndicator
v-bind="forwardedProps"
:class="cn('top-full z-[1] flex h-1.5 items-end justify-center overflow-hidden data-[state=visible]:animate-in data-[state=hidden]:animate-out data-[state=hidden]:fade-out data-[state=visible]:fade-in', props.class)"
>
<div class="relative top-[60%] h-2 w-2 rotate-45 rounded-tl-sm bg-border shadow-md" />
</NavigationMenuIndicator>
</template>

View file

@ -0,0 +1,12 @@
<script setup lang="ts">
import type { NavigationMenuItemProps } from "reka-ui"
import { NavigationMenuItem } from "reka-ui"
const props = defineProps<NavigationMenuItemProps>()
</script>
<template>
<NavigationMenuItem v-bind="props">
<slot />
</NavigationMenuItem>
</template>

View file

@ -0,0 +1,18 @@
<script setup lang="ts">
import type { NavigationMenuLinkEmits, NavigationMenuLinkProps } from "reka-ui"
import {
NavigationMenuLink,
useForwardPropsEmits,
} from "reka-ui"
const props = defineProps<NavigationMenuLinkProps>()
const emits = defineEmits<NavigationMenuLinkEmits>()
const forwarded = useForwardPropsEmits(props, emits)
</script>
<template>
<NavigationMenuLink v-bind="forwarded">
<slot />
</NavigationMenuLink>
</template>

View file

@ -0,0 +1,27 @@
<script setup lang="ts">
import type { NavigationMenuListProps } from "reka-ui"
import type { HTMLAttributes } from "vue"
import { reactiveOmit } from "@vueuse/core"
import { NavigationMenuList, useForwardProps } from "reka-ui"
import { cn } from "@/lib/utils"
const props = defineProps<NavigationMenuListProps & { class?: HTMLAttributes["class"] }>()
const delegatedProps = reactiveOmit(props, "class")
const forwardedProps = useForwardProps(delegatedProps)
</script>
<template>
<NavigationMenuList
v-bind="forwardedProps"
:class="
cn(
'group flex flex-1 list-none items-center justify-center gap-x-1',
props.class,
)
"
>
<slot />
</NavigationMenuList>
</template>

View file

@ -0,0 +1,31 @@
<script setup lang="ts">
import type { NavigationMenuTriggerProps } from "reka-ui"
import type { HTMLAttributes } from "vue"
import { reactiveOmit } from "@vueuse/core"
import { ChevronDown } from "@lucide/vue"
import {
NavigationMenuTrigger,
useForwardProps,
} from "reka-ui"
import { cn } from "@/lib/utils"
import { navigationMenuTriggerStyle } from "."
const props = defineProps<NavigationMenuTriggerProps & { class?: HTMLAttributes["class"] }>()
const delegatedProps = reactiveOmit(props, "class")
const forwardedProps = useForwardProps(delegatedProps)
</script>
<template>
<NavigationMenuTrigger
v-bind="forwardedProps"
:class="cn(navigationMenuTriggerStyle(), 'group', props.class)"
>
<slot />
<ChevronDown
class="relative top-px ml-1 h-3 w-3 transition duration-200 group-data-[state=open]:rotate-180"
aria-hidden="true"
/>
</NavigationMenuTrigger>
</template>

View file

@ -0,0 +1,30 @@
<script setup lang="ts">
import type { NavigationMenuViewportProps } from "reka-ui"
import type { HTMLAttributes } from "vue"
import { reactiveOmit } from "@vueuse/core"
import {
NavigationMenuViewport,
useForwardProps,
} from "reka-ui"
import { cn } from "@/lib/utils"
const props = defineProps<NavigationMenuViewportProps & { class?: HTMLAttributes["class"] }>()
const delegatedProps = reactiveOmit(props, "class")
const forwardedProps = useForwardProps(delegatedProps)
</script>
<template>
<div class="absolute left-0 top-full flex justify-center">
<NavigationMenuViewport
v-bind="forwardedProps"
:class="
cn(
'origin-top-center relative mt-1.5 h-[var(--reka-navigation-menu-viewport-height)] w-full overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-90 md:w-[var(--reka-navigation-menu-viewport-width)] left-[var(--reka-navigation-menu-viewport-left)]',
props.class,
)
"
/>
</div>
</template>

View file

@ -0,0 +1,14 @@
import { cva } from "class-variance-authority"
export { default as NavigationMenu } from "./NavigationMenu.vue"
export { default as NavigationMenuContent } from "./NavigationMenuContent.vue"
export { default as NavigationMenuIndicator } from "./NavigationMenuIndicator.vue"
export { default as NavigationMenuItem } from "./NavigationMenuItem.vue"
export { default as NavigationMenuLink } from "./NavigationMenuLink.vue"
export { default as NavigationMenuList } from "./NavigationMenuList.vue"
export { default as NavigationMenuTrigger } from "./NavigationMenuTrigger.vue"
export { default as NavigationMenuViewport } from "./NavigationMenuViewport.vue"
export const navigationMenuTriggerStyle = cva(
"group inline-flex h-10 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[active]:bg-accent/50 data-[state=open]:bg-accent/50",
)

View file

@ -0,0 +1,15 @@
<script setup lang="ts">
import type { DialogRootEmits, DialogRootProps } from "reka-ui"
import { DialogRoot, useForwardPropsEmits } from "reka-ui"
const props = defineProps<DialogRootProps>()
const emits = defineEmits<DialogRootEmits>()
const forwarded = useForwardPropsEmits(props, emits)
</script>
<template>
<DialogRoot v-bind="forwarded">
<slot />
</DialogRoot>
</template>

View file

@ -0,0 +1,12 @@
<script setup lang="ts">
import type { DialogCloseProps } from "reka-ui"
import { DialogClose } from "reka-ui"
const props = defineProps<DialogCloseProps>()
</script>
<template>
<DialogClose v-bind="props">
<slot />
</DialogClose>
</template>

View file

@ -0,0 +1,53 @@
<script setup lang="ts">
import type { DialogContentEmits, DialogContentProps } from "reka-ui"
import type { HTMLAttributes } from "vue"
import type { SheetVariants } from "."
import { reactiveOmit } from "@vueuse/core"
import { X } from "@lucide/vue"
import {
DialogClose,
DialogContent,
DialogOverlay,
DialogPortal,
useForwardPropsEmits,
} from "reka-ui"
import { cn } from "@/lib/utils"
import { sheetVariants } from "."
interface SheetContentProps extends DialogContentProps {
class?: HTMLAttributes["class"]
side?: SheetVariants["side"]
}
defineOptions({
inheritAttrs: false,
})
const props = defineProps<SheetContentProps>()
const emits = defineEmits<DialogContentEmits>()
const delegatedProps = reactiveOmit(props, "class", "side")
const forwarded = useForwardPropsEmits(delegatedProps, emits)
</script>
<template>
<DialogPortal>
<DialogOverlay
class="fixed inset-0 z-50 bg-black/30 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0"
/>
<DialogContent
:class="cn(sheetVariants({ side }), props.class)"
v-bind="{ ...forwarded, ...$attrs }"
>
<slot />
<DialogClose
class="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary"
>
<X class="w-4 h-4 text-muted-foreground" />
</DialogClose>
</DialogContent>
</DialogPortal>
</template>

View file

@ -0,0 +1,20 @@
<script setup lang="ts">
import type { DialogDescriptionProps } from "reka-ui"
import type { HTMLAttributes } from "vue"
import { reactiveOmit } from "@vueuse/core"
import { DialogDescription } from "reka-ui"
import { cn } from "@/lib/utils"
const props = defineProps<DialogDescriptionProps & { class?: HTMLAttributes["class"] }>()
const delegatedProps = reactiveOmit(props, "class")
</script>
<template>
<DialogDescription
:class="cn('text-sm text-muted-foreground', props.class)"
v-bind="delegatedProps"
>
<slot />
</DialogDescription>
</template>

View file

@ -0,0 +1,19 @@
<script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { cn } from "@/lib/utils"
const props = defineProps<{ class?: HTMLAttributes["class"] }>()
</script>
<template>
<div
:class="
cn(
'flex flex-col-reverse sm:flex-row sm:justify-end sm:gap-x-2',
props.class,
)
"
>
<slot />
</div>
</template>

View file

@ -0,0 +1,16 @@
<script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { cn } from "@/lib/utils"
const props = defineProps<{ class?: HTMLAttributes["class"] }>()
</script>
<template>
<div
:class="
cn('flex flex-col gap-y-2 text-center sm:text-left', props.class)
"
>
<slot />
</div>
</template>

View file

@ -0,0 +1,20 @@
<script setup lang="ts">
import type { DialogTitleProps } from "reka-ui"
import type { HTMLAttributes } from "vue"
import { reactiveOmit } from "@vueuse/core"
import { DialogTitle } from "reka-ui"
import { cn } from "@/lib/utils"
const props = defineProps<DialogTitleProps & { class?: HTMLAttributes["class"] }>()
const delegatedProps = reactiveOmit(props, "class")
</script>
<template>
<DialogTitle
:class="cn('text-lg font-semibold text-foreground', props.class)"
v-bind="delegatedProps"
>
<slot />
</DialogTitle>
</template>

View file

@ -0,0 +1,12 @@
<script setup lang="ts">
import type { DialogTriggerProps } from "reka-ui"
import { DialogTrigger } from "reka-ui"
const props = defineProps<DialogTriggerProps>()
</script>
<template>
<DialogTrigger v-bind="props">
<slot />
</DialogTrigger>
</template>

View file

@ -0,0 +1,32 @@
import type { VariantProps } from "class-variance-authority"
import { cva } from "class-variance-authority"
export { default as Sheet } from "./Sheet.vue"
export { default as SheetClose } from "./SheetClose.vue"
export { default as SheetContent } from "./SheetContent.vue"
export { default as SheetDescription } from "./SheetDescription.vue"
export { default as SheetFooter } from "./SheetFooter.vue"
export { default as SheetHeader } from "./SheetHeader.vue"
export { default as SheetTitle } from "./SheetTitle.vue"
export { default as SheetTrigger } from "./SheetTrigger.vue"
export const sheetVariants = cva(
"fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500",
{
variants: {
side: {
top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top",
bottom:
"inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm",
right:
"inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm",
},
},
defaultVariants: {
side: "right",
},
},
)
export type SheetVariants = VariantProps<typeof sheetVariants>

View file

@ -2,6 +2,8 @@
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { RouterLink } from 'vue-router' import { RouterLink } from 'vue-router'
import { Button } from '@/components/ui/button' import { Button } from '@/components/ui/button'
import { Card, CardContent } from '@/components/ui/card'
import { Badge } from '@/components/ui/badge'
const { t, tm, rt } = useI18n() const { t, tm, rt } = useI18n()
@ -82,41 +84,35 @@ const exteriorItems = tm('accommodation.exterior.items') as string[]
<p class="mt-3 text-muted-foreground">{{ t('accommodation.rooms.subtitle') }}</p> <p class="mt-3 text-muted-foreground">{{ t('accommodation.rooms.subtitle') }}</p>
</div> </div>
<ul class="mt-8 grid gap-6 md:grid-cols-2 lg:grid-cols-3"> <ul class="mt-8 grid gap-6 md:grid-cols-2 lg:grid-cols-3">
<li <li v-for="room in rooms" :key="room.key">
v-for="room in rooms" <Card class="overflow-hidden">
:key="room.key" <img
class="overflow-hidden rounded-lg border border-border bg-card" :src="room.image"
> alt=""
<img class="aspect-[4/3] w-full object-cover"
:src="room.image" loading="lazy"
alt="" />
class="aspect-[4/3] w-full object-cover" <CardContent class="p-5 pt-5">
loading="lazy" <div class="flex items-baseline justify-between gap-3">
/> <h3 class="font-serif text-xl font-semibold">
<div class="p-5"> {{ t(`accommodation.rooms.${room.key}.name`) }}
<div class="flex items-baseline justify-between gap-3"> </h3>
<h3 class="font-serif text-xl font-semibold"> <Badge
{{ t(`accommodation.rooms.${room.key}.name`) }} :variant="room.open ? 'default' : 'outline'"
</h3> class="text-[10px] uppercase tracking-wider"
<span >
class="rounded-full px-2 py-0.5 text-[10px] uppercase tracking-wider" {{
:class=" room.open
room.open ? t('accommodation.statusOpen')
? 'bg-primary/10 text-primary' : t('accommodation.statusComingSoon')
: 'border border-border text-muted-foreground' }}
" </Badge>
> </div>
{{ <p class="mt-3 text-sm leading-relaxed text-foreground/85">
room.open {{ t(`accommodation.rooms.${room.key}.summary`) }}
? t('accommodation.statusOpen') </p>
: t('accommodation.statusComingSoon') </CardContent>
}} </Card>
</span>
</div>
<p class="mt-3 text-sm leading-relaxed text-foreground/85">
{{ t(`accommodation.rooms.${room.key}.summary`) }}
</p>
</div>
</li> </li>
</ul> </ul>
</section> </section>
@ -131,27 +127,26 @@ const exteriorItems = tm('accommodation.exterior.items') as string[]
<p class="mt-3 text-muted-foreground">{{ t('accommodation.cabins.subtitle') }}</p> <p class="mt-3 text-muted-foreground">{{ t('accommodation.cabins.subtitle') }}</p>
</div> </div>
<ul class="mt-8 grid gap-6 md:grid-cols-3"> <ul class="mt-8 grid gap-6 md:grid-cols-3">
<li <li v-for="cabin in cabins" :key="cabin.key">
v-for="cabin in cabins" <Card class="overflow-hidden bg-background">
:key="cabin.key" <img
class="overflow-hidden rounded-lg border border-border bg-background" :src="cabin.image"
> alt=""
<img class="aspect-[4/3] w-full object-cover"
:src="cabin.image" loading="lazy"
alt="" />
class="aspect-[4/3] w-full object-cover" <div class="flex items-baseline justify-between gap-3 p-5">
loading="lazy" <h3 class="font-serif text-xl font-semibold">
/> {{ t(`accommodation.cabins.${cabin.key}`) }}
<div class="flex items-baseline justify-between gap-3 p-5"> </h3>
<h3 class="font-serif text-xl font-semibold"> <Badge
{{ t(`accommodation.cabins.${cabin.key}`) }} variant="outline"
</h3> class="text-[10px] uppercase tracking-wider text-muted-foreground"
<span >
class="rounded-full border border-border px-2 py-0.5 text-[10px] uppercase tracking-wider text-muted-foreground" {{ t('accommodation.statusComingSoon') }}
> </Badge>
{{ t('accommodation.statusComingSoon') }} </div>
</span> </Card>
</div>
</li> </li>
</ul> </ul>
</div> </div>
@ -166,12 +161,10 @@ const exteriorItems = tm('accommodation.exterior.items') as string[]
<p class="mt-3 text-muted-foreground">{{ t('accommodation.exterior.subtitle') }}</p> <p class="mt-3 text-muted-foreground">{{ t('accommodation.exterior.subtitle') }}</p>
</div> </div>
<ul class="mt-8 grid gap-4 sm:grid-cols-2 lg:grid-cols-3"> <ul class="mt-8 grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
<li <li v-for="(item, i) in exteriorItems" :key="i">
v-for="(item, i) in exteriorItems" <Card class="border-dashed bg-secondary/20 p-5 text-sm text-foreground/85">
:key="i" {{ rt(item) }}
class="rounded-lg border border-dashed border-border bg-secondary/20 p-5 text-sm text-foreground/85" </Card>
>
{{ rt(item) }}
</li> </li>
</ul> </ul>
</section> </section>

View file

@ -1,6 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { RouterLink } from 'vue-router' import { RouterLink } from 'vue-router'
import { Card } from '@/components/ui/card'
const { t } = useI18n() const { t } = useI18n()
@ -96,18 +97,14 @@ const pillars = [
<p class="mt-3 text-muted-foreground">{{ t('concept.slowFarming.body') }}</p> <p class="mt-3 text-muted-foreground">{{ t('concept.slowFarming.body') }}</p>
</div> </div>
<div class="mt-8 grid gap-6 md:grid-cols-3"> <div class="mt-8 grid gap-6 md:grid-cols-3">
<article <Card v-for="p in pillars" :key="p.key" class="p-6">
v-for="p in pillars"
:key="p.key"
class="rounded-lg border border-border bg-card p-6"
>
<h3 class="font-serif text-xl font-semibold"> <h3 class="font-serif text-xl font-semibold">
{{ t(`concept.slowFarming.${p.key}Title`) }} {{ t(`concept.slowFarming.${p.key}Title`) }}
</h3> </h3>
<p class="mt-3 text-sm leading-relaxed text-foreground/85"> <p class="mt-3 text-sm leading-relaxed text-foreground/85">
{{ t(`concept.slowFarming.${p.key}Body`) }} {{ t(`concept.slowFarming.${p.key}Body`) }}
</p> </p>
</article> </Card>
</div> </div>
</div> </div>
</section> </section>

View file

@ -2,6 +2,8 @@
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { RouterLink } from 'vue-router' import { RouterLink } from 'vue-router'
import { Button } from '@/components/ui/button' import { Button } from '@/components/ui/button'
import { Card, CardContent } from '@/components/ui/card'
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'
const { t } = useI18n() const { t } = useI18n()
@ -47,14 +49,14 @@ const events = [
<p class="mt-6 max-w-prose text-lg leading-relaxed text-foreground/90"> <p class="mt-6 max-w-prose text-lg leading-relaxed text-foreground/90">
{{ t('events.page.lede') }} {{ t('events.page.lede') }}
</p> </p>
<aside <Alert class="mt-8 max-w-prose bg-card text-foreground/85">
class="mt-8 max-w-prose rounded-md border border-border bg-card p-4 text-sm text-foreground/85" <AlertTitle class="text-xs uppercase tracking-wider text-accent">
>
<p class="text-xs font-semibold uppercase tracking-wider text-accent">
{{ t('events.page.noteHeading') }} {{ t('events.page.noteHeading') }}
</p> </AlertTitle>
<p class="mt-1">{{ t('events.page.calendarNote') }}</p> <AlertDescription>
</aside> {{ t('events.page.calendarNote') }}
</AlertDescription>
</Alert>
</div> </div>
</section> </section>
@ -64,28 +66,30 @@ const events = [
<component <component
:is="e.to ? 'RouterLink' : 'article'" :is="e.to ? 'RouterLink' : 'article'"
v-bind="e.to ? { to: e.to } : {}" v-bind="e.to ? { to: e.to } : {}"
class="group block h-full overflow-hidden rounded-lg border border-border bg-card transition hover:shadow-md" class="group block h-full"
> >
<img <Card class="h-full overflow-hidden transition hover:shadow-md">
:src="e.image" <img
alt="" :src="e.image"
class="aspect-[4/3] w-full object-cover transition group-hover:scale-[1.02]" alt=""
loading="lazy" class="aspect-[4/3] w-full object-cover transition group-hover:scale-[1.02]"
/> loading="lazy"
<div class="p-5"> />
<p class="text-xs uppercase tracking-wider text-accent"> <CardContent class="p-5 pt-5">
{{ t(`events.${e.key}.date`) }} <p class="text-xs uppercase tracking-wider text-accent">
</p> {{ t(`events.${e.key}.date`) }}
<h2 class="mt-1 font-serif text-xl font-semibold"> </p>
{{ t(`events.${e.key}.title`) }} <h2 class="mt-1 font-serif text-xl font-semibold">
</h2> {{ t(`events.${e.key}.title`) }}
<p class="mt-1 text-xs text-muted-foreground"> </h2>
{{ t(`events.${e.key}.location`) }} <p class="mt-1 text-xs text-muted-foreground">
</p> {{ t(`events.${e.key}.location`) }}
<p class="mt-3 text-sm leading-relaxed text-foreground/85"> </p>
{{ t(`events.${e.key}.description`) }} <p class="mt-3 text-sm leading-relaxed text-foreground/85">
</p> {{ t(`events.${e.key}.description`) }}
</div> </p>
</CardContent>
</Card>
</component> </component>
</li> </li>
</ul> </ul>

View file

@ -1,5 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { Card } from '@/components/ui/card'
const { t } = useI18n() const { t } = useI18n()
@ -47,24 +48,22 @@ const items = [
<section class="mx-auto max-w-7xl px-4 py-16 lg:px-6"> <section class="mx-auto max-w-7xl px-4 py-16 lg:px-6">
<ul class="grid gap-4 sm:grid-cols-2 lg:grid-cols-3"> <ul class="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
<li <li v-for="item in items" :key="item.key" class="group">
v-for="item in items" <Card class="overflow-hidden">
:key="item.key" <figure>
class="group overflow-hidden rounded-lg border border-border bg-card" <img
> :src="item.src"
<figure> :alt="t(`gallery.captions.${item.key}`)"
<img class="aspect-square w-full object-cover transition group-hover:scale-[1.02]"
:src="item.src" loading="lazy"
:alt="t(`gallery.captions.${item.key}`)" />
class="aspect-square w-full object-cover transition group-hover:scale-[1.02]" <figcaption
loading="lazy" class="border-t border-border px-4 py-3 font-serif text-sm text-foreground/85"
/> >
<figcaption {{ t(`gallery.captions.${item.key}`) }}
class="border-t border-border px-4 py-3 font-serif text-sm text-foreground/85" </figcaption>
> </figure>
{{ t(`gallery.captions.${item.key}`) }} </Card>
</figcaption>
</figure>
</li> </li>
</ul> </ul>
</section> </section>

View file

@ -2,6 +2,7 @@
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { RouterLink } from 'vue-router' import { RouterLink } from 'vue-router'
import { Button } from '@/components/ui/button' import { Button } from '@/components/ui/button'
import { Card, CardContent } from '@/components/ui/card'
import cosmicStag from '@/assets/cosmic-stag.avif' import cosmicStag from '@/assets/cosmic-stag.avif'
import heroLandscape from '@/assets/hero-landscape.webp' import heroLandscape from '@/assets/hero-landscape.webp'
import sectionTile from '@/assets/section-tile.webp' import sectionTile from '@/assets/section-tile.webp'
@ -192,25 +193,27 @@ const featuredEvents = [
v-for="e in featuredEvents" v-for="e in featuredEvents"
:key="e.key" :key="e.key"
:to="e.to" :to="e.to"
class="group overflow-hidden rounded-lg border border-border bg-card transition hover:shadow-md" class="group block"
> >
<img <Card class="overflow-hidden transition hover:shadow-md">
:src="e.image" <img
alt="" :src="e.image"
class="aspect-[4/3] w-full object-cover transition group-hover:scale-[1.02]" alt=""
loading="lazy" class="aspect-[4/3] w-full object-cover transition group-hover:scale-[1.02]"
/> loading="lazy"
<div class="p-5"> />
<p class="text-xs uppercase tracking-wider text-accent"> <CardContent class="p-5 pt-5">
{{ t(`events.${e.key}.date`) }} <p class="text-xs uppercase tracking-wider text-accent">
</p> {{ t(`events.${e.key}.date`) }}
<h3 class="mt-2 font-display text-lg uppercase tracking-wider"> </p>
{{ t(`events.${e.key}.title`) }} <h3 class="mt-2 font-display text-lg uppercase tracking-wider">
</h3> {{ t(`events.${e.key}.title`) }}
<p class="mt-1 text-xs text-foreground/70"> </h3>
{{ t(`events.${e.key}.location`) }} <p class="mt-1 text-xs text-foreground/70">
</p> {{ t(`events.${e.key}.location`) }}
</div> </p>
</CardContent>
</Card>
</RouterLink> </RouterLink>
</div> </div>
<div class="mt-10 text-center"> <div class="mt-10 text-center">

View file

@ -2,6 +2,7 @@
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { RouterLink } from 'vue-router' import { RouterLink } from 'vue-router'
import { Button } from '@/components/ui/button' import { Button } from '@/components/ui/button'
import { Card } from '@/components/ui/card'
const { t } = useI18n() const { t } = useI18n()
@ -31,18 +32,14 @@ const paths = ['exchange', 'rental', 'partial', 'funded'] as const
{{ t('longStays.pathsTitle') }} {{ t('longStays.pathsTitle') }}
</h2> </h2>
<div class="mt-8 grid gap-6 md:grid-cols-2"> <div class="mt-8 grid gap-6 md:grid-cols-2">
<article <Card v-for="key in paths" :key="key" class="p-6">
v-for="key in paths"
:key="key"
class="rounded-lg border border-border bg-card p-6"
>
<h3 class="font-serif text-xl font-semibold"> <h3 class="font-serif text-xl font-semibold">
{{ t(`longStays.paths.${key}Title`) }} {{ t(`longStays.paths.${key}Title`) }}
</h3> </h3>
<p class="mt-3 text-base leading-relaxed text-foreground/85"> <p class="mt-3 text-base leading-relaxed text-foreground/85">
{{ t(`longStays.paths.${key}Body`) }} {{ t(`longStays.paths.${key}Body`) }}
</p> </p>
</article> </Card>
</div> </div>
</section> </section>

View file

@ -1,6 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { Button } from '@/components/ui/button' import { Button } from '@/components/ui/button'
import { Card } from '@/components/ui/card'
const { t } = useI18n() const { t } = useI18n()
@ -28,18 +29,14 @@ const categories = ['fresh', 'pantry', 'craft'] as const
{{ t('marketplace.categoriesTitle') }} {{ t('marketplace.categoriesTitle') }}
</h2> </h2>
<div class="mt-8 grid gap-6 md:grid-cols-3"> <div class="mt-8 grid gap-6 md:grid-cols-3">
<article <Card v-for="key in categories" :key="key" class="border-dashed p-6">
v-for="key in categories"
:key="key"
class="rounded-lg border border-dashed border-border bg-card p-6"
>
<h3 class="font-serif text-xl font-semibold"> <h3 class="font-serif text-xl font-semibold">
{{ t(`marketplace.categories.${key}Title`) }} {{ t(`marketplace.categories.${key}Title`) }}
</h3> </h3>
<p class="mt-3 text-base leading-relaxed text-foreground/85"> <p class="mt-3 text-base leading-relaxed text-foreground/85">
{{ t(`marketplace.categories.${key}Body`) }} {{ t(`marketplace.categories.${key}Body`) }}
</p> </p>
</article> </Card>
</div> </div>
</section> </section>

View file

@ -2,6 +2,7 @@
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { RouterLink } from 'vue-router' import { RouterLink } from 'vue-router'
import { Button } from '@/components/ui/button' import { Button } from '@/components/ui/button'
import { Card } from '@/components/ui/card'
const { t } = useI18n() const { t } = useI18n()
@ -43,17 +44,15 @@ const applyKeys = ['model', 'window', 'open'] as const
<p class="mt-3 text-muted-foreground">{{ t('opportunities.groupsSubtitle') }}</p> <p class="mt-3 text-muted-foreground">{{ t('opportunities.groupsSubtitle') }}</p>
</div> </div>
<ul class="mt-8 grid gap-6 md:grid-cols-2 lg:grid-cols-3"> <ul class="mt-8 grid gap-6 md:grid-cols-2 lg:grid-cols-3">
<li <li v-for="key in groups" :key="key">
v-for="key in groups" <Card class="p-6">
:key="key" <h3 class="font-serif text-xl font-semibold">
class="rounded-lg border border-border bg-card p-6" {{ t(`opportunities.groups.${key}Title`) }}
> </h3>
<h3 class="font-serif text-xl font-semibold"> <p class="mt-3 text-sm leading-relaxed text-foreground/85">
{{ t(`opportunities.groups.${key}Title`) }} {{ t(`opportunities.groups.${key}Positions`) }}
</h3> </p>
<p class="mt-3 text-sm leading-relaxed text-foreground/85"> </Card>
{{ t(`opportunities.groups.${key}Positions`) }}
</p>
</li> </li>
</ul> </ul>
</section> </section>
@ -65,18 +64,14 @@ const applyKeys = ['model', 'window', 'open'] as const
{{ t('opportunities.applyTitle') }} {{ t('opportunities.applyTitle') }}
</h2> </h2>
<div class="mt-8 grid gap-6 md:grid-cols-3"> <div class="mt-8 grid gap-6 md:grid-cols-3">
<article <Card v-for="key in applyKeys" :key="key" class="p-6">
v-for="key in applyKeys"
:key="key"
class="rounded-lg border border-border bg-card p-6"
>
<h3 class="font-serif text-lg font-semibold"> <h3 class="font-serif text-lg font-semibold">
{{ t(`opportunities.apply.${key}Title`) }} {{ t(`opportunities.apply.${key}Title`) }}
</h3> </h3>
<p class="mt-3 text-sm leading-relaxed text-foreground/85"> <p class="mt-3 text-sm leading-relaxed text-foreground/85">
{{ t(`opportunities.apply.${key}Body`) }} {{ t(`opportunities.apply.${key}Body`) }}
</p> </p>
</article> </Card>
</div> </div>
<div class="mt-10 flex flex-wrap items-center gap-3"> <div class="mt-10 flex flex-wrap items-center gap-3">
<Button as-child> <Button as-child>

View file

@ -2,6 +2,7 @@
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { RouterLink } from 'vue-router' import { RouterLink } from 'vue-router'
import { Button } from '@/components/ui/button' import { Button } from '@/components/ui/button'
import { Card } from '@/components/ui/card'
const { t } = useI18n() const { t } = useI18n()
@ -31,18 +32,14 @@ const kinds = ['weekend', 'retreat', 'gathering', 'residency'] as const
{{ t('reservations.kindsTitle') }} {{ t('reservations.kindsTitle') }}
</h2> </h2>
<div class="mt-8 grid gap-6 sm:grid-cols-2 lg:grid-cols-4"> <div class="mt-8 grid gap-6 sm:grid-cols-2 lg:grid-cols-4">
<article <Card v-for="key in kinds" :key="key" class="p-6">
v-for="key in kinds"
:key="key"
class="rounded-lg border border-border bg-card p-6"
>
<h3 class="font-serif text-xl font-semibold"> <h3 class="font-serif text-xl font-semibold">
{{ t(`reservations.kinds.${key}Title`) }} {{ t(`reservations.kinds.${key}Title`) }}
</h3> </h3>
<p class="mt-3 text-sm leading-relaxed text-foreground/85"> <p class="mt-3 text-sm leading-relaxed text-foreground/85">
{{ t(`reservations.kinds.${key}Body`) }} {{ t(`reservations.kinds.${key}Body`) }}
</p> </p>
</article> </Card>
</div> </div>
</section> </section>
@ -87,7 +84,7 @@ const kinds = ['weekend', 'retreat', 'gathering', 'residency'] as const
</div> </div>
</div> </div>
<aside class="rounded-lg border border-border bg-card p-6 lg:col-span-2"> <Card class="p-6 lg:col-span-2">
<h3 class="font-serif text-xl font-semibold"> <h3 class="font-serif text-xl font-semibold">
{{ t('reservations.contactCard.title') }} {{ t('reservations.contactCard.title') }}
</h3> </h3>
@ -118,7 +115,7 @@ const kinds = ['weekend', 'retreat', 'gathering', 'residency'] as const
<dd class="mt-1">{{ t('reservations.contactCard.openingValue') }}</dd> <dd class="mt-1">{{ t('reservations.contactCard.openingValue') }}</dd>
</div> </div>
</dl> </dl>
</aside> </Card>
</div> </div>
</section> </section>
</div> </div>

View file

@ -2,6 +2,7 @@
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { RouterLink } from 'vue-router' import { RouterLink } from 'vue-router'
import { Button } from '@/components/ui/button' import { Button } from '@/components/ui/button'
import { Card } from '@/components/ui/card'
const { t } = useI18n() const { t } = useI18n()
@ -56,18 +57,14 @@ const applySteps = ['stepOne', 'stepTwo', 'stepThree'] as const
{{ t('symposium.includedTitle') }} {{ t('symposium.includedTitle') }}
</h2> </h2>
<div class="mt-8 grid gap-6 sm:grid-cols-2 lg:grid-cols-4"> <div class="mt-8 grid gap-6 sm:grid-cols-2 lg:grid-cols-4">
<article <Card v-for="key in included" :key="key" class="bg-background p-6">
v-for="key in included"
:key="key"
class="rounded-lg border border-border bg-background p-6"
>
<h3 class="font-serif text-lg font-semibold"> <h3 class="font-serif text-lg font-semibold">
{{ t(`symposium.included.${key}Title`) }} {{ t(`symposium.included.${key}Title`) }}
</h3> </h3>
<p class="mt-3 text-sm leading-relaxed text-foreground/85"> <p class="mt-3 text-sm leading-relaxed text-foreground/85">
{{ t(`symposium.included.${key}Body`) }} {{ t(`symposium.included.${key}Body`) }}
</p> </p>
</article> </Card>
</div> </div>
</div> </div>
</section> </section>
@ -89,17 +86,15 @@ const applySteps = ['stepOne', 'stepTwo', 'stepThree'] as const
{{ t('symposium.applyTitle') }} {{ t('symposium.applyTitle') }}
</h2> </h2>
<ol class="mt-8 grid gap-6 md:grid-cols-3"> <ol class="mt-8 grid gap-6 md:grid-cols-3">
<li <li v-for="step in applySteps" :key="step">
v-for="step in applySteps" <Card class="p-6">
:key="step" <h3 class="font-serif text-lg font-semibold">
class="rounded-lg border border-border bg-card p-6" {{ t(`symposium.apply.${step}Title`) }}
> </h3>
<h3 class="font-serif text-lg font-semibold"> <p class="mt-3 text-sm leading-relaxed text-foreground/85">
{{ t(`symposium.apply.${step}Title`) }} {{ t(`symposium.apply.${step}Body`) }}
</h3> </p>
<p class="mt-3 text-sm leading-relaxed text-foreground/85"> </Card>
{{ t(`symposium.apply.${step}Body`) }}
</p>
</li> </li>
</ol> </ol>
<div class="mt-10 flex flex-wrap items-center gap-3"> <div class="mt-10 flex flex-wrap items-center gap-3">

View file

@ -1,5 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { Card } from '@/components/ui/card'
const { t } = useI18n() const { t } = useI18n()
@ -55,11 +56,7 @@ const team = ['patrick', 'coco', 'charlie'] as const
<p class="mt-3 text-muted-foreground">{{ t('vision.philosophySubtitle') }}</p> <p class="mt-3 text-muted-foreground">{{ t('vision.philosophySubtitle') }}</p>
</div> </div>
<div class="mt-8 grid gap-6 sm:grid-cols-2 lg:grid-cols-5"> <div class="mt-8 grid gap-6 sm:grid-cols-2 lg:grid-cols-5">
<article <Card v-for="(p, i) in philosophy" :key="p" class="bg-background p-5">
v-for="(p, i) in philosophy"
:key="p"
class="rounded-lg border border-border bg-background p-5"
>
<div class="font-serif text-2xl text-accent">{{ i + 1 }}</div> <div class="font-serif text-2xl text-accent">{{ i + 1 }}</div>
<h3 class="mt-2 font-serif text-lg font-semibold"> <h3 class="mt-2 font-serif text-lg font-semibold">
{{ t(`vision.philosophy.${p}Title`) }} {{ t(`vision.philosophy.${p}Title`) }}
@ -67,7 +64,7 @@ const team = ['patrick', 'coco', 'charlie'] as const
<p class="mt-2 text-sm leading-relaxed text-foreground/85"> <p class="mt-2 text-sm leading-relaxed text-foreground/85">
{{ t(`vision.philosophy.${p}Body`) }} {{ t(`vision.philosophy.${p}Body`) }}
</p> </p>
</article> </Card>
</div> </div>
</div> </div>
</section> </section>
@ -81,16 +78,12 @@ const team = ['patrick', 'coco', 'charlie'] as const
<p class="mt-3 text-muted-foreground">{{ t('vision.pillarsSubtitle') }}</p> <p class="mt-3 text-muted-foreground">{{ t('vision.pillarsSubtitle') }}</p>
</div> </div>
<div class="mt-8 grid gap-6 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4"> <div class="mt-8 grid gap-6 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
<article <Card v-for="p in pillars" :key="p" class="p-6">
v-for="p in pillars"
:key="p"
class="rounded-lg border border-border bg-card p-6"
>
<h3 class="font-serif text-xl font-semibold">{{ t(`vision.pillars.${p}Title`) }}</h3> <h3 class="font-serif text-xl font-semibold">{{ t(`vision.pillars.${p}Title`) }}</h3>
<p class="mt-3 text-sm leading-relaxed text-foreground/85"> <p class="mt-3 text-sm leading-relaxed text-foreground/85">
{{ t(`vision.pillars.${p}Body`) }} {{ t(`vision.pillars.${p}Body`) }}
</p> </p>
</article> </Card>
</div> </div>
</section> </section>
@ -101,11 +94,7 @@ const team = ['patrick', 'coco', 'charlie'] as const
{{ t('vision.teamTitle') }} {{ t('vision.teamTitle') }}
</h2> </h2>
<div class="mt-8 grid gap-6 md:grid-cols-3"> <div class="mt-8 grid gap-6 md:grid-cols-3">
<article <Card v-for="m in team" :key="m" class="p-6">
v-for="m in team"
:key="m"
class="rounded-lg border border-border bg-card p-6"
>
<h3 class="font-serif text-xl font-semibold">{{ t(`vision.team.${m}Name`) }}</h3> <h3 class="font-serif text-xl font-semibold">{{ t(`vision.team.${m}Name`) }}</h3>
<p class="mt-1 text-xs uppercase tracking-wider text-accent"> <p class="mt-1 text-xs uppercase tracking-wider text-accent">
{{ t(`vision.team.${m}Role`) }} {{ t(`vision.team.${m}Role`) }}
@ -113,7 +102,7 @@ const team = ['patrick', 'coco', 'charlie'] as const
<p class="mt-3 text-sm leading-relaxed text-foreground/85"> <p class="mt-3 text-sm leading-relaxed text-foreground/85">
{{ t(`vision.team.${m}Body`) }} {{ t(`vision.team.${m}Body`) }}
</p> </p>
</article> </Card>
</div> </div>
</div> </div>
</section> </section>