fix(marketing): restaurer visibilité 12 fonctions dans 'Comment ça marche'

L'abstraction en 4 catégories (commit 8a7650f) cachait les 13 modes
derrière des regroupements ; l'utilisateur ne VOYAIT plus les 6 boutons
features originaux et percevait que des fonctions avaient disparu.

Refonte v2 :
- Bottom tab bar : 6 boutons FEATURES originaux 1-6 (Trans/Diari/Lang/Exp/Users/Part) — visibles, cliquables
- Right panel : grid 3×4 = 12 boutons FEATURES 1-12 (IA mode 0 reste accessible via Brain card)
- Mobile pills : 12 features scrollables horizontalement
- Auto-cycle : 1→12 skip 0, 1100ms each (~13s cycle complet)
- Manual click : isManual 4500ms reset puis reprend auto
- SUPPRESSION : CATEGORIES array + activeCategory state + handleCategorySelect + sub-mode dots indicator
- AJOUT : featureGridLabel() pour labels compacts (Recording/Recherche IA/Résumés/Intégrations/Audit trail/Conformité)

Préservé : phone shell statique, palette brand stricte b1/b2/b3, IA Mistral
card inchangée, 13 templates de modes (0-12) intacts, eyebrow brand-navy noir,
WCAG aria-labels + aria-pressed, prefers-reduced-motion guard.

Test adapté : assert featureGridLabel + 12 fonctions + labels grid.
This commit is contained in:
Allison
2026-04-29 13:17:09 -04:00
parent 8a7650f9fa
commit a14bcb9a1a
2 changed files with 78 additions and 140 deletions

View File

