design : accueil en trois variantes (console, parcours, établi) + couleur par axe

- 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>
This commit is contained in:
Yvv
2026-09-28 10:15:46 +02:00
co-authored by Claude Opus 5.5
parent c3ada04e52
commit e9fce4be8f
29 changed files with 2901 additions and 56 deletions
+321
View File
@@ -0,0 +1,321 @@
<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>