feat(marketing): round 2 — intègre 3 sections de dictia.ca/solutions/dictai (cycle/wave/cadre)

- Cycle "Trois options. Une seule est conforme." (entre PAS Problème & Solution)
  3 colonnes comparatives (humaine 315$/h / cloud US illégal / DictIA 173$/mo)
  Phases reveal 1→4 via IntersectionObserver + setTimeout chain
  Anneaux pulsants source node + horloge rotation + particules fuites cloud
  Overlay légal NON CONFORME sur col 2

- Wave "Onde de transformation" (entre Solution & Pipeline)
  Slider mouse-X interactif : 30 barres SVG morphent rouge → cyan
  Particules tombantes -$/-h (CSS keyframes staggered)
  Étiquettes douleur PAINS / SOLUTIONS flottantes
  Mobile : toggle button, pas de mouse interaction

- Cadre réglementaire "Moniteur d'Interception" (entre Conformité & Témoignages)
  Mappe 6 textes officiels : Loi 25, Loi 96, Cloud Act US, Guide IA Barreau, Cadre IA MCN, CAI
  Liens vers sources autoritaires (legisquebec, congress.gov, barreau, tresor, cai)
  HUD console typing reveal + caret blink + folder QC→US transition
  aria-live="polite" sur verdict, role="list" sur REGS

Texte 100% canonique extrait de Website-Sanity dictai-cycle/wave/contraste.tsx.
Toutes animations CSS pure + Alpine.js + IntersectionObserver natif (zéro lib JS externe).
prefers-reduced-motion désactive tout.
+802 lignes landing.html, +119 lignes tests (6 nouveaux test_round2_*), npm run build:css exécuté.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Allison
2026-04-28 12:21:16 -04:00
parent e49652d85d
commit 69baa1be2f
3 changed files with 1274 additions and 0 deletions

View File

