refactor(buttons): introduce shadcn-vue Button, gold-pill variants

Add the shadcn-vue Button component at src/components/ui/button/ and
sweep every site CTA through it instead of repeating the same
rounded-md class string across nine views.

The Button's variants are tuned to the new Wix-inspired aesthetic:
- default: gold fill, cream text, rounded-full, uppercase tracking 0.2em
- outline: gold outline + gold text on transparent, same shape
- ghost / link / secondary / destructive: rounded-full equivalents

Two follow-ups noted:
1. components.json had a stale "framework" key from older shadcn-vue
   schemas; dropped. The CLI still rejects the file on a path-resolve
   check against tsconfig — needs deeper investigation, so I wrote
   the Button by hand for now.
2. SiteHeader's nav-dropdown toggles and the locale switcher are not
   routed through Button on purpose — they're a different control
   pattern from CTAs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Padreug 2026-06-08 22:27:35 +02:00
commit 0a3bdb004b
10 changed files with 125 additions and 92 deletions

View file

@ -0,0 +1,26 @@
<script setup lang="ts">
import type { HTMLAttributes } from 'vue'
import { Primitive, type PrimitiveProps } from 'reka-ui'
import { type ButtonVariants, buttonVariants } from '.'
import { cn } from '@/lib/utils'
interface Props extends PrimitiveProps {
variant?: ButtonVariants['variant']
size?: ButtonVariants['size']
class?: HTMLAttributes['class']
}
const props = withDefaults(defineProps<Props>(), {
as: 'button',
})
</script>
<template>
<Primitive
:as="as"
:as-child="asChild"
:class="cn(buttonVariants({ variant, size }), props.class)"
>
<slot />
</Primitive>
</template>

View file

@ -0,0 +1,34 @@
import { cva, type VariantProps } from 'class-variance-authority'
export { default as Button } from './Button.vue'
export const buttonVariants = cva(
'inline-flex items-center justify-center whitespace-nowrap font-display uppercase tracking-[0.2em] ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
{
variants: {
variant: {
default: 'rounded-full bg-primary text-primary-foreground hover:opacity-90',
outline:
'rounded-full border border-accent/50 bg-transparent text-accent hover:bg-accent/10',
ghost: 'rounded-full text-accent hover:bg-accent/10',
link: 'text-accent underline-offset-4 hover:underline',
secondary:
'rounded-full bg-secondary text-secondary-foreground hover:bg-secondary/80',
destructive:
'rounded-full bg-destructive text-destructive-foreground hover:bg-destructive/90',
},
size: {
default: 'h-12 px-6 py-3 text-xs',
sm: 'h-9 px-4 text-[10px]',
lg: 'h-14 px-8 text-sm',
icon: 'h-10 w-10 rounded-full',
},
},
defaultVariants: {
variant: 'default',
size: 'default',
},
},
)
export type ButtonVariants = VariantProps<typeof buttonVariants>

View file

@ -1,6 +1,7 @@
<script setup lang="ts">
import { useI18n } from 'vue-i18n'
import { RouterLink } from 'vue-router'
import { Button } from '@/components/ui/button'
const { t, tm, rt } = useI18n()
@ -178,18 +179,14 @@ const exteriorItems = tm('accommodation.exterior.items') as string[]
<!-- CTAs -->
<section class="border-t border-border bg-secondary/30">
<div class="mx-auto flex max-w-7xl flex-wrap items-center gap-3 px-4 py-12 lg:px-6">
<a
href="mailto:chateaudufaune@ariege.io?subject=R%C3%A9servation%20%E2%80%94%20Ch%C3%A2teau%20du%20Faune"
class="rounded-md bg-primary px-5 py-3 text-sm font-medium text-primary-foreground hover:opacity-90"
>
<Button as-child>
<a href="mailto:chateaudufaune@ariege.io?subject=R%C3%A9servation%20%E2%80%94%20Ch%C3%A2teau%20du%20Faune">
{{ t('accommodation.ctaReserve') }}
</a>
<RouterLink
to="/reservations"
class="rounded-md border border-border px-5 py-3 text-sm font-medium hover:bg-muted"
>
{{ t('accommodation.ctaBack') }}
</RouterLink>
</Button>
<Button as-child variant="outline">
<RouterLink to="/reservations"> {{ t('accommodation.ctaBack') }}</RouterLink>
</Button>
</div>
</section>
</div>

View file

