Compare commits

..

3 commits

Author SHA1 Message Date
75470ea7f3 feat(hero): stack the title one word per line on phones
Below the sm breakpoint the wordmark renders as three centered tspan lines
(Chateau / du / Faune) with "du" at half size, instead of a thin one-line
strip. Line offsets are absolute SVG units so the gap above and below the
small line is equal. A matchMedia listener flips the layout and re-runs
the clip-box measurement so the video clip refits when it changes.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-20 23:07:23 +02:00
c2a3015fef feat(hero): widen the title wordmark
Eileen Caps is a very wide caps face, so at the old 52rem cap the title
came out shorter than the tagline above it. Raise the cap to 68rem and
size the SVG to the container width instead of a vw value, so it can't
spill past the container padding on phones.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-20 23:07:23 +02:00
10bad148ea feat(hero): trial Eileen Caps Black for the title wordmark
Swap the hero SVG clip text from Berry Rotunda to Eileen Caps Black
(D. Rakowski, freeware, fontspace.com/eileen-caps-font-f2027), self-hosted
as woff2. The face has no circumflex glyph, so the visible wordmark reads
"Chateau" unaccented rather than splicing a fallback-font â into the word;
the screen-reader h1 keeps "Château du Faune". Berry Rotunda stays as the
fallback.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-20 23:07:23 +02:00
3 changed files with 63 additions and 9 deletions

Binary file not shown.

View file

@ -16,6 +16,17 @@
font-display: swap;
}
/* Trial hero-title face: "Eileen Caps Black" (D. Rakowski, freeware via
fontspace.com/eileen-caps-font-f2027). Caps-only-ish display face with no
circumflex glyph, so the hero text drops the accent on "Château". */
@font-face {
font-family: 'Eileen Caps Black';
src: url('./assets/fonts/eileen-caps-black.woff2') format('woff2');
font-weight: normal;
font-style: normal;
font-display: swap;
}
:root {
/* Brand palette: deep forest green dominant background, cream
foreground, gold (Aboreto/calligraphic spirit) for CTAs and accents. */
@ -99,7 +110,7 @@
'Roboto', ui-sans-serif, system-ui, -apple-system, 'Segoe UI', sans-serif;
--font-display: 'Aboreto', 'Cormorant Garamond', Georgia, serif;
--font-script: 'Tangerine', 'Allura', cursive;
--font-title: 'Berry Rotunda', 'UnifrakturMaguntia', serif;
--font-title: 'Eileen Caps Black', 'Berry Rotunda', 'UnifrakturMaguntia', serif;
}
@layer base {

View file

@ -1,5 +1,5 @@
<script setup lang="ts">
import { computed, nextTick, onMounted, ref } from 'vue'
import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { RouterLink } from 'vue-router'
import { Button } from '@/components/ui/button'
@ -21,7 +21,7 @@ const tileStyle = {
const { t } = useI18n()
// The hero title is SVG text (Berry Rotunda) used as a clip over the
// The hero title is SVG text (Eileen Caps Black) used as a clip over the
// wordmark video. Measure the rendered glyph box once the font loads and fit
// the viewBox/foreignObject to it, so the wordmark fills the SVG width without
// clipping regardless of the font's metrics.
@ -32,7 +32,23 @@ const titleViewBox = computed(
() => `${titleBox.value.x} ${titleBox.value.y} ${titleBox.value.w} ${titleBox.value.h}`,
)
onMounted(async () => {
// Below the `sm` breakpoint the three words stack one per line (the wide
// caps face otherwise renders as a thin strip on phones); from sm up they sit
// on one line. Re-measure whenever the layout flips.
const titleWords = ['Chateau', 'du', 'Faune']
// Stacked layout: "du" at half size. Baseline offsets are absolute user
// units (not em, which would scale with each line's own size) chosen so the
// gap above and below the small line is the same.
const titleLineSize = [120, 60, 120]
const titleLineDy = [0, 83, 133]
const stackedQuery = '(max-width: 639px)'
const titleStacked = ref(false)
let stackedMql: MediaQueryList | null = null
const onStackedChange = (e: MediaQueryListEvent) => {
titleStacked.value = e.matches
}
async function measureTitle() {
try {
await (document as Document & { fonts?: FontFaceSet }).fonts?.ready
} catch {
@ -48,14 +64,30 @@ onMounted(async () => {
return
}
if (!bb.width) return
const padX = bb.height * 0.08
const padY = bb.height * 0.16
const lineH = bb.height / (titleStacked.value ? titleWords.length : 1)
const padX = lineH * 0.08
const padY = lineH * 0.16
titleBox.value = {
x: Math.round(bb.x - padX),
y: Math.round(bb.y - padY),
w: Math.round(bb.width + padX * 2),
h: Math.round(bb.height + padY * 2),
}
}
onMounted(() => {
stackedMql = window.matchMedia(stackedQuery)
titleStacked.value = stackedMql.matches
stackedMql.addEventListener('change', onStackedChange)
measureTitle()
})
onBeforeUnmount(() => {
stackedMql?.removeEventListener('change', onStackedChange)
})
watch(titleStacked, () => {
measureTitle()
})
const pathways = [
@ -116,7 +148,7 @@ const featuredEvents = [
(always one line); a gold drop-shadow gives the engraved edge. -->
<svg
ref="titleSvg"
class="hero-title -mt-1 block w-[min(92vw,52rem)] sm:mt-4"
class="hero-title -mt-1 block w-full max-w-[68rem] sm:mt-4"
:viewBox="titleViewBox"
aria-hidden="true"
>
@ -127,10 +159,21 @@ const featuredEvents = [
x="550"
y="122"
text-anchor="middle"
font-family="'Berry Rotunda', 'UnifrakturMaguntia', serif"
font-family="'Eileen Caps Black', 'Berry Rotunda', 'UnifrakturMaguntia', serif"
font-size="120"
>
Château du Faune
<template v-if="titleStacked">
<tspan
v-for="(word, i) in titleWords"
:key="word"
x="550"
:dy="titleLineDy[i]"
:font-size="titleLineSize[i]"
>
{{ word }}
</tspan>
</template>
<template v-else>{{ titleWords.join(' ') }}</template>
</text>
</clipPath>
</defs>