feat(marketing): section interactive 'Comment ça marche' (réacteur DictIA cyclant 6 features)
Ajoute une nouvelle section interactive sous les 6 fonctionnalités (préservées intégralement) reproduisant le composant React dictai-narrative.tsx en CSS pur + Alpine.js — sans Framer Motion ni autre lib JS. - Réacteur central holographique : 3 anneaux concentriques rotatifs (15 s / 22 s / 30 s) + 8 particules orbitales (cyan/blue/fuchsia) + wordmark DictIA glow pulsant - Auto-cycle Alpine.js entre 6 features (1.6 s) avec pause au hover/focus et reprise au leave/blur - Panneau feature active avec aria-live='polite' pour annonce lecteur d'écran (Transcription · Diarisation · 99+ langues · Exports · Utilisateurs illimités · Partage & Classement) - Card 'IA intégrée Mistral 7B LOCAL' avec 3 bullets souveraineté - Spec list cliquable / hover déclenchant feature dans réacteur - Layout responsive grid 2 cols desktop, stack mobile - prefers-reduced-motion désactive rings + orbites + auto-cycle - Position : APRÈS '6 fonctionnalités', AVANT 'Intégrations' - Sub-nav reste à 4 ancres (sous-partie visuelle de Fonctionnalités) - Tests : nouveau test_fonctionnalites_how_it_works_reactor_section valide structure, contenu canonique, a11y et Alpine bindings Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -984,6 +984,9 @@
|
|||||||
.min-h-\[110px\] {
|
.min-h-\[110px\] {
|
||||||
min-height: 110px;
|
min-height: 110px;
|
||||||
}
|
}
|
||||||
|
.min-h-\[480px\] {
|
||||||
|
min-height: 480px;
|
||||||
|
}
|
||||||
.min-h-\[calc\(100vh-62px\)\] {
|
.min-h-\[calc\(100vh-62px\)\] {
|
||||||
min-height: calc(100vh - 62px);
|
min-height: calc(100vh - 62px);
|
||||||
}
|
}
|
||||||
@@ -1140,6 +1143,9 @@
|
|||||||
.max-w-\[300px\] {
|
.max-w-\[300px\] {
|
||||||
max-width: 300px;
|
max-width: 300px;
|
||||||
}
|
}
|
||||||
|
.max-w-\[320px\] {
|
||||||
|
max-width: 320px;
|
||||||
|
}
|
||||||
.max-w-\[820px\] {
|
.max-w-\[820px\] {
|
||||||
max-width: 820px;
|
max-width: 820px;
|
||||||
}
|
}
|
||||||
@@ -1242,6 +1248,10 @@
|
|||||||
--tw-translate-y: calc(var(--spacing) * 0);
|
--tw-translate-y: calc(var(--spacing) * 0);
|
||||||
translate: var(--tw-translate-x) var(--tw-translate-y);
|
translate: var(--tw-translate-x) var(--tw-translate-y);
|
||||||
}
|
}
|
||||||
|
.translate-y-1 {
|
||||||
|
--tw-translate-y: calc(var(--spacing) * 1);
|
||||||
|
translate: var(--tw-translate-x) var(--tw-translate-y);
|
||||||
|
}
|
||||||
.translate-y-2 {
|
.translate-y-2 {
|
||||||
--tw-translate-y: calc(var(--spacing) * 2);
|
--tw-translate-y: calc(var(--spacing) * 2);
|
||||||
translate: var(--tw-translate-x) var(--tw-translate-y);
|
translate: var(--tw-translate-x) var(--tw-translate-y);
|
||||||
@@ -1916,6 +1926,12 @@
|
|||||||
border-color: color-mix(in oklab, var(--color-white) 8%, transparent);
|
border-color: color-mix(in oklab, var(--color-white) 8%, transparent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.border-white\/\[0\.10\] {
|
||||||
|
border-color: color-mix(in srgb, #fff 10%, transparent);
|
||||||
|
@supports (color: color-mix(in lab, red, red)) {
|
||||||
|
border-color: color-mix(in oklab, var(--color-white) 10%, transparent);
|
||||||
|
}
|
||||||
|
}
|
||||||
.border-white\/\[0\.12\] {
|
.border-white\/\[0\.12\] {
|
||||||
border-color: color-mix(in srgb, #fff 12%, transparent);
|
border-color: color-mix(in srgb, #fff 12%, transparent);
|
||||||
@supports (color: color-mix(in lab, red, red)) {
|
@supports (color: color-mix(in lab, red, red)) {
|
||||||
@@ -2950,6 +2966,10 @@
|
|||||||
--tw-tracking: 0.10em;
|
--tw-tracking: 0.10em;
|
||||||
letter-spacing: 0.10em;
|
letter-spacing: 0.10em;
|
||||||
}
|
}
|
||||||
|
.tracking-\[0\.14em\] {
|
||||||
|
--tw-tracking: 0.14em;
|
||||||
|
letter-spacing: 0.14em;
|
||||||
|
}
|
||||||
.tracking-\[0\.16em\] {
|
.tracking-\[0\.16em\] {
|
||||||
--tw-tracking: 0.16em;
|
--tw-tracking: 0.16em;
|
||||||
letter-spacing: 0.16em;
|
letter-spacing: 0.16em;
|
||||||
@@ -5064,6 +5084,12 @@
|
|||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.sm\:text-6xl {
|
||||||
|
@media (width >= 40rem) {
|
||||||
|
font-size: var(--text-6xl);
|
||||||
|
line-height: var(--tw-leading, var(--text-6xl--line-height));
|
||||||
|
}
|
||||||
|
}
|
||||||
.sm\:text-base {
|
.sm\:text-base {
|
||||||
@media (width >= 40rem) {
|
@media (width >= 40rem) {
|
||||||
font-size: var(--text-base);
|
font-size: var(--text-base);
|
||||||
@@ -5457,6 +5483,11 @@
|
|||||||
grid-template-columns: 1fr 240px;
|
grid-template-columns: 1fr 240px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.lg\:grid-cols-\[1fr_minmax\(0\,360px\)\] {
|
||||||
|
@media (width >= 64rem) {
|
||||||
|
grid-template-columns: 1fr minmax(0,360px);
|
||||||
|
}
|
||||||
|
}
|
||||||
.lg\:grid-cols-\[minmax\(0\,1fr\)_minmax\(0\,420px\)\] {
|
.lg\:grid-cols-\[minmax\(0\,1fr\)_minmax\(0\,420px\)\] {
|
||||||
@media (width >= 64rem) {
|
@media (width >= 64rem) {
|
||||||
grid-template-columns: minmax(0,1fr) minmax(0,420px);
|
grid-template-columns: minmax(0,1fr) minmax(0,420px);
|
||||||
|
|||||||
@@ -249,6 +249,221 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
{# ===== COMMENT ÇA MARCHE — RÉACTEUR INTERACTIF =====
|
||||||
|
Sous-partie visuelle de la section Fonctionnalités (sub-nav reste à 4 ancres).
|
||||||
|
Reproduit dictai-narrative.tsx (Website-Sanity) en CSS pur + Alpine.js.
|
||||||
|
#}
|
||||||
|
<section class="bg-brand-bg py-20" aria-labelledby="how-it-works-title">
|
||||||
|
<style>
|
||||||
|
/* Anneaux concentriques rotatifs (réacteur DictIA) */
|
||||||
|
.reactor-ring { position: absolute; border-radius: 9999px; pointer-events: none; }
|
||||||
|
.ring-outer { width: 420px; height: 420px; border: 1px solid rgba(37,99,235,0.25); animation: ring-rotate-cw 30s linear infinite; }
|
||||||
|
.ring-mid { width: 300px; height: 300px; border: 1px solid rgba(6,182,212,0.30); animation: ring-rotate-ccw 22s linear infinite; }
|
||||||
|
.ring-inner { width: 180px; height: 180px; border: 1px solid rgba(192,38,211,0.35); animation: ring-rotate-cw 15s linear infinite; }
|
||||||
|
@keyframes ring-rotate-cw { from { transform: rotate(0); } to { transform: rotate(360deg); } }
|
||||||
|
@keyframes ring-rotate-ccw { from { transform: rotate(0); } to { transform: rotate(-360deg); } }
|
||||||
|
|
||||||
|
/* Particules orbitales — 8 dots qui tournent autour du wordmark */
|
||||||
|
.reactor-orbit-host { position: absolute; left: 50%; top: 50%; width: 1px; height: 1px; }
|
||||||
|
.orbit { position: absolute; left: 0; top: 0; border-radius: 9999px; transform-origin: 0 0; }
|
||||||
|
.orbit-1 { width: 8px; height: 8px; background: #2563eb; animation: orbit-spin 12s linear infinite; --r: 210px; }
|
||||||
|
.orbit-2 { width: 6px; height: 6px; background: #06b6d4; animation: orbit-spin 18s linear infinite reverse; --r: 150px; }
|
||||||
|
.orbit-3 { width: 6px; height: 6px; background: #c026d3; animation: orbit-spin 9s linear infinite; --r: 90px; }
|
||||||
|
.orbit-4 { width: 5px; height: 5px; background: #06b6d4; animation: orbit-spin 14s linear infinite reverse; --r: 210px; animation-delay: -3.5s; }
|
||||||
|
.orbit-5 { width: 7px; height: 7px; background: #c026d3; animation: orbit-spin 20s linear infinite; --r: 150px; animation-delay: -5s; }
|
||||||
|
.orbit-6 { width: 5px; height: 5px; background: #2563eb; animation: orbit-spin 11s linear infinite reverse; --r: 90px; animation-delay: -2s; }
|
||||||
|
.orbit-7 { width: 4px; height: 4px; background: #06b6d4; animation: orbit-spin 16s linear infinite; --r: 210px; animation-delay: -8s; }
|
||||||
|
.orbit-8 { width: 5px; height: 5px; background: #c026d3; animation: orbit-spin 13s linear infinite reverse; --r: 150px; animation-delay: -1s; }
|
||||||
|
@keyframes orbit-spin {
|
||||||
|
from { transform: rotate(0deg) translateX(var(--r)) rotate(0deg); }
|
||||||
|
to { transform: rotate(360deg) translateX(var(--r)) rotate(-360deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Glow pulsant sous le wordmark central */
|
||||||
|
@keyframes reactor-glow-pulse {
|
||||||
|
0%, 100% { opacity: 0.55; transform: scale(1); }
|
||||||
|
50% { opacity: 0.85; transform: scale(1.08); }
|
||||||
|
}
|
||||||
|
.reactor-glow { animation: reactor-glow-pulse 3.2s ease-in-out infinite; }
|
||||||
|
|
||||||
|
/* Spec list item état actif */
|
||||||
|
.feature-list-item { transition: background-color 200ms ease-out, color 150ms ease-out, border-color 150ms ease-out; }
|
||||||
|
.feature-list-item.is-active { background: rgba(37,99,235,0.10); border-left-color: #2563eb; color: #060d1a; }
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.ring-outer, .ring-mid, .ring-inner,
|
||||||
|
.orbit, .reactor-glow { animation: none; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mobile: tighter ring sizes pour rester dans le cadre */
|
||||||
|
@media (max-width: 640px) {
|
||||||
|
.ring-outer { width: 320px; height: 320px; }
|
||||||
|
.ring-mid { width: 220px; height: 220px; }
|
||||||
|
.ring-inner { width: 140px; height: 140px; }
|
||||||
|
.orbit-1, .orbit-4, .orbit-7 { --r: 160px; }
|
||||||
|
.orbit-2, .orbit-5, .orbit-8 { --r: 110px; }
|
||||||
|
.orbit-3, .orbit-6 { --r: 70px; }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div class="max-w-[1200px] mx-auto px-6">
|
||||||
|
<div class="text-center max-w-2xl mx-auto mb-12">
|
||||||
|
<p class="eyebrow grad-text mb-3 inline-flex items-center gap-2 justify-center">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="w-4 h-4" aria-hidden="true">
|
||||||
|
<circle cx="12" cy="12" r="3"/>
|
||||||
|
<path d="M19.4 15a1.7 1.7 0 0 0 .3 1.9l.1.1a2 2 0 1 1-2.8 2.8l-.1-.1a1.7 1.7 0 0 0-1.9-.3 1.7 1.7 0 0 0-1 1.5V21a2 2 0 1 1-4 0v-.1a1.7 1.7 0 0 0-1-1.5 1.7 1.7 0 0 0-1.9.3l-.1.1a2 2 0 1 1-2.8-2.8l.1-.1a1.7 1.7 0 0 0 .3-1.9 1.7 1.7 0 0 0-1.5-1H3a2 2 0 1 1 0-4h.1a1.7 1.7 0 0 0 1.5-1 1.7 1.7 0 0 0-.3-1.9l-.1-.1a2 2 0 1 1 2.8-2.8l.1.1a1.7 1.7 0 0 0 1.9.3h.1a1.7 1.7 0 0 0 1-1.5V3a2 2 0 1 1 4 0v.1a1.7 1.7 0 0 0 1 1.5 1.7 1.7 0 0 0 1.9-.3l.1-.1a2 2 0 1 1 2.8 2.8l-.1.1a1.7 1.7 0 0 0-.3 1.9v.1a1.7 1.7 0 0 0 1.5 1H21a2 2 0 1 1 0 4h-.1a1.7 1.7 0 0 0-1.5 1z"/>
|
||||||
|
</svg>
|
||||||
|
COMMENT ÇA MARCHE
|
||||||
|
</p>
|
||||||
|
<h2 id="how-it-works-title" class="text-[clamp(2rem,3vw,2.75rem)] font-black text-brand-navy mb-3">
|
||||||
|
Du fichier au résumé — <span class="grad-text">en temps réel</span>
|
||||||
|
</h2>
|
||||||
|
<p class="text-base text-brand-navy/70">
|
||||||
|
Survolez une fonctionnalité pour voir la machine en action. Glissez pour calculer votre gain de productivité.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div x-data='{
|
||||||
|
features: ["Transcription", "Diarisation", "99+ langues", "Exports", "Utilisateurs illimités", "Partage & Classement"],
|
||||||
|
details: {
|
||||||
|
"Transcription": { tag: "WhisperX Large-v3", desc: "STT 95 %+ FR-CA" },
|
||||||
|
"Diarisation": { tag: "pyannote · 8 locuteurs max", desc: "Identification automatique" },
|
||||||
|
"99+ langues": { tag: "Détection automatique", desc: "FR · EN · ES · ZH · ..." },
|
||||||
|
"Exports": { tag: "DOCX, SRT, JSON, PDF", desc: "7 formats standards" },
|
||||||
|
"Utilisateurs illimités": { tag: "Aucun frais par utilisateur", desc: "Volume illimité" },
|
||||||
|
"Partage & Classement": { tag: "Permissions granulaires", desc: "Tags + dossiers" }
|
||||||
|
},
|
||||||
|
active: "Transcription",
|
||||||
|
isHovered: false,
|
||||||
|
timer: null,
|
||||||
|
init() {
|
||||||
|
if (window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches) return;
|
||||||
|
this.timer = setInterval(() => {
|
||||||
|
if (!this.isHovered) {
|
||||||
|
const idx = this.features.indexOf(this.active);
|
||||||
|
this.active = this.features[(idx + 1) % this.features.length];
|
||||||
|
}
|
||||||
|
}, 1600);
|
||||||
|
},
|
||||||
|
setActive(name) { this.isHovered = true; this.active = name; },
|
||||||
|
resumeCycle() { this.isHovered = false; }
|
||||||
|
}'
|
||||||
|
x-init="init()"
|
||||||
|
class="grid lg:grid-cols-[1fr_minmax(0,360px)] gap-8 items-stretch">
|
||||||
|
|
||||||
|
{# ── COLONNE GAUCHE : Réacteur central holographique ── #}
|
||||||
|
<div class="relative bg-brand-navy p-8 rounded overflow-hidden min-h-[480px] flex flex-col items-center justify-center">
|
||||||
|
{# Backdrop radial #}
|
||||||
|
<div class="absolute inset-0 pointer-events-none" aria-hidden="true"
|
||||||
|
style="background: radial-gradient(ellipse 80% 50% at 50% 50%, rgba(37,99,235,0.10) 0%, transparent 70%);"></div>
|
||||||
|
|
||||||
|
{# 3 anneaux concentriques rotatifs #}
|
||||||
|
<div class="absolute inset-0 flex items-center justify-center" aria-hidden="true">
|
||||||
|
<div class="reactor-ring ring-outer"></div>
|
||||||
|
<div class="reactor-ring ring-mid"></div>
|
||||||
|
<div class="reactor-ring ring-inner"></div>
|
||||||
|
{# 8 particules orbitales #}
|
||||||
|
<div class="reactor-orbit-host">
|
||||||
|
<span class="orbit orbit-1"></span>
|
||||||
|
<span class="orbit orbit-2"></span>
|
||||||
|
<span class="orbit orbit-3"></span>
|
||||||
|
<span class="orbit orbit-4"></span>
|
||||||
|
<span class="orbit orbit-5"></span>
|
||||||
|
<span class="orbit orbit-6"></span>
|
||||||
|
<span class="orbit orbit-7"></span>
|
||||||
|
<span class="orbit orbit-8"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{# Auto badge — top right #}
|
||||||
|
<div class="absolute top-4 right-4 inline-flex items-center gap-1.5 text-xs text-white/80 z-10 font-mono">
|
||||||
|
<span class="w-2 h-2 rounded-full bg-brand-b2 animate-pulse" aria-hidden="true"></span>
|
||||||
|
Auto
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{# Centre : DictIA wordmark + glow + feature panel #}
|
||||||
|
<div class="relative z-10 text-center">
|
||||||
|
<div class="relative inline-block mb-6">
|
||||||
|
<div class="absolute inset-0 reactor-glow pointer-events-none" aria-hidden="true"
|
||||||
|
style="background: radial-gradient(circle at 50% 50%, rgba(6,182,212,0.35) 0%, transparent 65%); filter: blur(24px); transform: scale(1.4);"></div>
|
||||||
|
<p class="relative font-black text-5xl sm:text-6xl grad-text leading-none tracking-tight">DictIA</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{# Panneau feature active — swap fluide via x-transition #}
|
||||||
|
<div class="bg-white/[0.06] border border-white/[0.10] rounded p-4 backdrop-blur-sm min-w-[260px] max-w-[320px] mx-auto"
|
||||||
|
role="status" aria-live="polite" aria-atomic="true">
|
||||||
|
<template x-for="feat in features" :key="feat">
|
||||||
|
<div x-show="active === feat"
|
||||||
|
x-transition:enter="transition ease-out duration-300"
|
||||||
|
x-transition:enter-start="opacity-0 translate-y-1"
|
||||||
|
x-transition:enter-end="opacity-100 translate-y-0"
|
||||||
|
x-transition:leave="transition ease-in duration-150"
|
||||||
|
x-transition:leave-start="opacity-100"
|
||||||
|
x-transition:leave-end="opacity-0">
|
||||||
|
<p class="text-[11px] uppercase tracking-[0.18em] text-white/60 mb-1 font-bold" x-text="feat"></p>
|
||||||
|
<p class="text-sm text-white font-semibold font-mono" x-text="details[feat].tag"></p>
|
||||||
|
<p class="text-xs text-white/65 mt-1" x-text="details[feat].desc"></p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{# ── COLONNE DROITE : IA intégrée Mistral 7B + spec list cliquable ── #}
|
||||||
|
<div class="flex flex-col gap-4">
|
||||||
|
|
||||||
|
{# Card "IA intégrée Mistral 7B LOCAL" #}
|
||||||
|
<div class="bg-white border border-brand-border rounded p-5">
|
||||||
|
<div class="flex items-center gap-2 mb-2 flex-wrap">
|
||||||
|
<p class="eyebrow text-brand-navy/70">IA intégrée</p>
|
||||||
|
<span class="px-2 py-0.5 rounded-full bg-brand-b1/10 text-brand-b1 text-[10px] font-bold uppercase tracking-[0.14em] border border-brand-b1/20">Local</span>
|
||||||
|
</div>
|
||||||
|
<p class="text-2xl font-black text-brand-navy mb-1">Mistral 7B</p>
|
||||||
|
<p class="text-sm text-brand-navy/70 mb-4">Résumé · Points d'action · Q&R</p>
|
||||||
|
<ul class="space-y-2 text-xs text-brand-navy/80" role="list">
|
||||||
|
<li class="flex items-start gap-2">
|
||||||
|
{{ icon_check | safe }}
|
||||||
|
<span>Données hébergées sur VOS serveurs · jamais partagées</span>
|
||||||
|
</li>
|
||||||
|
<li class="flex items-start gap-2">
|
||||||
|
{{ icon_check | safe }}
|
||||||
|
<span>Zéro connexion OpenAI · Google · Microsoft</span>
|
||||||
|
</li>
|
||||||
|
<li class="flex items-start gap-2">
|
||||||
|
{{ icon_check | safe }}
|
||||||
|
<span>Inférence hors-ligne · résultats en secondes</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{# Spec list cliquable / hover — déclenche feature dans réacteur #}
|
||||||
|
<div class="bg-white border border-brand-border rounded p-3">
|
||||||
|
<p class="eyebrow text-brand-navy/70 px-2 mb-2 mt-1">Fonctions clés</p>
|
||||||
|
<ul role="list" class="flex flex-col">
|
||||||
|
<template x-for="feat in features" :key="feat">
|
||||||
|
<li>
|
||||||
|
<button type="button"
|
||||||
|
@mouseenter="setActive(feat)"
|
||||||
|
@mouseleave="resumeCycle()"
|
||||||
|
@focus="setActive(feat)"
|
||||||
|
@blur="resumeCycle()"
|
||||||
|
@click="setActive(feat)"
|
||||||
|
:class="active === feat ? 'is-active' : 'border-l-2 border-transparent text-brand-navy/70'"
|
||||||
|
class="feature-list-item w-full text-left px-3 py-2 text-sm font-semibold border-l-2 hover:text-brand-navy hover:bg-brand-bg focus-visible:outline-2 focus-visible:outline-brand-b1 focus-visible:outline-offset-2 rounded-none"
|
||||||
|
:aria-pressed="active === feat ? 'true' : 'false'">
|
||||||
|
<span x-text="feat"></span>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
</template>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
{# ===== INTÉGRATIONS ===== #}
|
{# ===== INTÉGRATIONS ===== #}
|
||||||
<section id="integrations" class="bg-brand-bg py-20 scroll-mt-32" aria-labelledby="integrations-title">
|
<section id="integrations" class="bg-brand-bg py-20 scroll-mt-32" aria-labelledby="integrations-title">
|
||||||
<div class="max-w-[1200px] mx-auto px-6">
|
<div class="max-w-[1200px] mx-auto px-6">
|
||||||
|
|||||||
@@ -131,6 +131,55 @@ def test_fonctionnalites_renders_6_bento_cards():
|
|||||||
assert kw in body
|
assert kw in body
|
||||||
|
|
||||||
|
|
||||||
|
def test_fonctionnalites_how_it_works_reactor_section():
|
||||||
|
"""New 'Comment ça marche' interactive reactor section (post-6-features, pre-integrations).
|
||||||
|
|
||||||
|
Validates structure (heading + reactor + spec list + Mistral card), 6 cycling features,
|
||||||
|
canonical content, and a11y signals (aria-labelledby + aria-live status panel).
|
||||||
|
"""
|
||||||
|
client = app.test_client()
|
||||||
|
body = client.get('/fonctionnalites').data.decode('utf-8')
|
||||||
|
|
||||||
|
# Section heading + canonical phrasing
|
||||||
|
assert 'how-it-works-title' in body, "Missing how-it-works section anchor"
|
||||||
|
assert 'COMMENT ÇA MARCHE' in body, "Missing canonical eyebrow"
|
||||||
|
assert 'Du fichier au résumé' in body, "Missing canonical H2 phrasing"
|
||||||
|
assert 'en temps réel' in body
|
||||||
|
assert 'Survolez une fonctionnalité pour voir la machine en action' in body
|
||||||
|
|
||||||
|
# 6 cycling features (canonical names)
|
||||||
|
for feat in ['Transcription', 'Diarisation', '99+ langues', 'Exports',
|
||||||
|
'Utilisateurs illimités', 'Partage & Classement']:
|
||||||
|
assert feat in body, f"Missing cycling feature: {feat}"
|
||||||
|
|
||||||
|
# Reactor visual: rings, orbits, wordmark, Auto badge
|
||||||
|
assert 'reactor-ring' in body and 'ring-outer' in body and 'ring-mid' in body and 'ring-inner' in body
|
||||||
|
assert 'orbit orbit-1' in body and 'orbit orbit-8' in body, "Missing 8 orbital particles"
|
||||||
|
assert '>DictIA<' in body, "Missing reactor centre wordmark"
|
||||||
|
|
||||||
|
# Mistral 7B IA intégrée card with 3 canonical bullets
|
||||||
|
assert 'Mistral 7B' in body
|
||||||
|
assert 'IA intégrée' in body
|
||||||
|
assert 'Données hébergées sur VOS serveurs' in body
|
||||||
|
assert 'Zéro connexion OpenAI' in body
|
||||||
|
assert 'Inférence hors-ligne' in body
|
||||||
|
|
||||||
|
# Alpine reactor data + auto-cycle + hover/focus stop logic
|
||||||
|
assert "active: \"Transcription\"" in body
|
||||||
|
assert 'isHovered' in body
|
||||||
|
assert 'setActive(feat)' in body
|
||||||
|
assert 'resumeCycle()' in body
|
||||||
|
# Hover, focus + blur listeners on each list item
|
||||||
|
assert '@mouseenter="setActive(feat)"' in body
|
||||||
|
assert '@mouseleave="resumeCycle()"' in body
|
||||||
|
assert '@focus="setActive(feat)"' in body
|
||||||
|
assert '@blur="resumeCycle()"' in body
|
||||||
|
|
||||||
|
# Accessibility: aria-live status panel, prefers-reduced-motion guard
|
||||||
|
assert 'aria-live="polite"' in body
|
||||||
|
assert 'prefers-reduced-motion' in body, "Reduced-motion guard missing"
|
||||||
|
|
||||||
|
|
||||||
def test_fonctionnalites_export_formats_section():
|
def test_fonctionnalites_export_formats_section():
|
||||||
client = app.test_client()
|
client = app.test_client()
|
||||||
body = client.get('/fonctionnalites').data.decode('utf-8')
|
body = client.get('/fonctionnalites').data.decode('utf-8')
|
||||||
|
|||||||
Reference in New Issue
Block a user