feat(marketing): refonte narrative 'Comment ça marche' — processus 4 étapes step-by-step (capture → IA → distribution → gouvernance)

Au lieu d'un cycle générique 1→12, l'animation suit maintenant un FLOW PROCESSUS
canonique qui raconte l'histoire produit du début à la fin :

  ÉTAPE 1 · CAPTURE          → Recording (7) + Transcription (1)
  ÉTAPE 2 · TRANSFORMATION IA → Diarisation (2) + Langues (3) + Résumé (9) + Chat IA (0)
  ÉTAPE 3 · DISTRIBUTION     → Exports (4) + Partage (6) + Users (5) + Intégrations (10)
  ÉTAPE 4 · GOUVERNANCE      → Recherche IA (8) + Audit (11) + Conformité (12)

PROCESS_ORDER = [7, 1, 2, 3, 9, 0, 4, 6, 5, 10, 8, 11, 12] — 13 sub-modes,
mode 0 IA inclus dans le cycle (au lieu d'être skippé), 1100ms × 13 ≈ 14.3s
tour complet.

Composants ajoutés / refondus :
- PROCESS BREADCRUMB visible au-dessus du phone (4 pilules · flèches · past/active/future)
- PROGRESS BAR sous breadcrumb (gradient brand-b1 → b2 → b3, role=progressbar)
- BOTTOM TAB BAR : 4 boutons étapes (au lieu de 6 features individuelles)
- RIGHT PANEL : 4 mini-sections par étape (12 boutons regroupés selon flow)
- FEATURE INFO CARD : préfixe 'Étape X/4 · TITRE' pour contexte processus
- CONNECTING LINE : flèche directionnelle + dot animé qui se déplace selon processIdx
- Mobile pills : suivent PROCESS_ORDER (13 sub-modes au lieu de 1-12)

Alpine refactor :
- selectedFeature devient un getter computed sur PROCESS_ORDER[processIdx]
- nouveau STEPS array avec subModes mapping + activeStep / progressPercent getters
- goToStep(id) helper jump au 1er sub-mode d'une étape
- handleManualSelect(i) résout l'index dans PROCESS_ORDER

Préservé : palette brand stricte (b1/b2/b3/navy), V3 radii, Inter+JetBrains Mono,
phone shell statique, 13 templates de modes, IA Mistral premium card, eyebrow
'COMMENT ÇA MARCHE' brand-navy, WCAG (aria-current, aria-label, role=navigation,
role=progressbar, role=tablist), prefers-reduced-motion guards.

Tests : 9/9 fonctionnalites passent (test how_it_works_reactor étendu de +37 assertions
narratives : PROCESS_ORDER, STEPS, processIdx, activeStep, goToStep, breadcrumb,
progressbar, step-bottom, step-section, contexte processus dans card).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Allison
2026-04-29 13:31:30 -04:00
parent a14bcb9a1a
commit 224e51cc81
2 changed files with 238 additions and 63 deletions

View File

@@ -408,6 +408,20 @@
.dictia-feat-btn { transition: transform 0.18s ease, background-color 0.2s ease, opacity 0.2s ease; will-change: transform; }
.dictia-feat-btn:hover { transform: scale(1.08); opacity: 1 !important; }
/* Process breadcrumb step buttons (top of section, light bg) */
.dictia-step-btn { transition: transform 0.2s ease, background-color 0.25s ease, border-color 0.25s ease; will-change: transform; }
.dictia-step-btn:hover { transform: translateY(-1px); }
.dictia-step-btn.is-past { opacity: 0.55; }
.dictia-step-btn.is-future { opacity: 0.40; }
.dictia-step-btn.is-active { transform: scale(1.03); }
/* Step navigator bottom (dark bg, inside phone) */
.dictia-step-bottom:hover { transform: translateY(-1px); }
/* Step section right panel (mini groups by stage) */
.dictia-step-section { transition: border-color 0.3s ease, background-color 0.3s ease, box-shadow 0.3s ease; }
.dictia-step-section.is-active { box-shadow: 0 0 24px rgba(0,0,0,0.25), inset 0 0 16px rgba(255,255,255,0.02); }
/* Hide scrollbar mobile pills */
.dictia-hide-scrollbar::-webkit-scrollbar { display: none; }
@@ -452,6 +466,7 @@
.dictia-phone-glow-ring, .dictia-rec-dot, .dictia-wave-bar,
.dictia-typing-dot, .dictia-lang-highlight, .dictia-connecting-line,
.dictia-auto-pulse, .dictia-countdown-fill { animation: none !important; }
.dictia-step-btn, .dictia-step-bottom, .dictia-step-section { transition: none !important; }
}
</style>
@@ -496,23 +511,93 @@
</div>
</div>
{# ── DASHBOARD HOLOGRAPHIQUE — 2 colonnes (phone center + IA right) ── #}
<div x-data="dictiaDashboard()" x-init="init()"
class="w-full flex flex-col lg:flex-row lg:justify-center lg:items-start gap-6 lg:gap-10 relative">
{# ── DASHBOARD HOLOGRAPHIQUE — wrapper outer column (process breadcrumb + progress + 2-col layout) ── #}
<div x-data="dictiaDashboard()" x-init="init()" class="w-full relative">
{# Connecting line SVG (phone -> IA panel, desktop only) #}
{# ─────────────── PROCESS BREADCRUMB — 4 étapes du flow utilisateur (refonte narrative 2026-04-29 v3) ─────────────── #}
<nav class="dictia-process-breadcrumb mb-3 mx-auto max-w-3xl"
role="navigation" aria-label="Étapes du processus DictIA">
<ol class="flex items-stretch justify-center gap-1.5 sm:gap-2 px-2">
<template x-for="(step, sIdx) in STEPS" :key="step.id">
<li class="flex items-center flex-1 min-w-0">
<button type="button" @click="goToStep(step.id)"
class="dictia-step-btn group relative flex flex-col items-start justify-center w-full px-2 sm:px-3 py-2 rounded transition-all outline-none focus-visible:ring-2 focus-visible:ring-brand-b1 text-left overflow-hidden"
:class="{
'is-active': activeStep === step.id,
'is-past': activeStep > step.id,
'is-future': activeStep < step.id
}"
:style="`border:1px solid ${activeStep === step.id ? step.color + '70' : 'rgba(0,0,0,0.10)'}; background-color:${activeStep === step.id ? step.color + '14' : 'rgba(255,255,255,0.70)'};`"
:aria-current="activeStep === step.id ? 'step' : false"
:aria-label="`Étape ${step.id} : ${step.title}`">
<div class="flex items-center gap-1.5 w-full min-w-0">
<span class="font-mono font-black text-[10px] tracking-widest flex-shrink-0"
:style="`color:${activeStep === step.id ? step.color : 'rgba(0,0,0,0.35)'};`"
x-text="`0${step.id}`"></span>
<span class="font-bold text-[11px] sm:text-xs leading-tight truncate"
:style="`color:${activeStep === step.id ? 'rgb(2,6,23)' : 'rgba(0,0,0,0.55)'};`"
x-text="step.title"></span>
</div>
<span class="font-mono text-[9px] uppercase tracking-wider mt-0.5 hidden sm:block"
:style="`color:${activeStep === step.id ? step.color : 'rgba(0,0,0,0.40)'};`"
x-text="step.subtitle"></span>
{# Active indicator bottom border 2px #}
<span x-show="activeStep === step.id" class="absolute left-0 right-0 bottom-0" aria-hidden="true"
:style="`height:2px;background:${step.color};box-shadow:0 0 8px ${step.color}80;`"></span>
</button>
{# Arrow between steps (skip last) #}
<template x-if="sIdx < STEPS.length - 1">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"
class="hidden sm:block flex-shrink-0 mx-0.5 text-brand-navy/30" style="width:14px;height:14px;" aria-hidden="true">
<polyline points="9 18 15 12 9 6"/>
</svg>
</template>
</li>
</template>
</ol>
{# PROGRESS BAR — gradient brand-b1 → b2 → b3, width selon processIdx (13 sub-modes) #}
<div class="mx-auto mt-2 max-w-3xl px-2">
<div class="flex items-center justify-between mb-1">
<span class="font-mono text-[9px] uppercase tracking-widest text-brand-navy/50">
Progression du flow
</span>
<span class="font-mono text-[9px] font-bold text-brand-navy/65">
<span x-text="processIdx + 1"></span> / <span x-text="PROCESS_ORDER.length"></span>
</span>
</div>
<div class="w-full rounded-full overflow-hidden" style="height:2px;background:rgba(2,6,23,0.08);"
role="progressbar" :aria-valuenow="processIdx + 1" aria-valuemin="1" :aria-valuemax="PROCESS_ORDER.length">
<div class="h-full rounded-full transition-all duration-700 ease-out"
style="background:linear-gradient(90deg,#2563eb 0%,#06b6d4 50%,#c026d3 100%);box-shadow:0 0 8px rgba(192,38,211,0.35);"
:style="`width:${progressPercent}%;`"></div>
</div>
</div>
</nav>
{# ── 2 colonnes (phone center + IA right) ── #}
<div class="w-full flex flex-col lg:flex-row lg:justify-center lg:items-start gap-6 lg:gap-10 relative">
{# Connecting line SVG (phone -> IA panel, desktop only) — directional arrow with travelling dot #}
<div class="dictia-connecting-line-wrap absolute pointer-events-none hidden lg:block"
style="top:200px;left:50%;transform:translateX(-50%);width:160px;height:2px;z-index:1;"
style="top:200px;left:50%;transform:translateX(-50%);width:160px;height:12px;z-index:1;"
aria-hidden="true">
<svg width="160" height="2" viewBox="0 0 160 2" preserveAspectRatio="none">
<svg width="160" height="12" viewBox="0 0 160 12" preserveAspectRatio="none" overflow="visible">
<defs>
<linearGradient id="dictia-connecting-grad" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" stop-color="#06b6d4" stop-opacity="0.6"/>
<stop offset="100%" stop-color="#c026d3" stop-opacity="0.6"/>
</linearGradient>
</defs>
<line x1="0" y1="1" x2="160" y2="1" stroke="url(#dictia-connecting-grad)"
<line x1="0" y1="6" x2="148" y2="6" stroke="url(#dictia-connecting-grad)"
stroke-width="1.5" stroke-dasharray="4 4" class="dictia-connecting-line"/>
{# Arrow head pointing right (phone → right panel direction) #}
<polyline points="148,2 156,6 148,10" fill="none" stroke="#c026d3" stroke-opacity="0.7"
stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
{# Travelling dot — animated position based on processIdx #}
<circle r="2.5" cy="6" fill="#c026d3"
:cx="6 + (140 * processIdx / (PROCESS_ORDER.length - 1))"
style="filter:drop-shadow(0 0 4px #c026d3); transition:cx 0.7s ease-out;"/>
</svg>
</div>
@@ -1200,27 +1285,28 @@
</div>
{# BOTTOM : 6 boutons feature originaux (Trans / Diari / Lang / Exp / Users / Part) + Auto pill — hauteur fixe 90px #}
{# BOTTOM : Step navigator — 4 boutons étapes (au lieu de 6 features individuelles) + Auto pill — refonte narrative 2026-04-29 v3 #}
<div class="relative flex flex-col items-center justify-center gap-1.5 z-10 flex-shrink-0"
style="height: 90px; border-top: 1px solid rgba(255,255,255,0.06); background: linear-gradient(180deg, transparent, rgba(0,0,0,0.32));">
{# 6 boutons features originaux (FEATURES indexes 1-6) — visible bottom tab bar #}
<div class="flex items-end gap-1.5 justify-center">
<template x-for="i in [1,2,3,4,5,6]" :key="i">
<button type="button" @click="handleManualSelect(i)"
class="dictia-feat-btn outline-none cursor-pointer focus-visible:ring-2 focus-visible:ring-white/40 flex flex-col items-center justify-end relative"
style="width:34px;height:42px;border:none;padding:4px 0 6px;background:transparent;"
:aria-pressed="selectedFeature === i ? 'true' : 'false'"
:aria-label="`Fonction : ${FEATURES[i].title}`">
<span :style="`color: ${selectedFeature === i ? FEATURES[i].color : 'rgba(255,255,255,0.40)'}; transition: color 0.2s, transform 0.15s, filter 0.2s; filter: ${selectedFeature === i ? 'drop-shadow(0 0 6px ' + FEATURES[i].color + 'CC)' : 'none'}; transform: scale(${selectedFeature === i ? 1.15 : 1}); display: inline-block;`">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="width:16px;height:16px;" aria-hidden="true" x-html="featureIconPath(i)"></svg>
</span>
<span class="text-[9px] font-mono font-bold mt-0.5 leading-none uppercase tracking-wider"
:style="`color: ${selectedFeature === i ? FEATURES[i].color : 'rgba(255,255,255,0.55)'};`"
x-text="featureShortLabel(i)"></span>
{# 4 step buttons (mapped to STEPS) — bottom step navigator #}
<div class="flex items-end gap-1 justify-center" role="tablist" aria-label="Étapes du processus">
<template x-for="step in STEPS" :key="step.id">
<button type="button" @click="goToStep(step.id)"
class="dictia-feat-btn dictia-step-bottom outline-none cursor-pointer focus-visible:ring-2 focus-visible:ring-white/40 flex flex-col items-center justify-end relative"
style="min-width:48px;height:42px;border:none;padding:4px 6px 6px;background:transparent;"
:aria-pressed="activeStep === step.id ? 'true' : 'false'"
:aria-current="activeStep === step.id ? 'step' : false"
:aria-label="`Étape ${step.id} : ${step.title}`">
<span class="font-mono font-black text-[10px] tracking-widest leading-none"
:style="`color: ${activeStep === step.id ? step.color : 'rgba(255,255,255,0.40)'}; transition: color 0.2s; filter: ${activeStep === step.id ? 'drop-shadow(0 0 6px ' + step.color + 'CC)' : 'none'};`"
x-text="`0${step.id}`"></span>
<span class="text-[9px] font-mono font-bold mt-1 leading-none uppercase tracking-wider"
:style="`color: ${activeStep === step.id ? step.color : 'rgba(255,255,255,0.55)'};`"
x-text="step.short"></span>
{# Tab indicator bottom border 2px #}
<span x-show="selectedFeature === i" class="absolute" aria-hidden="true"
:style="`bottom: 0; left: 4px; right: 4px; height: 2px; background: ${FEATURES[i].color}; border-radius: 1px; box-shadow: 0 0 8px ${FEATURES[i].color};`"></span>
<span x-show="activeStep === step.id" class="absolute" aria-hidden="true"
:style="`bottom: 0; left: 4px; right: 4px; height: 2px; background: ${step.color}; border-radius: 1px; box-shadow: 0 0 8px ${step.color};`"></span>
</button>
</template>
</div>
@@ -1231,12 +1317,12 @@
<span class="inline-flex items-center gap-1.5 px-2.5 py-0.5 rounded-full"
style="background:rgba(16,185,129,0.12);border:1px solid rgba(16,185,129,0.30);">
<span class="dictia-auto-pulse block rounded-full" style="width:5px;height:5px;background:#10b981;"></span>
<span class="font-mono text-[9px] font-bold tracking-widest uppercase" style="color:#10b981;">Auto</span>
<span class="font-mono text-[9px] font-bold tracking-widest uppercase" style="color:#10b981;">Auto · 14&thinsp;s</span>
</span>
</template>
<template x-if="isManual">
<span class="inline-flex flex-col items-center gap-0.5">
<span class="font-mono text-[9px] text-white/60">Auto reprend bientôt</span>
<span class="font-mono text-[9px] text-white/60">Manuel · Auto reprend bientôt</span>
<span class="rounded-full overflow-hidden" style="width:56px;height:2px;background:rgba(255,255,255,0.12);">
<span class="block dictia-countdown-fill h-full" :key="selectedFeature" style="background:linear-gradient(90deg,#06b6d4,#c026d3);"></span>
</span>
@@ -1247,20 +1333,26 @@
</div>{# /dictia-phone-shell #}
</div>{# /phone wrapper (glow ring) #}
{# Feature info card sous le phone — fond bg-brand-navy SOLIDE (extension visuelle du phone, contraste WCAG AA garanti sur section claire) + accent border-left tab indicator #}
<div style="min-height:64px;width:280px;max-width:100%;" class="mt-3 mx-auto">
{# Feature info card sous le phone — affiche contexte processus (étape + sub-mode) — bg-brand-navy SOLIDE + accent border-left #}
<div style="min-height:80px;width:280px;max-width:100%;" class="mt-3 mx-auto">
<div class="dictia-feature-card rounded-xl px-4 py-3 relative bg-brand-navy border border-white/10 overflow-hidden"
:style="`box-shadow: 0 6px 24px rgba(0,0,0,0.20);`">
{# Border-left 3px accent — style tab indicator #}
<span class="absolute left-0 top-0 bottom-0" aria-hidden="true"
:style="`width:3px;background:${activeColor};box-shadow:0 0 8px ${activeColor}80;`"></span>
{# Badge top-right #}
<template x-if="FEATURES[displayMode].badge">
<span class="absolute text-[10px] px-1.5 py-0.5 rounded font-mono font-bold tracking-wider"
style="top:8px;right:8px;"
:style="`background-color: ${activeColor}22; color: ${activeColor}; border: 1px solid ${activeColor}55;`"
x-text="FEATURES[displayMode].badge"></span>
</template>
{# Process context header : ÉTAPE X/4 · TITRE — small breadcrumb pill #}
<div class="flex items-center justify-between gap-2 mb-2">
<span class="font-mono text-[9px] font-bold uppercase tracking-widest"
:style="`color:${STEPS[activeStep - 1].color};`">
Étape <span x-text="activeStep"></span>/4 · <span x-text="STEPS[activeStep - 1].title"></span>
</span>
{# Badge top-right (sub-mode) #}
<template x-if="FEATURES[displayMode].badge">
<span class="text-[9px] px-1.5 py-0.5 rounded font-mono font-bold tracking-wider"
:style="`background-color: ${activeColor}22; color: ${activeColor}; border: 1px solid ${activeColor}55;`"
x-text="FEATURES[displayMode].badge"></span>
</template>
</div>
<div class="flex items-start gap-2.5">
{# Icon container 32×32 #}
<span class="rounded flex items-center justify-center flex-shrink-0"
@@ -1271,7 +1363,7 @@
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="width:16px;height:16px;" x-html="featureIconPath(displayMode)"></svg>
</span>
</span>
<div class="flex-1 min-w-0 pr-12">
<div class="flex-1 min-w-0">
<p class="text-sm font-bold text-white leading-tight mb-0.5" x-text="FEATURES[displayMode].title"></p>
<p class="text-[11px] leading-snug text-white/65" x-text="FEATURES[displayMode].subtitle"></p>
</div>
@@ -1279,10 +1371,10 @@
</div>
</div>
{# Mobile : pills features horizontales scrollables (12 fonctions, indexes 1-12) #}
{# Mobile : pills features horizontales scrollables — suivent PROCESS_ORDER (13 sub-modes) #}
<div class="lg:hidden w-full overflow-x-auto dictia-hide-scrollbar" style="scrollbar-width:none;">
<div class="flex gap-2 px-1 pb-1" style="width:max-content;">
<template x-for="i in [1,2,3,4,5,6,7,8,9,10,11,12]" :key="i">
<template x-for="i in PROCESS_ORDER" :key="i">
<button type="button" @click="handleManualSelect(i)"
class="flex items-center gap-1.5 px-2.5 py-1.5 rounded-full shrink-0 transition-all focus-visible:ring-2 focus-visible:ring-brand-b1"
:style="`border: 1px solid ${selectedFeature === i ? FEATURES[i].color + '70' : 'rgba(0,0,0,0.10)'}; background-color: ${selectedFeature === i ? FEATURES[i].color + '18' : 'rgba(0,0,0,0.04)'}; outline: none;`"
@@ -1313,7 +1405,7 @@
</div>
</div>
<span class="text-[10px] font-mono font-bold px-2 py-0.5 rounded-full text-brand-b3 whitespace-nowrap"
style="background-color:rgba(192,38,211,0.10);border:1px solid rgba(192,38,211,0.25);">12 fonctions</span>
style="background-color:rgba(192,38,211,0.10);border:1px solid rgba(192,38,211,0.25);">12 fonctions · 4 étapes</span>
</div>
{# IA Mistral 7B premium card — couleurs brand-b3 (fuchsia officiel) #}
@@ -1409,26 +1501,49 @@
</div>
</div>
{# Feature grid 3×4 — 12 fonctions (FEATURES indexes 1-12, IA mode 0 accessible via Brain card ci-dessus) #}
<div class="grid grid-cols-3 gap-2">
<template x-for="i in [1,2,3,4,5,6,7,8,9,10,11,12]" :key="i">
<button type="button" @click="handleManualSelect(i)"
class="flex flex-col items-center gap-1.5 rounded-xl py-2.5 px-2 outline-none transition-all focus-visible:ring-2 focus-visible:ring-white/40 text-center"
:style="`border: 1px solid ${selectedFeature === i ? FEATURES[i].color + '70' : 'rgba(255,255,255,0.10)'}; background-color: ${selectedFeature === i ? 'rgba(8,12,24,0.95)' : 'rgba(8,12,24,0.85)'}; box-shadow: ${selectedFeature === i ? '0 0 16px ' + FEATURES[i].color + '40, inset 0 0 16px ' + FEATURES[i].color + '20' : 'none'}; cursor: pointer; min-height:64px;`"
:aria-pressed="selectedFeature === i ? 'true' : 'false'"
:aria-label="`Fonction : ${FEATURES[i].title}`">
<span :style="`color: ${selectedFeature === i ? FEATURES[i].color : 'rgba(255,255,255,0.55)'}; filter: ${selectedFeature === i ? 'drop-shadow(0 0 6px ' + FEATURES[i].color + 'BB)' : 'none'}; transition: all 0.2s; transform: scale(${selectedFeature === i ? 1.15 : 1}); display:inline-block;`">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="width:18px;height:18px;" aria-hidden="true" x-html="featureIconPath(i)"></svg>
</span>
<span class="text-[10px] font-bold leading-tight w-full truncate"
:style="`color: ${selectedFeature === i ? 'rgba(255,255,255,0.98)' : 'rgba(255,255,255,0.85)'};`"
x-text="featureGridLabel(i)"></span>
</button>
{# Feature grid REFONTE NARRATIVE — 4 mini-sections par étape (12 boutons regroupés selon flow) #}
<div class="flex flex-col gap-3">
<template x-for="step in STEPS" :key="step.id">
<div class="dictia-step-section rounded-xl p-2.5"
:class="activeStep === step.id ? 'is-active' : ''"
:style="`border:1px solid ${activeStep === step.id ? step.color + '60' : 'rgba(255,255,255,0.08)'}; background-color:${activeStep === step.id ? 'rgba(8,12,24,0.92)' : 'rgba(8,12,24,0.65)'}; transition:all 0.3s ease;`"
:aria-current="activeStep === step.id ? 'step' : false">
{# Mini header : badge numéro + titre #}
<div class="flex items-center gap-2 mb-2 px-1">
<span class="rounded-full flex items-center justify-center flex-shrink-0 font-mono font-black text-[10px]"
style="width:18px;height:18px;"
:style="`background:linear-gradient(135deg,${step.color}30,${step.color}14);border:1px solid ${step.color}60;color:${step.color};`"
aria-hidden="true"
x-text="step.id"></span>
<span class="font-mono text-[10px] font-bold uppercase tracking-widest"
:style="`color:${activeStep === step.id ? step.color : 'rgba(255,255,255,0.55)'};`">
Étape <span x-text="step.id"></span> · <span x-text="step.title"></span>
</span>
</div>
{# Mini buttons — flex wrap compact #}
<div class="flex flex-wrap gap-1.5">
<template x-for="i in step.subModes" :key="i">
<button type="button" @click="handleManualSelect(i)"
class="flex items-center gap-1.5 rounded py-1.5 px-2 outline-none transition-all focus-visible:ring-2 focus-visible:ring-white/40 cursor-pointer"
:style="`border:1px solid ${selectedFeature === i ? FEATURES[i].color + '70' : 'rgba(255,255,255,0.10)'}; background-color:${selectedFeature === i ? FEATURES[i].color + '20' : 'rgba(8,12,24,0.55)'}; box-shadow:${selectedFeature === i ? '0 0 12px ' + FEATURES[i].color + '40, inset 0 0 8px ' + FEATURES[i].color + '15' : 'none'}; min-height:30px;`"
:aria-pressed="selectedFeature === i ? 'true' : 'false'"
:aria-label="`Fonction : ${FEATURES[i].title}`">
<span :style="`color:${selectedFeature === i ? FEATURES[i].color : 'rgba(255,255,255,0.55)'}; filter:${selectedFeature === i ? 'drop-shadow(0 0 4px ' + FEATURES[i].color + 'BB)' : 'none'}; transition:all 0.2s; transform:scale(${selectedFeature === i ? 1.1 : 1}); display:inline-block;`">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="width:13px;height:13px;" aria-hidden="true" x-html="featureIconPath(i)"></svg>
</span>
<span class="text-[10px] font-semibold leading-none whitespace-nowrap"
:style="`color:${selectedFeature === i ? 'rgba(255,255,255,0.98)' : 'rgba(255,255,255,0.80)'};`"
x-text="featureGridLabel(i)"></span>
</button>
</template>
</div>
</div>
</template>
</div>
</div>
</div>
</div>{# /2-col flex layout #}
</div>{# /dictiaDashboard wrapper outer column #}
</div>
@@ -1453,29 +1568,47 @@
{ idx: 11, title: 'Audit trail', subtitle: 'Traçabilité Loi 25 immutable', color: '#2563eb', badge: 'Loi 25' }, /* brand-b1 */
{ idx: 12, title: 'Conformité', subtitle: '9 ordres pros · EFVP CAI', color: '#c026d3', badge: 'MCN' } /* brand-b3 */
],
selectedFeature: 1,
/* PROCESS NARRATIF — ordre canonique du flow utilisateur (refonte 2026-04-29 v3).
Cycle : Capture → Transformation IA → Distribution → Recherche & Gouvernance.
13 sub-modes (mode 0 IA inclus) — auto-cycle ≈ 14.3s tour complet. */
PROCESS_ORDER: [7, 1, 2, 3, 9, 0, 4, 6, 5, 10, 8, 11, 12],
STEPS: [
{ id: 1, title: 'Capture', short: 'Capture', subtitle: 'Audio in', subModes: [7, 1], color: '#06b6d4' }, /* brand-b2 */
{ id: 2, title: 'Transformation IA', short: 'Transform.', subtitle: 'Le moteur', subModes: [2, 3, 9, 0], color: '#c026d3' }, /* brand-b3 */
{ id: 3, title: 'Distribution', short: 'Distrib.', subtitle: 'Partage', subModes: [4, 6, 5, 10], color: '#2563eb' }, /* brand-b1 */
{ id: 4, title: 'Recherche & Gouvernance', short: 'Gouv.', subtitle: 'Long terme',subModes: [8, 11, 12], color: '#06b6d4' } /* brand-b2 */
],
processIdx: 0,
isManual: false,
autoCycleTimer: null,
manualResetTimer: null,
get selectedFeature() { return this.PROCESS_ORDER[this.processIdx]; },
get displayMode() { return this.selectedFeature; },
get activeColor() { return this.FEATURES[this.displayMode].color; },
get activeStep() {
const sf = this.selectedFeature;
const s = this.STEPS.find(st => st.subModes.includes(sf));
return s ? s.id : 1;
},
get progressPercent() {
return Math.round(((this.processIdx + 1) / this.PROCESS_ORDER.length) * 100);
},
init() {
if (window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;
this.startAutoCycle();
},
/* Auto-cycle logic : avance 1→12 (skip 0=IA mode, accessible via Brain card), 1100ms each.
Cycle complet : 12 features × 1100ms ≈ 13.2s puis recommence à 1. */
/* Auto-cycle logic : suit PROCESS_ORDER (13 sub-modes), 1100ms each.
Cycle complet : 13 modes × 1100ms ≈ 14.3s puis recommence à processIdx=0. */
startAutoCycle() {
if (this.autoCycleTimer) clearInterval(this.autoCycleTimer);
this.autoCycleTimer = setInterval(() => {
/* Avance 1→2→...→12, puis retour à 1 (skip 0=IA) */
this.selectedFeature = this.selectedFeature >= 12 ? 1 : this.selectedFeature + 1;
if (this.selectedFeature === 0) this.selectedFeature = 1;
this.processIdx = (this.processIdx + 1) % this.PROCESS_ORDER.length;
}, 1100);
},
/* Click manuel sur un bouton feature : sélectionne ce mode + freeze auto 4500ms */
/* Click manuel sur un bouton feature : trouve son index dans PROCESS_ORDER + freeze auto 4500ms */
handleManualSelect(i) {
this.selectedFeature = i;
const idx = this.PROCESS_ORDER.indexOf(i);
if (idx >= 0) this.processIdx = idx;
this.isManual = true;
if (this.autoCycleTimer) { clearInterval(this.autoCycleTimer); this.autoCycleTimer = null; }
if (this.manualResetTimer) clearTimeout(this.manualResetTimer);
@@ -1484,6 +1617,12 @@
this.startAutoCycle();
}, 4500);
},
/* Click sur une étape du breadcrumb / bottom step nav : jump au 1er sub-mode de l'étape */
goToStep(stepId) {
const step = this.STEPS.find(s => s.id === stepId);
if (!step || step.subModes.length === 0) return;
this.handleManualSelect(step.subModes[0]);
},
featureIconPath(i) {
const paths = {
0: '<path d="M9.5 2A2.5 2.5 0 0 1 12 4.5v15a2.5 2.5 0 0 1-4.96.44 2.5 2.5 0 0 1-2.96-3.08 3 3 0 0 1-.34-5.58 2.5 2.5 0 0 1 1.32-4.24 2.5 2.5 0 0 1 1.98-3A2.5 2.5 0 0 1 9.5 2Z"/><path d="M14.5 2A2.5 2.5 0 0 0 12 4.5v15a2.5 2.5 0 0 0 4.96.44 2.5 2.5 0 0 0 2.96-3.08 3 3 0 0 0 .34-5.58 2.5 2.5 0 0 0-1.32-4.24 2.5 2.5 0 0 0-1.98-3A2.5 2.5 0 0 0 14.5 2Z"/>',

View File

@@ -300,6 +300,42 @@ def test_fonctionnalites_how_it_works_reactor_section():
# Performance metrics : 3 cells with grad-text for 0ms / 24/7
assert 'grad-text' in body, "Missing grad-text on metrics"
# ── REFONTE NARRATIVE PROCESSUS 2026-04-29 v3 ──────────────────────────
# PROCESS_ORDER : ordre canonique du flow utilisateur (13 sub-modes, mode 0 inclus)
assert 'PROCESS_ORDER' in body, "Missing PROCESS_ORDER array (narrative refactor)"
assert '[7, 1, 2, 3, 9, 0, 4, 6, 5, 10, 8, 11, 12]' in body, \
"Missing canonical PROCESS_ORDER sequence (Capture → Transform IA → Distribution → Gouvernance)"
# 4 STEPS (étapes principales) + helpers
assert 'STEPS:' in body, "Missing STEPS array"
assert "'Capture'" in body, "Missing étape 1 Capture"
assert "'Transformation IA'" in body, "Missing étape 2 Transformation IA"
assert "'Distribution'" in body, "Missing étape 3 Distribution"
assert "'Recherche & Gouvernance'" in body, "Missing étape 4 Recherche & Gouvernance"
assert 'subModes:' in body, "Missing subModes mapping"
# Computed states + helpers
assert 'processIdx' in body, "Missing processIdx state"
assert 'activeStep' in body, "Missing activeStep getter"
assert 'goToStep' in body, "Missing goToStep helper"
assert 'progressPercent' in body, "Missing progressPercent getter"
# Process breadcrumb visible above phone
assert 'dictia-process-breadcrumb' in body, "Missing process breadcrumb wrapper"
assert 'Étapes du processus DictIA' in body, "Missing breadcrumb aria-label"
assert 'aria-current' in body, "Missing aria-current on active step"
# Progress bar
assert 'role="progressbar"' in body, "Missing progressbar role"
assert 'Progression du flow' in body, "Missing progress bar label"
# Bottom step navigator (4 buttons étapes au lieu de 6 features)
assert 'dictia-step-bottom' in body, "Missing bottom step navigator class"
assert 'Étapes du processus' in body, "Missing bottom tablist aria-label"
# Right panel : 4 mini-sections par étape
assert 'dictia-step-section' in body, "Missing right panel step sections"
# Feature info card : context processus (Étape X/4 · Titre)
assert 'STEPS[activeStep - 1]' in body, "Missing process context binding in feature card"
# Connecting line : travelling dot following processIdx
assert 'PROCESS_ORDER.length' in body, "Missing PROCESS_ORDER.length binding (travelling dot or progress)"
# Right panel header : updated badge
assert '12 fonctions · 4 étapes' in body, "Missing right panel updated badge"
def test_fonctionnalites_export_formats_section():
client = app.test_client()