@ -1,6 +1,7 @@
<script setup lang="ts">
import { useI18n } from 'vue-i18n'
import { RouterLink } from 'vue-router'
import { Button } from '@/components/ui/button'
const { t } = useI18n()
@ -90,12 +91,9 @@ const events = [
</ul>
<div class="mt-12 text-center">
<RouterLink
to="/symposium"
class="inline-block rounded-md border border-border px-5 py-3 text-sm font-medium hover:bg-muted"
>
{{ t('nav.symposium') }}
</RouterLink>
<Button as-child variant="outline">
<RouterLink to="/symposium">{{ t('nav.symposium') }} </RouterLink>
</Button>
</div>
</section>
</div>

View file

@ -1,6 +1,7 @@
<script setup lang="ts">
import { useI18n } from 'vue-i18n'
import { RouterLink } from 'vue-router'
import { Button } from '@/components/ui/button'
import cosmicStag from '@/assets/cosmic-stag.webp'
import heroLandscape from '@/assets/hero-landscape.webp'
@ -73,12 +74,9 @@ const featuredEvents = [
<p class="mt-6 text-base leading-relaxed text-foreground/90 md:text-lg">
{{ t('home.welcome.body') }}
</p>
<RouterLink
to="/concept"
class="mt-8 inline-block rounded-full border border-accent/50 px-6 py-3 text-xs uppercase tracking-[0.2em] text-accent hover:bg-accent/10"
>
{{ t('home.welcome.cta') }}
</RouterLink>
<Button as-child variant="outline" class="mt-8">
<RouterLink to="/concept">{{ t('home.welcome.cta') }}</RouterLink>
</Button>
</div>
</section>
@ -103,14 +101,11 @@ const featuredEvents = [
>
{{ t('home.bouge.body') }}
</h3>
<a
href="https://bouge.ariege.io"
target="_blank"
rel="noopener"
class="mt-10 inline-block rounded-full bg-primary px-6 py-3 text-xs uppercase tracking-[0.2em] text-primary-foreground hover:opacity-90"
>
<Button as-child class="mt-10">
<a href="https://bouge.ariege.io" target="_blank" rel="noopener">
{{ t('home.bouge.cta') }}
</a>
</Button>
</div>
</section>
@ -188,12 +183,9 @@ const featuredEvents = [
</RouterLink>
</div>
<div class="mt-10 text-center">
<RouterLink
to="/events"
class="inline-block rounded-full bg-primary px-6 py-3 text-xs uppercase tracking-[0.2em] text-primary-foreground hover:opacity-90"
>
{{ t('home.events.seeAll') }}
</RouterLink>
<Button as-child>
<RouterLink to="/events">{{ t('home.events.seeAll') }}</RouterLink>
</Button>
</div>
</div>
</section>

View file

@ -1,6 +1,7 @@
<script setup lang="ts">
import { useI18n } from 'vue-i18n'
import { RouterLink } from 'vue-router'
import { Button } from '@/components/ui/button'
const { t } = useI18n()
@ -62,18 +63,12 @@ const paths = ['exchange', 'rental', 'partial', 'funded'] as const
{{ t('longStays.symposiumTeaser.body') }}
</p>
<div class="mt-8 flex flex-wrap items-center gap-3">
<RouterLink
to="/symposium"
class="rounded-md bg-primary px-5 py-3 text-sm font-medium text-primary-foreground hover:opacity-90"
>
{{ t('longStays.ctaSymposium') }}
</RouterLink>
<a
href="mailto:chateaudufaune@ariege.io"
class="rounded-md border border-border px-5 py-3 text-sm font-medium hover:bg-muted"
>
{{ t('longStays.ctaContact') }}
</a>
<Button as-child>
<RouterLink to="/symposium">{{ t('longStays.ctaSymposium') }}</RouterLink>
</Button>
<Button as-child variant="outline">
<a href="mailto:chateaudufaune@ariege.io">{{ t('longStays.ctaContact') }}</a>
</Button>
</div>
</div>
</div>

View file

@ -1,5 +1,6 @@
<script setup lang="ts">
import { useI18n } from 'vue-i18n'
import { Button } from '@/components/ui/button'
const { t } = useI18n()
@ -47,12 +48,11 @@ const categories = ['fresh', 'pantry', 'craft'] as const
<p class="font-serif text-lg italic text-foreground/85">
{{ t('marketplace.notice') }}
</p>
<a
href="mailto:chateaudufaune@ariege.io?subject=Boutique%20%E2%80%94%20Ch%C3%A2teau%20du%20Faune"
class="mt-6 inline-block rounded-md border border-border bg-card px-5 py-3 text-sm font-medium hover:bg-muted"
>
<Button as-child variant="outline" class="mt-6">
<a href="mailto:chateaudufaune@ariege.io?subject=Boutique%20%E2%80%94%20Ch%C3%A2teau%20du%20Faune">
{{ t('marketplace.ctaContact') }}
</a>
</Button>
</div>
</section>
</div>

View file

@ -1,6 +1,7 @@
<script setup lang="ts">
import { useI18n } from 'vue-i18n'
import { RouterLink } from 'vue-router'
import { Button } from '@/components/ui/button'
const { t } = useI18n()
@ -78,18 +79,14 @@ const applyKeys = ['model', 'window', 'open'] as const
</article>
</div>
<div class="mt-10 flex flex-wrap items-center gap-3">
<a
href="mailto:chateaudufaune@ariege.io?subject=Application%20%E2%80%94%20Ch%C3%A2teau%20du%20Faune"
class="rounded-md bg-primary px-5 py-3 text-sm font-medium text-primary-foreground hover:opacity-90"
>
<Button as-child>
<a href="mailto:chateaudufaune@ariege.io?subject=Application%20%E2%80%94%20Ch%C3%A2teau%20du%20Faune">
{{ t('opportunities.ctaApply') }}
</a>
<RouterLink
to="/symposium"
class="rounded-md border border-border px-5 py-3 text-sm font-medium hover:bg-muted"
>
{{ t('opportunities.ctaSymposium') }}
</RouterLink>
</Button>
<Button as-child variant="outline">
<RouterLink to="/symposium">{{ t('opportunities.ctaSymposium') }}</RouterLink>
</Button>
</div>
</div>
</section>

View file

@ -1,6 +1,7 @@
<script setup lang="ts">
import { useI18n } from 'vue-i18n'
import { RouterLink } from 'vue-router'
import { Button } from '@/components/ui/button'
const { t } = useI18n()
@ -73,18 +74,16 @@ const kinds = ['weekend', 'retreat', 'gathering', 'residency'] as const
<p class="font-serif italic">{{ t('reservations.formPlaceholder') }}</p>
</div>
<div class="mt-6 flex flex-wrap items-center gap-3">
<a
href="mailto:chateaudufaune@ariege.io?subject=R%C3%A9servation%20%E2%80%94%20Ch%C3%A2teau%20du%20Faune"
class="rounded-md bg-primary px-5 py-3 text-sm font-medium text-primary-foreground hover:opacity-90"
>
<Button as-child>
<a href="mailto:chateaudufaune@ariege.io?subject=R%C3%A9servation%20%E2%80%94%20Ch%C3%A2teau%20du%20Faune">
{{ t('reservations.ctaEmail') }}
</a>
<RouterLink
to="/accommodation"
class="rounded-md border border-border px-5 py-3 text-sm font-medium hover:bg-muted"
>
</Button>
<Button as-child variant="outline">
<RouterLink to="/accommodation">
{{ t('reservations.ctaAccommodation') }}
</RouterLink>
</Button>
</div>
</div>

View file

@ -1,6 +1,7 @@
<script setup lang="ts">
import { useI18n } from 'vue-i18n'
import { RouterLink } from 'vue-router'
import { Button } from '@/components/ui/button'
const { t } = useI18n()
@ -102,18 +103,12 @@ const applySteps = ['stepOne', 'stepTwo', 'stepThree'] as const
</li>
</ol>
<div class="mt-10 flex flex-wrap items-center gap-3">
<RouterLink
to="/opportunities"
class="rounded-md bg-primary px-5 py-3 text-sm font-medium text-primary-foreground hover:opacity-90"
>
{{ t('symposium.ctaApply') }}
</RouterLink>
<a
href="mailto:chateaudufaune@ariege.io"
class="rounded-md border border-border px-5 py-3 text-sm font-medium hover:bg-muted"
>
{{ t('symposium.ctaContact') }}
</a>
<Button as-child>
<RouterLink to="/opportunities">{{ t('symposium.ctaApply') }}</RouterLink>
</Button>
<Button as-child variant="outline">
<a href="mailto:chateaudufaune@ariege.io">{{ t('symposium.ctaContact') }}</a>
</Button>
</div>
</div>
</section>