forked from LPK/librodrome
- typo : Bricolage Grotesque (titres) + Atkinson Hyperlegible (texte), Syne gardée pour le logo seul ; une seule source de polices (Bunny) - palettes : teinte propre à chaque axe dans les 4 ambiances ; pages d'axe en axis-scope, cartes sans bordure (canon design) - accueil : console des trois curseurs (tablist clavier), boîte à outils, bloc format éditorial + événement + reproduction ; ?v=a|b|c|0 - numérique : carte LPK ; bouquet rendu avec statut et légende - messages : sans bordure, deux colonnes en grand écran Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
322 lines
7.9 KiB
Vue
322 lines
7.9 KiB
Vue
<template>
|
|
<div class="vb">
|
|
<section class="vb-hero">
|
|
<HomeShadoks />
|
|
<div class="container-content vb-hero-inner">
|
|
<h1 class="vb-h1">
|
|
<span class="vb-h1-a">{{ heading[0] }}</span>
|
|
<span class="vb-h1-b">{{ heading[1] }}</span>
|
|
</h1>
|
|
<p class="vb-axes">{{ heading[3] }}</p>
|
|
<a :href="`#${steps[0]?.id}`" class="vb-start">
|
|
{{ heading[2] }}
|
|
<span class="i-lucide-arrow-down h-4 w-4" aria-hidden="true" />
|
|
</a>
|
|
</div>
|
|
</section>
|
|
|
|
<div class="container-content vb-layout">
|
|
<!-- Step rail: the onboarding is a real sequence, hence the numbers -->
|
|
<nav class="vb-rail" aria-label="Parcours">
|
|
<ol>
|
|
<li v-for="(step, i) in steps" :key="step.id">
|
|
<a
|
|
:href="`#${step.id}`"
|
|
class="vb-rail-link"
|
|
:class="{ 'vb-rail-link--on': current === step.id, 'vb-rail-link--done': i < currentIndex }"
|
|
:aria-current="current === step.id ? 'step' : undefined"
|
|
>
|
|
<span class="vb-rail-n">{{ i + 1 }}</span>
|
|
<span class="vb-rail-label">{{ step.label }}</span>
|
|
</a>
|
|
</li>
|
|
</ol>
|
|
</nav>
|
|
|
|
<div ref="stepsEl" class="vb-steps">
|
|
<section :id="steps[0]!.id" class="vb-step">
|
|
<p class="vb-step-n">1 / {{ steps.length }}</p>
|
|
<HomeCitations :lines="hero.citations ?? []" />
|
|
</section>
|
|
|
|
<section :id="steps[1]!.id" class="vb-step">
|
|
<p class="vb-step-n">2 / {{ steps.length }}</p>
|
|
<h2 class="vb-h2">{{ steps[1]!.label }}</h2>
|
|
<HomeConsole
|
|
v-model="active"
|
|
:axes="axes"
|
|
:stacks="bouquet?.inService"
|
|
:stacks-to="lpk?.to"
|
|
:stacks-label="lpk ? `En service chez ${lpk.name} :` : 'En service :'"
|
|
compact
|
|
/>
|
|
</section>
|
|
|
|
<section :id="steps[2]!.id" class="vb-step">
|
|
<p class="vb-step-n">3 / {{ steps.length }}</p>
|
|
<h2 class="vb-h2">{{ steps[2]!.label }}</h2>
|
|
<HomeToolbox
|
|
:axes="axes"
|
|
:lpk="lpk"
|
|
:bouquet="bouquet"
|
|
:gratewizard="gratewizard"
|
|
anchors
|
|
@open-player="$emit('open-player')"
|
|
@open-pdf="$emit('open-pdf')"
|
|
/>
|
|
</section>
|
|
|
|
<section :id="steps[3]!.id" class="vb-step">
|
|
<p class="vb-step-n">4 / {{ steps.length }}</p>
|
|
<HomeFormat
|
|
:format="format"
|
|
:book="book"
|
|
:evenement="evenement"
|
|
:hero="hero"
|
|
@open-player="$emit('open-player')"
|
|
@open-pdf="$emit('open-pdf')"
|
|
/>
|
|
</section>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import type { AxisKey } from '~/composables/useHomeData'
|
|
|
|
defineEmits<{
|
|
'open-player': []
|
|
'open-pdf': []
|
|
}>()
|
|
|
|
const { hero, axes, lpk, bouquet, evenement, book, format, gratewizard } = await useHomeData()
|
|
const heading = computed<string[]>(() => hero.value.heading ?? [])
|
|
const active = ref<AxisKey>('numerique')
|
|
|
|
// Step labels are existing sentences of home.yml
|
|
function tidy(s = '') {
|
|
const t = s.replace(/^[—\s]+|[—\s.]+$/g, '')
|
|
return t.charAt(0).toUpperCase() + t.slice(1)
|
|
}
|
|
|
|
const steps = computed(() => [
|
|
{ id: 'emancipation', label: tidy(hero.value.citations?.[0]) },
|
|
{ id: 'en-amont', label: tidy(hero.value.approach) },
|
|
{ id: 'les-moyens', label: tidy(heading.value[4]) },
|
|
{ id: 'le-format', label: tidy(format.value?.title) },
|
|
])
|
|
|
|
const stepsEl = ref<HTMLElement | null>(null)
|
|
const current = ref('emancipation')
|
|
const currentIndex = computed(() => steps.value.findIndex(s => s.id === current.value))
|
|
|
|
onMounted(() => {
|
|
const observer = new IntersectionObserver((entries) => {
|
|
for (const e of entries) {
|
|
if (e.isIntersecting) current.value = e.target.id
|
|
// Scrolling back up past a step: the previous one becomes current
|
|
else if (e.target.id === current.value && e.boundingClientRect.top > 0) {
|
|
const i = steps.value.findIndex(s => s.id === e.target.id)
|
|
current.value = steps.value[Math.max(0, i - 1)]!.id
|
|
}
|
|
}
|
|
}, { rootMargin: '-45% 0px -50% 0px' })
|
|
stepsEl.value?.querySelectorAll('.vb-step').forEach(el => observer.observe(el))
|
|
onUnmounted(() => observer.disconnect())
|
|
})
|
|
</script>
|
|
|
|
<style scoped>
|
|
.vb-hero {
|
|
position: relative;
|
|
overflow: hidden;
|
|
padding: clamp(3rem, 8vw, 6rem) 1rem clamp(2rem, 5vw, 3.5rem);
|
|
}
|
|
|
|
.vb-hero-inner {
|
|
position: relative;
|
|
z-index: 1;
|
|
}
|
|
|
|
.vb-h1 {
|
|
margin: 0;
|
|
display: flex;
|
|
flex-direction: column;
|
|
font-family: var(--font-display);
|
|
letter-spacing: -0.035em;
|
|
line-height: 0.98;
|
|
}
|
|
|
|
.vb-h1-a {
|
|
font-weight: 800;
|
|
font-size: clamp(2.4rem, 7vw, 5.2rem);
|
|
color: hsl(var(--color-text));
|
|
max-width: 14ch;
|
|
}
|
|
|
|
.vb-h1-b {
|
|
margin-top: 0.4rem;
|
|
font-weight: 400;
|
|
font-size: clamp(1.6rem, 4vw, 2.8rem);
|
|
color: hsl(var(--color-text) / 0.55);
|
|
}
|
|
|
|
.vb-axes {
|
|
margin: 1.5rem 0 0;
|
|
font-family: var(--font-mono);
|
|
font-size: 0.8rem;
|
|
color: hsl(var(--color-text) / 0.7);
|
|
}
|
|
|
|
.vb-start {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
min-height: 2.75rem;
|
|
margin-top: 1.75rem;
|
|
padding: 0.6rem 1.3rem;
|
|
border-radius: 20px;
|
|
font-family: var(--font-display);
|
|
font-weight: 700;
|
|
color: hsl(var(--color-bg));
|
|
background: hsl(var(--color-text));
|
|
transition: transform 0.1s ease;
|
|
}
|
|
|
|
.vb-start:hover { transform: translateY(-1px); }
|
|
.vb-start:active { transform: translateY(0); }
|
|
|
|
.vb-layout {
|
|
display: grid;
|
|
grid-template-columns: minmax(0, 1fr);
|
|
gap: 1.5rem;
|
|
padding: 0 1rem;
|
|
}
|
|
|
|
@media (min-width: 1024px) {
|
|
.vb-layout {
|
|
grid-template-columns: 15rem minmax(0, 1fr);
|
|
gap: 3rem;
|
|
}
|
|
}
|
|
|
|
/* Rail */
|
|
.vb-rail {
|
|
position: sticky;
|
|
top: var(--header-height);
|
|
z-index: 5;
|
|
align-self: start;
|
|
margin: 0 -1rem;
|
|
padding: 0.5rem 1rem;
|
|
background: hsl(var(--color-bg) / 0.92);
|
|
backdrop-filter: blur(10px);
|
|
}
|
|
|
|
.vb-rail ol {
|
|
list-style: none;
|
|
margin: 0;
|
|
padding: 0;
|
|
display: flex;
|
|
gap: 0.25rem;
|
|
overflow-x: auto;
|
|
}
|
|
|
|
@media (min-width: 1024px) {
|
|
.vb-rail {
|
|
top: calc(var(--header-height) + 2rem);
|
|
margin: 0;
|
|
padding: 0;
|
|
background: none;
|
|
backdrop-filter: none;
|
|
}
|
|
|
|
.vb-rail ol {
|
|
flex-direction: column;
|
|
gap: 0.35rem;
|
|
}
|
|
}
|
|
|
|
.vb-rail-link {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.6rem;
|
|
min-height: 2.25rem;
|
|
padding: 0.35rem 0.7rem 0.35rem 0.35rem;
|
|
border-radius: 20px;
|
|
white-space: nowrap;
|
|
font-size: 0.85rem;
|
|
color: hsl(var(--color-text) / 0.55);
|
|
transition: background 0.2s ease, color 0.2s ease;
|
|
}
|
|
|
|
@media (min-width: 1024px) {
|
|
.vb-rail-link { white-space: normal; line-height: 1.3; }
|
|
}
|
|
|
|
.vb-rail-link:hover {
|
|
background: hsl(var(--color-text) / 0.05);
|
|
}
|
|
|
|
.vb-rail-n {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
flex-shrink: 0;
|
|
width: 1.6rem;
|
|
height: 1.6rem;
|
|
border-radius: 50%;
|
|
font-family: var(--font-mono);
|
|
font-size: 0.75rem;
|
|
box-shadow: inset 0 0 0 1.5px hsl(var(--color-text) / 0.25);
|
|
}
|
|
|
|
.vb-rail-link--done .vb-rail-n {
|
|
box-shadow: none;
|
|
background: hsl(var(--color-text) / 0.12);
|
|
}
|
|
|
|
.vb-rail-link--on {
|
|
color: hsl(var(--color-text));
|
|
font-weight: 700;
|
|
}
|
|
|
|
.vb-rail-link--on .vb-rail-n {
|
|
box-shadow: none;
|
|
color: hsl(var(--color-bg));
|
|
background: hsl(var(--color-primary));
|
|
}
|
|
|
|
/* Steps */
|
|
.vb-step {
|
|
padding: clamp(2rem, 5vw, 3.5rem) 0;
|
|
scroll-margin-top: calc(var(--header-height) + 3.5rem);
|
|
}
|
|
|
|
.vb-step + .vb-step {
|
|
box-shadow: 0 -1px 0 hsl(var(--color-text) / 0.07);
|
|
}
|
|
|
|
.vb-step-n {
|
|
margin: 0 0 1rem;
|
|
font-family: var(--font-mono);
|
|
font-size: 0.72rem;
|
|
color: hsl(var(--color-primary));
|
|
}
|
|
|
|
.vb-h2 {
|
|
margin: 0 0 1.5rem;
|
|
font-family: var(--font-display);
|
|
font-weight: 800;
|
|
font-size: clamp(1.6rem, 3.6vw, 2.4rem);
|
|
letter-spacing: -0.03em;
|
|
line-height: 1.05;
|
|
color: hsl(var(--color-text));
|
|
max-width: 22ch;
|
|
}
|
|
|
|
@media (prefers-reduced-motion: reduce) {
|
|
.vb-start, .vb-rail-link { transition: none; }
|
|
}
|
|
</style>
|