@@ -422,6 +422,341 @@
</div>
</section>
{# ===== CYCLE — "Trois options. Une seule est conforme." ===== #}
{# Source canonique : InnovA-AI/Website-Sanity/components/sections/dictai-cycle.tsx
Animation : phase reveal phasé (1→4) déclenché par IntersectionObserver natif + Alpine.
3 colonnes comparatives : 01 Humaine · 02 Cloud US (overlay non-conforme) · 03 DictIA (featured + pulse rings) #}
<style>
/* Cycle pulse rings — appliqués au nœud source "Réunion en cours" */
@keyframes cycle-pulse-ring {
0% { transform: scale(1); opacity: 0.5; }
100% { transform: scale(2.1); opacity: 0; }
}
.cycle-pulse { animation: cycle-pulse-ring 2.4s ease-out infinite; }
.cycle-pulse-2 { animation-delay: 0.65s; }
.cycle-pulse-3 { animation-delay: 1.3s; }
/* Cycle col 3 (DictIA) — bordure lumineuse pulsante */
@keyframes cycle-glow {
0%, 100% { box-shadow: 0 0 0 0 rgba(0,189,216,0.12); }
50% { box-shadow: 0 0 35px 0 rgba(0,189,216,0.30); }
}
.cycle-card-dictia.is-visible { animation: cycle-glow 3.8s ease-in-out infinite; }
/* Cycle col 3 — anneaux concentriques autour du Lock */
@keyframes cycle-ring-out {
0%, 100% { transform: scale(1); opacity: 0.45; }
50% { transform: scale(1.07); opacity: 0.18; }
}
.cycle-ring-outer.is-visible { animation: cycle-ring-out 3.8s ease-in-out infinite; }
.cycle-ring-inner.is-visible { animation: cycle-ring-out 2.9s ease-in-out infinite 0.5s; }
/* Cycle SVG lignes de connexion — drawn via stroke-dashoffset */
.cycle-line { stroke-dasharray: 50; stroke-dashoffset: 50; transition: stroke-dashoffset 600ms ease-out; }
.cycle-line.is-visible { stroke-dashoffset: 0; }
/* Cycle phase reveal — colonnes 1+2 (phase 2), overlay (phase 3), col 3 (phase 4) */
.cycle-reveal { opacity: 0; transform: translateX(14px); transition: opacity 380ms ease-out, transform 380ms ease-out; }
.cycle-reveal.is-visible { opacity: 1; transform: translateX(0); }
.cycle-reveal-up { opacity: 0; transform: translateY(10px); transition: opacity 500ms ease-out, transform 500ms ease-out; }
.cycle-reveal-up.is-visible { opacity: 1; transform: translateY(0); }
/* Cycle horloge rotation (col 1) */
@keyframes cycle-clock-spin { to { transform: rotate(360deg); } }
.cycle-clock { animation: cycle-clock-spin 8s linear infinite; transform-origin: center; display: inline-block; }
/* Cycle col 2 — fuite particules rouges */
@keyframes cycle-leak {
0% { transform: translate(0,0) scale(1); opacity: 0; }
20% { opacity: 0.75; }
100% { transform: translate(var(--lx,18px), var(--ly,-22px)) scale(0.4); opacity: 0; }
}
.cycle-leak-particle { animation: cycle-leak 1.8s ease-out infinite; }
/* Reduced motion */
@media (prefers-reduced-motion: reduce) {
.cycle-pulse, .cycle-card-dictia, .cycle-ring-outer, .cycle-ring-inner,
.cycle-clock, .cycle-leak-particle { animation: none !important; }
.cycle-reveal, .cycle-reveal-up { opacity: 1 !important; transform: none !important; }
.cycle-line { stroke-dashoffset: 0 !important; }
}
</style>
<section
class="bg-white py-20 border-y border-brand-border relative overflow-hidden"
aria-labelledby="cycle-title"
x-data="{ phase: 0, observer: null }"
x-init="
observer = new IntersectionObserver((entries) => {
entries.forEach(e => {
if (e.isIntersecting && phase === 0) {
setTimeout(() => phase = 1, 250);
setTimeout(() => phase = 2, 1100);
setTimeout(() => phase = 3, 2200);
setTimeout(() => phase = 4, 3000);
observer.disconnect();
}
});
}, { rootMargin: '-60px' });
observer.observe($el);
"
>
{# Subtle dot-grid bg #}
<div class="absolute inset-0 pointer-events-none" aria-hidden="true"
style="background-image: radial-gradient(circle, rgba(11,15,26,0.04) 1px, transparent 1px); background-size: 28px 28px;"></div>
<div class="relative max-w-[1200px] mx-auto px-6">
<div class="max-w-2xl mb-10">
<p class="eyebrow text-amber-600 mb-4">⚠ CADRE RÉGLEMENTAIRE</p>
<h2 id="cycle-title" class="text-[clamp(2rem,3vw,2.75rem)] font-black mb-3 text-brand-navy">
Trois options. <span class="text-brand-navy/30">Une seule est conforme.</span>
</h2>
<p class="text-base text-brand-navy/70">
La Loi&nbsp;25, le Cloud Act américain et le Guide IA du Barreau du Québec délimitent vos options&nbsp;
<span class="font-semibold text-brand-b1">pas votre volonté.</span>
</p>
</div>
<div class="max-w-5xl mx-auto">
{# Nœud source : "Réunion en cours" #}
<div class="flex justify-center mb-2">
<div class="relative inline-flex items-center gap-2.5 px-5 py-3 bg-brand-bg border border-brand-border rounded">
<template x-if="phase >= 1">
<span class="absolute inset-0 cycle-pulse pointer-events-none border border-brand-border rounded"></span>
</template>
<template x-if="phase >= 1">
<span class="absolute inset-0 cycle-pulse cycle-pulse-2 pointer-events-none border border-brand-border rounded"></span>
</template>
<template x-if="phase >= 1">
<span class="absolute inset-0 cycle-pulse cycle-pulse-3 pointer-events-none border border-brand-border rounded"></span>
</template>
<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-3.5 h-3.5 shrink-0 text-brand-navy/40" aria-hidden="true"><path d="M3 21h18"/><path d="M5 21V8l7-4 7 4v13"/><path d="M9 21v-6h6v6"/></svg>
<span class="text-[11px] tracking-wide text-brand-navy/55">Réunion en cours — données confidentielles</span>
</div>
</div>
{# Lignes de connexion SVG — de la source vers les 3 colonnes #}
<div class="relative h-10">
<svg class="w-full h-full" viewBox="0 0 100 10" preserveAspectRatio="none" aria-hidden="true">
<line class="cycle-line" :class="phase >= 1 ? 'is-visible' : ''"
x1="50" y1="0" x2="14" y2="10"
stroke="rgba(148,163,184,0.45)" stroke-width="0.3" stroke-dasharray="1.2 0.9" />
<line class="cycle-line" :class="phase >= 1 ? 'is-visible' : ''"
x1="50" y1="0" x2="50" y2="10"
stroke="rgba(239,68,68,0.45)" stroke-width="0.3" stroke-dasharray="1.2 0.9"
style="transition-delay: 80ms;" />
<line class="cycle-line" :class="phase >= 1 ? 'is-visible' : ''"
x1="50" y1="0" x2="86" y2="10"
stroke="rgba(0,189,216,0.55)" stroke-width="0.3" stroke-dasharray="1.2 0.9"
style="transition-delay: 160ms;" />
</svg>
</div>
{# Grille 3 colonnes — équivalence du grid-cols-[2fr_2fr_3fr] desktop #}
<div class="grid grid-cols-1 md:grid-cols-[2fr_2fr_3fr] gap-3 items-stretch">
{# COL 1 — Retranscription humaine #}
<div class="cycle-reveal-up flex flex-col rounded border border-brand-border bg-brand-bg overflow-hidden"
:class="phase >= 2 ? 'is-visible' : ''">
<div class="px-5 py-3 border-b border-brand-border">
<div class="font-mono text-[10px] tracking-[0.28em] uppercase text-brand-navy/40 mb-0.5">01</div>
<div class="font-bold text-sm text-brand-navy/55">Retranscription humaine</div>
</div>
<div class="flex-1 flex flex-col items-center justify-center px-5 py-7 gap-6">
{# Stack de papiers #}
<div class="relative w-[100px] h-[90px]" aria-hidden="true">
{% for offset in [0,1,2,3,4] %}
<div class="absolute left-0 right-0 h-[17px] rounded-sm flex items-center px-2 gap-1.5 bg-white border border-brand-border"
style="bottom: {{ offset * 15 }}px; transform: rotate({{ '1deg' if offset is even else '-1deg' }});">
<div class="w-2.5 h-1.5 rounded-sm bg-brand-navy/15 shrink-0"></div>
<div class="flex-1 h-[1.5px] rounded-full bg-brand-navy/10"></div>
</div>
{% endfor %}
</div>
<div class="flex items-center gap-4 w-full">
<div class="flex flex-col items-center gap-1.5 flex-1">
<span class="cycle-clock">
<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-5 h-5 text-brand-navy/45" aria-hidden="true"><circle cx="12" cy="12" r="10"/><path d="M12 6v6l4 2"/></svg>
</span>
<span class="font-mono text-[10px] text-brand-navy/45">5&nbsp;jours</span>
</div>
<div class="w-px h-8 bg-brand-border"></div>
<div class="flex flex-col items-center gap-1.5 flex-1">
<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-5 h-5 text-brand-navy/45" aria-hidden="true"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><path d="M14 2v6h6"/></svg>
<span class="font-mono text-[10px] text-brand-navy/45">~85&nbsp;$/h</span>
</div>
</div>
</div>
<div class="px-5 pb-5 pt-4 border-t border-brand-border">
<div class="flex items-baseline gap-1.5 mb-2">
<span class="font-black text-3xl leading-none text-brand-navy/65">315</span>
<span class="text-xs text-brand-navy/45">$ / réunion</span>
</div>
<span class="inline-flex items-center gap-1.5 px-2.5 py-1 rounded bg-red-50 border border-red-100">
<span class="w-1 h-1 rounded-full bg-red-400"></span>
<span class="font-mono text-[9px] tracking-[0.16em] uppercase text-red-500/80">Lent · Coûteux</span>
</span>
</div>
</div>
{# COL 2 — IA cloud américaine #}
<div class="cycle-reveal-up relative flex flex-col rounded border border-red-200 bg-red-50/30 overflow-hidden"
:class="phase >= 2 ? 'is-visible' : ''"
style="transition-delay: 120ms;">
{# Overlay légal NON CONFORME (phase 3) #}
<div class="absolute inset-0 z-30 flex flex-col items-center justify-center pointer-events-none cycle-reveal-up"
:class="phase >= 3 ? 'is-visible' : ''"
style="backdrop-filter: blur(6px); background: rgba(255,255,255,0.78);">
<div class="flex flex-col items-center gap-3 px-6 py-5 rounded bg-white border border-red-300 shadow-lg">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" class="w-7 h-7 text-red-500" aria-hidden="true"><path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>
<div class="text-center">
<div class="font-mono font-bold tracking-[0.14em] text-sm uppercase text-red-600">NON CONFORME</div>
<div class="font-mono text-[10px] tracking-[0.20em] uppercase mt-1.5 text-red-500/70">Loi 25 · Cloud Act américain</div>
</div>
</div>
</div>
<div class="px-5 py-3 border-b border-red-100">
<div class="font-mono text-[10px] tracking-[0.28em] uppercase text-red-500/60 mb-0.5">02</div>
<div class="font-bold text-sm text-red-500/75">IA cloud américaine</div>
</div>
<div class="relative flex-1 flex flex-col items-center justify-center px-5 py-6 gap-5">
<div class="relative flex flex-col items-center gap-2">
<div class="relative w-14 h-14 rounded flex items-center justify-center bg-red-100/60 border border-red-200" aria-hidden="true">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" class="w-6 h-6 text-red-500/70"><path d="M2 12s3-7 10-7c2.5 0 4.7.9 6.4 2.4M22 12s-3 7-10 7c-2.5 0-4.7-.9-6.4-2.4"/><line x1="2" y1="2" x2="22" y2="22"/></svg>
{# Particules de fuite #}
{% for i in range(6) %}
<span class="cycle-leak-particle absolute w-[3px] h-[3px] rounded-full bg-red-400 pointer-events-none"
style="left: 50%; top: 50%; --lx: {{ (-30 + i*12) }}px; --ly: -{{ 14 + (i % 3) * 8 }}px; animation-delay: {{ i * 0.20 }}s;"></span>
{% endfor %}
</div>
<div class="px-2 py-0.5 rounded bg-red-50 border border-red-100">
<span class="font-mono text-[9px] tracking-[0.22em] uppercase text-red-500/65">USA</span>
</div>
</div>
<div class="flex flex-col gap-2.5 w-full">
{% for risk in [
('cloud', 'Données transférées aux États-Unis'),
('alert', "Entraînement de l'IA publique"),
('users', '500&ndash;750&nbsp;$/mois · facturation par utilisateur')
] %}
<div class="flex items-start gap-2.5">
{% if risk[0] == 'cloud' %}
<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 mt-0.5 shrink-0 text-red-500/60" aria-hidden="true"><path d="M2 12s3-7 10-7c2.5 0 4.7.9 6.4 2.4M22 12s-3 7-10 7c-2.5 0-4.7-.9-6.4-2.4"/><line x1="2" y1="2" x2="22" y2="22"/></svg>
{% elif risk[0] == 'alert' %}
<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 mt-0.5 shrink-0 text-red-500/60" aria-hidden="true"><path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>
{% else %}
<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 mt-0.5 shrink-0 text-red-500/60" aria-hidden="true"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.87"/></svg>
{% endif %}
<span class="text-[12px] leading-snug text-brand-navy/55">{{ risk[1] | safe }}</span>
</div>
{% endfor %}
</div>
</div>
<div class="px-5 pb-5 pt-4 border-t border-red-100">
<div class="flex items-center gap-2">
<div class="w-1.5 h-1.5 rounded-full bg-red-400"></div>
<span class="font-mono text-[9px] tracking-[0.20em] uppercase text-red-500/70">Violation légale possible</span>
</div>
</div>
</div>
{# COL 3 — DictIA (featured, 3fr) #}
<div class="cycle-card-dictia cycle-reveal-up relative flex flex-col rounded border border-brand-b1/35 bg-gradient-to-br from-brand-b1/[0.06] to-brand-b3/[0.04] overflow-hidden"
:class="phase >= 4 ? 'is-visible' : ''"
style="transition-delay: 240ms;">
{# Halo ambiant #}
<div class="absolute inset-0 pointer-events-none" aria-hidden="true"
style="background: radial-gradient(ellipse 80% 35% at 50% 0%, rgba(0,189,216,0.12) 0%, transparent 65%);"></div>
<div class="relative px-5 py-3 border-b border-brand-b1/15 flex items-center gap-2.5">
<div class="relative w-4 h-4 flex items-center justify-center shrink-0">
<div class="absolute inset-0 rounded-full bg-brand-b1/15 border border-brand-b1/30"></div>
<div class="w-1.5 h-1.5 rounded-full bg-brand-b1"></div>
</div>
<span class="text-[11px] uppercase tracking-[0.22em] font-semibold text-brand-b1/80">Solution</span>
</div>
<div class="relative flex-1 flex flex-col items-center justify-center px-5 py-7 gap-5">
{# Lock + anneaux concentriques #}
<div class="relative flex items-center justify-center" aria-hidden="true">
<span class="cycle-ring-outer absolute rounded-full border border-brand-b1/15"
:class="phase >= 4 ? 'is-visible' : ''"
style="width: 96px; height: 96px;"></span>
<span class="cycle-ring-inner absolute rounded-full border border-brand-b1/25"
:class="phase >= 4 ? 'is-visible' : ''"
style="width: 68px; height: 68px;"></span>
<div class="relative w-[52px] h-[52px] rounded flex items-center justify-center bg-gradient-to-br from-brand-b1/15 to-brand-b3/10 border border-brand-b1/35">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" class="w-6 h-6 text-brand-b1"><rect x="3" y="11" width="18" height="11" rx="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg>
</div>
</div>
{# Flux sécurisé #}
<div class="cycle-reveal flex items-center gap-2 px-3.5 py-1.5 rounded bg-brand-b1/5 border border-brand-b1/15"
:class="phase >= 4 ? 'is-visible' : ''"
style="transition-delay: 350ms;">
<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-3 h-3 shrink-0 text-brand-b1/55" aria-hidden="true"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/></svg>
<span class="text-[11px] text-brand-b1/70">Transcription sécurisée</span>
<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-3 h-3 text-brand-b1/45" aria-hidden="true"><path d="M5 12h14M13 5l7 7-7 7"/></svg>
<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-3 h-3 shrink-0 text-brand-b1/55" aria-hidden="true"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/></svg>
</div>
{# Badges conformité #}
<div class="flex flex-col gap-2 w-full">
{% for badge_label in ['Loi 25 conforme', '100&nbsp;% hébergé au Québec', 'Données jamais partagées'] %}
<div class="cycle-reveal flex items-center gap-2.5 px-3.5 py-2.5 rounded bg-brand-b1/[0.06] border border-brand-b1/20"
:class="phase >= 4 ? 'is-visible' : ''"
style="transition-delay: {{ 180 + loop.index0 * 170 }}ms;">
<span class="w-4 h-4 rounded-full flex items-center justify-center shrink-0 bg-brand-b1/15 border border-brand-b1/30">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" class="w-2.5 h-2.5 text-brand-b1" aria-hidden="true"><path d="M5 13l4 4L19 7"/></svg>
</span>
<span class="text-[12px] font-medium text-brand-navy/75">{{ badge_label | safe }}</span>
</div>
{% endfor %}
</div>
</div>
<div class="relative px-5 pb-5 pt-4 border-t border-brand-b1/15">
<div class="flex items-center gap-2 mb-2.5">
<span class="flex-1 h-px bg-brand-b1/15"></span>
<span class="font-mono font-bold text-[10px] uppercase tracking-[0.20em] text-brand-b1">UTILISATEURS ILLIMITÉS</span>
<span class="flex-1 h-px bg-brand-b1/15"></span>
</div>
<p class="text-center text-[10px] mb-3.5 text-brand-b1/55">Zéro frais caché · Du jamais vu</p>
<div class="flex items-baseline gap-2">
<span class="font-black leading-none text-5xl text-brand-b1">173</span>
<span class="text-sm text-brand-navy/45 mb-0.5">$ / mois</span>
</div>
<div class="flex items-center gap-1.5 mt-2">
<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-3 h-3 text-brand-b1/65" aria-hidden="true"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg>
<p class="text-[10px] text-brand-b1/60">Conforme Loi&nbsp;25 · 100&nbsp;% Québec</p>
</div>
</div>
</div>
</div>
{# Barre d'économies — apparaît avec phase 4 #}
<div class="cycle-reveal-up mt-10 flex flex-wrap items-center gap-x-8 gap-y-3 justify-center"
:class="phase >= 4 ? 'is-visible' : ''"
style="transition-delay: 700ms;">
<div class="flex items-center gap-2 text-[12px] text-brand-navy/55">
<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-3.5 h-3.5 text-emerald-500" aria-hidden="true"><polyline points="23 18 13.5 8.5 8.5 13.5 1 6"/><polyline points="17 18 23 18 23 12"/></svg>
Économies annuelles · 25 utilisateurs
</div>
{% for sav in [('3&nbsp;924&nbsp;$', 'vs Otter.ai'), ('6&nbsp;924&nbsp;$', 'vs MS Teams'), ('2&nbsp;004&nbsp;$', 'vs Sténographe')] %}
<div class="flex items-baseline gap-1.5">
<span class="font-black text-xl text-emerald-600">{{ sav[0] | safe }}</span>
<span class="text-xs text-brand-navy/55">{{ sav[1] }}</span>
</div>
{% endfor %}
</div>
</div>
</div>
</section>
{# ===== SOLUTION (S de PAS) ===== #}
<section class="relative bg-brand-navy text-white py-20 overflow-hidden" aria-labelledby="solution-title">
{# Single subtle orb in solution bg — less busy than hero #}
@@ -455,6 +790,245 @@
</div>
</section>
{# ===== WAVE — Onde de transformation chaos→ordre ===== #}
{# Source canonique : InnovA-AI/Website-Sanity/components/sections/dictai-wave.tsx
Animation : slider mouse-X interactif morphe les barres rouge→cyan, particules pain→solution.
Mobile : bouton toggle (activé/désactivé) — pas de mousemove. #}
<style>
/* Wave bars — chaque barre est composée d'un wrapper + 2 spans (rouge et cyan) qu'on fait fader */
.wave-bar { transition: opacity 60ms linear, transform 60ms linear; }
/* Wave pain labels — flottent légèrement */
@keyframes wave-pain-float {
0%, 100% { transform: translate(-50%, calc(-50% - 2px)); opacity: 0.7; }
50% { transform: translate(-50%, calc(-50% + 3px)); opacity: 0.45; }
}
.wave-pain { animation: wave-pain-float 3.4s ease-in-out infinite; }
.wave-pain:nth-child(2) { animation-delay: 0.4s; }
.wave-pain:nth-child(3) { animation-delay: 0.8s; }
.wave-pain:nth-child(4) { animation-delay: 1.2s; }
/* Wave fall particles — chute des $ et h */
@keyframes wave-fall {
0% { transform: translate(-50%, 0) scale(1); opacity: 0.85; }
50% { transform: translate(-50%, 22px) scale(0.7); opacity: 0.30; }
100% { transform: translate(-50%, 42px) scale(0.3); opacity: 0; }
}
.wave-fall { animation: wave-fall 2.4s ease-out infinite; }
/* Wave flow particles — flux cyan gauche→droite (état ordre) */
@keyframes wave-flow {
0% { left: 12%; opacity: 0; }
20% { opacity: 0.85; }
100% { left: 84%; opacity: 0; }
}
.wave-flow { animation: wave-flow 1.7s linear infinite; }
/* Wave hint — chevron CTA hover droite */
@keyframes wave-hint-pulse {
0%, 100% { transform: translateX(0); opacity: 0.55; }
50% { transform: translateX(7px); opacity: 1; }
}
.wave-hint { animation: wave-hint-pulse 1.5s ease-in-out infinite; }
@media (prefers-reduced-motion: reduce) {
.wave-pain, .wave-fall, .wave-flow, .wave-hint { animation: none !important; }
}
</style>
<section class="bg-brand-navy text-white py-20 border-t border-white/[0.06] overflow-hidden" aria-labelledby="wave-title"
x-data="{
p: 0,
isMobile: false,
activated: false,
init() {
this.isMobile = window.innerWidth < 768;
window.addEventListener('resize', () => { this.isMobile = window.innerWidth < 768; });
},
onMove(e) {
if (this.isMobile) return;
const r = e.currentTarget.getBoundingClientRect();
this.p = Math.max(0, Math.min(1, (e.clientX - r.left) / r.width));
},
onLeave() { if (!this.isMobile) this.p = 0; },
toggle() { this.activated = !this.activated; this.p = this.activated ? 1 : 0; },
clamp(v) { return Math.max(0, Math.min(1, v)); },
get chaosOp() { return this.clamp(1 - (this.p - 0.12) / 0.42); },
get orderOp() { return this.clamp((this.p - 0.42) / 0.42); }
}">
<div class="max-w-[1200px] mx-auto px-6">
<div class="flex items-center gap-3 mb-5">
<span class="eyebrow text-brand-b3">Le problème</span>
<span class="flex-1 h-px bg-white/10 max-w-[60px]"></span>
</div>
<h2 id="wave-title" class="text-[clamp(2rem,3vw,2.75rem)] font-black leading-[1.05] mb-3">
La transcription manuelle <span class="grad-text">coûte cher</span>.
</h2>
<p class="text-base text-white/55 mb-8 max-w-lg hidden md:block">
Déplacez votre curseur vers la droite pour transcrire l'avenir.
</p>
<p class="text-base text-white/55 mb-8 max-w-lg md:hidden">
Touchez pour basculer entre&nbsp;: aujourd'hui (chaos) et avec DictIA (ordre).
</p>
{# Scène interactive — aspect-ratio 2/1 desktop, 4/3 mobile #}
<div
class="relative w-full max-w-5xl mx-auto overflow-hidden rounded border border-white/[0.06]"
style="aspect-ratio: 2 / 1; min-height: 280px; cursor: ew-resize;"
:style="isMobile ? 'cursor: pointer; aspect-ratio: 4/3' : ''"
@mousemove="onMove($event)"
@mouseleave="onLeave()"
@click="if (isMobile) toggle()"
role="img"
aria-label="Visualisation interactive : transcription manuelle (chaos rouge) vs DictIA (ordre cyan)"
>
{# Fond dégradé radial dynamique #}
<div class="absolute inset-0"
:style="`background: radial-gradient(ellipse 75% 75% at 50% 50%, rgba(${Math.round(45 + (11-45)*p)},${Math.round(10 + (15-10)*p)},${Math.round(12 + (40-12)*p)},0.55) 0%, rgba(11,15,26,1) 100%);`"
aria-hidden="true"></div>
{# Grille subtile #}
<div class="absolute inset-0 opacity-[0.02]"
style="background-image: linear-gradient(rgba(255,255,255,0.1) 1px, transparent 1px), linear-gradient(90deg, rgba(255,255,255,0.1) 1px, transparent 1px); background-size: 40px 40px;"
aria-hidden="true"></div>
{# SVG : 30 barres d'onde — calculées par macro Jinja (chaos vs order par lerp p) #}
<svg class="absolute inset-0 w-full h-full pointer-events-none z-10" viewBox="0 0 100 50" preserveAspectRatio="none" aria-hidden="true">
<defs>
<filter id="dw-glow" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="0.9" result="blur"/>
<feMerge><feMergeNode in="blur"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
</defs>
{# Fixed bar geometry — nous générons 30 barres avec hauteur min/max statique;
l'opacité chaos vs order est gérée par 2 groupes superposés contrôlés par x-attr style. #}
{% set NUM_BARS = 30 %}
{% set chaos_heights = [10.5, 13.2, 15.8, 17.4, 18.1, 17.6, 15.9, 13.6, 11.2, 9.5, 8.7, 9.4, 11.7, 14.5, 17.2, 18.8, 18.6, 16.9, 14.3, 11.5, 9.2, 8.4, 9.8, 12.6, 15.5, 17.5, 17.8, 16.2, 13.4, 10.1] %}
{% set order_heights = [0.85, 0.95, 1.05, 1.15, 1.10, 1.00, 0.92, 0.88, 1.00, 1.12, 1.18, 1.14, 1.05, 0.95, 0.88, 0.92, 1.05, 1.15, 1.18, 1.10, 1.00, 0.92, 0.88, 0.95, 1.10, 1.18, 1.15, 1.05, 0.95, 0.90] %}
{% for i in range(NUM_BARS) %}
{% set spacing = 84 / NUM_BARS %}
{% set bw = spacing * 0.52 %}
{% set cx = 8 + i * spacing + spacing / 2 %}
{# Chaos bar — visible quand p ≈ 0 #}
<g class="wave-bar" :style="`opacity: ${1 - p}`">
<rect x="{{ cx - bw / 2 }}" y="{{ 25 - chaos_heights[i] / 2 }}" width="{{ bw }}" height="{{ chaos_heights[i] }}" rx="{{ bw / 2 }}" fill="rgba(255,75,75,0.78)"/>
</g>
{# Order bar — visible quand p ≈ 1 #}
<g class="wave-bar" :style="`opacity: ${p}`">
<rect x="{{ cx - bw / 2 }}" y="{{ 25 - order_heights[i] / 2 }}" width="{{ bw }}" height="{{ order_heights[i] }}" rx="{{ bw / 2 }}" fill="rgba(107,159,255,0.6)"/>
</g>
{% endfor %}
{# Bouclier holographique (état ordre) #}
<path d="M 50 8 L 68 14 L 68 27 Q 68 38 50 43 Q 32 38 32 27 L 32 14 Z"
fill="none" stroke="rgba(107,159,255,0.55)" stroke-width="0.55" stroke-dasharray="2.5 2"
:style="`opacity: ${orderOp * 0.4}`"/>
</svg>
{# Icône Microphone (gauche) #}
<div class="absolute z-20 left-[3%] top-1/2 -translate-y-1/2 flex flex-col items-center gap-1.5"
:style="`opacity: ${Math.max(0.25, 1 - p * 0.55)}`" aria-hidden="true">
<div class="w-10 h-10 md:w-12 md:h-12 rounded flex items-center justify-center backdrop-blur-md border"
:style="`background: rgba(${Math.round(255 + (107-255)*p)},${Math.round(60 + (130-60)*p)},${Math.round(60 + (159-60)*p)},0.12); border-color: rgba(${Math.round(255 + (107-255)*p)},${Math.round(80 + (159-80)*p)},${Math.round(80 + (255-80)*p)},0.28);`">
<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 md:w-5 md:h-5"
:style="`color: ${p < 0.5 ? '#FF6B6B' : '#6B9FFF'}`"><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="22"/></svg>
</div>
<span class="text-[8px] font-medium" :style="`color: ${p < 0.5 ? 'rgba(255,107,107,0.65)' : 'rgba(107,159,255,0.65)'}`">Audio</span>
</div>
{# Icône Texte transcrit (droite) #}
<div class="absolute z-20 right-[3%] top-1/2 -translate-y-1/2 flex flex-col items-center gap-1.5"
:style="`opacity: ${orderOp}`" aria-hidden="true">
<div class="w-10 h-10 md:w-12 md:h-12 rounded flex items-center justify-center backdrop-blur-md border border-brand-b1/30"
style="background: linear-gradient(135deg, rgba(107,159,255,0.15), rgba(45,127,249,0.07)); box-shadow: 0 0 18px rgba(107,159,255,0.18);">
<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 md:w-5 md:h-5 text-brand-b1"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="9" y1="13" x2="15" y2="13"/></svg>
</div>
<span class="text-[8px] font-medium text-brand-b1/70">Texte</span>
</div>
{# Particules tombantes chaos ($, h) #}
<div class="absolute inset-0 z-10 pointer-events-none" :style="`opacity: ${chaosOp}`" aria-hidden="true">
{% for fp in [
{'x': 22, 'd': 0, 'type': 'money'},
{'x': 36, 'd': 0.9, 'type': 'time'},
{'x': 50, 'd': 1.5, 'type': 'money'},
{'x': 63, 'd': 0.4, 'type': 'time'},
{'x': 77, 'd': 1.2, 'type': 'money'},
{'x': 30, 'd': 1.9, 'type': 'time'},
{'x': 56, 'd': 0.7, 'type': 'money'},
{'x': 43, 'd': 2.1, 'type': 'time'}
] %}
<span class="wave-fall absolute font-bold text-[9px] md:text-[10px]"
style="left: {{ fp.x }}%; top: 30%; color: {{ '#FF6B6B' if fp.type == 'money' else '#FFD93D' }}; text-shadow: 0 0 8px rgba({{ '255,80,80' if fp.type == 'money' else '255,210,60' }},0.6); animation-delay: {{ fp.d }}s;">{{ '-$' if fp.type == 'money' else '-1h' }}</span>
{% endfor %}
</div>
{# Flux cyan (ordre) #}
<div class="absolute inset-0 z-10 pointer-events-none" :style="`opacity: ${orderOp}`" aria-hidden="true">
{% for fy in [18, 21, 24, 27, 30] %}
<span class="wave-flow absolute w-1 h-1 rounded-full bg-brand-b1"
style="top: {{ fy }}%; left: 12%; box-shadow: 0 0 5px rgba(107,159,255,0.7); animation-delay: {{ loop.index0 * 0.32 }}s;"></span>
{% endfor %}
</div>
{# Étiquettes douleur (chaos) #}
<div class="absolute inset-0 pointer-events-none" :style="`opacity: ${chaosOp}`" aria-hidden="true">
{% for pain in [
{'text': '4 à 6h pour transcrire 1h', 'x': 20, 'y': 12},
{'text': '600 à 1&nbsp;800&nbsp;$ / enregistrement', 'x': 64, 'y': 10},
{'text': 'Délais de 48h à 5 jours', 'x': 74, 'y': 38},
{'text': 'Risques de confidentialité', 'x': 18, 'y': 40}
] %}
<div class="wave-pain absolute text-[8px] md:text-[10px] font-medium px-2 py-0.5 rounded whitespace-nowrap"
style="left: {{ pain.x }}%; top: {{ pain.y }}%; transform: translate(-50%, -50%); background: rgba(255,50,50,0.1); border: 1px solid rgba(255,80,80,0.18); color: rgba(255,150,130,0.85);">
{{ pain.text | safe }}
</div>
{% endfor %}
</div>
{# Cartes solution (ordre) #}
<div class="absolute inset-0 pointer-events-none" :style="`opacity: ${orderOp}`" aria-hidden="true">
{% for sol in [
{'text': '~2 min pour 1h d\'audio', 'x': 20, 'y': 12},
{'text': 'Dès 173&nbsp;$/mois — illimité', 'x': 64, 'y': 10},
{'text': '90&nbsp;%+ d\'économies', 'x': 74, 'y': 38},
{'text': '100&nbsp;% Confidentiel — Hébergé au Qc', 'x': 18, 'y': 40}
] %}
<div class="absolute flex items-center gap-1.5 px-2 py-1 rounded backdrop-blur-sm"
style="left: {{ sol.x }}%; top: {{ sol.y }}%; transform: translate(-50%, -50%); background: rgba(107,159,255,0.10); border: 1px solid rgba(107,159,255,0.20);">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" class="w-2.5 h-2.5 text-brand-b1 shrink-0" aria-hidden="true"><path d="M5 13l4 4L19 7"/></svg>
<span class="text-[8px] md:text-[10px] text-white/80 whitespace-nowrap">{{ sol.text | safe }}</span>
</div>
{% endfor %}
</div>
{# Séparateur central + hint "→" #}
<div class="absolute left-1/2 top-0 bottom-0 w-px z-10 hidden md:block pointer-events-none"
:style="`opacity: ${Math.max(0, 1 - p * 2.8)}; background: linear-gradient(to bottom, transparent 10%, rgba(255,255,255,0.06) 30%, rgba(255,255,255,0.06) 70%, transparent 90%);`"
aria-hidden="true">
<div class="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 whitespace-nowrap"
:style="`opacity: ${Math.max(0, 1 - p * 3.2)}`">
<span class="wave-hint inline-block text-[9px] text-white/40 tracking-wider uppercase px-3 py-1 rounded-full border border-white/[0.08] bg-brand-navy/80 backdrop-blur-sm">
Survolez vers la droite →
</span>
</div>
</div>
{# Toggle mobile #}
<button type="button"
x-show="isMobile"
@click.stop="toggle()"
class="absolute bottom-3 left-1/2 -translate-x-1/2 z-30 inline-flex items-center gap-2 px-5 py-2.5 rounded text-xs font-bold transition-all border focus-visible:outline-2 focus-visible:outline-brand-b1 focus-visible:outline-offset-2"
:class="activated ? 'border-brand-b1/30 text-brand-b1' : 'border-white/15 text-white/70'"
:style="activated ? 'background: linear-gradient(135deg, rgba(107,159,255,0.2), rgba(45,127,249,0.1));' : 'background: linear-gradient(135deg, rgba(255,255,255,0.08), rgba(255,255,255,0.03));'"
x-cloak>
<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-3.5 h-3.5"><rect x="9" y="2" width="6" height="12" rx="3"/><path d="M19 10v2a7 7 0 0 1-14 0v-2"/></svg>
<span x-text="activated ? 'Voir sans DictIA' : 'Activer DictIA'"></span>
</button>
</div>
</div>
</section>
{# ===== PIPELINE — Comment ça marche : 4 étapes ===== #}
{# Source canonique : InnovA-AI/Website-Sanity/components/sections/dictai-pipeline.tsx
Animation traduite : Framer Motion auto-advance + sweep ring → Alpine.js setInterval + CSS keyframes #}
@@ -1108,6 +1682,234 @@
</div>
</section>
{# ===== CADRE RÉGLEMENTAIRE — Moniteur d'Interception ===== #}
{# Source canonique : InnovA-AI/Website-Sanity/components/sections/dictai-contraste.tsx (REGS + MoniteurInterception)
Animation : cycle automatique en 4 étapes (folder QC→US, alerte, HUD log, flash REGS séquentiel) + Alpine
Cartographie 6 textes : Loi 25, Loi 96, Cloud Act, Guide IA Barreau, Cadre IA MCN, CAI #}
<style>
/* Cadre — folder qui glisse de QC vers US */
.cadre-folder { transition: left 1.4s cubic-bezier(0.4,0,0.2,1), color 0.4s, filter 0.4s; }
/* Cadre — pulse halo autour du folder en alerte */
@keyframes cadre-folder-halo {
0%, 100% { transform: scale(1); opacity: 0.5; }
50% { transform: scale(1.8); opacity: 0; }
}
.cadre-halo { animation: cadre-folder-halo 1.2s ease-out infinite; }
/* Cadre — REGS row flash amber pendant le sweep */
.cadre-reg { transition: border-color 180ms, background-color 180ms; }
.cadre-reg.is-flash { border-color: rgba(245,158,11,0.50) !important; background-color: rgba(245,158,11,0.06) !important; }
.cadre-reg.is-flash .cadre-reg-label { color: #d97706 !important; }
/* Cadre — alerte clignotante footer */
@keyframes cadre-blink {
0%, 100% { opacity: 1; }
50% { opacity: 0.3; }
}
.cadre-blink { animation: cadre-blink 0.85s ease-in-out infinite; }
/* Cadre — caret HUD blink */
@keyframes cadre-caret {
0%, 100% { opacity: 1; }
50% { opacity: 0; }
}
.cadre-caret { animation: cadre-caret 0.7s ease-in-out infinite; }
@media (prefers-reduced-motion: reduce) {
.cadre-folder, .cadre-halo, .cadre-reg, .cadre-blink, .cadre-caret {
animation: none !important; transition: none !important;
}
}
</style>
<section
class="bg-white py-20 border-b border-brand-border relative overflow-hidden"
aria-labelledby="cadre-title"
x-data="{
step: 0,
hudLines: [],
flashIdx: -1,
timers: [],
observer: null,
HUD: ['&gt; Interception IA détectée.', '&gt; Données utilisées pour entraînement.', '&gt; Statut : Violation Légale.'],
runCycle() {
this.step = 0; this.hudLines = []; this.flashIdx = -1;
this.timers.push(setTimeout(() => this.step = 1, 900));
this.timers.push(setTimeout(() => this.step = 2, 2300));
this.timers.push(setTimeout(() => {
this.step = 3;
this.HUD.forEach((line, i) => {
this.timers.push(setTimeout(() => this.hudLines.push(line), i * 650));
});
}, 2800));
for (let i = 0; i < 6; i++) {
this.timers.push(setTimeout(() => this.flashIdx = i, 4700 + i * 200));
}
this.timers.push(setTimeout(() => this.flashIdx = -1, 4700 + 6 * 200 + 500));
this.timers.push(setTimeout(() => this.runCycle(), 8500));
},
init() {
this.observer = new IntersectionObserver((entries) => {
entries.forEach(e => {
if (e.isIntersecting && this.step === 0 && this.timers.length === 0) {
this.timers.push(setTimeout(() => this.runCycle(), 300));
this.observer.disconnect();
}
});
}, { rootMargin: '-100px' });
this.observer.observe($el);
}
}"
>
<div class="absolute inset-0 pointer-events-none" aria-hidden="true"
style="background-image: radial-gradient(circle, rgba(11,15,26,0.04) 1px, transparent 1px); background-size: 28px 28px;"></div>
<div class="relative max-w-[1200px] mx-auto px-6">
<div class="text-center max-w-2xl mx-auto mb-12">
<p class="eyebrow text-amber-600 mb-4">⚠ CADRE RÉGLEMENTAIRE QUÉBEC</p>
<h2 id="cadre-title" class="text-[clamp(2rem,3vw,2.75rem)] font-black mb-4 text-brand-navy">
Ce que vos outils actuels enfreignent <span class="grad-text">en secret</span>.
</h2>
<p class="text-base text-brand-navy/70">
Six textes officiels encadrent l'usage de l'IA et la circulation des données vocales au Québec.
Visualisez en temps réel comment une transcription cloud quitte la province et déclenche les violations.
</p>
</div>
{# Carte principale — header + body 2 colonnes (animation à gauche, REGS à droite) #}
<div class="max-w-5xl mx-auto rounded border border-brand-border bg-brand-bg overflow-hidden">
{# Header — Moniteur d'Interception #}
<div class="flex items-center gap-2.5 px-5 pt-4 pb-3 border-b border-brand-border bg-white">
<span class="w-1.5 h-1.5 rounded-full shrink-0 transition-all duration-300"
:class="step >= 2 ? 'bg-amber-500 shadow-[0_0_6px_#F59E0B]' : 'bg-brand-navy/30'"
aria-hidden="true"></span>
<span class="font-medium text-[10px] uppercase tracking-[0.22em] text-brand-navy/55">
Moniteur d'Interception
</span>
<span x-show="step >= 2"
class="ml-auto font-bold text-[9px] uppercase tracking-[0.18em] text-amber-600 cadre-blink"
x-cloak>
⚠ Alerte Active
</span>
</div>
{# Body — 2 colonnes #}
<div class="flex flex-col md:flex-row">
{# LEFT — animation track (~42%) #}
<div class="relative md:w-[42%] px-5 py-5 overflow-hidden bg-brand-bg" style="min-height: 200px;">
{# Grille bg #}
<div class="absolute inset-0 pointer-events-none" aria-hidden="true"
style="background-image: linear-gradient(rgba(11,15,26,0.04) 1px, transparent 1px), linear-gradient(90deg, rgba(11,15,26,0.04) 1px, transparent 1px); background-size: 20px 20px;"></div>
{# Labels QC / US #}
<div class="relative flex items-center justify-between mb-3">
<span class="font-bold text-[9px] uppercase tracking-[0.16em] transition-colors duration-300"
:class="step >= 2 ? 'text-brand-navy/30' : 'text-brand-b3'">
QC
</span>
<span class="font-bold text-[9px] uppercase tracking-[0.16em] text-brand-navy/30">
US
</span>
</div>
{# Track #}
<div class="relative h-10">
<div class="absolute top-0 bottom-0 transition-colors duration-300"
:style="`left: 50%; width: 0; border-left: 1.5px dashed ${step >= 2 ? 'rgba(245,158,11,0.6)' : 'rgba(11,15,26,0.18)'};`"
aria-hidden="true"></div>
<div class="cadre-folder absolute top-1/2 -translate-y-1/2 -translate-x-1/2"
:style="`left: ${step === 0 ? '6%' : '54%'}`">
<span x-show="step >= 2" class="cadre-halo absolute inset-[-8px] rounded-full bg-amber-500/15" x-cloak></span>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"
class="relative w-5 h-5 transition-colors duration-300"
:class="step >= 2 ? 'text-amber-500' : 'text-brand-b1'"
:style="`filter: drop-shadow(0 0 ${step >= 2 ? '6px rgba(245,158,11,0.7)' : '4px rgba(0,98,255,0.5)'})`"
aria-hidden="true">
<path d="M3 7a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/>
</svg>
</div>
</div>
{# HUD panel #}
<div x-show="step >= 3"
x-transition:enter="transition ease-out duration-300"
x-transition:enter-start="opacity-0 translate-y-2"
x-transition:enter-end="opacity-100 translate-y-0"
class="mt-3 rounded px-3 py-2.5 font-mono"
style="background: rgba(11,15,26,0.92); border: 1px solid rgba(245,158,11,0.30); font-size: 10px; color: #F59E0B; line-height: 1.65;"
x-cloak>
<template x-for="(line, i) in hudLines" :key="i">
<div x-html="line"></div>
</template>
<span x-show="hudLines.length < HUD.length" class="cadre-caret"></span>
</div>
</div>
{# RIGHT — REGS list (~58%) #}
<div class="md:w-[58%] px-5 py-5 border-t md:border-t-0 md:border-l border-brand-border bg-white">
<p class="font-semibold text-[10px] uppercase tracking-[0.18em] mb-3 text-brand-navy/45">
Ce que vos outils actuels enfreignent en secret&nbsp;:
</p>
<ul class="flex flex-col gap-1.5" role="list">
{% for reg in [
{'label': 'Loi 25 (P-39.1)', 'detail': 'Renseignements personnels sensibles — pénalité jusqu\'à 25 M$', 'href': 'https://www.legisquebec.gouv.qc.ca/fr/document/lc/P-39.1', 'risk': True},
{'label': 'Loi 96 (C-11)', 'detail': 'Francisation — PME 25+ employés depuis juin 2025', 'href': 'https://www.legisquebec.gouv.qc.ca/fr/document/lc/C-11', 'risk': True},
{'label': 'US Cloud Act', 'detail': 'Accès aux données hébergées hors QC par les autorités américaines', 'href': 'https://www.congress.gov/bill/115th-congress/senate-bill/2383', 'risk': True},
{'label': 'Guide IA — Barreau QC', 'detail': 'Recommande systèmes fermés déployés en interne (publié 2024)', 'href': 'https://www.barreau.qc.ca/', 'risk': False},
{'label': 'Cadre IA — MCN', 'detail': 'Gouvernance IA pour organismes publics (déc.&nbsp;2025, conformité 19 juin 2026)', 'href': 'https://www.tresor.gouv.qc.ca/', 'risk': False},
{'label': 'CAI', 'detail': 'Commission d\'accès à l\'information — application active', 'href': 'https://www.cai.gouv.qc.ca/', 'risk': False}
] %}
<li role="listitem">
<a href="{{ reg.href }}" target="_blank" rel="noopener noreferrer"
class="cadre-reg group flex items-start gap-2 rounded px-2.5 py-1.5 no-underline border focus-visible:outline-2 focus-visible:outline-amber-500 focus-visible:outline-offset-2"
:class="flashIdx === {{ loop.index0 }} ? 'is-flash' : ''"
style="border-color: {{ 'rgba(239,68,68,0.20)' if reg.risk else 'rgba(0,98,255,0.15)' }};">
<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-3.5 h-3.5 mt-0.5 shrink-0"
style="color: {{ 'rgba(239,68,68,0.65)' if reg.risk else 'rgba(0,98,255,0.55)' }};"
aria-hidden="true"><path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>
<div class="min-w-0 flex-1">
<div class="flex items-start gap-1.5 flex-wrap">
<span class="cadre-reg-label font-semibold text-[11px] leading-tight shrink-0 transition-colors"
style="color: {{ '#dc2626' if reg.risk else '#1d4ed8' }};">{{ reg.label }}</span>
<span class="text-[10px] leading-tight text-brand-navy/55">{{ reg.detail | safe }}</span>
</div>
</div>
<span class="text-[9px] shrink-0 mt-0.5 opacity-0 group-hover:opacity-60 transition-opacity text-brand-navy/70">Lire ↗</span>
</a>
</li>
{% endfor %}
</ul>
<p class="mt-3 text-right text-[9px] text-brand-navy/30">
Les textes officiels font foi. Les informations ci-dessus sont fournies à titre indicatif.
</p>
</div>
</div>
{# Footer — verdict #}
<div class="px-5 py-3 flex items-center gap-2.5 border-t border-brand-border bg-white">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"
class="w-4 h-4 transition-colors"
:class="step >= 2 ? 'text-amber-500 cadre-blink' : 'text-brand-navy/35'"
aria-hidden="true"><path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>
<span class="font-bold text-[10px] uppercase tracking-[0.20em] transition-colors"
:class="step >= 2 ? 'text-amber-600 cadre-blink' : 'text-brand-navy/35'"
style="text-shadow: 0 0 6px rgba(245,158,11,0.20);"
aria-live="polite">
<span x-text="step >= 2 ? 'NON CONFORME — Loi 25 · Cloud Act' : 'Surveillance active...'"></span>
</span>
</div>
</div>
<p class="text-center text-xs text-brand-navy/55 mt-6 max-w-2xl mx-auto">
DictIA est conçu pour les secteurs réglementés du Québec — Loi&nbsp;25, Cloud Act, Barreau, ChAD, AMF. Hébergement OVH&nbsp;Beauharnois,
code source AGPL&nbsp;v3 vérifiable, audit trail intégré.
</p>
</div>
</section>
{# ===== TÉMOIGNAGES (placeholder pré-lancement) ===== #}
<section class="bg-brand-bg py-20" aria-labelledby="testimonials-title">
<div class="max-w-[1200px] mx-auto px-6">