@@ -480,8 +480,8 @@
{# Stats row #}
<div class="flex flex-wrap items-center justify-center gap-x-8 gap-y-3 text-brand-navy">
<div class="flex items-center gap-2">
<span class="text-2xl font-black grad-text">4</span>
<span class="text-xs font-medium text-brand-navy/60 uppercase tracking-wider">catégories · 6 modules</span>
<span class="text-2xl font-black grad-text">12</span>
<span class="text-xs font-medium text-brand-navy/60 uppercase tracking-wider">fonctions · modules</span>
</div>
<div class="w-px h-6 bg-brand-navy/15 hidden sm:block" aria-hidden="true"></div>
<div class="flex items-center gap-2">
@@ -1200,34 +1200,27 @@
</div>
{# BOTTOM : 4 catégories tabs (Capture / IA / Distribution / Gouvernance) + sub-mode dots + Auto pill — hauteur fixe 90px #}
<div class="relative flex flex-col items-center justify-center gap-1 z-10 flex-shrink-0"
{# BOTTOM : 6 boutons feature originaux (Trans / Diari / Lang / Exp / Users / Part) + Auto pill — hauteur fixe 90px #}
<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));">
{# Sub-mode dots indicator (shows position within active category) #}
<div class="flex items-center gap-1" style="height:6px;" aria-hidden="true">
<template x-for="(sm, di) in CATEGORIES[activeCategory].submodes" :key="di">
<span class="block rounded-full"
:style="`width:${sm === selectedFeature ? '10px' : '4px'};height:4px;background:${sm === selectedFeature ? CATEGORIES[activeCategory].color : 'rgba(255,255,255,0.20)'};transition:width 0.2s, background 0.2s;`"></span>
</template>
</div>
{# 4 catégories tabs #}
<div class="flex items-end gap-1">
<template x-for="(cat, ci) in CATEGORIES" :key="ci">
<button type="button" @click="handleCategorySelect(ci)"
class="dictia-feat-btn dictia-cat-btn outline-none cursor-pointer focus-visible:ring-2 focus-visible:ring-white/40 flex flex-col items-center justify-end relative"
style="width:54px;height:42px;border:none;padding:4px 0 6px;background:transparent;"
:aria-pressed="activeCategory === ci ? 'true' : 'false'"
:aria-label="`Catégorie : ${cat.title}`">
<span :style="`color: ${activeCategory === ci ? cat.color : 'rgba(255,255,255,0.30)'}; transition: color 0.2s, transform 0.15s, filter 0.2s; filter: ${activeCategory === ci ? 'drop-shadow(0 0 6px ' + cat.color + 'CC)' : 'none'}; transform: scale(${activeCategory === ci ? 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="cat.iconPath"></svg>
{# 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: ${activeCategory === ci ? cat.color : 'rgba(255,255,255,0.40)'};`"
x-text="cat.short"></span>
:style="`color: ${selectedFeature === i ? FEATURES[i].color : 'rgba(255,255,255,0.55)'};`"
x-text="featureShortLabel(i)"></span>
{# Tab indicator bottom border 2px #}
<span x-show="activeCategory === ci" class="absolute" aria-hidden="true"
:style="`bottom: 0; left: 6px; right: 6px; height: 2px; background: ${cat.color}; border-radius: 1px; box-shadow: 0 0 8px ${cat.color};`"></span>
<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>
</button>
</template>
</div>
@@ -1286,35 +1279,41 @@
</div>
</div>
{# Mobile : pills catégories horizontales scrollables #}
{# Mobile : pills features horizontales scrollables (12 fonctions, indexes 1-12) #}
<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="(cat, ci) in CATEGORIES" :key="ci">
<button type="button" @click="handleCategorySelect(ci)"
<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 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 ${activeCategory === ci ? cat.color + '70' : 'rgba(0,0,0,0.10)'}; background-color: ${activeCategory === ci ? cat.color + '18' : 'rgba(0,0,0,0.04)'}; outline: none;`">
<span :style="`color: ${activeCategory === ci ? cat.color : 'rgba(0,0,0,0.40)'}; transition: color 0.2s;`">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="width:12px;height:12px;" aria-hidden="true" x-html="cat.iconPath"></svg>
: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;`"
:aria-pressed="selectedFeature === i ? 'true' : 'false'"
:aria-label="`Fonction : ${FEATURES[i].title}`">
<span :style="`color: ${selectedFeature === i ? FEATURES[i].color : 'rgba(0,0,0,0.40)'}; transition: color 0.2s;`">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="width:12px;height:12px;" aria-hidden="true" x-html="featureIconPath(i)"></svg>
</span>
<span class="text-[11px] font-bold whitespace-nowrap"
:style="`color: ${activeCategory === ci ? cat.color : 'rgba(0,0,0,0.50)'};`"
x-text="cat.title"></span>
:style="`color: ${selectedFeature === i ? FEATURES[i].color : 'rgba(0,0,0,0.50)'};`"
x-text="FEATURES[i].title"></span>
</button>
</template>
</div>
</div>
</div>
{# ─────────── ZONE RIGHT : IA Mistral premium card + grid 6 features ─────────── #}
{# ─────────── ZONE RIGHT : IA Mistral premium card + grid 12 features ─────────── #}
<div class="flex flex-col gap-3 w-full lg:w-[320px] flex-shrink-0 relative z-10">
{# Eyebrow + title — alignés sur le système typo #}
<div class="mb-1 flex items-center gap-2">
<span class="block w-1 h-6 rounded-full" style="background:linear-gradient(180deg,#06b6d4,#c026d3);" aria-hidden="true"></span>
<div>
<p class="text-xs font-mono font-bold uppercase tracking-widest text-brand-b3">4 catégories · 12 sous-modes</p>
<p class="font-bold text-lg leading-snug text-brand-navy">Le moteur IA local</p>
<div class="mb-1 flex items-center justify-between gap-2">
<div class="flex items-center gap-2">
<span class="block w-1 h-6 rounded-full" style="background:linear-gradient(180deg,#06b6d4,#c026d3);" aria-hidden="true"></span>
<div>
<p class="text-xs font-mono font-bold uppercase tracking-widest text-brand-b3">Fonctions clés</p>
<p class="font-bold text-lg leading-snug text-brand-navy">Le moteur IA local</p>
</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>
</div>
{# IA Mistral 7B premium card — couleurs brand-b3 (fuchsia officiel) #}
@@ -1410,32 +1409,20 @@
</div>
</div>
{# Category grid 2×2 — 4 catégories avec sous-modes affichés dessous (architecture lisible) #}
<div class="grid grid-cols-2 gap-2">
<template x-for="(cat, ci) in CATEGORIES" :key="ci">
<button type="button" @click="handleCategorySelect(ci)"
class="flex flex-col items-start gap-2 rounded-xl py-3 px-3 outline-none transition-all focus-visible:ring-2 focus-visible:ring-white/40 text-left"
:style="`border: 1px solid ${activeCategory === ci ? cat.color + '70' : 'rgba(255,255,255,0.10)'}; background-color: ${activeCategory === ci ? 'rgba(8,12,24,0.95)' : 'rgba(8,12,24,0.85)'}; box-shadow: ${activeCategory === ci ? '0 0 16px ' + cat.color + '40, inset 0 0 16px ' + cat.color + '20' : 'none'}; cursor: pointer;`"
:aria-pressed="activeCategory === ci ? 'true' : 'false'"
:aria-label="`Catégorie : ${cat.title}`">
<div class="flex items-center gap-2 w-full">
<span :style="`color: ${activeCategory === ci ? cat.color : 'rgba(255,255,255,0.55)'}; filter: ${activeCategory === ci ? 'drop-shadow(0 0 6px ' + cat.color + 'BB)' : 'none'}; transition: all 0.2s; 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="cat.iconPath"></svg>
</span>
<span class="text-[11px] font-bold leading-tight flex-1"
:style="`color: ${activeCategory === ci ? 'rgba(255,255,255,0.98)' : 'rgba(255,255,255,0.85)'};`"
x-text="cat.title"></span>
</div>
<p class="text-[10px] font-mono leading-snug w-full"
:style="`color: ${activeCategory === ci ? cat.color + 'EE' : 'rgba(255,255,255,0.55)'};`"
x-text="cat.subtitle"></p>
{# Mini sous-modes dots dans la card #}
<div class="flex items-center gap-0.5 mt-auto" aria-hidden="true">
<template x-for="(sm, si) in cat.submodes" :key="si">
<span class="block rounded-full"
:style="`width:${activeCategory === ci && sm === selectedFeature ? '8px' : '3px'};height:3px;background:${activeCategory === ci ? (sm === selectedFeature ? cat.color : cat.color + '55') : 'rgba(255,255,255,0.15)'};transition:all 0.2s;`"></span>
</template>
</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>
</template>
</div>
@@ -1445,11 +1432,11 @@
</div>
{# Alpine logic — dictiaDashboard + 4 catégories × 12 sous-modes (refonte stratégique 2026-04-29) #}
{# Alpine logic — dictiaDashboard : 12 fonctions visibles (1-12) + IA accessible via Brain card (refonte 2026-04-29) #}
<script>
function dictiaDashboard() {
return {
/* FEATURES indexes (0-12) — même structure que v1 (0=IA, 1-6=existants), nouveaux 7-12 ajoutés */
/* FEATURES indexes (0-12) — IA mode 0 accessible via Brain card; 1-6 = originaux, 7-12 = nouveaux */
FEATURES: [
{ idx: 0, title: 'IA intégrée', subtitle: "Résumé, actions, Q&R", color: '#c026d3', badge: 'Mistral 7B' }, /* brand-b3 */
{ idx: 1, title: 'Transcription', subtitle: 'Parole → texte en temps réel', color: '#06b6d4', badge: 'Whisper AI' }, /* brand-b2 */
@@ -1466,43 +1453,7 @@
{ 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 */
],
/* CATEGORIES : 4 regroupements logiques. Chaque catégorie a ses sous-modes (ids des FEATURES). */
CATEGORIES: [
{
title: 'Capture',
short: 'Capture',
subtitle: 'Audio in : upload, recording, recherche',
color: '#06b6d4', /* brand-b2 cyan */
submodes: [1, 7, 8], /* Transcription (upload), Recording live, Recherche sémantique */
iconPath: '<rect x="9" y="2" width="6" height="12" rx="3"/><path d="M19 10v2a7 7 0 0 1-14 0v-2"/><line x1="12" y1="19" x2="12" y2="23"/>'
},
{
title: 'Transformation IA',
short: 'IA',
subtitle: 'Diarisation, langues, résumés, Q&R',
color: '#2563eb', /* brand-b1 blue */
submodes: [2, 3, 9, 0], /* Diarisation, Langues, Résumé+actions, Chat IA */
iconPath: '<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"/>'
},
{
title: 'Distribution',
short: 'Distrib',
subtitle: 'Exports, intégrations, partage',
color: '#c026d3', /* brand-b3 fuchsia */
submodes: [4, 10, 6, 5], /* Exports, Intégrations, Partage, Users */
iconPath: '<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/>'
},
{
title: 'Gouvernance',
short: 'Gouv',
subtitle: 'Audit, Loi 25, AGPL, ordres pros',
color: '#2563eb', /* brand-b1 blue (sobre, institutionnel) */
submodes: [11, 12], /* Audit trail, Conformité Loi 25 */
iconPath: '<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/><path d="M9 12l2 2 4-4"/>'
}
],
selectedFeature: 1,
activeCategory: 0,
isManual: false,
autoCycleTimer: null,
manualResetTimer: null,
@@ -1512,42 +1463,19 @@
if (window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;
this.startAutoCycle();
},
/* Auto-cycle logic : avance dans les sous-modes de la catégorie active toutes les 1100ms.
Quand on atteint le dernier sous-mode → switch catégorie suivante (boucle 0→1→2→3→0). */
/* 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. */
startAutoCycle() {
if (this.autoCycleTimer) clearInterval(this.autoCycleTimer);
this.autoCycleTimer = setInterval(() => {
const cat = this.CATEGORIES[this.activeCategory];
const currentIdx = cat.submodes.indexOf(this.selectedFeature);
if (currentIdx < cat.submodes.length - 1) {
/* Avance dans la catégorie courante */
this.selectedFeature = cat.submodes[currentIdx + 1];
} else {
/* Fin de catégorie → passe à la suivante */
this.activeCategory = (this.activeCategory + 1) % this.CATEGORIES.length;
this.selectedFeature = this.CATEGORIES[this.activeCategory].submodes[0];
}
/* 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;
}, 1100);
},
/* Click manuel sur un sous-mode (compatibilité 6 boutons originaux) */
/* Click manuel sur un bouton feature : sélectionne ce mode + freeze auto 4500ms */
handleManualSelect(i) {
this.selectedFeature = i;
/* Trouve la catégorie qui contient ce sous-mode */
for (let ci = 0; ci < this.CATEGORIES.length; ci++) {
if (this.CATEGORIES[ci].submodes.includes(i)) { this.activeCategory = ci; break; }
}
this.isManual = true;
if (this.autoCycleTimer) { clearInterval(this.autoCycleTimer); this.autoCycleTimer = null; }
if (this.manualResetTimer) clearTimeout(this.manualResetTimer);
this.manualResetTimer = setTimeout(() => {
this.isManual = false;
this.startAutoCycle();
}, 4500);
},
/* Click manuel sur une catégorie : sélectionne son premier sous-mode */
handleCategorySelect(ci) {
this.activeCategory = ci;
this.selectedFeature = this.CATEGORIES[ci].submodes[0];
this.isManual = true;
if (this.autoCycleTimer) { clearInterval(this.autoCycleTimer); this.autoCycleTimer = null; }
if (this.manualResetTimer) clearTimeout(this.manualResetTimer);
@@ -1578,6 +1506,15 @@
const labels = { 1: 'Trans', 2: 'Diari', 3: 'Lang', 4: 'Exp', 5: 'Users', 6: 'Part',
7: 'Rec', 8: 'Search', 9: 'Résu', 10: 'Hub', 11: 'Audit', 12: 'Conf' };
return labels[i] || '';
},
/* Labels compacts pour la grid 3×4 du right panel (raccourcis pour ne pas tronquer) */
featureGridLabel(i) {
const labels = {
1: 'Transcription', 2: 'Diarisation', 3: '99+ langues', 4: 'Exports',
5: 'Utilisateurs', 6: 'Partage', 7: 'Recording', 8: 'Recherche IA',
9: 'Résumés', 10: 'Intégrations', 11: 'Audit trail', 12: 'Conformité'
};
return labels[i] || '';
}
};
}