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:
@@ -135,6 +135,7 @@
|
||||
--leading-tight: 1.25;
|
||||
--leading-snug: 1.375;
|
||||
--leading-relaxed: 1.625;
|
||||
--radius-sm: 0.25rem;
|
||||
--radius-md: 0.375rem;
|
||||
--radius-lg: 0.5rem;
|
||||
--radius-xl: 0.75rem;
|
||||
@@ -144,6 +145,7 @@
|
||||
--animate-spin: spin 1s linear infinite;
|
||||
--animate-pulse: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
|
||||
--blur-sm: 8px;
|
||||
--blur-md: 12px;
|
||||
--blur-xl: 24px;
|
||||
--blur-3xl: 64px;
|
||||
--default-transition-duration: 150ms;
|
||||
@@ -365,6 +367,9 @@
|
||||
.inset-0 {
|
||||
inset: calc(var(--spacing) * 0);
|
||||
}
|
||||
.inset-\[-8px\] {
|
||||
inset: -8px;
|
||||
}
|
||||
.inset-x-0 {
|
||||
inset-inline: calc(var(--spacing) * 0);
|
||||
}
|
||||
@@ -461,6 +466,9 @@
|
||||
.right-4 {
|
||||
right: calc(var(--spacing) * 4);
|
||||
}
|
||||
.right-\[3\%\] {
|
||||
right: 3%;
|
||||
}
|
||||
.-bottom-10 {
|
||||
bottom: calc(var(--spacing) * -10);
|
||||
}
|
||||
@@ -470,6 +478,9 @@
|
||||
.bottom-1\/4 {
|
||||
bottom: calc(1 / 4 * 100%);
|
||||
}
|
||||
.bottom-3 {
|
||||
bottom: calc(var(--spacing) * 3);
|
||||
}
|
||||
.bottom-4 {
|
||||
bottom: calc(var(--spacing) * 4);
|
||||
}
|
||||
@@ -512,12 +523,18 @@
|
||||
.left-80 {
|
||||
left: calc(var(--spacing) * 80);
|
||||
}
|
||||
.left-\[3\%\] {
|
||||
left: 3%;
|
||||
}
|
||||
.z-10 {
|
||||
z-index: 10;
|
||||
}
|
||||
.z-20 {
|
||||
z-index: 20;
|
||||
}
|
||||
.z-30 {
|
||||
z-index: 30;
|
||||
}
|
||||
.z-40 {
|
||||
z-index: 40;
|
||||
}
|
||||
@@ -674,9 +691,15 @@
|
||||
.mb-2 {
|
||||
margin-bottom: calc(var(--spacing) * 2);
|
||||
}
|
||||
.mb-2\.5 {
|
||||
margin-bottom: calc(var(--spacing) * 2.5);
|
||||
}
|
||||
.mb-3 {
|
||||
margin-bottom: calc(var(--spacing) * 3);
|
||||
}
|
||||
.mb-3\.5 {
|
||||
margin-bottom: calc(var(--spacing) * 3.5);
|
||||
}
|
||||
.mb-4 {
|
||||
margin-bottom: calc(var(--spacing) * 4);
|
||||
}
|
||||
@@ -821,9 +844,21 @@
|
||||
.h-72 {
|
||||
height: calc(var(--spacing) * 72);
|
||||
}
|
||||
.h-\[1\.5px\] {
|
||||
height: 1.5px;
|
||||
}
|
||||
.h-\[2px\] {
|
||||
height: 2px;
|
||||
}
|
||||
.h-\[3px\] {
|
||||
height: 3px;
|
||||
}
|
||||
.h-\[17px\] {
|
||||
height: 17px;
|
||||
}
|
||||
.h-\[52px\] {
|
||||
height: 52px;
|
||||
}
|
||||
.h-\[62px\] {
|
||||
height: 62px;
|
||||
}
|
||||
@@ -833,6 +868,9 @@
|
||||
.h-\[88px\] {
|
||||
height: 88px;
|
||||
}
|
||||
.h-\[90px\] {
|
||||
height: 90px;
|
||||
}
|
||||
.h-\[95vh\] {
|
||||
height: 95vh;
|
||||
}
|
||||
@@ -917,6 +955,12 @@
|
||||
.min-h-screen {
|
||||
min-height: 100vh;
|
||||
}
|
||||
.w-1 {
|
||||
width: calc(var(--spacing) * 1);
|
||||
}
|
||||
.w-1\.5 {
|
||||
width: calc(var(--spacing) * 1.5);
|
||||
}
|
||||
.w-1\/2 {
|
||||
width: calc(1 / 2 * 100%);
|
||||
}
|
||||
@@ -992,12 +1036,21 @@
|
||||
.w-80 {
|
||||
width: calc(var(--spacing) * 80);
|
||||
}
|
||||
.w-\[3px\] {
|
||||
width: 3px;
|
||||
}
|
||||
.w-\[52px\] {
|
||||
width: 52px;
|
||||
}
|
||||
.w-\[68px\] {
|
||||
width: 68px;
|
||||
}
|
||||
.w-\[88px\] {
|
||||
width: 88px;
|
||||
}
|
||||
.w-\[100px\] {
|
||||
width: 100px;
|
||||
}
|
||||
.w-\[400px\] {
|
||||
width: 400px;
|
||||
}
|
||||
@@ -1016,6 +1069,9 @@
|
||||
.w-full {
|
||||
width: 100%;
|
||||
}
|
||||
.w-px {
|
||||
width: 1px;
|
||||
}
|
||||
.max-w-2xl {
|
||||
max-width: var(--container-2xl);
|
||||
}
|
||||
@@ -1251,6 +1307,9 @@
|
||||
.grid-cols-7 {
|
||||
grid-template-columns: repeat(7, minmax(0, 1fr));
|
||||
}
|
||||
.grid-cols-\[2fr_2fr_3fr\] {
|
||||
grid-template-columns: 2fr 2fr 3fr;
|
||||
}
|
||||
.grid-cols-\[180px_1fr_170px\] {
|
||||
grid-template-columns: 180px 1fr 170px;
|
||||
}
|
||||
@@ -1311,6 +1370,9 @@
|
||||
.gap-4 {
|
||||
gap: calc(var(--spacing) * 4);
|
||||
}
|
||||
.gap-5 {
|
||||
gap: calc(var(--spacing) * 5);
|
||||
}
|
||||
.gap-6 {
|
||||
gap: calc(var(--spacing) * 6);
|
||||
}
|
||||
@@ -1388,6 +1450,10 @@
|
||||
-moz-column-gap: calc(var(--spacing) * 6);
|
||||
column-gap: calc(var(--spacing) * 6);
|
||||
}
|
||||
.gap-x-8 {
|
||||
-moz-column-gap: calc(var(--spacing) * 8);
|
||||
column-gap: calc(var(--spacing) * 8);
|
||||
}
|
||||
.space-x-1 {
|
||||
:where(& > :not(:last-child)) {
|
||||
--tw-space-x-reverse: 0;
|
||||
@@ -1429,6 +1495,9 @@
|
||||
.gap-y-2 {
|
||||
row-gap: calc(var(--spacing) * 2);
|
||||
}
|
||||
.gap-y-3 {
|
||||
row-gap: calc(var(--spacing) * 3);
|
||||
}
|
||||
.divide-y {
|
||||
:where(& > :not(:last-child)) {
|
||||
--tw-divide-y-reverse: 0;
|
||||
@@ -1480,6 +1549,9 @@
|
||||
.rounded-none {
|
||||
border-radius: 0;
|
||||
}
|
||||
.rounded-sm {
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
.rounded-xl {
|
||||
border-radius: var(--radius-xl);
|
||||
}
|
||||
@@ -1622,6 +1694,21 @@
|
||||
.border-brand-b1 {
|
||||
border-color: #0062ff;
|
||||
}
|
||||
.border-brand-b1\/15 {
|
||||
border-color: color-mix(in oklab, #0062ff 15%, transparent);
|
||||
}
|
||||
.border-brand-b1\/20 {
|
||||
border-color: color-mix(in oklab, #0062ff 20%, transparent);
|
||||
}
|
||||
.border-brand-b1\/25 {
|
||||
border-color: color-mix(in oklab, #0062ff 25%, transparent);
|
||||
}
|
||||
.border-brand-b1\/30 {
|
||||
border-color: color-mix(in oklab, #0062ff 30%, transparent);
|
||||
}
|
||||
.border-brand-b1\/35 {
|
||||
border-color: color-mix(in oklab, #0062ff 35%, transparent);
|
||||
}
|
||||
.border-brand-b2\/40 {
|
||||
border-color: color-mix(in oklab, #00bdd8 40%, transparent);
|
||||
}
|
||||
@@ -1664,6 +1751,9 @@
|
||||
border-color: color-mix(in oklab, var(--color-purple-500) 30%, transparent);
|
||||
}
|
||||
}
|
||||
.border-red-100 {
|
||||
border-color: var(--color-red-100);
|
||||
}
|
||||
.border-red-200 {
|
||||
border-color: var(--color-red-200);
|
||||
}
|
||||
@@ -1703,6 +1793,12 @@
|
||||
border-color: color-mix(in oklab, var(--color-white) 10%, transparent);
|
||||
}
|
||||
}
|
||||
.border-white\/15 {
|
||||
border-color: color-mix(in srgb, #fff 15%, transparent);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
border-color: color-mix(in oklab, var(--color-white) 15%, transparent);
|
||||
}
|
||||
}
|
||||
.border-white\/20 {
|
||||
border-color: color-mix(in srgb, #fff 20%, transparent);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
@@ -1844,12 +1940,21 @@
|
||||
.bg-amber-100 {
|
||||
background-color: var(--color-amber-100);
|
||||
}
|
||||
.bg-amber-500 {
|
||||
background-color: var(--color-amber-500);
|
||||
}
|
||||
.bg-amber-500\/10 {
|
||||
background-color: color-mix(in srgb, oklch(76.9% 0.188 70.08) 10%, transparent);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
background-color: color-mix(in oklab, var(--color-amber-500) 10%, transparent);
|
||||
}
|
||||
}
|
||||
.bg-amber-500\/15 {
|
||||
background-color: color-mix(in srgb, oklch(76.9% 0.188 70.08) 15%, transparent);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
background-color: color-mix(in oklab, var(--color-amber-500) 15%, transparent);
|
||||
}
|
||||
}
|
||||
.bg-amber-500\/20 {
|
||||
background-color: color-mix(in srgb, oklch(76.9% 0.188 70.08) 20%, transparent);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
@@ -1898,12 +2003,18 @@
|
||||
.bg-brand-b1 {
|
||||
background-color: #0062ff;
|
||||
}
|
||||
.bg-brand-b1\/5 {
|
||||
background-color: color-mix(in oklab, #0062ff 5%, transparent);
|
||||
}
|
||||
.bg-brand-b1\/10 {
|
||||
background-color: color-mix(in oklab, #0062ff 10%, transparent);
|
||||
}
|
||||
.bg-brand-b1\/15 {
|
||||
background-color: color-mix(in oklab, #0062ff 15%, transparent);
|
||||
}
|
||||
.bg-brand-b1\/\[0\.06\] {
|
||||
background-color: color-mix(in oklab, #0062ff 6%, transparent);
|
||||
}
|
||||
.bg-brand-b3\/10 {
|
||||
background-color: color-mix(in oklab, #00c896 10%, transparent);
|
||||
}
|
||||
@@ -1925,6 +2036,18 @@
|
||||
.bg-brand-navy {
|
||||
background-color: #060d1a;
|
||||
}
|
||||
.bg-brand-navy\/10 {
|
||||
background-color: color-mix(in oklab, #060d1a 10%, transparent);
|
||||
}
|
||||
.bg-brand-navy\/15 {
|
||||
background-color: color-mix(in oklab, #060d1a 15%, transparent);
|
||||
}
|
||||
.bg-brand-navy\/30 {
|
||||
background-color: color-mix(in oklab, #060d1a 30%, transparent);
|
||||
}
|
||||
.bg-brand-navy\/80 {
|
||||
background-color: color-mix(in oklab, #060d1a 80%, transparent);
|
||||
}
|
||||
.bg-brand-navy\/\[0\.97\] {
|
||||
background-color: color-mix(in oklab, #060d1a 97%, transparent);
|
||||
}
|
||||
@@ -2033,9 +2156,24 @@
|
||||
.bg-red-50 {
|
||||
background-color: var(--color-red-50);
|
||||
}
|
||||
.bg-red-50\/30 {
|
||||
background-color: color-mix(in srgb, oklch(97.1% 0.013 17.38) 30%, transparent);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
background-color: color-mix(in oklab, var(--color-red-50) 30%, transparent);
|
||||
}
|
||||
}
|
||||
.bg-red-100 {
|
||||
background-color: var(--color-red-100);
|
||||
}
|
||||
.bg-red-100\/60 {
|
||||
background-color: color-mix(in srgb, oklch(93.6% 0.032 17.717) 60%, transparent);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
background-color: color-mix(in oklab, var(--color-red-100) 60%, transparent);
|
||||
}
|
||||
}
|
||||
.bg-red-400 {
|
||||
background-color: var(--color-red-400);
|
||||
}
|
||||
.bg-red-500\/10 {
|
||||
background-color: color-mix(in srgb, oklch(63.7% 0.237 25.331) 10%, transparent);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
@@ -2169,6 +2307,14 @@
|
||||
--tw-gradient-from: var(--color-blue-600);
|
||||
--tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
|
||||
}
|
||||
.from-brand-b1\/15 {
|
||||
--tw-gradient-from: color-mix(in oklab, #0062ff 15%, transparent);
|
||||
--tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
|
||||
}
|
||||
.from-brand-b1\/\[0\.06\] {
|
||||
--tw-gradient-from: color-mix(in oklab, #0062ff 6%, transparent);
|
||||
--tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
|
||||
}
|
||||
.from-orange-500 {
|
||||
--tw-gradient-from: var(--color-orange-500);
|
||||
--tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
|
||||
@@ -2208,6 +2354,14 @@
|
||||
--tw-gradient-to: var(--color-amber-600);
|
||||
--tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
|
||||
}
|
||||
.to-brand-b3\/10 {
|
||||
--tw-gradient-to: color-mix(in oklab, #00c896 10%, transparent);
|
||||
--tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
|
||||
}
|
||||
.to-brand-b3\/\[0\.04\] {
|
||||
--tw-gradient-to: color-mix(in oklab, #00c896 4%, transparent);
|
||||
--tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
|
||||
}
|
||||
.to-purple-500 {
|
||||
--tw-gradient-to: var(--color-purple-500);
|
||||
--tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
|
||||
@@ -2292,6 +2446,9 @@
|
||||
.px-3 {
|
||||
padding-inline: calc(var(--spacing) * 3);
|
||||
}
|
||||
.px-3\.5 {
|
||||
padding-inline: calc(var(--spacing) * 3.5);
|
||||
}
|
||||
.px-4 {
|
||||
padding-inline: calc(var(--spacing) * 4);
|
||||
}
|
||||
@@ -2331,9 +2488,15 @@
|
||||
.py-4 {
|
||||
padding-block: calc(var(--spacing) * 4);
|
||||
}
|
||||
.py-5 {
|
||||
padding-block: calc(var(--spacing) * 5);
|
||||
}
|
||||
.py-6 {
|
||||
padding-block: calc(var(--spacing) * 6);
|
||||
}
|
||||
.py-7 {
|
||||
padding-block: calc(var(--spacing) * 7);
|
||||
}
|
||||
.py-8 {
|
||||
padding-block: calc(var(--spacing) * 8);
|
||||
}
|
||||
@@ -2515,6 +2678,9 @@
|
||||
font-size: var(--text-xs);
|
||||
line-height: var(--tw-leading, var(--text-xs--line-height));
|
||||
}
|
||||
.text-\[8px\] {
|
||||
font-size: 8px;
|
||||
}
|
||||
.text-\[9px\] {
|
||||
font-size: 9px;
|
||||
}
|
||||
@@ -2524,6 +2690,9 @@
|
||||
.text-\[11px\] {
|
||||
font-size: 11px;
|
||||
}
|
||||
.text-\[12px\] {
|
||||
font-size: 12px;
|
||||
}
|
||||
.text-\[13px\] {
|
||||
font-size: 13px;
|
||||
}
|
||||
@@ -2606,6 +2775,30 @@
|
||||
--tw-tracking: 0.12em;
|
||||
letter-spacing: 0.12em;
|
||||
}
|
||||
.tracking-\[0\.14em\] {
|
||||
--tw-tracking: 0.14em;
|
||||
letter-spacing: 0.14em;
|
||||
}
|
||||
.tracking-\[0\.16em\] {
|
||||
--tw-tracking: 0.16em;
|
||||
letter-spacing: 0.16em;
|
||||
}
|
||||
.tracking-\[0\.18em\] {
|
||||
--tw-tracking: 0.18em;
|
||||
letter-spacing: 0.18em;
|
||||
}
|
||||
.tracking-\[0\.20em\] {
|
||||
--tw-tracking: 0.20em;
|
||||
letter-spacing: 0.20em;
|
||||
}
|
||||
.tracking-\[0\.22em\] {
|
||||
--tw-tracking: 0.22em;
|
||||
letter-spacing: 0.22em;
|
||||
}
|
||||
.tracking-\[0\.28em\] {
|
||||
--tw-tracking: 0.28em;
|
||||
letter-spacing: 0.28em;
|
||||
}
|
||||
.tracking-tight {
|
||||
--tw-tracking: var(--tracking-tight);
|
||||
letter-spacing: var(--tracking-tight);
|
||||
@@ -2730,6 +2923,24 @@
|
||||
.text-brand-b1 {
|
||||
color: #0062ff;
|
||||
}
|
||||
.text-brand-b1\/45 {
|
||||
color: color-mix(in oklab, #0062ff 45%, transparent);
|
||||
}
|
||||
.text-brand-b1\/55 {
|
||||
color: color-mix(in oklab, #0062ff 55%, transparent);
|
||||
}
|
||||
.text-brand-b1\/60 {
|
||||
color: color-mix(in oklab, #0062ff 60%, transparent);
|
||||
}
|
||||
.text-brand-b1\/65 {
|
||||
color: color-mix(in oklab, #0062ff 65%, transparent);
|
||||
}
|
||||
.text-brand-b1\/70 {
|
||||
color: color-mix(in oklab, #0062ff 70%, transparent);
|
||||
}
|
||||
.text-brand-b1\/80 {
|
||||
color: color-mix(in oklab, #0062ff 80%, transparent);
|
||||
}
|
||||
.text-brand-b2 {
|
||||
color: #00bdd8;
|
||||
}
|
||||
@@ -2742,18 +2953,33 @@
|
||||
.text-brand-navy\/30 {
|
||||
color: color-mix(in oklab, #060d1a 30%, transparent);
|
||||
}
|
||||
.text-brand-navy\/35 {
|
||||
color: color-mix(in oklab, #060d1a 35%, transparent);
|
||||
}
|
||||
.text-brand-navy\/40 {
|
||||
color: color-mix(in oklab, #060d1a 40%, transparent);
|
||||
}
|
||||
.text-brand-navy\/45 {
|
||||
color: color-mix(in oklab, #060d1a 45%, transparent);
|
||||
}
|
||||
.text-brand-navy\/50 {
|
||||
color: color-mix(in oklab, #060d1a 50%, transparent);
|
||||
}
|
||||
.text-brand-navy\/55 {
|
||||
color: color-mix(in oklab, #060d1a 55%, transparent);
|
||||
}
|
||||
.text-brand-navy\/60 {
|
||||
color: color-mix(in oklab, #060d1a 60%, transparent);
|
||||
}
|
||||
.text-brand-navy\/65 {
|
||||
color: color-mix(in oklab, #060d1a 65%, transparent);
|
||||
}
|
||||
.text-brand-navy\/70 {
|
||||
color: color-mix(in oklab, #060d1a 70%, transparent);
|
||||
}
|
||||
.text-brand-navy\/75 {
|
||||
color: color-mix(in oklab, #060d1a 75%, transparent);
|
||||
}
|
||||
.text-brand-navy\/80 {
|
||||
color: color-mix(in oklab, #060d1a 80%, transparent);
|
||||
}
|
||||
@@ -2769,6 +2995,9 @@
|
||||
.text-emerald-500 {
|
||||
color: var(--color-emerald-500);
|
||||
}
|
||||
.text-emerald-600 {
|
||||
color: var(--color-emerald-600);
|
||||
}
|
||||
.text-gray-200 {
|
||||
color: var(--color-gray-200);
|
||||
}
|
||||
@@ -2832,6 +3061,36 @@
|
||||
.text-red-500 {
|
||||
color: var(--color-red-500);
|
||||
}
|
||||
.text-red-500\/60 {
|
||||
color: color-mix(in srgb, oklch(63.7% 0.237 25.331) 60%, transparent);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
color: color-mix(in oklab, var(--color-red-500) 60%, transparent);
|
||||
}
|
||||
}
|
||||
.text-red-500\/65 {
|
||||
color: color-mix(in srgb, oklch(63.7% 0.237 25.331) 65%, transparent);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
color: color-mix(in oklab, var(--color-red-500) 65%, transparent);
|
||||
}
|
||||
}
|
||||
.text-red-500\/70 {
|
||||
color: color-mix(in srgb, oklch(63.7% 0.237 25.331) 70%, transparent);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
color: color-mix(in oklab, var(--color-red-500) 70%, transparent);
|
||||
}
|
||||
}
|
||||
.text-red-500\/75 {
|
||||
color: color-mix(in srgb, oklch(63.7% 0.237 25.331) 75%, transparent);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
color: color-mix(in oklab, var(--color-red-500) 75%, transparent);
|
||||
}
|
||||
}
|
||||
.text-red-500\/80 {
|
||||
color: color-mix(in srgb, oklch(63.7% 0.237 25.331) 80%, transparent);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
color: color-mix(in oklab, var(--color-red-500) 80%, transparent);
|
||||
}
|
||||
}
|
||||
.text-red-600 {
|
||||
color: var(--color-red-600);
|
||||
}
|
||||
@@ -2880,6 +3139,12 @@
|
||||
color: color-mix(in oklab, var(--color-white) 50%, transparent);
|
||||
}
|
||||
}
|
||||
.text-white\/55 {
|
||||
color: color-mix(in srgb, #fff 55%, transparent);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
color: color-mix(in oklab, var(--color-white) 55%, transparent);
|
||||
}
|
||||
}
|
||||
.text-white\/60 {
|
||||
color: color-mix(in srgb, #fff 60%, transparent);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
@@ -2938,6 +3203,9 @@
|
||||
--tw-numeric-spacing: tabular-nums;
|
||||
font-variant-numeric: var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,);
|
||||
}
|
||||
.no-underline {
|
||||
text-decoration-line: none;
|
||||
}
|
||||
.underline {
|
||||
text-decoration-line: underline;
|
||||
}
|
||||
@@ -3007,6 +3275,9 @@
|
||||
.opacity-100 {
|
||||
opacity: 100%;
|
||||
}
|
||||
.opacity-\[0\.02\] {
|
||||
opacity: 0.02;
|
||||
}
|
||||
.shadow {
|
||||
--tw-shadow: 0 1px 3px 0 var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 1px 2px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1));
|
||||
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
|
||||
@@ -3015,6 +3286,10 @@
|
||||
--tw-shadow: 0 25px 50px -12px var(--tw-shadow-color, rgb(0 0 0 / 0.25));
|
||||
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
|
||||
}
|
||||
.shadow-\[0_0_6px_\#F59E0B\] {
|
||||
--tw-shadow: 0 0 6px var(--tw-shadow-color, #F59E0B);
|
||||
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
|
||||
}
|
||||
.shadow-\[0_0_28px_rgba\(0\,98\,255\,0\.35\)\] {
|
||||
--tw-shadow: 0 0 28px var(--tw-shadow-color, rgba(0,98,255,0.35));
|
||||
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
|
||||
@@ -3095,6 +3370,10 @@
|
||||
--tw-backdrop-blur: blur(8px);
|
||||
backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);
|
||||
}
|
||||
.backdrop-blur-md {
|
||||
--tw-backdrop-blur: blur(var(--blur-md));
|
||||
backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);
|
||||
}
|
||||
.backdrop-blur-sm {
|
||||
--tw-backdrop-blur: blur(var(--blur-sm));
|
||||
backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);
|
||||
@@ -3215,6 +3494,13 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
.group-hover\:opacity-60 {
|
||||
&:is(:where(.group):hover *) {
|
||||
@media (hover: hover) {
|
||||
opacity: 60%;
|
||||
}
|
||||
}
|
||||
}
|
||||
.group-hover\:opacity-100 {
|
||||
&:is(:where(.group):hover *) {
|
||||
@media (hover: hover) {
|
||||
@@ -4215,6 +4501,11 @@
|
||||
outline-offset: 4px;
|
||||
}
|
||||
}
|
||||
.focus-visible\:outline-amber-500 {
|
||||
&:focus-visible {
|
||||
outline-color: var(--color-amber-500);
|
||||
}
|
||||
}
|
||||
.focus-visible\:outline-brand-b1 {
|
||||
&:focus-visible {
|
||||
outline-color: #0062ff;
|
||||
@@ -4619,11 +4910,31 @@
|
||||
margin-bottom: calc(var(--spacing) * 4);
|
||||
}
|
||||
}
|
||||
.md\:block {
|
||||
@media (width >= 48rem) {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
.md\:flex {
|
||||
@media (width >= 48rem) {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
.md\:hidden {
|
||||
@media (width >= 48rem) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
.md\:h-5 {
|
||||
@media (width >= 48rem) {
|
||||
height: calc(var(--spacing) * 5);
|
||||
}
|
||||
}
|
||||
.md\:h-12 {
|
||||
@media (width >= 48rem) {
|
||||
height: calc(var(--spacing) * 12);
|
||||
}
|
||||
}
|
||||
.md\:h-48 {
|
||||
@media (width >= 48rem) {
|
||||
height: calc(var(--spacing) * 48);
|
||||
@@ -4634,11 +4945,31 @@
|
||||
min-height: 12rem;
|
||||
}
|
||||
}
|
||||
.md\:w-5 {
|
||||
@media (width >= 48rem) {
|
||||
width: calc(var(--spacing) * 5);
|
||||
}
|
||||
}
|
||||
.md\:w-12 {
|
||||
@media (width >= 48rem) {
|
||||
width: calc(var(--spacing) * 12);
|
||||
}
|
||||
}
|
||||
.md\:w-48 {
|
||||
@media (width >= 48rem) {
|
||||
width: calc(var(--spacing) * 48);
|
||||
}
|
||||
}
|
||||
.md\:w-\[42\%\] {
|
||||
@media (width >= 48rem) {
|
||||
width: 42%;
|
||||
}
|
||||
}
|
||||
.md\:w-\[58\%\] {
|
||||
@media (width >= 48rem) {
|
||||
width: 58%;
|
||||
}
|
||||
}
|
||||
.md\:grid-cols-2 {
|
||||
@media (width >= 48rem) {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
@@ -4654,6 +4985,11 @@
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
}
|
||||
}
|
||||
.md\:grid-cols-\[2fr_2fr_3fr\] {
|
||||
@media (width >= 48rem) {
|
||||
grid-template-columns: 2fr 2fr 3fr;
|
||||
}
|
||||
}
|
||||
.md\:flex-row {
|
||||
@media (width >= 48rem) {
|
||||
flex-direction: row;
|
||||
@@ -4674,6 +5010,18 @@
|
||||
gap: calc(var(--spacing) * 5);
|
||||
}
|
||||
}
|
||||
.md\:border-t-0 {
|
||||
@media (width >= 48rem) {
|
||||
border-top-style: var(--tw-border-style);
|
||||
border-top-width: 0px;
|
||||
}
|
||||
}
|
||||
.md\:border-l {
|
||||
@media (width >= 48rem) {
|
||||
border-left-style: var(--tw-border-style);
|
||||
border-left-width: 1px;
|
||||
}
|
||||
}
|
||||
.md\:p-2 {
|
||||
@media (width >= 48rem) {
|
||||
padding: calc(var(--spacing) * 2);
|
||||
@@ -4770,6 +5118,11 @@
|
||||
line-height: var(--tw-leading, var(--text-sm--line-height));
|
||||
}
|
||||
}
|
||||
.md\:text-\[10px\] {
|
||||
@media (width >= 48rem) {
|
||||
font-size: 10px;
|
||||
}
|
||||
}
|
||||
.lg\:relative {
|
||||
@media (width >= 64rem) {
|
||||
position: relative;
|
||||
|
||||
@@ -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 25, le Cloud Act américain et le Guide IA du Barreau du Québec délimitent vos options —
|
||||
<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 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 $/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–750 $/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 % 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 25 · 100 % 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 924 $', 'vs Otter.ai'), ('6 924 $', 'vs MS Teams'), ('2 004 $', '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 : 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 800 $ / 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 $/mois — illimité', 'x': 64, 'y': 10},
|
||||
{'text': '90 %+ d\'économies', 'x': 74, 'y': 38},
|
||||
{'text': '100 % 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: ['> Interception IA détectée.', '> Données utilisées pour entraînement.', '> 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 :
|
||||
</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. 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 25, Cloud Act, Barreau, ChAD, AMF. Hébergement OVH Beauharnois,
|
||||
code source AGPL 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">
|
||||
|
||||
@@ -754,6 +754,125 @@ def test_cta_final_uses_safe_pre_launch_wording():
|
||||
assert phrase not in body, f"Forbidden pre-launch CTA: {phrase}"
|
||||
|
||||
|
||||
def test_round2_cycle_section_present():
|
||||
"""Round 2 — Cycle section ('Trois options. Une seule est conforme.') must be on landing.
|
||||
|
||||
Sourced from dictai-cycle.tsx; covers the 3-column comparative narrative
|
||||
(humaine / cloud US / DictIA) with canonical pricing 315 $ vs 173 $ and savings.
|
||||
"""
|
||||
client = app.test_client()
|
||||
body = client.get('/').data.decode('utf-8')
|
||||
assert 'cycle-title' in body, "Cycle section H2 id must be present"
|
||||
assert 'Trois options' in body
|
||||
assert 'Une seule est conforme' in body
|
||||
assert 'Retranscription humaine' in body
|
||||
assert 'IA cloud américaine' in body
|
||||
assert 'NON CONFORME' in body
|
||||
assert '315' in body and '173' in body, "Canonical Cycle pricing must appear"
|
||||
assert 'Loi 25 conforme' in body
|
||||
assert '100 % hébergé au Québec' in body or '100 % hébergé au Québec' in body
|
||||
# Phase animation hooks
|
||||
assert 'cycle-pulse' in body, "Pulse rings keyframe class missing"
|
||||
assert 'cycle-card-dictia' in body
|
||||
# Reduced-motion safety
|
||||
assert 'prefers-reduced-motion' in body
|
||||
|
||||
|
||||
def test_round2_wave_section_present():
|
||||
"""Round 2 — Wave section (chaos→ordre interactive slider) must be on landing.
|
||||
|
||||
Sourced from dictai-wave.tsx; mouse-X morphs 30 bars red→cyan + pain/solution cards.
|
||||
"""
|
||||
client = app.test_client()
|
||||
body = client.get('/').data.decode('utf-8')
|
||||
assert 'wave-title' in body, "Wave section H2 id must be present"
|
||||
assert 'La transcription manuelle' in body
|
||||
assert 'coûte cher' in body
|
||||
# Canonical pain labels
|
||||
assert '4 à 6h pour transcrire 1h' in body
|
||||
assert 'Délais de 48h à 5 jours' in body
|
||||
# Canonical solution labels (NBSP-aware)
|
||||
assert '~2 min pour 1h d' in body
|
||||
assert '173 $/mois' in body or '173 $/mois' in body
|
||||
# Alpine state for interactive slider
|
||||
assert 'onMove($event)' in body
|
||||
assert 'isMobile' in body
|
||||
# Mobile fallback toggle
|
||||
assert 'Activer DictIA' in body
|
||||
assert 'Voir sans DictIA' in body
|
||||
|
||||
|
||||
def test_round2_cadre_reglementaire_section_present():
|
||||
"""Round 2 — Cadre réglementaire (Moniteur d'Interception) with 6 REGS list.
|
||||
|
||||
Sourced from dictai-contraste.tsx (REGS + MoniteurInterception subcomponent).
|
||||
"""
|
||||
client = app.test_client()
|
||||
body = client.get('/').data.decode('utf-8')
|
||||
assert 'cadre-title' in body, "Cadre réglementaire H2 id must be present"
|
||||
assert "Moniteur d'Interception" in body
|
||||
assert 'enfreignent' in body
|
||||
# 6 REGS — each must appear with its hyperlink
|
||||
for reg_label in ['Loi 25 (P-39.1)', 'Loi 96 (C-11)', 'US Cloud Act',
|
||||
'Guide IA — Barreau QC', 'Cadre IA — MCN', 'CAI']:
|
||||
assert reg_label in body, f"Missing REG label: {reg_label}"
|
||||
# Authoritative sources
|
||||
assert 'legisquebec.gouv.qc.ca' in body
|
||||
assert 'cai.gouv.qc.ca' in body
|
||||
assert 'tresor.gouv.qc.ca' in body
|
||||
# HUD lines
|
||||
assert 'Interception IA détectée' in body
|
||||
assert 'NON CONFORME' in body
|
||||
# Cycle animation hooks
|
||||
assert 'cadre-folder' in body
|
||||
assert 'runCycle' in body
|
||||
|
||||
|
||||
def test_round2_no_external_js_libs_added():
|
||||
"""Round 2 must NOT add Framer Motion / GSAP / canvas-confetti / etc."""
|
||||
client = app.test_client()
|
||||
body = client.get('/').data.decode('utf-8')
|
||||
forbidden_libs = ['framer-motion', 'gsap', 'canvas-confetti', 'three.min.js',
|
||||
'lottie-web', 'anime.min.js']
|
||||
for lib in forbidden_libs:
|
||||
assert lib not in body, f"Round 2 must not introduce JS lib: {lib}"
|
||||
|
||||
|
||||
def test_round2_preserves_existing_sections():
|
||||
"""Round 2 inserts must NOT remove Hero / Pipeline / Hub / Bento / Comparatif / Conformité."""
|
||||
client = app.test_client()
|
||||
body = client.get('/').data.decode('utf-8')
|
||||
# Hero (round 0)
|
||||
assert 'hero-title' in body
|
||||
assert 'sans risquer votre permis' in body
|
||||
# Pipeline (round 1) — auto-advance + 4 nodes
|
||||
assert 'pipeline-title' in body
|
||||
assert 'Du fichier au résumé' in body
|
||||
# Hub (round 1)
|
||||
assert 'hub-title' in body
|
||||
assert 'se connecte à tout' in body
|
||||
# Bento + ROI calculator
|
||||
assert 'bento-title' in body
|
||||
assert 'roiCalculator()' in body
|
||||
# Comparatif + Conformité
|
||||
assert 'comparatif-title' in body
|
||||
assert 'conformite-title' in body
|
||||
# Trust bar 9 ordres
|
||||
assert 'Mappé aux 9' in body
|
||||
|
||||
|
||||
def test_round2_oqlf_nbsp_in_new_sections():
|
||||
"""OQLF — non-breaking space before currency $ and % in round 2 sections."""
|
||||
client = app.test_client()
|
||||
body = client.get('/').data.decode('utf-8')
|
||||
# Cycle section savings
|
||||
assert '3 924 $' in body or '3 924 $' in body
|
||||
# Wave solution card pricing
|
||||
assert '173 $/mois' in body or 'Dès 173' in body
|
||||
# Cadre — Loi 25 fine
|
||||
assert '25 M$' in body or '25 M$' in body
|
||||
|
||||
|
||||
def test_routes_passes_testimonials_and_faq_to_template():
|
||||
"""marketing.routes.landing() must pass testimonials and faq to render_template."""
|
||||
# Import the module to verify the data lists exist
|
||||
|
||||
Reference in New Issue
Block a user