forked from LPK/librodrome
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:
@@ -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>
|
||||
Reference in New Issue
Block a user