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] || '';
}
};
}

View File

@@ -192,12 +192,13 @@ def test_fonctionnalites_how_it_works_reactor_section():
assert 'handleManualSelect' in body, "Missing manual select handler"
assert '1100' in body, "Missing 1100ms sub-mode cycle interval (refonte 2026-04-29)"
assert '4500' in body, "Missing 4500ms manual reset timer"
# Refonte 2026-04-29 : 4 catégories × 12 sous-modes
assert 'CATEGORIES' in body, "Missing CATEGORIES array (refonte 4 catégories)"
assert 'activeCategory' in body, "Missing activeCategory state"
assert 'handleCategorySelect' in body, "Missing category click handler"
for cat_title in ['Capture', 'Transformation IA', 'Distribution', 'Gouvernance']:
assert cat_title in body, f"Missing category : {cat_title}"
# Refonte 2026-04-29 (v2) : 12 fonctions visibles directement (1-12), IA mode 0 via Brain card.
# Bottom tab bar = 6 boutons originaux (Trans/Diari/Lang/Exp/Users/Part) ; right panel grid = 12 features.
assert 'featureGridLabel' in body, "Missing featureGridLabel (right grid 12 buttons)"
assert '12 fonctions' in body, "Missing '12 fonctions' badge in right panel header"
# Labels compacts grid right panel
for grid_label in ['Recording', 'Recherche IA', 'Résumés', 'Intégrations', 'Audit trail', 'Conformité']:
assert grid_label in body, f"Missing right grid label : {grid_label}"
# Nouveaux modes 7-12
assert 'recModeData' in body, "Missing Mode 7 (Recording live)"
assert 'searchModeData' in body, "Missing Mode 8 (Recherche sémantique)"
@@ -273,9 +274,9 @@ def test_fonctionnalites_how_it_works_reactor_section():
assert 'dictia-bg-orbs' in body, "Missing floating orbs"
assert 'dictia-connecting-line' in body, "Missing connecting line phone↔IA"
# Stats row in section header (refonte 2026-04-29 : 4 catégories · 6 modules)
# Stats row in section header (refonte v2 2026-04-29 : 12 fonctions visibles)
assert 'modules' in body, "Missing stats row 'modules'"
assert 'catégories' in body, "Missing stats row 'catégories' (refonte)"
assert 'fonctions' in body, "Missing stats row 'fonctions' (refonte v2)"
assert 'cloud' in body, "Missing stats row 'cloud'"
assert 'Voir une démo' in body, "Missing demo CTA link"