forked from LPK/librodrome
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:
+2
-2
@@ -21,9 +21,9 @@ export default defineAppConfig({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
libredecision: {
|
libredecision: {
|
||||||
url: import.meta.dev ? 'http://localhost:3002' : 'https://decision.librodrome.org',
|
url: 'https://decision.librodrome.org',
|
||||||
},
|
},
|
||||||
sejeteral0: {
|
sejeteral0: {
|
||||||
url: import.meta.dev ? 'http://localhost:3009' : 'https://collectivites.librodrome.org',
|
url: 'https://collectivites.librodrome.org',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
--sidebar-width: 280px;
|
--sidebar-width: 280px;
|
||||||
|
|
||||||
--font-display: 'Bricolage Grotesque', system-ui, sans-serif;
|
--font-display: 'Bricolage Grotesque', system-ui, sans-serif;
|
||||||
--font-sans: 'Atkinson Hyperlegible', system-ui, sans-serif;
|
--font-sans: 'Public Sans', system-ui, sans-serif;
|
||||||
--font-logo: 'Syne', sans-serif;
|
--font-logo: 'Syne', sans-serif;
|
||||||
|
|
||||||
/* One hue per axis — overridden by each palette (stores/palette.ts) */
|
/* One hue per axis — overridden by each palette (stores/palette.ts) */
|
||||||
@@ -29,6 +29,13 @@
|
|||||||
--ease-in-out-expo: cubic-bezier(0.87, 0, 0.13, 1);
|
--ease-in-out-expo: cubic-bezier(0.87, 0, 0.13, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Fader level of the home console — registered so it can transition smoothly */
|
||||||
|
@property --level {
|
||||||
|
syntax: '<number>';
|
||||||
|
inherits: true;
|
||||||
|
initial-value: 0;
|
||||||
|
}
|
||||||
|
|
||||||
*,
|
*,
|
||||||
*::before,
|
*::before,
|
||||||
*::after {
|
*::after {
|
||||||
|
|||||||
@@ -378,6 +378,15 @@ function onTouchEnd(e: TouchEvent) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ── Lifecycle ──
|
// ── Lifecycle ──
|
||||||
|
// While the reader is open, the persistent mini player (above it) lifts up
|
||||||
|
// so it does not cover the reader navigation.
|
||||||
|
if (import.meta.client) {
|
||||||
|
watch(isOpen, (open) => {
|
||||||
|
document.documentElement.classList.toggle('book-player-open', open)
|
||||||
|
}, { immediate: true })
|
||||||
|
onUnmounted(() => document.documentElement.classList.remove('book-player-open'))
|
||||||
|
}
|
||||||
|
|
||||||
watch(isOpen, async (open) => {
|
watch(isOpen, async (open) => {
|
||||||
if (open) {
|
if (open) {
|
||||||
showSommaire.value = false
|
showSommaire.value = false
|
||||||
|
|||||||
@@ -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 {
|
.book-cover-3d {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 360px;
|
max-width: 360px;
|
||||||
aspect-ratio: 3 / 4;
|
aspect-ratio: 1756 / 2480;
|
||||||
background-size: 200% auto;
|
background-size: cover;
|
||||||
background-position: right center;
|
background-position: center;
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
border-radius: 0.75rem;
|
border-radius: 0.75rem;
|
||||||
overflow: hidden;
|
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>
|
<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">
|
<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">
|
<div class="console-scale" aria-hidden="true">
|
||||||
<span>je m'affranchis</span>
|
<span class="console-scale-bottom">{{ bottomLabel }}</span>
|
||||||
<span class="console-ticks"><i v-for="n in 5" :key="n" /></span>
|
<span class="console-scale-line" />
|
||||||
<span>je subis</span>
|
<span class="console-scale-top">{{ topLabel }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="console-faders"
|
class="console-faders"
|
||||||
role="tablist"
|
role="tablist"
|
||||||
aria-label="Les trois axes de l'autonomie"
|
:aria-label="`Les trois axes — ${bottomLabel} ou ${topLabel}`"
|
||||||
@keydown="onKeydown"
|
@keydown="onKeydown"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
@@ -23,17 +31,20 @@
|
|||||||
role="tab"
|
role="tab"
|
||||||
class="fader"
|
class="fader"
|
||||||
:class="{ 'fader--active': axis.key === active }"
|
: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-selected="axis.key === active"
|
||||||
:aria-controls="`panel-${uid}`"
|
:aria-controls="`panel-${uid}`"
|
||||||
:tabindex="axis.key === active ? 0 : -1"
|
:tabindex="axis.key === active ? 0 : -1"
|
||||||
@click="select(axis.key)"
|
@click="choose(axis.key)"
|
||||||
>
|
>
|
||||||
<span class="fader-track" aria-hidden="true">
|
<span class="fader-track" aria-hidden="true">
|
||||||
<span class="fader-fill" />
|
<span class="fader-fill" />
|
||||||
<span class="fader-cap" />
|
<span class="fader-cap" />
|
||||||
</span>
|
</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>
|
<span class="fader-lever">{{ axis.lever }}</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -61,7 +72,7 @@
|
|||||||
<NuxtLink v-if="tool.to" :to="tool.to" class="panel-tool">
|
<NuxtLink v-if="tool.to" :to="tool.to" class="panel-tool">
|
||||||
<span class="panel-tool-name">{{ tool.label }}</span>
|
<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.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>
|
</NuxtLink>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
@@ -89,6 +100,9 @@ import type { AxisKey, HomeAxis, HomeStack } from '~/composables/useHomeData'
|
|||||||
|
|
||||||
const props = withDefaults(defineProps<{
|
const props = withDefaults(defineProps<{
|
||||||
axes: HomeAxis[]
|
axes: HomeAxis[]
|
||||||
|
prefix?: string
|
||||||
|
topLabel?: string
|
||||||
|
bottomLabel?: string
|
||||||
upstreamLabel?: string
|
upstreamLabel?: string
|
||||||
stacks?: HomeStack[]
|
stacks?: HomeStack[]
|
||||||
stacksLabel?: string
|
stacksLabel?: string
|
||||||
@@ -96,6 +110,9 @@ const props = withDefaults(defineProps<{
|
|||||||
maxStacks?: number
|
maxStacks?: number
|
||||||
compact?: boolean
|
compact?: boolean
|
||||||
}>(), {
|
}>(), {
|
||||||
|
prefix: 'Autonomie',
|
||||||
|
topLabel: 'Émancipation',
|
||||||
|
bottomLabel: 'Asservissement',
|
||||||
maxStacks: 8,
|
maxStacks: 8,
|
||||||
upstreamLabel: 'en amont',
|
upstreamLabel: 'en amont',
|
||||||
stacksLabel: 'En service',
|
stacksLabel: 'En service',
|
||||||
@@ -104,21 +121,58 @@ const props = withDefaults(defineProps<{
|
|||||||
|
|
||||||
const active = defineModel<AxisKey>({ default: 'numerique' })
|
const active = defineModel<AxisKey>({ default: 'numerique' })
|
||||||
const uid = useId()
|
const uid = useId()
|
||||||
|
const root = ref<HTMLElement | null>(null)
|
||||||
const tabs = ref<HTMLButtonElement[]>([])
|
const tabs = ref<HTMLButtonElement[]>([])
|
||||||
const ready = ref(false)
|
|
||||||
|
|
||||||
const current = computed(() => props.axes.find(a => a.key === active.value) ?? props.axes[0])
|
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.
|
// ── Motion ──────────────────────────────────────────────────────────────
|
||||||
function level(key: AxisKey) {
|
// The cursors move on their own, slowly: each one drifts, and every few
|
||||||
if (!ready.value) return 0
|
// beats another axis is pushed up towards émancipation. Visitors watch it;
|
||||||
return key === active.value ? 0.94 : 0.3
|
// 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
|
active.value = key
|
||||||
}
|
}
|
||||||
|
|
||||||
|
watch(active, place)
|
||||||
|
|
||||||
function onKeydown(e: KeyboardEvent) {
|
function onKeydown(e: KeyboardEvent) {
|
||||||
const keys = ['ArrowRight', 'ArrowLeft', 'ArrowUp', 'ArrowDown', 'Home', 'End']
|
const keys = ['ArrowRight', 'ArrowLeft', 'ArrowUp', 'ArrowDown', 'Home', 'End']
|
||||||
if (!keys.includes(e.key)) return
|
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 === 'ArrowLeft' || e.key === 'ArrowDown') next = idx === 0 ? last : idx - 1
|
||||||
if (e.key === 'Home') next = 0
|
if (e.key === 'Home') next = 0
|
||||||
if (e.key === 'End') next = last
|
if (e.key === 'End') next = last
|
||||||
active.value = props.axes[next]!.key
|
choose(props.axes[next]!.key)
|
||||||
nextTick(() => tabs.value[next]?.focus())
|
nextTick(() => tabs.value[next]?.focus())
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
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>
|
</script>
|
||||||
|
|
||||||
@@ -155,7 +220,7 @@ onMounted(() => {
|
|||||||
.console-board {
|
.console-board {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: auto 1fr;
|
grid-template-columns: auto 1fr;
|
||||||
gap: 0.75rem;
|
gap: 0.9rem;
|
||||||
padding: 1.25rem 1.25rem 1rem;
|
padding: 1.25rem 1.25rem 1rem;
|
||||||
border-radius: 16px;
|
border-radius: 16px;
|
||||||
background: hsl(var(--color-surface));
|
background: hsl(var(--color-surface));
|
||||||
@@ -164,32 +229,34 @@ onMounted(() => {
|
|||||||
0 18px 40px -24px hsl(var(--color-text) / 0.35);
|
0 18px 40px -24px hsl(var(--color-text) / 0.35);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Vertical scale, read from bottom to top */
|
||||||
.console-scale {
|
.console-scale {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: flex-end;
|
gap: 0.5rem;
|
||||||
height: var(--track-h);
|
height: var(--track-h);
|
||||||
font-family: var(--font-mono);
|
writing-mode: vertical-rl;
|
||||||
font-size: 0.62rem;
|
transform: rotate(180deg);
|
||||||
line-height: 1.2;
|
font-family: var(--font-display);
|
||||||
color: hsl(var(--color-text) / 0.45);
|
font-size: 0.72rem;
|
||||||
text-align: right;
|
letter-spacing: 0.02em;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.console-ticks {
|
.console-scale-bottom {
|
||||||
display: flex;
|
color: hsl(var(--color-text) / 0.45);
|
||||||
flex-direction: column;
|
|
||||||
gap: calc(var(--track-h) / 9);
|
|
||||||
align-items: flex-end;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.console-ticks i {
|
.console-scale-top {
|
||||||
display: block;
|
font-weight: 800;
|
||||||
width: 0.5rem;
|
color: hsl(var(--color-text));
|
||||||
height: 1px;
|
}
|
||||||
background: hsl(var(--color-text) / 0.2);
|
|
||||||
|
.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 {
|
.console-faders {
|
||||||
@@ -203,15 +270,13 @@ onMounted(() => {
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.5rem;
|
gap: 0.55rem;
|
||||||
padding: 0 0.25rem 0.25rem;
|
padding: 0 0.25rem 0.25rem;
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: background 0.2s ease;
|
/* --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:hover {
|
|
||||||
background: hsl(var(--hue) / 0.06);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.fader:focus-visible {
|
.fader:focus-visible {
|
||||||
@@ -233,6 +298,7 @@ onMounted(() => {
|
|||||||
height: calc(var(--level) * (var(--track-h) - var(--cap-h)) + var(--cap-h) / 2);
|
height: calc(var(--level) * (var(--track-h) - var(--cap-h)) + var(--cap-h) / 2);
|
||||||
border-radius: inherit;
|
border-radius: inherit;
|
||||||
background: hsl(var(--hue) / 0.45);
|
background: hsl(var(--hue) / 0.45);
|
||||||
|
transition: background 0.6s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fader--active .fader-fill {
|
.fader--active .fader-fill {
|
||||||
@@ -267,25 +333,28 @@ onMounted(() => {
|
|||||||
background: hsl(var(--hue));
|
background: hsl(var(--hue));
|
||||||
}
|
}
|
||||||
|
|
||||||
.console--ready .fader-fill,
|
.fader-label {
|
||||||
.console--ready .fader-cap {
|
display: flex;
|
||||||
transition:
|
flex-direction: column;
|
||||||
height 0.7s var(--ease-out-expo),
|
align-items: center;
|
||||||
transform 0.7s var(--ease-out-expo);
|
line-height: 1.1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Orchestrated first rise: one fader after the other */
|
.fader-prefix {
|
||||||
.console--ready .fader-fill,
|
font-family: var(--font-mono);
|
||||||
.console--ready .fader-cap {
|
font-size: 0.6rem;
|
||||||
transition-delay: calc(var(--i) * 90ms);
|
letter-spacing: 0.14em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: hsl(var(--color-text) / 0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
.fader-name {
|
.fader-name {
|
||||||
font-family: var(--font-display);
|
font-family: var(--font-display);
|
||||||
font-weight: 700;
|
font-weight: 800;
|
||||||
font-size: 0.95rem;
|
font-size: 1.05rem;
|
||||||
letter-spacing: -0.01em;
|
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 {
|
.fader--active .fader-name {
|
||||||
@@ -293,11 +362,21 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.fader-lever {
|
.fader-lever {
|
||||||
font-family: var(--font-mono);
|
padding: 0.2rem 0.55rem;
|
||||||
font-size: 0.64rem;
|
border-radius: 20px;
|
||||||
line-height: 1.25;
|
font-family: var(--font-display);
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
line-height: 1.2;
|
||||||
text-align: center;
|
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 ── */
|
/* ── Panel ── */
|
||||||
@@ -455,15 +534,19 @@ onMounted(() => {
|
|||||||
|
|
||||||
@media (max-width: 480px) {
|
@media (max-width: 480px) {
|
||||||
.console { --track-h: 8.5rem; }
|
.console { --track-h: 8.5rem; }
|
||||||
.console-board { padding: 1rem 0.75rem 0.75rem; gap: 0.4rem; }
|
.console-board { padding: 1rem 0.6rem 0.75rem; gap: 0.35rem; }
|
||||||
.console-scale { white-space: normal; max-width: 3.4rem; font-size: 0.56rem; }
|
.console-scale { font-size: 0.62rem; }
|
||||||
.fader-cap { width: 2.2rem; margin-left: -1.1rem; }
|
.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) {
|
@media (prefers-reduced-motion: reduce) {
|
||||||
.console--ready .fader-fill,
|
.fader,
|
||||||
.console--ready .fader-cap,
|
.fader-fill,
|
||||||
|
.fader-name,
|
||||||
|
.fader-lever,
|
||||||
.panel-enter-active,
|
.panel-enter-active,
|
||||||
.panel-leave-active {
|
.panel-leave-active {
|
||||||
transition: none;
|
transition: none;
|
||||||
|
|||||||
@@ -9,7 +9,12 @@
|
|||||||
<span class="va-h1-a">{{ heading[0] }}</span>
|
<span class="va-h1-a">{{ heading[0] }}</span>
|
||||||
<span class="va-h1-b">{{ heading[1] }}</span>
|
<span class="va-h1-b">{{ heading[1] }}</span>
|
||||||
</h1>
|
</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 class="va-means">{{ heading[4] }}</p>
|
||||||
<p v-if="hero.approach" class="va-approach">{{ hero.approach }}</p>
|
<p v-if="hero.approach" class="va-approach">{{ hero.approach }}</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -19,6 +24,9 @@
|
|||||||
<HomeConsole
|
<HomeConsole
|
||||||
v-model="active"
|
v-model="active"
|
||||||
:axes="axes"
|
:axes="axes"
|
||||||
|
:prefix="consoleLabels?.prefix"
|
||||||
|
:top-label="consoleLabels?.top"
|
||||||
|
:bottom-label="consoleLabels?.bottom"
|
||||||
:stacks="bouquet?.inService"
|
:stacks="bouquet?.inService"
|
||||||
:stacks-to="lpk?.to"
|
:stacks-to="lpk?.to"
|
||||||
:stacks-label="lpk ? `En service chez ${lpk.name} :` : 'En service :'"
|
:stacks-label="lpk ? `En service chez ${lpk.name} :` : 'En service :'"
|
||||||
@@ -51,7 +59,7 @@
|
|||||||
<!-- La boîte à outils -->
|
<!-- La boîte à outils -->
|
||||||
<section class="va-section va-section--tools">
|
<section class="va-section va-section--tools">
|
||||||
<div class="container-content">
|
<div class="container-content">
|
||||||
<h2 class="va-h2">La boîte à outils</h2>
|
<h2 class="va-h2">{{ toolboxTitle }}</h2>
|
||||||
<HomeToolbox
|
<HomeToolbox
|
||||||
:axes="axes"
|
:axes="axes"
|
||||||
:lpk="lpk"
|
:lpk="lpk"
|
||||||
@@ -74,8 +82,20 @@ defineEmits<{
|
|||||||
'open-pdf': []
|
'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 ?? [])
|
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')
|
const active = ref<AxisKey>('numerique')
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -126,9 +146,16 @@ const active = ref<AxisKey>('numerique')
|
|||||||
|
|
||||||
.va-axes {
|
.va-axes {
|
||||||
margin: 1.75rem 0 0;
|
margin: 1.75rem 0 0;
|
||||||
font-family: var(--font-mono);
|
font-family: var(--font-display);
|
||||||
font-size: 0.8rem;
|
font-weight: 800;
|
||||||
color: hsl(var(--color-text) / 0.7);
|
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 {
|
.va-means {
|
||||||
@@ -9,7 +9,7 @@
|
|||||||
<component :is="tool.to ? NuxtLink : 'span'" :to="tool.to" class="tool-name">
|
<component :is="tool.to ? NuxtLink : 'span'" :to="tool.to" class="tool-name">
|
||||||
{{ tool.label }}
|
{{ tool.label }}
|
||||||
</component>
|
</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 class="tool-desc">{{ tool.description }}</p>
|
||||||
<p v-if="tool.presentation" class="tool-app">
|
<p v-if="tool.presentation" class="tool-app">
|
||||||
<strong>{{ tool.presentation.title }}</strong> — {{ tool.presentation.text }}
|
<strong>{{ tool.presentation.title }}</strong> — {{ tool.presentation.text }}
|
||||||
@@ -50,6 +50,9 @@
|
|||||||
{{ action.label }}
|
{{ action.label }}
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
|
<span v-for="action in tool.actions.filter(a => a.note)" :key="`note-${action.label}`" class="tool-note">
|
||||||
|
{{ action.note }}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -113,16 +116,15 @@ a.tool-name:hover {
|
|||||||
color: hsl(var(--hue));
|
color: hsl(var(--hue));
|
||||||
}
|
}
|
||||||
|
|
||||||
.tool-state {
|
.tool-status {
|
||||||
margin-left: 0.4rem;
|
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 {
|
.tool-note {
|
||||||
content: '◌ ';
|
align-self: center;
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
font-size: 0.66rem;
|
||||||
|
color: hsl(var(--axis-cit));
|
||||||
}
|
}
|
||||||
|
|
||||||
.tool-desc {
|
.tool-desc {
|
||||||
|
|||||||
@@ -23,14 +23,14 @@
|
|||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<!-- Numérique : the stacks LPK serves -->
|
<!-- Numérique : the stacks LaPlanK serves -->
|
||||||
<NuxtLink
|
<NuxtLink
|
||||||
v-if="axis.key === 'numerique' && lpk && bouquet"
|
v-if="axis.key === 'numerique' && lpk && bouquet"
|
||||||
:to="lpk.to"
|
:to="lpk.to"
|
||||||
class="toolbox-lpk"
|
class="toolbox-lpk"
|
||||||
>
|
>
|
||||||
<span class="toolbox-lpk-head">
|
<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>
|
||||||
<span class="toolbox-lpk-stacks">
|
<span class="toolbox-lpk-stacks">
|
||||||
<span v-for="s in bouquet.inService" :key="s.name" class="toolbox-lpk-stack">{{ s.name }}</span>
|
<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<{
|
withDefaults(defineProps<{
|
||||||
axes: HomeAxis[]
|
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
|
bouquet?: { title: string, inService: { name?: string }[], stacks: unknown[] } | null
|
||||||
gratewizard?: { title: string, subtitle: string }
|
gratewizard?: { title: string, subtitle: string }
|
||||||
showPromise?: boolean
|
showPromise?: boolean
|
||||||
@@ -153,7 +153,7 @@ function onAction(id: string) {
|
|||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* LPK */
|
/* LaPlanK */
|
||||||
.toolbox-lpk {
|
.toolbox-lpk {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
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>
|
|
||||||
@@ -3,22 +3,7 @@
|
|||||||
<div class="container-content flex h-[var(--header-height)] items-center justify-between px-4">
|
<div class="container-content flex h-[var(--header-height)] items-center justify-between px-4">
|
||||||
<!-- Logo -->
|
<!-- Logo -->
|
||||||
<NuxtLink to="/" class="logo-link flex items-center gap-2.5">
|
<NuxtLink to="/" class="logo-link flex items-center gap-2.5">
|
||||||
<svg class="logo-icon" viewBox="0 0 46 78" fill="none" aria-hidden="true">
|
<UiSectionSign class="logo-icon" />
|
||||||
<defs>
|
|
||||||
<linearGradient id="sect-grad" x1="0" y1="0" x2="0" y2="1">
|
|
||||||
<stop offset="0%" stop-color="hsl(var(--color-primary))"/>
|
|
||||||
<stop offset="100%" stop-color="hsl(var(--color-accent))"/>
|
|
||||||
</linearGradient>
|
|
||||||
</defs>
|
|
||||||
<path
|
|
||||||
d="M 33 10 C 26 7 18 7 14 11 C 8 15 7 24 10 31 C 13 38 21 40 27 44 C 33 48 38 55 35 62 C 32 69 24 72 17 70 C 10 68 8 72 10 75 C 12 78 20 79 28 76"
|
|
||||||
stroke="url(#sect-grad)"
|
|
||||||
stroke-width="5"
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
fill="none"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
<span class="logo-text">{{ site?.identity.name }}</span>
|
<span class="logo-text">{{ site?.identity.name }}</span>
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
|
|
||||||
@@ -88,9 +73,19 @@ const AXIS_HUES = ['--axis-num', '--axis-eco', '--axis-cit']
|
|||||||
}
|
}
|
||||||
|
|
||||||
.logo-icon {
|
.logo-icon {
|
||||||
width: 1.6rem;
|
width: auto;
|
||||||
height: 2rem;
|
height: 2.1rem;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
transform: rotate(-8deg);
|
||||||
|
transition: transform 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo-link:hover .logo-icon {
|
||||||
|
transform: rotate(0deg) scale(1.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.logo-icon { transition: none; }
|
||||||
}
|
}
|
||||||
|
|
||||||
.logo-text {
|
.logo-text {
|
||||||
|
|||||||
@@ -179,6 +179,7 @@ onClickOutside(widgetRef, () => {
|
|||||||
.mini-player {
|
.mini-player {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
bottom: 1rem;
|
bottom: 1rem;
|
||||||
|
transition: bottom 0.3s ease;
|
||||||
right: max(1rem, calc((100vw - 80rem) / 2));
|
right: max(1rem, calc((100vw - 80rem) / 2));
|
||||||
z-index: 70;
|
z-index: 70;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -186,6 +187,11 @@ onClickOutside(widgetRef, () => {
|
|||||||
align-items: flex-end;
|
align-items: flex-end;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Book player open: stay above its navigation bar */
|
||||||
|
:global(html.book-player-open .mini-player) {
|
||||||
|
bottom: 5.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
/* ═══════════════════════════════════════
|
/* ═══════════════════════════════════════
|
||||||
PILL
|
PILL
|
||||||
═══════════════════════════════════════ */
|
═══════════════════════════════════════ */
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
<template>
|
||||||
|
<!-- § — glyphe de Playfair Display 700 (SIL OFL), vectorisé ; dégradé primaire → accent -->
|
||||||
|
<svg class="section-sign" viewBox="0 0 511 970" aria-hidden="true">
|
||||||
|
<defs>
|
||||||
|
<linearGradient :id="gradId" x1="0" y1="0" x2="0" y2="1">
|
||||||
|
<stop offset="0%" stop-color="hsl(var(--color-primary))" />
|
||||||
|
<stop offset="100%" stop-color="hsl(var(--color-accent))" />
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<path :fill="`url(#${gradId})`" d="M277 0Q320 0 349 7.5Q378 15 395 22Q426 37 443.5 59.5Q461 82 461 115Q461 143 441.5 164Q422 185 387 185Q354 185 332.5 165.5Q311 146 311 116Q311 92 324.5 71Q338 50 362 42Q357 34 344.5 29Q332 24 316 21.5Q300 19 284 19Q246 19 223 32.5Q200 46 190 66.5Q180 87 180 110Q180 150 200 177.5Q220 205 256 230Q292 255 340 287Q394 324 432.5 354.5Q471 385 491 418Q511 451 511 495Q511 548 475.5 593Q440 638 352 677L343 667Q373 646 388 623Q403 600 403 567Q403 536 386 511.5Q369 487 333.5 459Q298 431 241 394Q178 353 135.5 318.5Q93 284 71.5 248Q50 212 50 167Q50 116 78.5 78.5Q107 41 158.5 20.5Q210 0 277 0ZM158 292 168 303Q137 323 122.5 346Q108 369 108 403Q108 433 124.5 458Q141 483 177 511Q213 539 270 576Q332 617 374.5 651.5Q417 686 439 722Q461 758 461 803Q461 853 432 891Q403 929 352 949.5Q301 970 233 970Q190 970 161 962.5Q132 955 115 948Q85 933 67 910.5Q49 888 49 855Q49 827 69 806Q89 785 123 785Q157 785 178 804.5Q199 824 199 854Q199 878 185.5 898.5Q172 919 148 928Q152 935 165 940.5Q178 946 194.5 948.5Q211 951 226 951Q265 951 287.5 937.5Q310 924 320.5 903Q331 882 331 860Q331 820 311 792.5Q291 765 255 740Q219 715 171 683Q116 646 78 615.5Q40 585 20 551.5Q0 518 0 475Q0 421 34.5 376Q69 331 158 292Z" />
|
||||||
|
</svg>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
const gradId = `sect-grad-${useId()}`
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
<template>
|
||||||
|
<span v-if="label" class="status" :class="`status--${tone}`">
|
||||||
|
<span class="status-dot" aria-hidden="true" />
|
||||||
|
<span>{{ label }}</span>
|
||||||
|
<span v-if="since" class="status-since">· {{ since }}</span>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
// Project maturity, as written in YAML: `status: En production` (+ `since`),
|
||||||
|
// `status: En développement`, or the legacy `gestation: true`.
|
||||||
|
const props = defineProps<{
|
||||||
|
status?: string
|
||||||
|
gestation?: boolean
|
||||||
|
since?: string
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const label = computed(() => props.status || (props.gestation ? 'En gestation' : ''))
|
||||||
|
|
||||||
|
const tone = computed(() => {
|
||||||
|
const s = label.value.toLowerCase()
|
||||||
|
if (s.includes('production')) return 'prod'
|
||||||
|
if (s.includes('développement') || s.includes('developpement')) return 'dev'
|
||||||
|
return 'gest'
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.status {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.35rem;
|
||||||
|
padding: 0.15rem 0.6rem;
|
||||||
|
border-radius: 20px;
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
font-size: 0.68rem;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 1.5;
|
||||||
|
white-space: nowrap;
|
||||||
|
vertical-align: middle;
|
||||||
|
color: hsl(var(--color-text) / 0.75);
|
||||||
|
background: hsl(var(--color-text) / 0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-dot {
|
||||||
|
width: 0.5rem;
|
||||||
|
height: 0.5rem;
|
||||||
|
border-radius: 50%;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* En production : plein */
|
||||||
|
.status--prod .status-dot { background: hsl(var(--axis-cit)); }
|
||||||
|
.status--prod { color: hsl(var(--axis-cit)); background: hsl(var(--axis-cit) / 0.1); }
|
||||||
|
|
||||||
|
/* En développement : demi-plein */
|
||||||
|
.status--dev .status-dot {
|
||||||
|
background: linear-gradient(to top, hsl(var(--axis-eco)) 50%, transparent 50%);
|
||||||
|
box-shadow: inset 0 0 0 1.5px hsl(var(--axis-eco));
|
||||||
|
}
|
||||||
|
.status--dev { color: hsl(var(--axis-eco)); background: hsl(var(--axis-eco) / 0.1); }
|
||||||
|
|
||||||
|
/* En gestation : cercle */
|
||||||
|
.status--gest .status-dot { box-shadow: inset 0 0 0 1.5px hsl(var(--color-text) / 0.45); }
|
||||||
|
|
||||||
|
.status-since {
|
||||||
|
opacity: 0.75;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -71,11 +71,9 @@ export function useAudioPlayer() {
|
|||||||
store.setSong(song)
|
store.setSong(song)
|
||||||
firedMilestones.clear()
|
firedMilestones.clear()
|
||||||
|
|
||||||
// Try OGG first, fall back to MP3
|
// Only MP3 files are published (no .ogg on disk nor in production):
|
||||||
const oggPath = song.file.replace(/\.mp3$/, '.ogg')
|
// asking for OGG first cost a 404 and a delay on every track.
|
||||||
const canOgg = el.canPlayType('audio/ogg; codecs=vorbis')
|
el.src = song.file
|
||||||
|
|
||||||
el.src = canOgg ? oggPath : song.file
|
|
||||||
el.volume = store.volume
|
el.volume = store.volume
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -84,20 +82,8 @@ export function useAudioPlayer() {
|
|||||||
startTimeUpdate()
|
startTimeUpdate()
|
||||||
trackAudioPlay(song.id, song.title)
|
trackAudioPlay(song.id, song.title)
|
||||||
}
|
}
|
||||||
catch {
|
catch (err) {
|
||||||
// If OGG failed, try MP3
|
console.error('Playback failed:', err)
|
||||||
if (el.src !== song.file) {
|
|
||||||
el.src = song.file
|
|
||||||
try {
|
|
||||||
await el.play()
|
|
||||||
store.play()
|
|
||||||
startTimeUpdate()
|
|
||||||
trackAudioPlay(song.id, song.title)
|
|
||||||
}
|
|
||||||
catch (err) {
|
|
||||||
console.error('Playback failed:', err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ export interface HomeAction {
|
|||||||
secondary?: boolean
|
secondary?: boolean
|
||||||
to?: string
|
to?: string
|
||||||
href?: string
|
href?: string
|
||||||
|
/** Short status line shown under the button (e.g. the v1 in production) */
|
||||||
|
note?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface HomeTool {
|
export interface HomeTool {
|
||||||
@@ -21,6 +23,9 @@ export interface HomeTool {
|
|||||||
to?: string
|
to?: string
|
||||||
href?: string
|
href?: string
|
||||||
gestation?: boolean
|
gestation?: boolean
|
||||||
|
/** « En production », « En développement »… — wins over `gestation` */
|
||||||
|
status?: string
|
||||||
|
since?: string
|
||||||
icon?: string
|
icon?: string
|
||||||
presentation?: { title: string, text: string }
|
presentation?: { title: string, text: string }
|
||||||
actions?: HomeAction[]
|
actions?: HomeAction[]
|
||||||
@@ -86,9 +91,9 @@ export async function useHomeData() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const lpk = computed(() => (site.value as any)?.lpk as
|
const lpk = computed(() => (site.value as any)?.lpk as
|
||||||
{ name: string, fullName: string, text: string, role: string, to: string } | undefined)
|
{ name: string, text: string, role: string, to: string } | undefined)
|
||||||
|
|
||||||
/** Bouquet sweethomeCloud, flattened — the stacks served by LPK */
|
/** Bouquet sweetHome, flattened — the stacks served by LaPlanK */
|
||||||
const bouquet = computed(() => {
|
const bouquet = computed(() => {
|
||||||
const section = ((cloud.value as any)?.sections ?? []).find((s: any) => s.type === 'territoire')
|
const section = ((cloud.value as any)?.sections ?? []).find((s: any) => s.type === 'territoire')
|
||||||
const b = section?.bouquet
|
const b = section?.bouquet
|
||||||
@@ -139,6 +144,8 @@ export async function useHomeData() {
|
|||||||
evenement,
|
evenement,
|
||||||
book: computed(() => (home.value as any)?.book),
|
book: computed(() => (home.value as any)?.book),
|
||||||
format: computed(() => (home.value as any)?.format as { title: string, text: string } | undefined),
|
format: computed(() => (home.value as any)?.format as { title: string, text: string } | undefined),
|
||||||
|
toolboxTitle: computed(() => (home.value as any)?.toolbox?.title as string | undefined),
|
||||||
|
consoleLabels: computed(() => (home.value as any)?.console as { prefix: string, top: string, bottom: string } | undefined),
|
||||||
gratewizard: computed(() => (home.value as any)?.gratewizard),
|
gratewizard: computed(() => (home.value as any)?.gratewizard),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ interface SiteContent {
|
|||||||
}
|
}
|
||||||
lpk?: {
|
lpk?: {
|
||||||
name: string
|
name: string
|
||||||
fullName: string
|
|
||||||
text: string
|
text: string
|
||||||
role: string
|
role: string
|
||||||
to: string
|
to: string
|
||||||
|
|||||||
@@ -86,10 +86,7 @@
|
|||||||
<p style="color: hsl(var(--color-text-muted))" class="leading-relaxed">
|
<p style="color: hsl(var(--color-text-muted))" class="leading-relaxed">
|
||||||
{{ content.project.text }}
|
{{ content.project.text }}
|
||||||
</p>
|
</p>
|
||||||
<span v-if="content?.gestation" class="gestation-badge mt-3">
|
<UiStatusBadge class="mt-3" :status="content?.status" :gestation="content?.gestation" />
|
||||||
<div class="i-lucide-flask-conical h-3 w-3" />
|
|
||||||
En gestation
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Extended content -->
|
<!-- Extended content -->
|
||||||
|
|||||||
@@ -352,6 +352,7 @@
|
|||||||
<h2 class="font-display text-xl font-bold" style="color: hsl(var(--color-text))">
|
<h2 class="font-display text-xl font-bold" style="color: hsl(var(--color-text))">
|
||||||
Décision collective
|
Décision collective
|
||||||
</h2>
|
</h2>
|
||||||
|
<UiStatusBadge class="ml-auto" v-bind="statusOf('decision')" />
|
||||||
</div>
|
</div>
|
||||||
<p class="leading-relaxed mt-3" style="color: hsl(var(--color-text-muted))">
|
<p class="leading-relaxed mt-3" style="color: hsl(var(--color-text-muted))">
|
||||||
Se donner les moyens de la décision collective.
|
Se donner les moyens de la décision collective.
|
||||||
@@ -376,10 +377,7 @@
|
|||||||
<h2 class="font-display text-xl font-bold" style="color: hsl(var(--color-text))">
|
<h2 class="font-display text-xl font-bold" style="color: hsl(var(--color-text))">
|
||||||
Tarifs de l'eau
|
Tarifs de l'eau
|
||||||
</h2>
|
</h2>
|
||||||
<span class="gestation-badge">
|
<UiStatusBadge class="ml-auto" v-bind="statusOf('tarifs-eau')" />
|
||||||
<div class="i-lucide-flask-conical h-3 w-3" />
|
|
||||||
En gestation
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
<p class="leading-relaxed mt-3" style="color: hsl(var(--color-text-muted))">
|
<p class="leading-relaxed mt-3" style="color: hsl(var(--color-text-muted))">
|
||||||
Application pour obtenir justice sociale et incitation dynamique à la réduction.
|
Application pour obtenir justice sociale et incitation dynamique à la réduction.
|
||||||
@@ -404,6 +402,10 @@ definePageMeta({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const { data: content } = await usePageContent('citoyenne')
|
const { data: content } = await usePageContent('citoyenne')
|
||||||
|
const statusOf = (id: string) => {
|
||||||
|
const item = ((content.value as any)?.items ?? []).find((i: any) => i.id === id) ?? {}
|
||||||
|
return { status: item.status, since: item.since, gestation: item.gestation }
|
||||||
|
}
|
||||||
|
|
||||||
const appConfig = useAppConfig()
|
const appConfig = useAppConfig()
|
||||||
const decisionUrl = (appConfig.libredecision as { url: string })?.url ?? '#'
|
const decisionUrl = (appConfig.libredecision as { url: string })?.url ?? '#'
|
||||||
@@ -449,6 +451,7 @@ useSeoPage({
|
|||||||
|
|
||||||
.item-header {
|
.item-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.75rem;
|
gap: 0.75rem;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -396,6 +396,7 @@
|
|||||||
<h2 class="font-display text-xl font-bold" style="color: hsl(var(--color-text))">
|
<h2 class="font-display text-xl font-bold" style="color: hsl(var(--color-text))">
|
||||||
Monnaie libre
|
Monnaie libre
|
||||||
</h2>
|
</h2>
|
||||||
|
<UiStatusBadge class="ml-auto" v-bind="statusOf('monnaie-libre')" />
|
||||||
</div>
|
</div>
|
||||||
<p class="leading-relaxed mt-3" style="color: hsl(var(--color-text-muted))">
|
<p class="leading-relaxed mt-3" style="color: hsl(var(--color-text-muted))">
|
||||||
La Ğ1 (June) : une monnaie co-créée par ses membres, sans dette ni intérêt. Le dividende universel comme base.
|
La Ğ1 (June) : une monnaie co-créée par ses membres, sans dette ni intérêt. Le dividende universel comme base.
|
||||||
@@ -424,10 +425,7 @@
|
|||||||
<h2 class="font-display text-xl font-bold" style="color: hsl(var(--color-text))">
|
<h2 class="font-display text-xl font-bold" style="color: hsl(var(--color-text))">
|
||||||
Productions collectives
|
Productions collectives
|
||||||
</h2>
|
</h2>
|
||||||
<span class="gestation-badge">
|
<UiStatusBadge class="ml-auto" v-bind="statusOf('productions-collectives')" />
|
||||||
<div class="i-lucide-flask-conical h-3 w-3" />
|
|
||||||
En gestation
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
<p class="leading-relaxed mt-3" style="color: hsl(var(--color-text-muted))">
|
<p class="leading-relaxed mt-3" style="color: hsl(var(--color-text-muted))">
|
||||||
Une plateforme pour faciliter la création d'équipes et la réalisation de productions à l'échelle des bassins de vie. Passer la seconde.
|
Une plateforme pour faciliter la création d'équipes et la réalisation de productions à l'échelle des bassins de vie. Passer la seconde.
|
||||||
@@ -451,6 +449,10 @@ definePageMeta({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const { data: content } = await usePageContent('economique')
|
const { data: content } = await usePageContent('economique')
|
||||||
|
const statusOf = (id: string) => {
|
||||||
|
const item = ((content.value as any)?.items ?? []).find((i: any) => i.id === id) ?? {}
|
||||||
|
return { status: item.status, since: item.since, gestation: item.gestation }
|
||||||
|
}
|
||||||
|
|
||||||
useSeoPage({
|
useSeoPage({
|
||||||
title: content.value?.meta?.title ?? 'Autonomie économique',
|
title: content.value?.meta?.title ?? 'Autonomie économique',
|
||||||
@@ -484,6 +486,7 @@ const showPdfReader = ref(false)
|
|||||||
|
|
||||||
.item-header {
|
.item-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.75rem;
|
gap: 0.75rem;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
<p class="text-lg leading-relaxed" style="color: hsl(var(--color-text-muted))">
|
<p class="text-lg leading-relaxed" style="color: hsl(var(--color-text-muted))">
|
||||||
{{ content?.description }}
|
{{ content?.description }}
|
||||||
</p>
|
</p>
|
||||||
|
<UiStatusBadge class="mt-4" :status="content?.status" :since="content?.since" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Navigation prev / index / next -->
|
<!-- Navigation prev / index / next -->
|
||||||
|
|||||||
@@ -18,10 +18,9 @@
|
|||||||
{{ content?.description }}
|
{{ content?.description }}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<span v-if="content?.gestation" class="gestation-badge mx-auto mb-8">
|
<div class="mb-8 text-center">
|
||||||
<div class="i-lucide-flask-conical h-3 w-3" />
|
<UiStatusBadge :status="content?.status" :gestation="content?.gestation" />
|
||||||
En gestation
|
</div>
|
||||||
</span>
|
|
||||||
|
|
||||||
<!-- Content -->
|
<!-- Content -->
|
||||||
<div v-if="content?.content" class="prose-block mb-10">
|
<div v-if="content?.content" class="prose-block mb-10">
|
||||||
|
|||||||
+14
-10
@@ -332,20 +332,16 @@
|
|||||||
<div class="container-content relative z-10 text-center">
|
<div class="container-content relative z-10 text-center">
|
||||||
<!-- Logo SVG vectoriel — inline pour currentColor -->
|
<!-- Logo SVG vectoriel — inline pour currentColor -->
|
||||||
<div class="evt-logo-wrap mx-auto mb-6" aria-hidden="true">
|
<div class="evt-logo-wrap mx-auto mb-6" aria-hidden="true">
|
||||||
<svg viewBox="0 0 64 80" fill="none" class="evt-logo">
|
<UiSectionSign class="evt-logo" />
|
||||||
<path d="M 28 6 C 44 2,52 12,47 14 C 40 18,16 18,12 28 C 8 38,16 50,28 54 C 38 56,52 60,52 68 C 52 76,42 78,34 74"
|
|
||||||
stroke="currentColor" stroke-width="16" stroke-linecap="round" stroke-linejoin="round" opacity="0.12"/>
|
|
||||||
<path d="M 28 6 C 44 2,52 12,47 14 C 40 18,16 18,12 28 C 8 38,16 50,28 54 C 38 56,52 60,52 68 C 52 76,42 78,34 74"
|
|
||||||
stroke="currentColor" stroke-width="9" stroke-linecap="round" stroke-linejoin="round" opacity="0.38"/>
|
|
||||||
<path d="M 28 6 C 44 2,52 12,47 14 C 40 18,16 18,12 28 C 8 38,16 50,28 54 C 38 56,52 60,52 68 C 52 76,42 78,34 74"
|
|
||||||
stroke="currentColor" stroke-width="5" stroke-linecap="round" stroke-linejoin="round" opacity="0.92"/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p class="mb-3 font-mono text-sm tracking-widest text-accent uppercase">{{ content?.kicker }}</p>
|
<p class="mb-3 font-mono text-sm tracking-widest text-accent uppercase">{{ content?.kicker }}</p>
|
||||||
<h1 class="page-title font-display font-extrabold tracking-tight" style="color: hsl(var(--color-text))">
|
<h1 class="page-title font-display font-extrabold tracking-tight" style="color: hsl(var(--color-text))">
|
||||||
{{ content?.title }}
|
{{ content?.title }}
|
||||||
</h1>
|
</h1>
|
||||||
|
<p v-if="evtContent?.format" class="evt-format font-display">
|
||||||
|
{{ evtContent.format }}
|
||||||
|
</p>
|
||||||
<p v-if="evtContent?.subtitle" class="mt-2 text-base italic" style="color: hsl(var(--color-text-muted))">
|
<p v-if="evtContent?.subtitle" class="mt-2 text-base italic" style="color: hsl(var(--color-text-muted))">
|
||||||
{{ evtContent.subtitle }}
|
{{ evtContent.subtitle }}
|
||||||
</p>
|
</p>
|
||||||
@@ -447,6 +443,14 @@ useSeoPage({
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
.evt-format {
|
||||||
|
margin-top: 0.35rem;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: clamp(1.25rem, 3vw, 1.75rem);
|
||||||
|
letter-spacing: -0.02em;
|
||||||
|
color: hsl(var(--axis-cit));
|
||||||
|
}
|
||||||
|
|
||||||
.page-title {
|
.page-title {
|
||||||
font-size: clamp(2.5rem, 6vw, 3.5rem);
|
font-size: clamp(2.5rem, 6vw, 3.5rem);
|
||||||
}
|
}
|
||||||
@@ -657,12 +661,12 @@ useSeoPage({
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
color: hsl(var(--color-primary));
|
color: hsl(var(--color-primary));
|
||||||
filter: drop-shadow(0 0 14px hsl(var(--color-primary) / 0.25));
|
filter: drop-shadow(0 6px 14px hsl(var(--color-primary) / 0.25));
|
||||||
}
|
}
|
||||||
|
|
||||||
.evt-logo {
|
.evt-logo {
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
width: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Badge gestation hero ── */
|
/* ── Badge gestation hero ── */
|
||||||
|
|||||||
+1
-30
@@ -1,27 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<HomeVariantParcours
|
<HomeLanding @open-player="showBookPlayer = true" @open-pdf="showPdfReader = true" />
|
||||||
v-if="variant === 'b'"
|
|
||||||
@open-player="showBookPlayer = true"
|
|
||||||
@open-pdf="showPdfReader = true"
|
|
||||||
/>
|
|
||||||
<HomeVariantEtabli
|
|
||||||
v-else-if="variant === 'c'"
|
|
||||||
@open-player="showBookPlayer = true"
|
|
||||||
@open-pdf="showPdfReader = true"
|
|
||||||
/>
|
|
||||||
<template v-else-if="variant === '0'">
|
|
||||||
<HomeHeroSection />
|
|
||||||
<HomeAxisGrid @open-player="showBookPlayer = true" @open-pdf="showPdfReader = true" />
|
|
||||||
</template>
|
|
||||||
<HomeVariantConsole
|
|
||||||
v-else
|
|
||||||
@open-player="showBookPlayer = true"
|
|
||||||
@open-pdf="showPdfReader = true"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<HomeMessages />
|
<HomeMessages />
|
||||||
<HomeVariantSwitch v-if="showSwitch" :current="variant" />
|
|
||||||
<BookPlayer v-model="showBookPlayer" />
|
<BookPlayer v-model="showBookPlayer" />
|
||||||
<BookPdfReader v-model="showPdfReader" />
|
<BookPdfReader v-model="showPdfReader" />
|
||||||
</div>
|
</div>
|
||||||
@@ -33,15 +13,6 @@ useSeoPage({
|
|||||||
description: 'Construire une autonomie collective à l\'échelle des bassins de vie. Un livre, des chansons et des outils pour l\'émancipation.',
|
description: 'Construire une autonomie collective à l\'échelle des bassins de vie. Un livre, des chansons et des outils pour l\'émancipation.',
|
||||||
})
|
})
|
||||||
|
|
||||||
// Home variants under review: ?v=a (console, default) · ?v=b (parcours) · ?v=c (établi) · ?v=0 (previous home)
|
|
||||||
const route = useRoute()
|
|
||||||
const VARIANTS = ['a', 'b', 'c', '0'] as const
|
|
||||||
const variant = computed(() => {
|
|
||||||
const v = String(route.query.v ?? 'a')
|
|
||||||
return (VARIANTS as readonly string[]).includes(v) ? v : 'a'
|
|
||||||
})
|
|
||||||
const showSwitch = computed(() => route.query.v !== undefined)
|
|
||||||
|
|
||||||
const showBookPlayer = ref(false)
|
const showBookPlayer = ref(false)
|
||||||
const showPdfReader = ref(false)
|
const showPdfReader = ref(false)
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -247,10 +247,19 @@
|
|||||||
<p style="color: hsl(var(--color-text-muted))" class="leading-relaxed">
|
<p style="color: hsl(var(--color-text-muted))" class="leading-relaxed">
|
||||||
{{ content.project.text }}
|
{{ content.project.text }}
|
||||||
</p>
|
</p>
|
||||||
<span v-if="content?.gestation" class="gestation-badge mt-3">
|
<UiStatusBadge
|
||||||
<div class="i-lucide-flask-conical h-3 w-3" />
|
class="mt-3"
|
||||||
En gestation
|
:status="content.project.status"
|
||||||
</span>
|
:gestation="content.project.gestation || content?.gestation"
|
||||||
|
:since="content.project.since"
|
||||||
|
/>
|
||||||
|
<div v-if="content.project.action?.href" class="project-action mt-4">
|
||||||
|
<UiBaseButton :href="content.project.action.href" target="_blank">
|
||||||
|
<div class="i-lucide-external-link mr-2 h-4 w-4" />
|
||||||
|
{{ content.project.action.label }}
|
||||||
|
</UiBaseButton>
|
||||||
|
<span v-if="content.project.action.note" class="project-action-note">{{ content.project.action.note }}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -477,7 +486,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Bouquet sweethomeCloud -->
|
<!-- Bouquet sweetHome -->
|
||||||
<div v-if="section.bouquet" class="territoire-bouquet">
|
<div v-if="section.bouquet" class="territoire-bouquet">
|
||||||
<h3 class="territoire-bouquet-title">
|
<h3 class="territoire-bouquet-title">
|
||||||
<div class="i-lucide-package h-4 w-4" />
|
<div class="i-lucide-package h-4 w-4" />
|
||||||
@@ -621,13 +630,6 @@
|
|||||||
|
|
||||||
</div><!-- /sections-area -->
|
</div><!-- /sections-area -->
|
||||||
|
|
||||||
<div v-if="slug === 'logiciel-libre'" class="text-center mt-6">
|
|
||||||
<UiBaseButton href="https://axiom-team.fr/dons" target="_blank">
|
|
||||||
<div class="i-lucide-external-link mr-2 h-4 w-4" />
|
|
||||||
Voir la v1 bridée (premier flux)
|
|
||||||
</UiBaseButton>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="slug === 'tarifs-eau'" class="text-center mt-6">
|
<div v-if="slug === 'tarifs-eau'" class="text-center mt-6">
|
||||||
<UiBaseButton :href="sejeteral0Url" target="_blank">
|
<UiBaseButton :href="sejeteral0Url" target="_blank">
|
||||||
<div class="i-lucide-external-link mr-2 h-4 w-4" />
|
<div class="i-lucide-external-link mr-2 h-4 w-4" />
|
||||||
@@ -1650,6 +1652,19 @@ useSeoPage({
|
|||||||
color: hsl(var(--color-primary) / 0.5);
|
color: hsl(var(--color-primary) / 0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.project-action {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-action-note {
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
font-size: 0.72rem;
|
||||||
|
color: hsl(var(--axis-cit));
|
||||||
|
}
|
||||||
|
|
||||||
/* Bouquet — status of each stack */
|
/* Bouquet — status of each stack */
|
||||||
.territoire-bouquet-provider {
|
.territoire-bouquet-provider {
|
||||||
font-size: 0.8rem;
|
font-size: 0.8rem;
|
||||||
|
|||||||
@@ -319,7 +319,7 @@
|
|||||||
<NuxtLink v-if="lpk" :to="lpk.to" class="lpk-card">
|
<NuxtLink v-if="lpk" :to="lpk.to" class="lpk-card">
|
||||||
<span class="lpk-name">{{ lpk.name }}</span>
|
<span class="lpk-name">{{ lpk.name }}</span>
|
||||||
<span class="lpk-body">
|
<span class="lpk-body">
|
||||||
<span class="lpk-full">{{ lpk.fullName }} — {{ lpk.text }}</span>
|
<span class="lpk-full">{{ lpk.text }}</span>
|
||||||
<span class="lpk-role">{{ lpk.role }}</span>
|
<span class="lpk-role">{{ lpk.role }}</span>
|
||||||
</span>
|
</span>
|
||||||
<span class="i-lucide-arrow-right h-5 w-5 lpk-arrow" aria-hidden="true" />
|
<span class="i-lucide-arrow-right h-5 w-5 lpk-arrow" aria-hidden="true" />
|
||||||
@@ -342,10 +342,7 @@
|
|||||||
<h2 class="font-display text-xl font-bold" style="color: hsl(var(--color-text))">
|
<h2 class="font-display text-xl font-bold" style="color: hsl(var(--color-text))">
|
||||||
{{ pillar.label }}
|
{{ pillar.label }}
|
||||||
</h2>
|
</h2>
|
||||||
<span v-if="pillar.gestation" class="gestation-badge">
|
<UiStatusBadge class="ml-auto" :status="pillar.status" :gestation="pillar.gestation" :since="pillar.since" />
|
||||||
<div class="i-lucide-flask-conical h-3 w-3" />
|
|
||||||
En gestation
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p class="leading-relaxed whitespace-pre-line mt-3" style="color: hsl(var(--color-text-muted))">{{ pillar.text }}</p>
|
<p class="leading-relaxed whitespace-pre-line mt-3" style="color: hsl(var(--color-text-muted))">{{ pillar.text }}</p>
|
||||||
@@ -376,6 +373,7 @@
|
|||||||
<div :class="`i-lucide-${action.icon}`" class="h-3.5 w-3.5" />
|
<div :class="`i-lucide-${action.icon}`" class="h-3.5 w-3.5" />
|
||||||
{{ action.label }}
|
{{ action.label }}
|
||||||
</a>
|
</a>
|
||||||
|
<span v-for="action in (pillar as any).actions.filter((a: any) => a.note)" :key="`n-${action.label}`" class="pillar-action-note">{{ action.note }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -439,6 +437,13 @@ useSeoPage({
|
|||||||
line-height: 1.45;
|
line-height: 1.45;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.pillar-action-note {
|
||||||
|
align-self: center;
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
font-size: 0.7rem;
|
||||||
|
color: hsl(var(--axis-cit));
|
||||||
|
}
|
||||||
|
|
||||||
.lpk-arrow { color: hsl(var(--color-primary)); flex-shrink: 0; }
|
.lpk-arrow { color: hsl(var(--color-primary)); flex-shrink: 0; }
|
||||||
|
|
||||||
@media (prefers-reduced-motion: reduce) {
|
@media (prefers-reduced-motion: reduce) {
|
||||||
@@ -520,6 +525,7 @@ useSeoPage({
|
|||||||
|
|
||||||
.pillar-header {
|
.pillar-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.75rem;
|
gap: 0.75rem;
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-1
@@ -91,10 +91,11 @@ export default defineNuxtConfig({
|
|||||||
head: {
|
head: {
|
||||||
htmlAttrs: { lang: 'fr' },
|
htmlAttrs: { lang: 'fr' },
|
||||||
link: [
|
link: [
|
||||||
|
{ rel: 'icon', type: 'image/svg+xml', href: '/favicon.svg' },
|
||||||
{ rel: 'preconnect', href: 'https://fonts.bunny.net' },
|
{ rel: 'preconnect', href: 'https://fonts.bunny.net' },
|
||||||
{
|
{
|
||||||
rel: 'stylesheet',
|
rel: 'stylesheet',
|
||||||
href: 'https://fonts.bunny.net/css?family=bricolage-grotesque:400,500,600,700,800|atkinson-hyperlegible:400,400i,700,700i|jetbrains-mono:400,500,700|syne:400',
|
href: 'https://fonts.bunny.net/css?family=bricolage-grotesque:400,500,600,700,800|public-sans:400,400i,600,700,700i|jetbrains-mono:400,500,700|syne:400',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
meta: [
|
meta: [
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-230 0 970 970">
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="g" x1="0" y1="0" x2="0" y2="1">
|
||||||
|
<stop offset="0%" stop-color="#d4520f"/>
|
||||||
|
<stop offset="100%" stop-color="#e02b41"/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<path fill="url(#g)" d="M277 0Q320 0 349 7.5Q378 15 395 22Q426 37 443.5 59.5Q461 82 461 115Q461 143 441.5 164Q422 185 387 185Q354 185 332.5 165.5Q311 146 311 116Q311 92 324.5 71Q338 50 362 42Q357 34 344.5 29Q332 24 316 21.5Q300 19 284 19Q246 19 223 32.5Q200 46 190 66.5Q180 87 180 110Q180 150 200 177.5Q220 205 256 230Q292 255 340 287Q394 324 432.5 354.5Q471 385 491 418Q511 451 511 495Q511 548 475.5 593Q440 638 352 677L343 667Q373 646 388 623Q403 600 403 567Q403 536 386 511.5Q369 487 333.5 459Q298 431 241 394Q178 353 135.5 318.5Q93 284 71.5 248Q50 212 50 167Q50 116 78.5 78.5Q107 41 158.5 20.5Q210 0 277 0ZM158 292 168 303Q137 323 122.5 346Q108 369 108 403Q108 433 124.5 458Q141 483 177 511Q213 539 270 576Q332 617 374.5 651.5Q417 686 439 722Q461 758 461 803Q461 853 432 891Q403 929 352 949.5Q301 970 233 970Q190 970 161 962.5Q132 955 115 948Q85 933 67 910.5Q49 888 49 855Q49 827 69 806Q89 785 123 785Q157 785 178 804.5Q199 824 199 854Q199 878 185.5 898.5Q172 919 148 928Q152 935 165 940.5Q178 946 194.5 948.5Q211 951 226 951Q265 951 287.5 937.5Q310 924 320.5 903Q331 882 331 860Q331 820 311 792.5Q291 765 255 740Q219 715 171 683Q116 646 78 615.5Q40 585 20 551.5Q0 518 0 475Q0 421 34.5 376Q69 331 158 292Z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.4 KiB |
Reference in New Issue
Block a user