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,492 @@
|
||||
<template>
|
||||
<div class="vc">
|
||||
<section class="vc-hero">
|
||||
<HomeShadoks />
|
||||
<div class="container-content vc-hero-inner">
|
||||
<h1 class="vc-h1">
|
||||
{{ heading[0] }} <span>{{ heading[1] }}</span>
|
||||
</h1>
|
||||
<p v-if="format" class="vc-lede">{{ format.text }}</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="container-content vc-bench" aria-label="La boîte à outils">
|
||||
<!-- Filters -->
|
||||
<div class="vc-filters">
|
||||
<div class="vc-chips" role="group" aria-label="Filtrer par axe">
|
||||
<button
|
||||
type="button"
|
||||
class="vc-chip"
|
||||
:class="{ 'vc-chip--on': filter === 'all' }"
|
||||
:aria-pressed="filter === 'all'"
|
||||
@click="filter = 'all'"
|
||||
>
|
||||
Tout
|
||||
</button>
|
||||
<button
|
||||
v-for="axis in axes"
|
||||
:key="axis.key"
|
||||
type="button"
|
||||
class="vc-chip"
|
||||
:class="{ 'vc-chip--on': filter === axis.key }"
|
||||
:style="{ '--hue': `var(${axis.hue})` }"
|
||||
:aria-pressed="filter === axis.key"
|
||||
@click="filter = axis.key"
|
||||
>
|
||||
<span class="vc-chip-dot" aria-hidden="true" />
|
||||
{{ axis.short }}
|
||||
</button>
|
||||
</div>
|
||||
<label class="vc-toggle">
|
||||
<input v-model="onlyService" type="checkbox">
|
||||
<span>{{ bouquet?.statuses?.service ?? 'En service' }} seulement</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<!-- Axis by axis -->
|
||||
<div v-for="axis in visibleAxes" :key="axis.key" class="vc-axis" :style="{ '--hue': `var(${axis.hue})` }">
|
||||
<header class="vc-axis-head">
|
||||
<p class="vc-axis-lever">{{ axis.lever }}</p>
|
||||
<h2 class="vc-axis-title">
|
||||
<NuxtLink :to="axis.to">{{ axis.title }}</NuxtLink>
|
||||
</h2>
|
||||
<p v-if="axis.promise" class="vc-axis-promise">{{ axis.promise }}</p>
|
||||
</header>
|
||||
|
||||
<div class="vc-grid">
|
||||
<HomeToolItem
|
||||
v-for="tool in toolsOf(axis)"
|
||||
:key="tool.label"
|
||||
:tool="tool"
|
||||
class="vc-card"
|
||||
@action="onAction"
|
||||
/>
|
||||
<button
|
||||
v-if="axis.key === 'economie' && gratewizard"
|
||||
type="button"
|
||||
class="vc-card vc-gw"
|
||||
@click="launch()"
|
||||
>
|
||||
<span class="i-lucide-sparkles h-5 w-5" aria-hidden="true" />
|
||||
<strong>{{ gratewizard.title }}</strong>
|
||||
<span>{{ gratewizard.subtitle }}</span>
|
||||
</button>
|
||||
<p v-if="!toolsOf(axis).length" class="vc-empty">
|
||||
Tout est en gestation sur cet axe —
|
||||
<button type="button" class="vc-link" @click="onlyService = false">afficher aussi les outils en gestation</button>.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- LPK bouquet, category by category -->
|
||||
<div v-if="axis.key === 'numerique' && bouquet && lpk" class="vc-bouquet">
|
||||
<p class="vc-bouquet-head">
|
||||
<strong>{{ lpk.name }}</strong> {{ lpk.fullName }} — {{ lpk.text }}
|
||||
</p>
|
||||
<ul class="vc-legend" aria-label="Légende des statuts">
|
||||
<li v-for="(label, key) in bouquet.statuses" :key="key">
|
||||
<span class="vc-dot" :class="`vc-dot--${key}`" aria-hidden="true" />{{ label }}
|
||||
</li>
|
||||
</ul>
|
||||
<div class="vc-cats">
|
||||
<div v-for="cat in bouquet.categories" v-show="stacksOf(cat).length" :key="cat.label" class="vc-cat">
|
||||
<p class="vc-cat-label">
|
||||
<span :class="`i-lucide-${cat.icon}`" class="h-3.5 w-3.5" aria-hidden="true" />
|
||||
{{ cat.label }}
|
||||
</p>
|
||||
<ul>
|
||||
<li v-for="s in stacksOf(cat)" :key="s.role + s.name">
|
||||
<span class="vc-dot" :class="`vc-dot--${s.status}`" :title="bouquet.statuses[s.status]" />
|
||||
<span class="vc-stack-role">{{ s.role }}</span>
|
||||
<a v-if="s.url && s.name" :href="s.url" target="_blank" rel="noopener" class="vc-stack-name">{{ s.name }}</a>
|
||||
<span v-else-if="s.name" class="vc-stack-name">{{ s.name }}</span>
|
||||
<span class="sr-only">— {{ bouquet.statuses[s.status] }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<NuxtLink :to="lpk.to" class="vc-more">
|
||||
{{ bouquet.title }}
|
||||
<span class="i-lucide-arrow-right h-4 w-4" aria-hidden="true" />
|
||||
</NuxtLink>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="vc-section">
|
||||
<div class="container-content">
|
||||
<HomeCitations :lines="hero.citations ?? []" class="vc-cites" />
|
||||
<HomeFormat
|
||||
:format="format"
|
||||
:book="book"
|
||||
:evenement="evenement"
|
||||
:hero="hero"
|
||||
:show-header="false"
|
||||
@open-player="$emit('open-player')"
|
||||
@open-pdf="$emit('open-pdf')"
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { AxisKey, HomeAxis, HomeStack } from '~/composables/useHomeData'
|
||||
|
||||
const emit = defineEmits<{
|
||||
'open-player': []
|
||||
'open-pdf': []
|
||||
}>()
|
||||
|
||||
const { hero, axes, lpk, bouquet, evenement, book, format, gratewizard } = await useHomeData()
|
||||
const heading = computed<string[]>(() => hero.value.heading ?? [])
|
||||
const { launch } = useGrateWizard()
|
||||
|
||||
const filter = ref<'all' | AxisKey>('all')
|
||||
const onlyService = ref(false)
|
||||
|
||||
const visibleAxes = computed(() =>
|
||||
filter.value === 'all' ? axes.value : axes.value.filter(a => a.key === filter.value))
|
||||
|
||||
function toolsOf(axis: HomeAxis) {
|
||||
return onlyService.value ? axis.items.filter(t => !t.gestation) : axis.items
|
||||
}
|
||||
|
||||
function stacksOf(cat: { items: (HomeStack | string)[] }) {
|
||||
const items = cat.items.filter((i): i is HomeStack => typeof i !== 'string')
|
||||
return onlyService.value ? items.filter(i => i.status === 'service') : items
|
||||
}
|
||||
|
||||
function onAction(id: string) {
|
||||
if (id === 'open-player') emit('open-player')
|
||||
else if (id === 'open-pdf') emit('open-pdf')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.vc-hero {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
padding: clamp(2.5rem, 6vw, 4.5rem) 1rem clamp(1.5rem, 3vw, 2.5rem);
|
||||
}
|
||||
|
||||
.vc-hero-inner {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.vc-h1 {
|
||||
margin: 0;
|
||||
max-width: 20ch;
|
||||
font-family: var(--font-display);
|
||||
font-weight: 800;
|
||||
font-size: clamp(2.2rem, 5.6vw, 4rem);
|
||||
letter-spacing: -0.035em;
|
||||
line-height: 1;
|
||||
color: hsl(var(--color-text));
|
||||
}
|
||||
|
||||
.vc-h1 span {
|
||||
font-weight: 400;
|
||||
color: hsl(var(--color-text) / 0.55);
|
||||
}
|
||||
|
||||
.vc-lede {
|
||||
margin: 1.25rem 0 0;
|
||||
max-width: 40rem;
|
||||
font-size: 1.1rem;
|
||||
line-height: 1.5;
|
||||
color: hsl(var(--color-text) / 0.75);
|
||||
}
|
||||
|
||||
.vc-bench {
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
/* Filters */
|
||||
.vc-filters {
|
||||
position: sticky;
|
||||
top: var(--header-height);
|
||||
z-index: 5;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 0.75rem;
|
||||
margin: 0 -1rem 1.5rem;
|
||||
padding: 0.75rem 1rem;
|
||||
background: hsl(var(--color-bg) / 0.92);
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.vc-chips {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.35rem;
|
||||
}
|
||||
|
||||
.vc-chip {
|
||||
--hue: var(--color-text);
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.45rem;
|
||||
min-height: 2.25rem;
|
||||
padding: 0.35rem 0.95rem;
|
||||
border-radius: 20px;
|
||||
font-family: var(--font-display);
|
||||
font-weight: 700;
|
||||
font-size: 0.9rem;
|
||||
color: hsl(var(--color-text) / 0.75);
|
||||
background: hsl(var(--color-surface));
|
||||
transition: transform 0.1s ease, background 0.2s ease;
|
||||
}
|
||||
|
||||
.vc-chip:hover { transform: translateY(-1px); }
|
||||
.vc-chip:active { transform: translateY(0); }
|
||||
|
||||
.vc-chip--on {
|
||||
color: hsl(var(--color-bg));
|
||||
background: hsl(var(--hue));
|
||||
}
|
||||
|
||||
.vc-chip-dot {
|
||||
width: 0.55rem;
|
||||
height: 0.55rem;
|
||||
border-radius: 50%;
|
||||
background: hsl(var(--hue));
|
||||
}
|
||||
|
||||
.vc-chip--on .vc-chip-dot {
|
||||
background: hsl(var(--color-bg));
|
||||
}
|
||||
|
||||
.vc-toggle {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
min-height: 2.25rem;
|
||||
font-size: 0.85rem;
|
||||
color: hsl(var(--color-text) / 0.75);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.vc-toggle input {
|
||||
width: 1.1rem;
|
||||
height: 1.1rem;
|
||||
accent-color: hsl(var(--color-primary));
|
||||
}
|
||||
|
||||
/* Axis */
|
||||
.vc-axis + .vc-axis {
|
||||
margin-top: 3rem;
|
||||
}
|
||||
|
||||
.vc-axis-head {
|
||||
display: grid;
|
||||
gap: 0.2rem;
|
||||
margin-bottom: 1rem;
|
||||
padding-left: 0.9rem;
|
||||
box-shadow: inset 3px 0 0 hsl(var(--hue));
|
||||
}
|
||||
|
||||
.vc-axis-lever {
|
||||
margin: 0;
|
||||
font-family: var(--font-mono);
|
||||
font-size: 0.72rem;
|
||||
color: hsl(var(--hue));
|
||||
}
|
||||
|
||||
.vc-axis-title {
|
||||
margin: 0;
|
||||
font-family: var(--font-display);
|
||||
font-weight: 800;
|
||||
font-size: clamp(1.4rem, 3vw, 1.9rem);
|
||||
letter-spacing: -0.025em;
|
||||
color: hsl(var(--color-text));
|
||||
}
|
||||
|
||||
.vc-axis-title a:hover { color: hsl(var(--hue)); }
|
||||
|
||||
.vc-axis-promise {
|
||||
margin: 0.2rem 0 0;
|
||||
max-width: 44rem;
|
||||
font-size: 0.98rem;
|
||||
line-height: 1.45;
|
||||
color: hsl(var(--color-text) / 0.72);
|
||||
}
|
||||
|
||||
.vc-grid {
|
||||
display: grid;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.vc-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
.vc-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
|
||||
}
|
||||
|
||||
.vc-grid > .vc-card {
|
||||
background: hsl(var(--color-surface));
|
||||
border-radius: 16px;
|
||||
padding: 1.1rem;
|
||||
}
|
||||
|
||||
.vc-gw {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 0.3rem;
|
||||
text-align: left;
|
||||
font-size: 0.85rem;
|
||||
color: hsl(var(--color-text) / 0.7);
|
||||
background: hsl(var(--hue) / 0.1);
|
||||
transition: transform 0.12s ease;
|
||||
}
|
||||
|
||||
.vc-gw:hover { transform: translateY(-3px); }
|
||||
|
||||
.vc-gw > span:first-child { color: hsl(var(--hue)); }
|
||||
|
||||
.vc-gw strong {
|
||||
font-family: var(--font-display);
|
||||
font-size: 1.05rem;
|
||||
color: hsl(var(--color-text));
|
||||
}
|
||||
|
||||
.vc-empty {
|
||||
margin: 0;
|
||||
font-size: 0.9rem;
|
||||
color: hsl(var(--color-text) / 0.6);
|
||||
}
|
||||
|
||||
.vc-link {
|
||||
color: hsl(var(--hue));
|
||||
text-decoration: underline;
|
||||
text-underline-offset: 2px;
|
||||
}
|
||||
|
||||
/* Bouquet */
|
||||
.vc-bouquet {
|
||||
margin-top: 1rem;
|
||||
padding: 1.25rem;
|
||||
border-radius: 16px;
|
||||
background: hsl(var(--hue) / 0.08);
|
||||
}
|
||||
|
||||
.vc-bouquet-head {
|
||||
margin: 0;
|
||||
font-size: 0.95rem;
|
||||
line-height: 1.45;
|
||||
color: hsl(var(--color-text) / 0.75);
|
||||
}
|
||||
|
||||
.vc-bouquet-head strong {
|
||||
font-family: var(--font-display);
|
||||
font-size: 1.1rem;
|
||||
color: hsl(var(--hue));
|
||||
}
|
||||
|
||||
.vc-legend {
|
||||
list-style: none;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.3rem 1rem;
|
||||
margin: 0.75rem 0 1rem;
|
||||
padding: 0;
|
||||
font-family: var(--font-mono);
|
||||
font-size: 0.7rem;
|
||||
color: hsl(var(--color-text) / 0.6);
|
||||
}
|
||||
|
||||
.vc-legend li {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.4rem;
|
||||
}
|
||||
|
||||
.vc-cats {
|
||||
columns: 16rem;
|
||||
column-gap: 1.5rem;
|
||||
}
|
||||
|
||||
.vc-cat {
|
||||
break-inside: avoid;
|
||||
margin-bottom: 1.1rem;
|
||||
}
|
||||
|
||||
.vc-cat-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.4rem;
|
||||
margin: 0 0 0.4rem;
|
||||
font-family: var(--font-display);
|
||||
font-weight: 700;
|
||||
font-size: 0.9rem;
|
||||
color: hsl(var(--color-text));
|
||||
}
|
||||
|
||||
.vc-cat ul {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: grid;
|
||||
gap: 0.2rem;
|
||||
}
|
||||
|
||||
.vc-cat li {
|
||||
display: grid;
|
||||
grid-template-columns: 0.5rem minmax(0, 1fr);
|
||||
column-gap: 0.45rem;
|
||||
align-items: baseline;
|
||||
font-size: 0.82rem;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.vc-dot {
|
||||
display: inline-block;
|
||||
flex-shrink: 0;
|
||||
width: 0.5rem;
|
||||
height: 0.5rem;
|
||||
border-radius: 50%;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.vc-dot--service { background: hsl(var(--hue)); }
|
||||
.vc-dot--catalogue { box-shadow: inset 0 0 0 1.5px hsl(var(--hue)); }
|
||||
.vc-dot--avenir { box-shadow: inset 0 0 0 1px hsl(var(--color-text) / 0.3); }
|
||||
|
||||
.vc-cat li > .vc-dot { grid-row: 1; }
|
||||
.vc-cat li > :not(.vc-dot) { grid-column: 2; }
|
||||
|
||||
.vc-stack-role { color: hsl(var(--color-text) / 0.6); }
|
||||
|
||||
.vc-stack-name {
|
||||
font-weight: 700;
|
||||
color: hsl(var(--color-text));
|
||||
}
|
||||
|
||||
a.vc-stack-name:hover { color: hsl(var(--hue)); }
|
||||
|
||||
.vc-more {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.35rem;
|
||||
font-family: var(--font-display);
|
||||
font-weight: 700;
|
||||
color: hsl(var(--hue));
|
||||
}
|
||||
|
||||
.vc-section {
|
||||
padding: clamp(3rem, 7vw, 5rem) 1rem 0;
|
||||
}
|
||||
|
||||
.vc-cites {
|
||||
margin-bottom: 2.5rem;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.vc-chip, .vc-gw { transition: none; }
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user