design : accueil console seul, vrai §, curseurs animés, statuts, lecteur
- accueil : la console devient l'accueil unique (variantes B/C, sélecteur et ancien accueil retirés) ; curseurs qui montent de l'asservissement et dérivent seuls, un axe mis en avant à tour de rôle, arrêt au clic, immobiles en prefers-reduced-motion ; « AUTONOMIE » devant chaque axe, leviers mis en valeur, ligne des trois autonomies en couleurs - logo : § de Playfair Display (SIL OFL) vectorisé, composant partagé UiSectionSign (en-tête, page événement) + favicon.svg - UiStatusBadge (production / développement / gestation) sur l'accueil, les pages d'axe et les pages projet - liens libreDecision / SejeteralO fixés sur la prod (plus de repli localhost) - logiciel libre : bouton v1 dans la carte projet en tête de page - couverture du livre : recadrage corrigé (BookSection) - texte courant en Public Sans (zéros non barrés) - lecteur : la pastille persistante ne masque plus la navigation du BookPlayer ; lecture directe du MP3 (les .ogg n'existent pas : 404) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,379 +0,0 @@
|
||||
<template>
|
||||
<div class="axis-block">
|
||||
<!-- Header -->
|
||||
<div class="flex items-center gap-3 mb-6">
|
||||
<div class="axis-icon" :class="`axis-icon--${color}`">
|
||||
<div :class="iconClass(icon)" class="h-6 w-6" />
|
||||
</div>
|
||||
<h2 class="font-display text-2xl font-bold text-white">{{ title }}</h2>
|
||||
</div>
|
||||
|
||||
<!-- Items grid -->
|
||||
<div class="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
||||
<div
|
||||
v-for="(item, i) in items"
|
||||
:key="i"
|
||||
class="axis-item card-surface"
|
||||
:class="{ 'axis-item--gestation': item.gestation }"
|
||||
>
|
||||
<!-- Overlay link — full card clickable (z-index 0) -->
|
||||
<component
|
||||
:is="itemTag(item)"
|
||||
v-bind="itemAttrs(item)"
|
||||
class="axis-item-overlay"
|
||||
:aria-label="item.label"
|
||||
tabindex="-1"
|
||||
/>
|
||||
|
||||
<!-- Card body (position relative, z-index 1 — above overlay) -->
|
||||
<div class="axis-item-body">
|
||||
<!-- Item icon -->
|
||||
<div v-if="item.icon" class="axis-item-icon" :class="`axis-item-icon--${color}`">
|
||||
<span v-if="item.icon === 'g1'" class="axis-item-icon-g1">Ğ1</span>
|
||||
<div v-else :class="iconClass(item.icon)" class="h-5 w-5" />
|
||||
</div>
|
||||
|
||||
<h3 class="font-display text-lg font-semibold text-white mb-1">
|
||||
{{ item.label }}
|
||||
<span v-if="item.gestation" class="gestation-badge">
|
||||
<div class="i-lucide-flask-conical h-3 w-3" />
|
||||
En gestation
|
||||
</span>
|
||||
</h3>
|
||||
|
||||
<p class="text-sm text-white/60 leading-relaxed">{{ item.description }}</p>
|
||||
|
||||
<!-- Presentation inline (projet en gestation) -->
|
||||
<div v-if="item.presentation" class="axis-presentation-inline">
|
||||
<div class="axis-pi-header">
|
||||
<div class="i-lucide-sparkles h-3 w-3" />
|
||||
<span class="axis-pi-title">{{ item.presentation.title }}</span>
|
||||
</div>
|
||||
<p class="axis-pi-text">{{ item.presentation.text }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Actions zone — z-index 1, above overlay, boutons ouvrent liens externes -->
|
||||
<div v-if="item.actions?.length" class="axis-actions">
|
||||
<!-- Primary row -->
|
||||
<div class="axis-actions-row">
|
||||
<component
|
||||
:is="action.href ? 'a' : 'button'"
|
||||
v-for="action in primaryActions(item.actions)"
|
||||
:key="action.id"
|
||||
v-bind="action.href ? { href: action.href, target: '_blank', rel: 'noopener noreferrer' } : {}"
|
||||
class="axis-action-btn"
|
||||
:class="{ 'axis-action-btn--highlight': action.highlight }"
|
||||
@click.stop="!action.href && handleAction(action.id)"
|
||||
>
|
||||
<div :class="iconClass(action.icon)" class="h-3.5 w-3.5" />
|
||||
{{ action.label }}
|
||||
</component>
|
||||
</div>
|
||||
<!-- Secondary row -->
|
||||
<div v-if="secondaryActions(item.actions).length" class="axis-actions-secondary">
|
||||
<template v-for="action in secondaryActions(item.actions)" :key="action.label">
|
||||
<NuxtLink
|
||||
v-if="action.to"
|
||||
:to="action.to"
|
||||
class="axis-action-btn axis-action-btn--secondary"
|
||||
@click.stop
|
||||
>
|
||||
<div :class="iconClass(action.icon)" class="h-3.5 w-3.5" />
|
||||
{{ action.label }}
|
||||
</NuxtLink>
|
||||
<button
|
||||
v-else
|
||||
class="axis-action-btn axis-action-btn--secondary"
|
||||
@click.stop="handleAction(action.id)"
|
||||
>
|
||||
<div :class="iconClass(action.icon)" class="h-3.5 w-3.5" />
|
||||
{{ action.label }}
|
||||
</button>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
interface AxisAction {
|
||||
id: string
|
||||
label: string
|
||||
icon: string
|
||||
highlight?: boolean
|
||||
secondary?: boolean
|
||||
to?: string
|
||||
href?: string
|
||||
}
|
||||
|
||||
interface AxisItem {
|
||||
label: string
|
||||
description: string
|
||||
to?: string
|
||||
href?: string
|
||||
gestation?: boolean
|
||||
icon?: string
|
||||
actions?: AxisAction[]
|
||||
presentation?: { title: string; text: string }
|
||||
}
|
||||
|
||||
defineProps<{
|
||||
title: string
|
||||
icon: string
|
||||
color?: 'primary' | 'accent'
|
||||
items: AxisItem[]
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
'open-player': []
|
||||
'open-pdf': []
|
||||
'launch-gratewizard': []
|
||||
}>()
|
||||
|
||||
function primaryActions(actions: AxisAction[]) {
|
||||
return actions.filter(a => !a.secondary)
|
||||
}
|
||||
|
||||
function secondaryActions(actions: AxisAction[]) {
|
||||
return actions.filter(a => a.secondary)
|
||||
}
|
||||
|
||||
function handleAction(id: string) {
|
||||
if (id === 'open-player') emit('open-player')
|
||||
else if (id === 'open-pdf') emit('open-pdf')
|
||||
else if (id === 'launch-gratewizard') emit('launch-gratewizard')
|
||||
}
|
||||
|
||||
function iconClass(name: string) {
|
||||
return `i-lucide-${name}`
|
||||
}
|
||||
|
||||
function itemTag(item: AxisItem) {
|
||||
if (item.href) return 'a'
|
||||
if (item.to) return resolveComponent('NuxtLink')
|
||||
return 'div'
|
||||
}
|
||||
|
||||
function itemAttrs(item: AxisItem) {
|
||||
if (item.href) return { href: item.href, target: '_blank', rel: 'noopener noreferrer' }
|
||||
if (item.to) return { to: item.to }
|
||||
return {}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.axis-block {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.axis-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 2.75rem;
|
||||
height: 2.75rem;
|
||||
border-radius: 0.75rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.axis-icon--primary {
|
||||
background: hsl(var(--color-primary) / 0.12);
|
||||
border: 1px solid hsl(var(--color-primary) / 0.2);
|
||||
color: hsl(var(--color-primary));
|
||||
}
|
||||
|
||||
.axis-icon--accent {
|
||||
background: hsl(var(--color-accent) / 0.12);
|
||||
border: 1px solid hsl(var(--color-accent) / 0.2);
|
||||
color: hsl(var(--color-accent));
|
||||
}
|
||||
|
||||
.axis-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-radius: 0.75rem;
|
||||
border: 1px solid hsl(var(--color-text) / 0.08);
|
||||
background: hsl(var(--color-surface));
|
||||
transition: border-color 0.2s, box-shadow 0.2s;
|
||||
position: relative;
|
||||
/* overflow: visible pour laisser le tooltip sortir du cadre */
|
||||
}
|
||||
|
||||
/* Lien overlay — couvre toute la carte, AU-DESSUS du contenu */
|
||||
.axis-item-overlay {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: 1;
|
||||
border-radius: inherit;
|
||||
}
|
||||
|
||||
.axis-item:hover {
|
||||
border-color: hsl(var(--color-primary) / 0.25);
|
||||
box-shadow: 0 4px 24px hsl(var(--color-primary) / 0.06);
|
||||
}
|
||||
|
||||
.axis-item--gestation {
|
||||
opacity: 0.75;
|
||||
}
|
||||
|
||||
.axis-item--gestation:hover {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.axis-item-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 1.25rem;
|
||||
flex: 1;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.axis-item-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 2.25rem;
|
||||
height: 2.25rem;
|
||||
border-radius: 0.5rem;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.axis-item-icon--primary {
|
||||
background: hsl(var(--color-primary) / 0.18);
|
||||
color: hsl(var(--color-primary));
|
||||
box-shadow: 0 0 14px hsl(var(--color-primary) / 0.15);
|
||||
}
|
||||
|
||||
.axis-item-icon--accent {
|
||||
background: hsl(var(--color-accent) / 0.18);
|
||||
color: hsl(var(--color-accent));
|
||||
box-shadow: 0 0 14px hsl(var(--color-accent) / 0.15);
|
||||
}
|
||||
|
||||
.axis-item-icon-g1 {
|
||||
font-family: var(--font-display);
|
||||
font-weight: 700;
|
||||
font-size: 1.1rem;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.gestation-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
margin-left: 0.5rem;
|
||||
padding: 0.125rem 0.5rem;
|
||||
border-radius: 9999px;
|
||||
background: hsl(var(--color-accent) / 0.12);
|
||||
color: hsl(var(--color-accent));
|
||||
font-size: 0.7rem;
|
||||
font-weight: 500;
|
||||
font-family: var(--font-mono);
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.axis-actions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0;
|
||||
border-top: 1px solid hsl(var(--color-text) / 0.06);
|
||||
background: hsl(var(--color-bg) / 0.4);
|
||||
border-bottom-left-radius: 0.75rem;
|
||||
border-bottom-right-radius: 0.75rem;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.axis-actions-row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.375rem;
|
||||
padding: 0.75rem 1.25rem;
|
||||
}
|
||||
|
||||
.axis-actions-secondary {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.375rem;
|
||||
padding: 0.5rem 1.25rem 0.75rem;
|
||||
border-top: 1px solid hsl(var(--color-text) / 0.04);
|
||||
}
|
||||
|
||||
.axis-action-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.375rem;
|
||||
padding: 0.375rem 0.75rem;
|
||||
border-radius: 0.5rem;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 500;
|
||||
color: hsl(var(--color-text) / 0.7);
|
||||
background: hsl(var(--color-text) / 0.05);
|
||||
border: 1px solid hsl(var(--color-text) / 0.1);
|
||||
transition: all 0.2s;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.axis-action-btn:hover {
|
||||
color: hsl(var(--color-text));
|
||||
background: hsl(var(--color-primary) / 0.12);
|
||||
border-color: hsl(var(--color-primary) / 0.3);
|
||||
}
|
||||
|
||||
.axis-action-btn--highlight {
|
||||
color: hsl(var(--color-primary));
|
||||
background: hsl(var(--color-primary) / 0.12);
|
||||
border-color: hsl(var(--color-primary) / 0.25);
|
||||
}
|
||||
|
||||
.axis-action-btn--highlight:hover {
|
||||
background: hsl(var(--color-primary) / 0.2);
|
||||
border-color: hsl(var(--color-primary) / 0.4);
|
||||
}
|
||||
|
||||
.axis-action-btn--secondary {
|
||||
color: hsl(var(--color-text) / 0.45);
|
||||
background: transparent;
|
||||
border-color: hsl(var(--color-text) / 0.06);
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.axis-action-btn--secondary:hover {
|
||||
color: hsl(var(--color-accent));
|
||||
background: hsl(var(--color-accent) / 0.08);
|
||||
border-color: hsl(var(--color-accent) / 0.2);
|
||||
}
|
||||
|
||||
/* Presentation inline — projet en gestation, affiché dans la card */
|
||||
.axis-presentation-inline {
|
||||
margin-top: 0.75rem;
|
||||
padding: 0.5rem 0.75rem;
|
||||
border-radius: 8px;
|
||||
background: hsl(var(--color-accent) / 0.07);
|
||||
border: 1px solid hsl(var(--color-accent) / 0.18);
|
||||
}
|
||||
|
||||
.axis-pi-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.3rem;
|
||||
margin-bottom: 0.2rem;
|
||||
color: hsl(var(--color-accent));
|
||||
}
|
||||
|
||||
.axis-pi-title {
|
||||
font-size: 0.75rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
|
||||
.axis-pi-text {
|
||||
font-size: 0.72rem;
|
||||
color: hsl(var(--color-text) / 0.55);
|
||||
line-height: 1.45;
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
@@ -1,237 +0,0 @@
|
||||
<template>
|
||||
<section class="section-padding">
|
||||
<div class="container-content flex flex-col gap-16">
|
||||
<UiScrollReveal>
|
||||
<div id="numerique">
|
||||
<HomeAxisBlock
|
||||
v-if="axes?.numerique"
|
||||
:title="axes.numerique.title"
|
||||
:icon="axes.numerique.icon"
|
||||
color="primary"
|
||||
:items="axes.numerique.items"
|
||||
/>
|
||||
</div>
|
||||
</UiScrollReveal>
|
||||
|
||||
<UiScrollReveal :delay="100">
|
||||
<div id="economique">
|
||||
<HomeAxisBlock
|
||||
v-if="axes?.economie"
|
||||
:title="axes.economie.title"
|
||||
:icon="axes.economie.icon"
|
||||
color="accent"
|
||||
:items="axes.economie.items"
|
||||
@open-player="$emit('open-player')"
|
||||
@open-pdf="$emit('open-pdf')"
|
||||
@launch-gratewizard="launchGW"
|
||||
/>
|
||||
|
||||
<!-- Bloc GrateWizard — dans la section économique -->
|
||||
<button v-if="gw" class="gw-block mt-4" @click="launchGW">
|
||||
<div class="gw-icon">
|
||||
<div class="i-lucide-sparkles h-5 w-5" />
|
||||
</div>
|
||||
<div class="gw-text">
|
||||
<h3 class="font-display text-base font-bold text-white sm:text-lg">
|
||||
{{ gw.title }}
|
||||
</h3>
|
||||
<p class="text-white/55 text-xs sm:text-sm mt-0.5">
|
||||
{{ gw.subtitle }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="gw-arrow">
|
||||
<div class="i-lucide-arrow-up-right h-4 w-4" />
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</UiScrollReveal>
|
||||
|
||||
<UiScrollReveal :delay="200">
|
||||
<div id="citoyenne">
|
||||
<HomeAxisBlock
|
||||
v-if="axes?.politique"
|
||||
:title="axes.politique.title"
|
||||
:icon="axes.politique.icon"
|
||||
color="primary"
|
||||
:items="axes.politique.items"
|
||||
/>
|
||||
</div>
|
||||
</UiScrollReveal>
|
||||
|
||||
<!-- Bloc Événement -->
|
||||
<UiScrollReveal v-if="evenement" :delay="350">
|
||||
<NuxtLink :to="evenement.to" class="event-block">
|
||||
<div class="event-content">
|
||||
<div class="event-icon">
|
||||
<div class="i-lucide-calendar-heart h-7 w-7" />
|
||||
</div>
|
||||
<div>
|
||||
<h2 class="font-display text-2xl font-bold text-white sm:text-3xl">
|
||||
{{ evenement.title }}
|
||||
</h2>
|
||||
<p class="font-display text-xl text-white/70 sm:text-2xl">
|
||||
{{ evenement.subtitle }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<span v-if="evenement.gestation" class="event-badge">
|
||||
<div class="i-lucide-flask-conical h-3.5 w-3.5" />
|
||||
En gestation
|
||||
</span>
|
||||
</NuxtLink>
|
||||
</UiScrollReveal>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
defineEmits<{
|
||||
'open-player': []
|
||||
'open-pdf': []
|
||||
}>()
|
||||
|
||||
const { data: content } = await usePageContent('home')
|
||||
const { launch } = useGrateWizard()
|
||||
|
||||
const axes = computed(() => (content.value as any)?.axes)
|
||||
const gw = computed(() => (content.value as any)?.gratewizard)
|
||||
const evenement = computed(() => (content.value as any)?.evenement)
|
||||
|
||||
function launchGW() {
|
||||
launch()
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.event-block {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 1.5rem;
|
||||
padding: 2rem 2.5rem;
|
||||
border-radius: 1rem;
|
||||
border: 2px solid hsl(var(--color-accent) / 0.25);
|
||||
background: linear-gradient(135deg, hsl(var(--color-accent) / 0.08), hsl(var(--color-primary) / 0.04));
|
||||
transition: border-color 0.3s, box-shadow 0.3s;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.event-block:hover {
|
||||
border-color: hsl(var(--color-accent) / 0.45);
|
||||
box-shadow: 0 0 40px hsl(var(--color-accent) / 0.08);
|
||||
}
|
||||
|
||||
.event-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1.25rem;
|
||||
}
|
||||
|
||||
.event-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 3.5rem;
|
||||
height: 3.5rem;
|
||||
border-radius: 0.75rem;
|
||||
background: hsl(var(--color-accent) / 0.15);
|
||||
color: hsl(var(--color-accent));
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.event-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.3rem;
|
||||
padding: 0.25rem 0.75rem;
|
||||
border-radius: 9999px;
|
||||
background: hsl(var(--color-accent) / 0.12);
|
||||
color: hsl(var(--color-accent));
|
||||
font-size: 0.75rem;
|
||||
font-weight: 500;
|
||||
font-family: var(--font-mono);
|
||||
white-space: nowrap;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* GrateWizard block */
|
||||
.gw-block {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
width: 100%;
|
||||
padding: 1.25rem 1.5rem;
|
||||
border-radius: 0.75rem;
|
||||
border: none;
|
||||
background: linear-gradient(135deg, hsl(280 50% 20% / 0.35), hsl(260 40% 15% / 0.25));
|
||||
box-shadow: 0 0 40px hsl(280 60% 50% / 0.06), inset 0 1px 0 hsl(280 60% 70% / 0.08);
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
text-align: left;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.gw-block:hover {
|
||||
background: linear-gradient(135deg, hsl(280 50% 24% / 0.45), hsl(260 40% 18% / 0.35));
|
||||
box-shadow: 0 0 60px hsl(280 60% 50% / 0.12), inset 0 1px 0 hsl(280 60% 70% / 0.12);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.gw-block:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.gw-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
border-radius: 0.625rem;
|
||||
background: hsl(280 60% 55% / 0.18);
|
||||
color: hsl(280 60% 72%);
|
||||
flex-shrink: 0;
|
||||
box-shadow: 0 0 20px hsl(280 60% 50% / 0.15);
|
||||
}
|
||||
|
||||
.gw-text {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.gw-arrow {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
border-radius: 50%;
|
||||
background: hsl(280 60% 55% / 0.1);
|
||||
color: hsl(280 60% 65%);
|
||||
flex-shrink: 0;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.gw-block:hover .gw-arrow {
|
||||
background: hsl(280 60% 55% / 0.2);
|
||||
color: hsl(280 60% 80%);
|
||||
transform: translate(2px, -2px);
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.event-block {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.gw-block {
|
||||
padding: 1.25rem;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.gw-arrow {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -89,9 +89,9 @@ const titleLine2 = computed(() => titleParts.value[1])
|
||||
.book-cover-3d {
|
||||
width: 100%;
|
||||
max-width: 360px;
|
||||
aspect-ratio: 3 / 4;
|
||||
background-size: 200% auto;
|
||||
background-position: right center;
|
||||
aspect-ratio: 1756 / 2480;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
border-radius: 0.75rem;
|
||||
overflow: hidden;
|
||||
|
||||
@@ -1,120 +0,0 @@
|
||||
<template>
|
||||
<section class="relative overflow-hidden section-padding hero-section">
|
||||
<!-- Background gradient -->
|
||||
<div class="absolute inset-0 bg-gradient-to-b from-primary/10 via-transparent to-surface-bg" />
|
||||
<div class="absolute inset-0 bg-[radial-gradient(ellipse_at_top,hsl(12_76%_48%/0.12),transparent_70%)]" />
|
||||
|
||||
<!-- Shadok bird decoration -->
|
||||
<svg class="shadok-bird" viewBox="0 0 180 260" fill="none" aria-hidden="true">
|
||||
<ellipse cx="90" cy="100" rx="45" ry="40" fill="currentColor" opacity="0.85"/>
|
||||
<circle cx="130" cy="60" r="22" fill="currentColor" opacity="0.8"/>
|
||||
<path d="M110 85 Q125 70 128 63" stroke="currentColor" stroke-width="8" stroke-linecap="round" opacity="0.7" fill="none"/>
|
||||
<circle cx="136" cy="55" r="5" fill="currentColor" opacity="0.3"/>
|
||||
<circle cx="137" cy="54" r="2" fill="currentColor" opacity="0.6"/>
|
||||
<polygon points="150,58 175,50 152,65" fill="currentColor" opacity="0.6"/>
|
||||
<line x1="75" y1="138" x2="60" y2="230" stroke="currentColor" stroke-width="3.5" stroke-linecap="round" opacity="0.6"/>
|
||||
<line x1="105" y1="138" x2="115" y2="230" stroke="currentColor" stroke-width="3.5" stroke-linecap="round" opacity="0.6"/>
|
||||
<circle cx="66" cy="190" r="4" fill="currentColor" opacity="0.4"/>
|
||||
<circle cx="111" cy="190" r="4" fill="currentColor" opacity="0.4"/>
|
||||
<path d="M60 230 L45 233 M60 230 L55 236 M60 230 L65 235" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" opacity="0.5"/>
|
||||
<path d="M115 230 L100 233 M115 230 L110 236 M115 230 L120 235" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" opacity="0.5"/>
|
||||
<path d="M48 95 Q20 80 15 65" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.5" fill="none"/>
|
||||
<path d="M48 100 Q22 92 10 85" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.4" fill="none"/>
|
||||
<path d="M48 105 Q25 102 12 100" stroke="currentColor" stroke-width="3" stroke-linecap="round" opacity="0.3" fill="none"/>
|
||||
</svg>
|
||||
|
||||
<!-- Shadok boulanger -->
|
||||
<svg class="shadok-boulanger" viewBox="0 0 240 300" fill="none" aria-hidden="true">
|
||||
<ellipse cx="120" cy="155" rx="40" ry="48" fill="currentColor" opacity="0.85"/>
|
||||
<circle cx="120" cy="92" r="25" fill="currentColor" opacity="0.8"/>
|
||||
<ellipse cx="120" cy="68" rx="18" ry="22" fill="currentColor" opacity="0.35"/>
|
||||
<rect x="105" y="78" width="30" height="5" rx="1" fill="currentColor" opacity="0.4"/>
|
||||
<path d="M110 88 Q114 84 118 88" stroke="currentColor" stroke-width="2" stroke-linecap="round" fill="none" opacity="0.4"/>
|
||||
<path d="M124 88 Q128 84 132 88" stroke="currentColor" stroke-width="2" stroke-linecap="round" fill="none" opacity="0.4"/>
|
||||
<path d="M112 102 Q120 108 128 102" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" fill="none" opacity="0.35"/>
|
||||
<line x1="160" y1="145" x2="190" y2="135" stroke="currentColor" stroke-width="3.5" stroke-linecap="round" opacity="0.6"/>
|
||||
<rect x="185" y="125" width="40" height="10" rx="5" fill="currentColor" opacity="0.4" transform="rotate(-15 205 130)"/>
|
||||
<line x1="80" y1="148" x2="55" y2="175" stroke="currentColor" stroke-width="3.5" stroke-linecap="round" opacity="0.6"/>
|
||||
<line x1="105" y1="200" x2="95" y2="258" stroke="currentColor" stroke-width="3.5" stroke-linecap="round" opacity="0.6"/>
|
||||
<line x1="135" y1="200" x2="145" y2="258" stroke="currentColor" stroke-width="3.5" stroke-linecap="round" opacity="0.6"/>
|
||||
<rect x="30" y="180" width="50" height="40" rx="4" fill="currentColor" opacity="0.25"/>
|
||||
<rect x="35" y="185" width="40" height="20" rx="2" fill="currentColor" opacity="0.15"/>
|
||||
<ellipse cx="55" cy="195" rx="12" ry="6" fill="currentColor" opacity="0.12"/>
|
||||
</svg>
|
||||
|
||||
<!-- Content -->
|
||||
<div class="container-content relative z-10 px-4">
|
||||
<div class="mx-auto max-w-2xl">
|
||||
<HomeTypewriterText
|
||||
v-if="hero"
|
||||
:hero="hero"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const { data: content } = await usePageContent('home')
|
||||
|
||||
const hero = computed(() => {
|
||||
const raw = (content.value as any)?.hero
|
||||
if (!raw) return null
|
||||
return {
|
||||
heading: Array.isArray(raw.heading) ? raw.heading : [],
|
||||
citations: Array.isArray(raw.citations) ? raw.citations : [],
|
||||
approach: raw.approach || '',
|
||||
axes: Array.isArray(raw.axes) ? raw.axes : [],
|
||||
audience: raw.audience || '',
|
||||
addressees: Array.isArray(raw.addressees) ? raw.addressees : [],
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.hero-section {
|
||||
min-height: 70vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.shadok-bird {
|
||||
position: absolute;
|
||||
right: 4%;
|
||||
top: 12%;
|
||||
width: clamp(110px, 15vw, 220px);
|
||||
opacity: 0.3;
|
||||
pointer-events: none;
|
||||
color: hsl(var(--color-primary));
|
||||
animation: shadok-float 8s ease-in-out infinite;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
@keyframes shadok-float {
|
||||
0%, 100% { transform: translateY(0); }
|
||||
50% { transform: translateY(-12px); }
|
||||
}
|
||||
|
||||
.shadok-boulanger {
|
||||
position: absolute;
|
||||
left: 3%;
|
||||
bottom: 8%;
|
||||
width: clamp(100px, 13vw, 190px);
|
||||
opacity: 0.25;
|
||||
pointer-events: none;
|
||||
color: hsl(var(--color-accent));
|
||||
animation: shadok-float-boulanger 9s ease-in-out infinite;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
@keyframes shadok-float-boulanger {
|
||||
0%, 100% { transform: translateY(0); }
|
||||
50% { transform: translateY(-8px); }
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.shadok-bird { display: none; }
|
||||
.shadok-boulanger { display: none; }
|
||||
}
|
||||
</style>
|
||||
@@ -1,17 +1,25 @@
|
||||
<template>
|
||||
<div class="console" :class="{ 'console--ready': ready, 'console--compact': compact }">
|
||||
<div
|
||||
ref="root"
|
||||
class="console"
|
||||
:class="{ 'console--compact': compact, 'console--still': reduced }"
|
||||
@pointerenter="hovered = true"
|
||||
@pointerleave="hovered = false"
|
||||
@focusin="hovered = true"
|
||||
@focusout="hovered = false"
|
||||
>
|
||||
<div class="console-board">
|
||||
<!-- Scale: from « je subis » to « je m'affranchis » (evenement.yml) -->
|
||||
<!-- Scale, read bottom to top: from asservissement to émancipation -->
|
||||
<div class="console-scale" aria-hidden="true">
|
||||
<span>je m'affranchis</span>
|
||||
<span class="console-ticks"><i v-for="n in 5" :key="n" /></span>
|
||||
<span>je subis</span>
|
||||
<span class="console-scale-bottom">{{ bottomLabel }}</span>
|
||||
<span class="console-scale-line" />
|
||||
<span class="console-scale-top">{{ topLabel }}</span>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="console-faders"
|
||||
role="tablist"
|
||||
aria-label="Les trois axes de l'autonomie"
|
||||
:aria-label="`Les trois axes — ${bottomLabel} ou ${topLabel}`"
|
||||
@keydown="onKeydown"
|
||||
>
|
||||
<button
|
||||
@@ -23,17 +31,20 @@
|
||||
role="tab"
|
||||
class="fader"
|
||||
:class="{ 'fader--active': axis.key === active }"
|
||||
:style="{ '--hue': `var(${axis.hue})`, '--level': level(axis.key), '--i': i }"
|
||||
:style="{ '--hue': `var(${axis.hue})`, '--level': levels[axis.key] ?? 0, '--i': i }"
|
||||
:aria-selected="axis.key === active"
|
||||
:aria-controls="`panel-${uid}`"
|
||||
:tabindex="axis.key === active ? 0 : -1"
|
||||
@click="select(axis.key)"
|
||||
@click="choose(axis.key)"
|
||||
>
|
||||
<span class="fader-track" aria-hidden="true">
|
||||
<span class="fader-fill" />
|
||||
<span class="fader-cap" />
|
||||
</span>
|
||||
<span class="fader-name">{{ axis.short }}</span>
|
||||
<span class="fader-label">
|
||||
<span class="fader-prefix">{{ prefix }}</span>
|
||||
<span class="fader-name">{{ axis.short }}</span>
|
||||
</span>
|
||||
<span class="fader-lever">{{ axis.lever }}</span>
|
||||
</button>
|
||||
</div>
|
||||
@@ -61,7 +72,7 @@
|
||||
<NuxtLink v-if="tool.to" :to="tool.to" class="panel-tool">
|
||||
<span class="panel-tool-name">{{ tool.label }}</span>
|
||||
<span v-if="tool.presentation" class="panel-tool-app">{{ tool.presentation.title }}</span>
|
||||
<span v-if="tool.gestation" class="panel-tool-state">en gestation</span>
|
||||
<span v-if="tool.status || tool.gestation" class="panel-tool-state">{{ (tool.status || 'En gestation').toLowerCase() }}</span>
|
||||
</NuxtLink>
|
||||
</li>
|
||||
</ul>
|
||||
@@ -89,6 +100,9 @@ import type { AxisKey, HomeAxis, HomeStack } from '~/composables/useHomeData'
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
axes: HomeAxis[]
|
||||
prefix?: string
|
||||
topLabel?: string
|
||||
bottomLabel?: string
|
||||
upstreamLabel?: string
|
||||
stacks?: HomeStack[]
|
||||
stacksLabel?: string
|
||||
@@ -96,6 +110,9 @@ const props = withDefaults(defineProps<{
|
||||
maxStacks?: number
|
||||
compact?: boolean
|
||||
}>(), {
|
||||
prefix: 'Autonomie',
|
||||
topLabel: 'Émancipation',
|
||||
bottomLabel: 'Asservissement',
|
||||
maxStacks: 8,
|
||||
upstreamLabel: 'en amont',
|
||||
stacksLabel: 'En service',
|
||||
@@ -104,21 +121,58 @@ const props = withDefaults(defineProps<{
|
||||
|
||||
const active = defineModel<AxisKey>({ default: 'numerique' })
|
||||
const uid = useId()
|
||||
const root = ref<HTMLElement | null>(null)
|
||||
const tabs = ref<HTMLButtonElement[]>([])
|
||||
const ready = ref(false)
|
||||
|
||||
const current = computed(() => props.axes.find(a => a.key === active.value) ?? props.axes[0])
|
||||
|
||||
// Faders rise from « je subis » once mounted; the selected one is pushed to the top.
|
||||
function level(key: AxisKey) {
|
||||
if (!ready.value) return 0
|
||||
return key === active.value ? 0.94 : 0.3
|
||||
// ── Motion ──────────────────────────────────────────────────────────────
|
||||
// The cursors move on their own, slowly: each one drifts, and every few
|
||||
// beats another axis is pushed up towards émancipation. Visitors watch it;
|
||||
// they can still pick an axis (click, arrows), which stops the rotation.
|
||||
// SSR renders every cursor at the bottom, so the first move is the rise.
|
||||
const BEAT_MS = 2600
|
||||
const BEATS_PER_AXIS = 3
|
||||
|
||||
const levels = reactive<Partial<Record<AxisKey, number>>>({})
|
||||
const reduced = ref(false)
|
||||
const hovered = ref(false)
|
||||
const visible = ref(true)
|
||||
const chosen = ref(false)
|
||||
let beat = 0
|
||||
let timer: ReturnType<typeof setInterval> | undefined
|
||||
|
||||
function place() {
|
||||
props.axes.forEach((axis, i) => {
|
||||
const isActive = axis.key === active.value
|
||||
if (reduced.value) {
|
||||
levels[axis.key] = isActive ? 0.94 : 0.3
|
||||
return
|
||||
}
|
||||
const wave = Math.sin(beat * 0.9 + i * 2.1)
|
||||
levels[axis.key] = isActive ? 0.87 + 0.06 * wave : 0.3 + 0.14 * wave
|
||||
})
|
||||
}
|
||||
|
||||
function select(key: AxisKey) {
|
||||
function advance() {
|
||||
const idx = props.axes.findIndex(a => a.key === active.value)
|
||||
active.value = props.axes[(idx + 1) % props.axes.length]!.key
|
||||
}
|
||||
|
||||
function onBeat() {
|
||||
if (!visible.value) return
|
||||
beat++
|
||||
if (!chosen.value && !hovered.value && beat % BEATS_PER_AXIS === 0) advance()
|
||||
place()
|
||||
}
|
||||
|
||||
function choose(key: AxisKey) {
|
||||
chosen.value = true
|
||||
active.value = key
|
||||
}
|
||||
|
||||
watch(active, place)
|
||||
|
||||
function onKeydown(e: KeyboardEvent) {
|
||||
const keys = ['ArrowRight', 'ArrowLeft', 'ArrowUp', 'ArrowDown', 'Home', 'End']
|
||||
if (!keys.includes(e.key)) return
|
||||
@@ -130,12 +184,23 @@ function onKeydown(e: KeyboardEvent) {
|
||||
if (e.key === 'ArrowLeft' || e.key === 'ArrowDown') next = idx === 0 ? last : idx - 1
|
||||
if (e.key === 'Home') next = 0
|
||||
if (e.key === 'End') next = last
|
||||
active.value = props.axes[next]!.key
|
||||
choose(props.axes[next]!.key)
|
||||
nextTick(() => tabs.value[next]?.focus())
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
requestAnimationFrame(() => { ready.value = true })
|
||||
reduced.value = window.matchMedia('(prefers-reduced-motion: reduce)').matches
|
||||
requestAnimationFrame(place)
|
||||
if (reduced.value) return
|
||||
|
||||
timer = setInterval(onBeat, BEAT_MS)
|
||||
const io = new IntersectionObserver(([entry]) => { visible.value = !!entry?.isIntersecting })
|
||||
if (root.value) io.observe(root.value)
|
||||
|
||||
onUnmounted(() => {
|
||||
clearInterval(timer)
|
||||
io.disconnect()
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -155,7 +220,7 @@ onMounted(() => {
|
||||
.console-board {
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr;
|
||||
gap: 0.75rem;
|
||||
gap: 0.9rem;
|
||||
padding: 1.25rem 1.25rem 1rem;
|
||||
border-radius: 16px;
|
||||
background: hsl(var(--color-surface));
|
||||
@@ -164,32 +229,34 @@ onMounted(() => {
|
||||
0 18px 40px -24px hsl(var(--color-text) / 0.35);
|
||||
}
|
||||
|
||||
/* Vertical scale, read from bottom to top */
|
||||
.console-scale {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
align-items: flex-end;
|
||||
gap: 0.5rem;
|
||||
height: var(--track-h);
|
||||
font-family: var(--font-mono);
|
||||
font-size: 0.62rem;
|
||||
line-height: 1.2;
|
||||
color: hsl(var(--color-text) / 0.45);
|
||||
text-align: right;
|
||||
writing-mode: vertical-rl;
|
||||
transform: rotate(180deg);
|
||||
font-family: var(--font-display);
|
||||
font-size: 0.72rem;
|
||||
letter-spacing: 0.02em;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.console-ticks {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: calc(var(--track-h) / 9);
|
||||
align-items: flex-end;
|
||||
.console-scale-bottom {
|
||||
color: hsl(var(--color-text) / 0.45);
|
||||
}
|
||||
|
||||
.console-ticks i {
|
||||
display: block;
|
||||
width: 0.5rem;
|
||||
height: 1px;
|
||||
background: hsl(var(--color-text) / 0.2);
|
||||
.console-scale-top {
|
||||
font-weight: 800;
|
||||
color: hsl(var(--color-text));
|
||||
}
|
||||
|
||||
.console-scale-line {
|
||||
flex: 1;
|
||||
width: 1px;
|
||||
background: linear-gradient(to bottom, hsl(var(--color-text) / 0.12), hsl(var(--color-text) / 0.45));
|
||||
}
|
||||
|
||||
.console-faders {
|
||||
@@ -203,15 +270,13 @@ onMounted(() => {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
gap: 0.55rem;
|
||||
padding: 0 0.25rem 0.25rem;
|
||||
border-radius: 12px;
|
||||
cursor: pointer;
|
||||
transition: background 0.2s ease;
|
||||
}
|
||||
|
||||
.fader:hover {
|
||||
background: hsl(var(--hue) / 0.06);
|
||||
/* --level is a registered property (main.css): the cursors glide */
|
||||
transition: --level 2.6s cubic-bezier(0.45, 0.05, 0.55, 0.95);
|
||||
transition-delay: calc(var(--i) * 160ms);
|
||||
}
|
||||
|
||||
.fader:focus-visible {
|
||||
@@ -233,6 +298,7 @@ onMounted(() => {
|
||||
height: calc(var(--level) * (var(--track-h) - var(--cap-h)) + var(--cap-h) / 2);
|
||||
border-radius: inherit;
|
||||
background: hsl(var(--hue) / 0.45);
|
||||
transition: background 0.6s ease;
|
||||
}
|
||||
|
||||
.fader--active .fader-fill {
|
||||
@@ -267,25 +333,28 @@ onMounted(() => {
|
||||
background: hsl(var(--hue));
|
||||
}
|
||||
|
||||
.console--ready .fader-fill,
|
||||
.console--ready .fader-cap {
|
||||
transition:
|
||||
height 0.7s var(--ease-out-expo),
|
||||
transform 0.7s var(--ease-out-expo);
|
||||
.fader-label {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
/* Orchestrated first rise: one fader after the other */
|
||||
.console--ready .fader-fill,
|
||||
.console--ready .fader-cap {
|
||||
transition-delay: calc(var(--i) * 90ms);
|
||||
.fader-prefix {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 0.6rem;
|
||||
letter-spacing: 0.14em;
|
||||
text-transform: uppercase;
|
||||
color: hsl(var(--color-text) / 0.5);
|
||||
}
|
||||
|
||||
.fader-name {
|
||||
font-family: var(--font-display);
|
||||
font-weight: 700;
|
||||
font-size: 0.95rem;
|
||||
font-weight: 800;
|
||||
font-size: 1.05rem;
|
||||
letter-spacing: -0.01em;
|
||||
color: hsl(var(--color-text) / 0.6);
|
||||
color: hsl(var(--color-text) / 0.65);
|
||||
transition: color 0.6s ease;
|
||||
}
|
||||
|
||||
.fader--active .fader-name {
|
||||
@@ -293,11 +362,21 @@ onMounted(() => {
|
||||
}
|
||||
|
||||
.fader-lever {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 0.64rem;
|
||||
line-height: 1.25;
|
||||
padding: 0.2rem 0.55rem;
|
||||
border-radius: 20px;
|
||||
font-family: var(--font-display);
|
||||
font-weight: 600;
|
||||
font-size: 0.8rem;
|
||||
line-height: 1.2;
|
||||
text-align: center;
|
||||
color: hsl(var(--color-text) / 0.45);
|
||||
color: hsl(var(--color-text) / 0.8);
|
||||
background: hsl(var(--hue) / 0.1);
|
||||
transition: background 0.6s ease, color 0.6s ease;
|
||||
}
|
||||
|
||||
.fader--active .fader-lever {
|
||||
color: hsl(var(--color-bg));
|
||||
background: hsl(var(--hue));
|
||||
}
|
||||
|
||||
/* ── Panel ── */
|
||||
@@ -455,15 +534,19 @@ onMounted(() => {
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.console { --track-h: 8.5rem; }
|
||||
.console-board { padding: 1rem 0.75rem 0.75rem; gap: 0.4rem; }
|
||||
.console-scale { white-space: normal; max-width: 3.4rem; font-size: 0.56rem; }
|
||||
.console-board { padding: 1rem 0.6rem 0.75rem; gap: 0.35rem; }
|
||||
.console-scale { font-size: 0.62rem; }
|
||||
.fader-cap { width: 2.2rem; margin-left: -1.1rem; }
|
||||
.fader-name { font-size: 0.85rem; }
|
||||
.fader-name { font-size: 0.88rem; }
|
||||
.fader-prefix { font-size: 0.52rem; letter-spacing: 0.08em; }
|
||||
.fader-lever { font-size: 0.68rem; padding: 0.2rem 0.4rem; }
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.console--ready .fader-fill,
|
||||
.console--ready .fader-cap,
|
||||
.fader,
|
||||
.fader-fill,
|
||||
.fader-name,
|
||||
.fader-lever,
|
||||
.panel-enter-active,
|
||||
.panel-leave-active {
|
||||
transition: none;
|
||||
|
||||
@@ -9,7 +9,12 @@
|
||||
<span class="va-h1-a">{{ heading[0] }}</span>
|
||||
<span class="va-h1-b">{{ heading[1] }}</span>
|
||||
</h1>
|
||||
<p class="va-axes">{{ heading[3] }}</p>
|
||||
<p class="va-axes">
|
||||
<template v-for="(seg, i) in axesLine" :key="i">
|
||||
<span v-if="seg.hue" class="va-axes-word" :style="{ '--hue': `var(${seg.hue})` }">{{ seg.text }}</span>
|
||||
<template v-else>{{ seg.text }}</template>
|
||||
</template>
|
||||
</p>
|
||||
<p class="va-means">{{ heading[4] }}</p>
|
||||
<p v-if="hero.approach" class="va-approach">{{ hero.approach }}</p>
|
||||
</div>
|
||||
@@ -19,6 +24,9 @@
|
||||
<HomeConsole
|
||||
v-model="active"
|
||||
:axes="axes"
|
||||
:prefix="consoleLabels?.prefix"
|
||||
:top-label="consoleLabels?.top"
|
||||
:bottom-label="consoleLabels?.bottom"
|
||||
:stacks="bouquet?.inService"
|
||||
:stacks-to="lpk?.to"
|
||||
:stacks-label="lpk ? `En service chez ${lpk.name} :` : 'En service :'"
|
||||
@@ -51,7 +59,7 @@
|
||||
<!-- La boîte à outils -->
|
||||
<section class="va-section va-section--tools">
|
||||
<div class="container-content">
|
||||
<h2 class="va-h2">La boîte à outils</h2>
|
||||
<h2 class="va-h2">{{ toolboxTitle }}</h2>
|
||||
<HomeToolbox
|
||||
:axes="axes"
|
||||
:lpk="lpk"
|
||||
@@ -74,8 +82,20 @@ defineEmits<{
|
||||
'open-pdf': []
|
||||
}>()
|
||||
|
||||
const { hero, axes, lpk, bouquet, evenement, book, format, gratewizard } = await useHomeData()
|
||||
const { hero, axes, lpk, bouquet, evenement, book, format, gratewizard, consoleLabels, toolboxTitle } = await useHomeData()
|
||||
const heading = computed<string[]>(() => hero.value.heading ?? [])
|
||||
|
||||
// « Autonomie numérique, économique, citoyenne. » — each axis word in its own hue
|
||||
const AXIS_WORDS: Record<string, string> = {
|
||||
numérique: '--axis-num',
|
||||
économique: '--axis-eco',
|
||||
citoyenne: '--axis-cit',
|
||||
}
|
||||
const axesLine = computed(() =>
|
||||
(heading.value[3] ?? '')
|
||||
.split(/(numérique|économique|citoyenne)/)
|
||||
.filter(Boolean)
|
||||
.map(text => ({ text, hue: AXIS_WORDS[text] })))
|
||||
const active = ref<AxisKey>('numerique')
|
||||
</script>
|
||||
|
||||
@@ -126,9 +146,16 @@ const active = ref<AxisKey>('numerique')
|
||||
|
||||
.va-axes {
|
||||
margin: 1.75rem 0 0;
|
||||
font-family: var(--font-mono);
|
||||
font-size: 0.8rem;
|
||||
color: hsl(var(--color-text) / 0.7);
|
||||
font-family: var(--font-display);
|
||||
font-weight: 800;
|
||||
font-size: clamp(1.3rem, 2.6vw, 1.85rem);
|
||||
letter-spacing: -0.025em;
|
||||
line-height: 1.15;
|
||||
color: hsl(var(--color-text));
|
||||
}
|
||||
|
||||
.va-axes-word {
|
||||
color: hsl(var(--hue));
|
||||
}
|
||||
|
||||
.va-means {
|
||||
@@ -9,7 +9,7 @@
|
||||
<component :is="tool.to ? NuxtLink : 'span'" :to="tool.to" class="tool-name">
|
||||
{{ tool.label }}
|
||||
</component>
|
||||
<span v-if="tool.gestation" class="tool-state">en gestation</span>
|
||||
<UiStatusBadge class="tool-status" :status="tool.status" :gestation="tool.gestation" :since="tool.since" />
|
||||
<p class="tool-desc">{{ tool.description }}</p>
|
||||
<p v-if="tool.presentation" class="tool-app">
|
||||
<strong>{{ tool.presentation.title }}</strong> — {{ tool.presentation.text }}
|
||||
@@ -50,6 +50,9 @@
|
||||
{{ action.label }}
|
||||
</button>
|
||||
</template>
|
||||
<span v-for="action in tool.actions.filter(a => a.note)" :key="`note-${action.label}`" class="tool-note">
|
||||
{{ action.note }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -113,16 +116,15 @@ a.tool-name:hover {
|
||||
color: hsl(var(--hue));
|
||||
}
|
||||
|
||||
.tool-state {
|
||||
.tool-status {
|
||||
margin-left: 0.4rem;
|
||||
font-family: var(--font-mono);
|
||||
font-size: 0.64rem;
|
||||
color: hsl(var(--color-text) / 0.5);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.tool-state::before {
|
||||
content: '◌ ';
|
||||
.tool-note {
|
||||
align-self: center;
|
||||
font-family: var(--font-mono);
|
||||
font-size: 0.66rem;
|
||||
color: hsl(var(--axis-cit));
|
||||
}
|
||||
|
||||
.tool-desc {
|
||||
|
||||
@@ -23,14 +23,14 @@
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<!-- Numérique : the stacks LPK serves -->
|
||||
<!-- Numérique : the stacks LaPlanK serves -->
|
||||
<NuxtLink
|
||||
v-if="axis.key === 'numerique' && lpk && bouquet"
|
||||
:to="lpk.to"
|
||||
class="toolbox-lpk"
|
||||
>
|
||||
<span class="toolbox-lpk-head">
|
||||
<strong>{{ lpk.name }}</strong> <span>{{ lpk.fullName }} — {{ lpk.text }}</span>
|
||||
<strong>{{ lpk.name }}</strong> <span>{{ lpk.text }}</span>
|
||||
</span>
|
||||
<span class="toolbox-lpk-stacks">
|
||||
<span v-for="s in bouquet.inService" :key="s.name" class="toolbox-lpk-stack">{{ s.name }}</span>
|
||||
@@ -65,7 +65,7 @@ import type { HomeAxis } from '~/composables/useHomeData'
|
||||
|
||||
withDefaults(defineProps<{
|
||||
axes: HomeAxis[]
|
||||
lpk?: { name: string, fullName: string, text: string, to: string }
|
||||
lpk?: { name: string, text: string, to: string }
|
||||
bouquet?: { title: string, inService: { name?: string }[], stacks: unknown[] } | null
|
||||
gratewizard?: { title: string, subtitle: string }
|
||||
showPromise?: boolean
|
||||
@@ -153,7 +153,7 @@ function onAction(id: string) {
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
/* LPK */
|
||||
/* LaPlanK */
|
||||
.toolbox-lpk {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@@ -1,492 +0,0 @@
|
||||
<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>
|
||||
@@ -1,321 +0,0 @@
|
||||
<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>
|
||||
@@ -1,71 +0,0 @@
|
||||
<template>
|
||||
<nav class="vswitch" aria-label="Variantes de l'accueil">
|
||||
<span class="vswitch-label">Variante</span>
|
||||
<NuxtLink
|
||||
v-for="v in variants"
|
||||
:key="v.id"
|
||||
:to="{ path: '/', query: { v: v.id } }"
|
||||
class="vswitch-item"
|
||||
:class="{ 'vswitch-item--on': current === v.id }"
|
||||
:aria-current="current === v.id ? 'page' : undefined"
|
||||
>
|
||||
{{ v.label }}
|
||||
</NuxtLink>
|
||||
</nav>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
defineProps<{
|
||||
current: string
|
||||
}>()
|
||||
|
||||
const variants = [
|
||||
{ id: 'a', label: 'A · Console' },
|
||||
{ id: 'b', label: 'B · Parcours' },
|
||||
{ id: 'c', label: 'C · Établi' },
|
||||
{ id: '0', label: 'Actuelle' },
|
||||
]
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.vswitch {
|
||||
position: fixed;
|
||||
left: 50%;
|
||||
bottom: 1rem;
|
||||
z-index: 60;
|
||||
transform: translateX(-50%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
padding: 0.3rem;
|
||||
border-radius: 20px;
|
||||
background: hsl(var(--color-text));
|
||||
box-shadow: 0 10px 30px -10px hsl(0 0% 0% / 0.5);
|
||||
font-size: 0.78rem;
|
||||
max-width: calc(100vw - 1rem);
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.vswitch-label {
|
||||
padding: 0 0.5rem;
|
||||
font-family: var(--font-mono);
|
||||
font-size: 0.68rem;
|
||||
color: hsl(var(--color-bg) / 0.6);
|
||||
}
|
||||
|
||||
.vswitch-item {
|
||||
padding: 0.35rem 0.75rem;
|
||||
border-radius: 20px;
|
||||
white-space: nowrap;
|
||||
color: hsl(var(--color-bg) / 0.85);
|
||||
}
|
||||
|
||||
.vswitch-item:hover {
|
||||
background: hsl(var(--color-bg) / 0.12);
|
||||
}
|
||||
|
||||
.vswitch-item--on {
|
||||
color: hsl(var(--color-text));
|
||||
background: hsl(var(--color-bg));
|
||||
}
|
||||
</style>
|
||||
@@ -1,279 +0,0 @@
|
||||
<template>
|
||||
<div class="hero-content">
|
||||
<!-- 5-line heading with alternating typography -->
|
||||
<div class="hero-heading">
|
||||
<h1 v-if="hero.heading?.length" class="hero-lines">
|
||||
<span
|
||||
v-for="(line, i) in hero.heading"
|
||||
:key="i"
|
||||
class="hero-line"
|
||||
:class="`hero-line--${i + 1}`"
|
||||
>{{ line }}</span>
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<!-- Discrete aside block -->
|
||||
<details class="hero-aside">
|
||||
<summary class="hero-aside-toggle">En savoir plus</summary>
|
||||
|
||||
<div class="hero-aside-body">
|
||||
<!-- Citations -->
|
||||
<blockquote v-if="hero.citations?.length" class="hero-citations">
|
||||
<p v-for="(cite, i) in hero.citations" :key="i" class="hero-cite">
|
||||
{{ cite }}
|
||||
</p>
|
||||
</blockquote>
|
||||
|
||||
<!-- Approach + Axes -->
|
||||
<div v-if="hero.approach" class="hero-approach">
|
||||
<p class="hero-approach-text">{{ hero.approach }}</p>
|
||||
<dl v-if="hero.axes?.length" class="hero-axes">
|
||||
<div v-for="(axis, i) in hero.axes" :key="i" class="hero-axis">
|
||||
<dt>{{ axis.label }}</dt>
|
||||
<dd>{{ axis.value }}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
<!-- Audience + Addressees -->
|
||||
<div v-if="hero.audience" class="hero-approach hero-audience">
|
||||
<p class="hero-approach-text">{{ hero.audience }}</p>
|
||||
<dl v-if="hero.addressees?.length" class="hero-axes">
|
||||
<div v-for="(item, i) in hero.addressees" :key="i" class="hero-axis">
|
||||
<dt>{{ item.label }}</dt>
|
||||
<dd>{{ item.value }}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
interface HeroAxis {
|
||||
label: string
|
||||
value: string
|
||||
}
|
||||
|
||||
interface HeroData {
|
||||
heading: string[]
|
||||
citations: string[]
|
||||
approach: string
|
||||
axes: HeroAxis[]
|
||||
audience: string
|
||||
addressees: HeroAxis[]
|
||||
}
|
||||
|
||||
defineProps<{
|
||||
hero: HeroData
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.hero-content {
|
||||
text-align: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
/* ── Heading — 5 lines ── */
|
||||
|
||||
.hero-heading {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.hero-lines {
|
||||
margin: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 0.1em;
|
||||
}
|
||||
|
||||
.hero-line {
|
||||
display: block;
|
||||
font-family: var(--font-display);
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
/* l1: Construire une autonomie collective. — bold, large */
|
||||
.hero-line--1 {
|
||||
font-weight: 700;
|
||||
font-size: clamp(1.45rem, 3.8vw, 2.5rem);
|
||||
color: hsl(var(--color-text) / 0.95);
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
|
||||
/* l2: à l'échelle des bassins de vie — light, same size, softer */
|
||||
.hero-line--2 {
|
||||
font-weight: 300;
|
||||
font-size: clamp(1.3rem, 3.2vw, 2.1rem);
|
||||
color: hsl(var(--color-text) / 0.55);
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
|
||||
/* l3: Pousser les curseurs — accent color, medium */
|
||||
.hero-line--3 {
|
||||
font-weight: 600;
|
||||
font-size: clamp(1.1rem, 2.6vw, 1.6rem);
|
||||
color: hsl(var(--color-accent));
|
||||
margin-top: 0.5em;
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
|
||||
/* l4: Autonomie numérique, économique, citoyenne. — small-caps feel */
|
||||
.hero-line--4 {
|
||||
font-family: var(--font-sans);
|
||||
font-weight: 500;
|
||||
font-size: clamp(0.9rem, 2vw, 1.15rem);
|
||||
color: hsl(var(--color-text) / 0.65);
|
||||
letter-spacing: 0.06em;
|
||||
text-transform: uppercase;
|
||||
margin-top: 0.15em;
|
||||
}
|
||||
|
||||
/* l5: — s'en donner les moyens — — italic, same tone as l4 */
|
||||
.hero-line--5 {
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
font-size: clamp(0.95rem, 2.2vw, 1.2rem);
|
||||
color: hsl(var(--color-text) / 0.65);
|
||||
margin-top: 0.3em;
|
||||
}
|
||||
|
||||
/* ── Discrete aside block ── */
|
||||
|
||||
.hero-aside {
|
||||
width: 100%;
|
||||
max-width: 36em;
|
||||
}
|
||||
|
||||
.hero-aside-toggle {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.4rem;
|
||||
margin: 0 auto;
|
||||
padding: 0.35rem 0.85rem;
|
||||
border-radius: 9999px;
|
||||
font-family: var(--font-sans);
|
||||
font-size: 0.78rem;
|
||||
font-weight: 500;
|
||||
color: hsl(var(--color-text) / 0.35);
|
||||
border: 1px solid hsl(var(--color-text) / 0.08);
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.hero-aside-toggle::-webkit-details-marker {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.hero-aside-toggle::before {
|
||||
content: '▸';
|
||||
font-size: 0.65em;
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
|
||||
.hero-aside[open] .hero-aside-toggle::before {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.hero-aside-toggle:hover {
|
||||
color: hsl(var(--color-text) / 0.55);
|
||||
border-color: hsl(var(--color-text) / 0.15);
|
||||
}
|
||||
|
||||
.hero-aside-body {
|
||||
margin-top: 1.25rem;
|
||||
animation: aside-reveal 0.3s ease;
|
||||
}
|
||||
|
||||
@keyframes aside-reveal {
|
||||
from { opacity: 0; transform: translateY(-6px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
/* ── Citations ── */
|
||||
|
||||
.hero-citations {
|
||||
margin: 0 0 1.25rem;
|
||||
padding: 0.6rem 0 0.6rem 1rem;
|
||||
border-left: 2px solid hsl(var(--color-accent) / 0.3);
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.hero-cite {
|
||||
font-family: var(--font-display);
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
font-size: 0.92rem;
|
||||
line-height: 1.55;
|
||||
color: hsl(var(--color-text) / 0.45);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.hero-cite + .hero-cite {
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
|
||||
/* ── Approach + Axes ── */
|
||||
|
||||
.hero-approach {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.hero-audience {
|
||||
margin-top: 1rem;
|
||||
padding-top: 0.75rem;
|
||||
border-top: 1px solid hsl(var(--color-text) / 0.07);
|
||||
}
|
||||
|
||||
.hero-approach-text {
|
||||
font-family: var(--font-sans);
|
||||
font-size: 0.82rem;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.03em;
|
||||
text-transform: uppercase;
|
||||
color: hsl(var(--color-text) / 0.35);
|
||||
margin: 0 0 0.6rem;
|
||||
}
|
||||
|
||||
.hero-axes {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 0.2rem;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.hero-axis {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 0.4rem;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.hero-axis dt {
|
||||
font-family: var(--font-display);
|
||||
font-weight: 600;
|
||||
font-size: 0.9rem;
|
||||
color: hsl(var(--color-primary));
|
||||
}
|
||||
|
||||
.hero-axis dt::after {
|
||||
content: ' →';
|
||||
color: hsl(var(--color-text) / 0.2);
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.hero-axis dd {
|
||||
font-family: var(--font-sans);
|
||||
font-size: 0.88rem;
|
||||
color: hsl(var(--color-text) / 0.5);
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user