feat(marketing): round 4 — Cadre + Cycle cinématiques (radar, data packet flight, stamp impact, savings counter)
Round 4 transforme les 2 sections "Cadre réglementaire" en expériences cinématiques : CADRE (Moniteur d'Interception) - Radar sweep circulaire vert continu en background HUD (4s loop, SVG + @keyframes) - 6 paquets data "voice.wav" en flight QC→US via offset-path bezier (stagger 420ms, glow rouge) - Console typewriter char-by-char 3 lignes (28ms/char + caret blink, 3e ligne rouge glow) - 6 REGS reveal cascadé via revealRegsCascade (stagger 120ms) + hover red glow + border-left - Verdict NON CONFORME : pulse glow rouge + scan-line traversante 3s - Decorative grid 40×40 console-style + grid existant 20×20 - Eyebrow ⚠ remplacé par SVG warning-triangle inline CYCLE (Trois options) - Phase reveal 1→4 séquentiel (déjà existant) avec animations renforcées - Col 1 horloge accélérée 1 tour/3s (au lieu de 8s) - Col 1 prix counter Alpine 0→315 (easeOutCubic 1.4s) via priceHumain + countTo - Col 2 stamp NON CONFORME impact (rotate -22→-3deg + scale 2.4→1, cubic-bezier 1.6 ease) - Col 2 flash rouge background à l'impact (cycle-col-flash) + 10 particules de fuite (au lieu de 6) - Col 3 checkmark draw via stroke-dashoffset 24→0 - Col 3 glow border vert pulsant (cycle-conforme-glow, double-couche emerald + cyan) - Col 3 badge "Loi 25 conforme" top-right avec pulse subtil (cycle-conforme-badge) - Connecting lines avec dash flow continu (cycle-line-flow @keyframes) - Live red dot "Réunion en cours" avec pulse box-shadow - Section "Économies annuelles · 25 utilisateurs" : 3 cards avec counter Alpine (sav1=3924, sav2=6924, sav3=2004) + hover lift + emerald shadow - Eyebrow ⚠ remplacé par SVG warning-triangle Accessibilité & performance - prefers-reduced-motion désactive TOUT (radar, packets, typewriter, stamp, glow, counter) - Mobile (<768px) cache radar + packets + leak particles (CPU-intensive) - Counter helper countTo respecte reduced-motion via matchMedia - Tous les SVG ont aria-hidden, scènes ont role=img/listitem appropriés - HUD console role=log + aria-live=polite - OQLF NBSP préservé (315 $/réunion, Loi 25, 100 % Québec, 25 utilisateurs, 3 924 $) Tests : 4 tests round 4 ajoutés (cadre cinematic, cycle cinematic, no-emoji warning, reduced-motion guards). 65/68 landing tests passent (3 failures pré-existantes unrelated : nav /blog, footer /blog, trust-bar phrasing). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -49,7 +49,8 @@
|
|||||||
--color-green-700: oklch(52.7% 0.154 150.069);
|
--color-green-700: oklch(52.7% 0.154 150.069);
|
||||||
--color-green-800: oklch(44.8% 0.119 151.328);
|
--color-green-800: oklch(44.8% 0.119 151.328);
|
||||||
--color-green-900: oklch(39.3% 0.095 152.535);
|
--color-green-900: oklch(39.3% 0.095 152.535);
|
||||||
--color-emerald-300: oklch(84.5% 0.143 164.978);
|
--color-emerald-50: oklch(97.9% 0.021 166.113);
|
||||||
|
--color-emerald-100: oklch(95% 0.052 163.051);
|
||||||
--color-emerald-500: oklch(69.6% 0.17 162.48);
|
--color-emerald-500: oklch(69.6% 0.17 162.48);
|
||||||
--color-emerald-600: oklch(59.6% 0.145 163.225);
|
--color-emerald-600: oklch(59.6% 0.145 163.225);
|
||||||
--color-emerald-700: oklch(50.8% 0.118 165.612);
|
--color-emerald-700: oklch(50.8% 0.118 165.612);
|
||||||
@@ -128,6 +129,7 @@
|
|||||||
--font-weight-semibold: 600;
|
--font-weight-semibold: 600;
|
||||||
--font-weight-bold: 700;
|
--font-weight-bold: 700;
|
||||||
--font-weight-black: 900;
|
--font-weight-black: 900;
|
||||||
|
--tracking-tighter: -0.05em;
|
||||||
--tracking-tight: -0.025em;
|
--tracking-tight: -0.025em;
|
||||||
--tracking-wide: 0.025em;
|
--tracking-wide: 0.025em;
|
||||||
--tracking-wider: 0.05em;
|
--tracking-wider: 0.05em;
|
||||||
@@ -147,7 +149,6 @@
|
|||||||
--blur-sm: 8px;
|
--blur-sm: 8px;
|
||||||
--blur-md: 12px;
|
--blur-md: 12px;
|
||||||
--blur-xl: 24px;
|
--blur-xl: 24px;
|
||||||
--blur-3xl: 64px;
|
|
||||||
--default-transition-duration: 150ms;
|
--default-transition-duration: 150ms;
|
||||||
--default-transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
--default-transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
}
|
}
|
||||||
@@ -391,6 +392,9 @@
|
|||||||
.-top-3 {
|
.-top-3 {
|
||||||
top: calc(var(--spacing) * -3);
|
top: calc(var(--spacing) * -3);
|
||||||
}
|
}
|
||||||
|
.-top-8 {
|
||||||
|
top: calc(var(--spacing) * -8);
|
||||||
|
}
|
||||||
.-top-12 {
|
.-top-12 {
|
||||||
top: calc(var(--spacing) * -12);
|
top: calc(var(--spacing) * -12);
|
||||||
}
|
}
|
||||||
@@ -439,6 +443,9 @@
|
|||||||
.-right-1\.5 {
|
.-right-1\.5 {
|
||||||
right: calc(var(--spacing) * -1.5);
|
right: calc(var(--spacing) * -1.5);
|
||||||
}
|
}
|
||||||
|
.-right-8 {
|
||||||
|
right: calc(var(--spacing) * -8);
|
||||||
|
}
|
||||||
.-right-12 {
|
.-right-12 {
|
||||||
right: calc(var(--spacing) * -12);
|
right: calc(var(--spacing) * -12);
|
||||||
}
|
}
|
||||||
@@ -526,6 +533,9 @@
|
|||||||
.left-\[3\%\] {
|
.left-\[3\%\] {
|
||||||
left: 3%;
|
left: 3%;
|
||||||
}
|
}
|
||||||
|
.left-\[6\%\] {
|
||||||
|
left: 6%;
|
||||||
|
}
|
||||||
.z-10 {
|
.z-10 {
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
}
|
}
|
||||||
@@ -829,6 +839,9 @@
|
|||||||
.h-32 {
|
.h-32 {
|
||||||
height: calc(var(--spacing) * 32);
|
height: calc(var(--spacing) * 32);
|
||||||
}
|
}
|
||||||
|
.h-40 {
|
||||||
|
height: calc(var(--spacing) * 40);
|
||||||
|
}
|
||||||
.h-48 {
|
.h-48 {
|
||||||
height: calc(var(--spacing) * 48);
|
height: calc(var(--spacing) * 48);
|
||||||
}
|
}
|
||||||
@@ -958,6 +971,9 @@
|
|||||||
.w-1\/2 {
|
.w-1\/2 {
|
||||||
width: calc(1 / 2 * 100%);
|
width: calc(1 / 2 * 100%);
|
||||||
}
|
}
|
||||||
|
.w-1\/3 {
|
||||||
|
width: calc(1 / 3 * 100%);
|
||||||
|
}
|
||||||
.w-2 {
|
.w-2 {
|
||||||
width: calc(var(--spacing) * 2);
|
width: calc(var(--spacing) * 2);
|
||||||
}
|
}
|
||||||
@@ -1445,10 +1461,6 @@
|
|||||||
-moz-column-gap: calc(var(--spacing) * 6);
|
-moz-column-gap: calc(var(--spacing) * 6);
|
||||||
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 {
|
.space-x-1 {
|
||||||
:where(& > :not(:last-child)) {
|
:where(& > :not(:last-child)) {
|
||||||
--tw-space-x-reverse: 0;
|
--tw-space-x-reverse: 0;
|
||||||
@@ -1490,9 +1502,6 @@
|
|||||||
.gap-y-2 {
|
.gap-y-2 {
|
||||||
row-gap: calc(var(--spacing) * 2);
|
row-gap: calc(var(--spacing) * 2);
|
||||||
}
|
}
|
||||||
.gap-y-3 {
|
|
||||||
row-gap: calc(var(--spacing) * 3);
|
|
||||||
}
|
|
||||||
.divide-y {
|
.divide-y {
|
||||||
:where(& > :not(:last-child)) {
|
:where(& > :not(:last-child)) {
|
||||||
--tw-divide-y-reverse: 0;
|
--tw-divide-y-reverse: 0;
|
||||||
@@ -1590,6 +1599,10 @@
|
|||||||
border-style: var(--tw-border-style);
|
border-style: var(--tw-border-style);
|
||||||
border-width: 2px;
|
border-width: 2px;
|
||||||
}
|
}
|
||||||
|
.border-\[3px\] {
|
||||||
|
border-style: var(--tw-border-style);
|
||||||
|
border-width: 3px;
|
||||||
|
}
|
||||||
.border-y {
|
.border-y {
|
||||||
border-block-style: var(--tw-border-style);
|
border-block-style: var(--tw-border-style);
|
||||||
border-block-width: 1px;
|
border-block-width: 1px;
|
||||||
@@ -1719,6 +1732,21 @@
|
|||||||
.border-brand-border {
|
.border-brand-border {
|
||||||
border-color: #e6ebf2;
|
border-color: #e6ebf2;
|
||||||
}
|
}
|
||||||
|
.border-emerald-100 {
|
||||||
|
border-color: var(--color-emerald-100);
|
||||||
|
}
|
||||||
|
.border-emerald-500\/40 {
|
||||||
|
border-color: color-mix(in srgb, oklch(69.6% 0.17 162.48) 40%, transparent);
|
||||||
|
@supports (color: color-mix(in lab, red, red)) {
|
||||||
|
border-color: color-mix(in oklab, var(--color-emerald-500) 40%, transparent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.border-emerald-500\/45 {
|
||||||
|
border-color: color-mix(in srgb, oklch(69.6% 0.17 162.48) 45%, transparent);
|
||||||
|
@supports (color: color-mix(in lab, red, red)) {
|
||||||
|
border-color: color-mix(in oklab, var(--color-emerald-500) 45%, transparent);
|
||||||
|
}
|
||||||
|
}
|
||||||
.border-gray-300 {
|
.border-gray-300 {
|
||||||
border-color: var(--color-gray-300);
|
border-color: var(--color-gray-300);
|
||||||
}
|
}
|
||||||
@@ -1956,12 +1984,6 @@
|
|||||||
background-color: color-mix(in oklab, var(--color-amber-500) 10%, transparent);
|
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 {
|
.bg-amber-500\/20 {
|
||||||
background-color: color-mix(in srgb, oklch(76.9% 0.188 70.08) 20%, transparent);
|
background-color: color-mix(in srgb, oklch(76.9% 0.188 70.08) 20%, transparent);
|
||||||
@supports (color: color-mix(in lab, red, red)) {
|
@supports (color: color-mix(in lab, red, red)) {
|
||||||
@@ -2064,6 +2086,21 @@
|
|||||||
.bg-brand-navy2 {
|
.bg-brand-navy2 {
|
||||||
background-color: #0b1525;
|
background-color: #0b1525;
|
||||||
}
|
}
|
||||||
|
.bg-emerald-50 {
|
||||||
|
background-color: var(--color-emerald-50);
|
||||||
|
}
|
||||||
|
.bg-emerald-500\/12 {
|
||||||
|
background-color: color-mix(in srgb, oklch(69.6% 0.17 162.48) 12%, transparent);
|
||||||
|
@supports (color: color-mix(in lab, red, red)) {
|
||||||
|
background-color: color-mix(in oklab, var(--color-emerald-500) 12%, transparent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.bg-emerald-500\/15 {
|
||||||
|
background-color: color-mix(in srgb, oklch(69.6% 0.17 162.48) 15%, transparent);
|
||||||
|
@supports (color: color-mix(in lab, red, red)) {
|
||||||
|
background-color: color-mix(in oklab, var(--color-emerald-500) 15%, transparent);
|
||||||
|
}
|
||||||
|
}
|
||||||
.bg-emerald-600 {
|
.bg-emerald-600 {
|
||||||
background-color: var(--color-emerald-600);
|
background-color: var(--color-emerald-600);
|
||||||
}
|
}
|
||||||
@@ -2166,6 +2203,12 @@
|
|||||||
background-color: color-mix(in oklab, var(--color-red-50) 30%, transparent);
|
background-color: color-mix(in oklab, var(--color-red-50) 30%, transparent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.bg-red-50\/40 {
|
||||||
|
background-color: color-mix(in srgb, oklch(97.1% 0.013 17.38) 40%, transparent);
|
||||||
|
@supports (color: color-mix(in lab, red, red)) {
|
||||||
|
background-color: color-mix(in oklab, var(--color-red-50) 40%, transparent);
|
||||||
|
}
|
||||||
|
}
|
||||||
.bg-red-100 {
|
.bg-red-100 {
|
||||||
background-color: var(--color-red-100);
|
background-color: var(--color-red-100);
|
||||||
}
|
}
|
||||||
@@ -2178,6 +2221,9 @@
|
|||||||
.bg-red-400 {
|
.bg-red-400 {
|
||||||
background-color: var(--color-red-400);
|
background-color: var(--color-red-400);
|
||||||
}
|
}
|
||||||
|
.bg-red-500 {
|
||||||
|
background-color: var(--color-red-500);
|
||||||
|
}
|
||||||
.bg-red-500\/10 {
|
.bg-red-500\/10 {
|
||||||
background-color: color-mix(in srgb, oklch(63.7% 0.237 25.331) 10%, transparent);
|
background-color: color-mix(in srgb, oklch(63.7% 0.237 25.331) 10%, transparent);
|
||||||
@supports (color: color-mix(in lab, red, red)) {
|
@supports (color: color-mix(in lab, red, red)) {
|
||||||
@@ -2190,6 +2236,18 @@
|
|||||||
background-color: color-mix(in oklab, var(--color-red-500) 20%, transparent);
|
background-color: color-mix(in oklab, var(--color-red-500) 20%, transparent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.bg-red-500\/40 {
|
||||||
|
background-color: color-mix(in srgb, oklch(63.7% 0.237 25.331) 40%, transparent);
|
||||||
|
@supports (color: color-mix(in lab, red, red)) {
|
||||||
|
background-color: color-mix(in oklab, var(--color-red-500) 40%, transparent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.bg-red-500\/85 {
|
||||||
|
background-color: color-mix(in srgb, oklch(63.7% 0.237 25.331) 85%, transparent);
|
||||||
|
@supports (color: color-mix(in lab, red, red)) {
|
||||||
|
background-color: color-mix(in oklab, var(--color-red-500) 85%, transparent);
|
||||||
|
}
|
||||||
|
}
|
||||||
.bg-red-600 {
|
.bg-red-600 {
|
||||||
background-color: var(--color-red-600);
|
background-color: var(--color-red-600);
|
||||||
}
|
}
|
||||||
@@ -2229,6 +2287,12 @@
|
|||||||
background-color: color-mix(in oklab, var(--color-white) 30%, transparent);
|
background-color: color-mix(in oklab, var(--color-white) 30%, transparent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.bg-white\/80 {
|
||||||
|
background-color: color-mix(in srgb, #fff 80%, transparent);
|
||||||
|
@supports (color: color-mix(in lab, red, red)) {
|
||||||
|
background-color: color-mix(in oklab, var(--color-white) 80%, transparent);
|
||||||
|
}
|
||||||
|
}
|
||||||
.bg-white\/95 {
|
.bg-white\/95 {
|
||||||
background-color: color-mix(in srgb, #fff 95%, transparent);
|
background-color: color-mix(in srgb, #fff 95%, transparent);
|
||||||
@supports (color: color-mix(in lab, red, red)) {
|
@supports (color: color-mix(in lab, red, red)) {
|
||||||
@@ -2812,6 +2876,10 @@
|
|||||||
--tw-tracking: var(--tracking-tight);
|
--tw-tracking: var(--tracking-tight);
|
||||||
letter-spacing: var(--tracking-tight);
|
letter-spacing: var(--tracking-tight);
|
||||||
}
|
}
|
||||||
|
.tracking-tighter {
|
||||||
|
--tw-tracking: var(--tracking-tighter);
|
||||||
|
letter-spacing: var(--tracking-tighter);
|
||||||
|
}
|
||||||
.tracking-wide {
|
.tracking-wide {
|
||||||
--tw-tracking: var(--tracking-wide);
|
--tw-tracking: var(--tracking-wide);
|
||||||
letter-spacing: var(--tracking-wide);
|
letter-spacing: var(--tracking-wide);
|
||||||
@@ -2995,6 +3063,9 @@
|
|||||||
.text-brand-navy\/80 {
|
.text-brand-navy\/80 {
|
||||||
color: color-mix(in oklab, #060d1a 80%, transparent);
|
color: color-mix(in oklab, #060d1a 80%, transparent);
|
||||||
}
|
}
|
||||||
|
.text-brand-navy\/85 {
|
||||||
|
color: color-mix(in oklab, #060d1a 85%, transparent);
|
||||||
|
}
|
||||||
.text-brand-navy\/90 {
|
.text-brand-navy\/90 {
|
||||||
color: color-mix(in oklab, #060d1a 90%, transparent);
|
color: color-mix(in oklab, #060d1a 90%, transparent);
|
||||||
}
|
}
|
||||||
@@ -3007,6 +3078,15 @@
|
|||||||
.text-emerald-600 {
|
.text-emerald-600 {
|
||||||
color: var(--color-emerald-600);
|
color: var(--color-emerald-600);
|
||||||
}
|
}
|
||||||
|
.text-emerald-600\/70 {
|
||||||
|
color: color-mix(in srgb, oklch(59.6% 0.145 163.225) 70%, transparent);
|
||||||
|
@supports (color: color-mix(in lab, red, red)) {
|
||||||
|
color: color-mix(in oklab, var(--color-emerald-600) 70%, transparent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.text-emerald-700 {
|
||||||
|
color: var(--color-emerald-700);
|
||||||
|
}
|
||||||
.text-gray-200 {
|
.text-gray-200 {
|
||||||
color: var(--color-gray-200);
|
color: var(--color-gray-200);
|
||||||
}
|
}
|
||||||
@@ -3097,6 +3177,12 @@
|
|||||||
color: color-mix(in oklab, var(--color-red-500) 80%, transparent);
|
color: color-mix(in oklab, var(--color-red-500) 80%, transparent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.text-red-500\/85 {
|
||||||
|
color: color-mix(in srgb, oklch(63.7% 0.237 25.331) 85%, transparent);
|
||||||
|
@supports (color: color-mix(in lab, red, red)) {
|
||||||
|
color: color-mix(in oklab, var(--color-red-500) 85%, transparent);
|
||||||
|
}
|
||||||
|
}
|
||||||
.text-red-600 {
|
.text-red-600 {
|
||||||
color: var(--color-red-600);
|
color: var(--color-red-600);
|
||||||
}
|
}
|
||||||
@@ -3288,10 +3374,18 @@
|
|||||||
--tw-shadow: 0 0 6px var(--tw-shadow-color, #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);
|
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_rgba\(239\,68\,68\,0\.6\)\] {
|
||||||
|
--tw-shadow: 0 0 6px var(--tw-shadow-color, rgba(239,68,68,0.6));
|
||||||
|
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\)\] {
|
.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));
|
--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);
|
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_8px_30px_-6px_rgba\(239\,68\,68\,0\.55\)\] {
|
||||||
|
--tw-shadow: 0 8px 30px -6px var(--tw-shadow-color, rgba(239,68,68,0.55));
|
||||||
|
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
|
||||||
|
}
|
||||||
.shadow-cta {
|
.shadow-cta {
|
||||||
--tw-shadow: 0 4px 20px var(--tw-shadow-color, rgba(0, 98, 255, 0.28));
|
--tw-shadow: 0 4px 20px var(--tw-shadow-color, rgba(0, 98, 255, 0.28));
|
||||||
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
|
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
|
||||||
|
|||||||
@@ -411,10 +411,10 @@
|
|||||||
|
|
||||||
{# ===== CYCLE — "Trois options. Une seule est conforme." ===== #}
|
{# ===== CYCLE — "Trois options. Une seule est conforme." ===== #}
|
||||||
{# Source canonique : InnovA-AI/Website-Sanity/components/sections/dictai-cycle.tsx
|
{# Source canonique : InnovA-AI/Website-Sanity/components/sections/dictai-cycle.tsx
|
||||||
Animation : phase reveal phasé (1→4) déclenché par IntersectionObserver natif + Alpine.
|
Round 4 cinématique : phase reveal séquentiel + horloge accélérée + prix counter + stamp impact NON CONFORME
|
||||||
3 colonnes comparatives : 01 Humaine · 02 Cloud US (overlay non-conforme) · 03 DictIA (featured + pulse rings) #}
|
+ checkmark draw + glow vert + connecting line dash flow + section "Économies annuelles" avec 3 counters animés #}
|
||||||
<style>
|
<style>
|
||||||
/* Cycle pulse rings — appliqués au nœud source "Réunion en cours" */
|
/* Cycle pulse rings — nœud source "Réunion en cours" */
|
||||||
@keyframes cycle-pulse-ring {
|
@keyframes cycle-pulse-ring {
|
||||||
0% { transform: scale(1); opacity: 0.5; }
|
0% { transform: scale(1); opacity: 0.5; }
|
||||||
100% { transform: scale(2.1); opacity: 0; }
|
100% { transform: scale(2.1); opacity: 0; }
|
||||||
@@ -423,12 +423,19 @@
|
|||||||
.cycle-pulse-2 { animation-delay: 0.65s; }
|
.cycle-pulse-2 { animation-delay: 0.65s; }
|
||||||
.cycle-pulse-3 { animation-delay: 1.3s; }
|
.cycle-pulse-3 { animation-delay: 1.3s; }
|
||||||
|
|
||||||
/* Cycle col 3 (DictIA) — bordure lumineuse pulsante */
|
/* Live red dot — pulse plus marqué pour signal "réunion en cours" */
|
||||||
@keyframes cycle-glow {
|
@keyframes cycle-live-dot {
|
||||||
0%, 100% { box-shadow: 0 0 0 0 rgba(0,189,216,0.12); }
|
0%, 100% { opacity: 1; box-shadow: 0 0 0 0 rgba(239,68,68,0.55); }
|
||||||
50% { box-shadow: 0 0 35px 0 rgba(0,189,216,0.30); }
|
50% { opacity: 0.6; box-shadow: 0 0 0 5px rgba(239,68,68,0); }
|
||||||
}
|
}
|
||||||
.cycle-card-dictia.is-visible { animation: cycle-glow 3.8s ease-in-out infinite; }
|
.cycle-live-dot { animation: cycle-live-dot 1.4s ease-in-out infinite; }
|
||||||
|
|
||||||
|
/* Cycle col 3 (DictIA) — bordure lumineuse pulsante VERT (round 4 : conformité) */
|
||||||
|
@keyframes cycle-conforme-glow {
|
||||||
|
0%, 100% { box-shadow: 0 0 0 0 rgba(34,197,94,0.18), 0 0 0 0 rgba(0,189,216,0.10); }
|
||||||
|
50% { box-shadow: 0 0 32px 2px rgba(34,197,94,0.32), 0 0 60px 0 rgba(0,189,216,0.14); }
|
||||||
|
}
|
||||||
|
.cycle-card-dictia.is-visible { animation: cycle-conforme-glow 3.4s ease-in-out infinite; }
|
||||||
|
|
||||||
/* Cycle col 3 — anneaux concentriques autour du Lock */
|
/* Cycle col 3 — anneaux concentriques autour du Lock */
|
||||||
@keyframes cycle-ring-out {
|
@keyframes cycle-ring-out {
|
||||||
@@ -438,48 +445,123 @@
|
|||||||
.cycle-ring-outer.is-visible { animation: cycle-ring-out 3.8s ease-in-out infinite; }
|
.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-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 SVG lignes de connexion — drawn via stroke-dashoffset, dash flow continu (round 4) */
|
||||||
.cycle-line { stroke-dasharray: 50; stroke-dashoffset: 50; transition: stroke-dashoffset 600ms ease-out; }
|
.cycle-line { stroke-dasharray: 1.2 0.9; stroke-dashoffset: 50; transition: stroke-dashoffset 600ms ease-out; }
|
||||||
.cycle-line.is-visible { stroke-dashoffset: 0; }
|
.cycle-line.is-visible { stroke-dashoffset: 0; }
|
||||||
|
@keyframes cycle-dash-flow { to { stroke-dashoffset: -42; } }
|
||||||
|
.cycle-line.is-visible.cycle-line-flow { animation: cycle-dash-flow 12s linear infinite; }
|
||||||
|
|
||||||
/* Cycle phase reveal — colonnes 1+2 (phase 2), overlay (phase 3), col 3 (phase 4) */
|
/* Cycle phase reveal */
|
||||||
.cycle-reveal { opacity: 0; transform: translateX(14px); transition: opacity 380ms ease-out, transform 380ms ease-out; }
|
.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.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 { 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-reveal-up.is-visible { opacity: 1; transform: translateY(0); }
|
||||||
|
|
||||||
/* Cycle horloge rotation (col 1) */
|
/* Cycle horloge rotation (col 1) — accélérée 1 tour / 3s pour rendre "lent" PALPABLE */
|
||||||
@keyframes cycle-clock-spin { to { transform: rotate(360deg); } }
|
@keyframes cycle-clock-spin { to { transform: rotate(360deg); } }
|
||||||
.cycle-clock { animation: cycle-clock-spin 8s linear infinite; transform-origin: center; display: inline-block; }
|
.cycle-clock { animation: cycle-clock-spin 3s linear infinite; transform-origin: center; display: inline-block; }
|
||||||
|
|
||||||
/* Cycle col 2 — fuite particules rouges */
|
/* Cycle col 2 — fuite particules rouges (continu, plus dense round 4) */
|
||||||
@keyframes cycle-leak {
|
@keyframes cycle-leak {
|
||||||
0% { transform: translate(0,0) scale(1); opacity: 0; }
|
0% { transform: translate(0,0) scale(1); opacity: 0; }
|
||||||
20% { opacity: 0.75; }
|
20% { opacity: 0.85; }
|
||||||
100% { transform: translate(var(--lx,18px), var(--ly,-22px)) scale(0.4); opacity: 0; }
|
100% { transform: translate(var(--lx,18px), var(--ly,-22px)) scale(0.35); opacity: 0; }
|
||||||
}
|
}
|
||||||
.cycle-leak-particle { animation: cycle-leak 1.8s ease-out infinite; }
|
.cycle-leak-particle { animation: cycle-leak 1.8s ease-out infinite; }
|
||||||
|
|
||||||
/* Reduced motion */
|
/* Cycle col 2 — STAMP NON CONFORME impact (round 4 : tampon huissier qui claque) */
|
||||||
|
@keyframes cycle-stamp-impact {
|
||||||
|
0% { transform: translate(-50%,-50%) rotate(-22deg) scale(2.4); opacity: 0; filter: blur(2px); }
|
||||||
|
55% { transform: translate(-50%,-50%) rotate(-7deg) scale(0.92); opacity: 1; filter: blur(0); }
|
||||||
|
70% { transform: translate(-50%,-50%) rotate(-3deg) scale(1.06); }
|
||||||
|
100% { transform: translate(-50%,-50%) rotate(-3deg) scale(1); opacity: 1; }
|
||||||
|
}
|
||||||
|
.cycle-stamp.is-visible { animation: cycle-stamp-impact 720ms cubic-bezier(0.5,1.6,0.4,1) forwards; }
|
||||||
|
|
||||||
|
/* Cycle col 2 — flash background rouge à l'impact stamp */
|
||||||
|
@keyframes cycle-flash-red {
|
||||||
|
0% { background-color: rgba(239,68,68,0); }
|
||||||
|
35% { background-color: rgba(239,68,68,0.18); }
|
||||||
|
100% { background-color: rgba(239,68,68,0); }
|
||||||
|
}
|
||||||
|
.cycle-col-flash.is-visible { animation: cycle-flash-red 700ms ease-out 350ms forwards; }
|
||||||
|
|
||||||
|
/* Cycle col 3 — checkmark draw (stroke-dashoffset) */
|
||||||
|
.cycle-check-svg path { stroke-dasharray: 24; stroke-dashoffset: 24; transition: stroke-dashoffset 350ms ease-out 200ms; }
|
||||||
|
.cycle-check-svg.is-visible path { stroke-dashoffset: 0; }
|
||||||
|
|
||||||
|
/* Cycle col 3 — badge "Loi 25 conforme" pulse subtil */
|
||||||
|
@keyframes cycle-badge-pulse {
|
||||||
|
0%, 100% { transform: scale(1); box-shadow: 0 0 0 0 rgba(34,197,94,0.4); }
|
||||||
|
50% { transform: scale(1.04); box-shadow: 0 0 0 6px rgba(34,197,94,0); }
|
||||||
|
}
|
||||||
|
.cycle-conforme-badge.is-visible { animation: cycle-badge-pulse 2.4s ease-in-out infinite 600ms; }
|
||||||
|
|
||||||
|
/* Cycle "Économies annuelles" cards — hover lift + glow */
|
||||||
|
.cycle-savings-card {
|
||||||
|
transition: transform 220ms ease-out, box-shadow 220ms ease-out, border-color 220ms ease-out;
|
||||||
|
}
|
||||||
|
.cycle-savings-card:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 8px 24px -8px rgba(34,197,94,0.35), 0 4px 12px -4px rgba(11,15,26,0.08);
|
||||||
|
border-color: rgba(34,197,94,0.45);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mobile — désactiver fuites particules + glow heavy */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.cycle-leak-particle { display: none; }
|
||||||
|
.cycle-card-dictia.is-visible { animation: none; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Reduced motion — TOUT figé */
|
||||||
@media (prefers-reduced-motion: reduce) {
|
@media (prefers-reduced-motion: reduce) {
|
||||||
.cycle-pulse, .cycle-card-dictia, .cycle-ring-outer, .cycle-ring-inner,
|
.cycle-pulse, .cycle-card-dictia, .cycle-ring-outer, .cycle-ring-inner,
|
||||||
.cycle-clock, .cycle-leak-particle { animation: none !important; }
|
.cycle-clock, .cycle-leak-particle, .cycle-live-dot, .cycle-conforme-badge,
|
||||||
|
.cycle-line.cycle-line-flow, .cycle-stamp, .cycle-col-flash { animation: none !important; }
|
||||||
.cycle-reveal, .cycle-reveal-up { opacity: 1 !important; transform: none !important; }
|
.cycle-reveal, .cycle-reveal-up { opacity: 1 !important; transform: none !important; }
|
||||||
.cycle-line { stroke-dashoffset: 0 !important; }
|
.cycle-line { stroke-dashoffset: 0 !important; }
|
||||||
|
.cycle-check-svg path { stroke-dashoffset: 0 !important; }
|
||||||
|
.cycle-savings-card { transition: none !important; }
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<section
|
<section
|
||||||
class="bg-white py-20 border-y border-brand-border relative overflow-hidden"
|
class="bg-white py-20 border-y border-brand-border relative overflow-hidden"
|
||||||
aria-labelledby="cycle-title"
|
aria-labelledby="cycle-title"
|
||||||
x-data="{ phase: 0, observer: null }"
|
x-data="{
|
||||||
|
phase: 0,
|
||||||
|
observer: null,
|
||||||
|
priceHumain: 0,
|
||||||
|
sav1: 0, sav2: 0, sav3: 0,
|
||||||
|
/* easeOutCubic counter helper */
|
||||||
|
countTo(prop, target, duration) {
|
||||||
|
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) { this[prop] = target; return; }
|
||||||
|
const start = performance.now();
|
||||||
|
const tick = (now) => {
|
||||||
|
const t = Math.min(1, (now - start) / duration);
|
||||||
|
const eased = 1 - Math.pow(1 - t, 3);
|
||||||
|
this[prop] = Math.round(target * eased);
|
||||||
|
if (t < 1) requestAnimationFrame(tick);
|
||||||
|
};
|
||||||
|
requestAnimationFrame(tick);
|
||||||
|
},
|
||||||
|
fmt(n) { return n.toLocaleString('fr-CA').replace(/ /g, ' '); }
|
||||||
|
}"
|
||||||
x-init="
|
x-init="
|
||||||
observer = new IntersectionObserver((entries) => {
|
observer = new IntersectionObserver((entries) => {
|
||||||
entries.forEach(e => {
|
entries.forEach(e => {
|
||||||
if (e.isIntersecting && phase === 0) {
|
if (e.isIntersecting && phase === 0) {
|
||||||
setTimeout(() => phase = 1, 250);
|
setTimeout(() => { phase = 1; }, 250);
|
||||||
setTimeout(() => phase = 2, 1100);
|
setTimeout(() => { phase = 2; countTo('priceHumain', 315, 1400); }, 1100);
|
||||||
setTimeout(() => phase = 3, 2200);
|
setTimeout(() => phase = 3, 2200);
|
||||||
setTimeout(() => phase = 4, 3000);
|
setTimeout(() => {
|
||||||
|
phase = 4;
|
||||||
|
/* Savings counters fire 600ms after col 3 reveal (round 4) */
|
||||||
|
setTimeout(() => {
|
||||||
|
countTo('sav1', 3924, 1500);
|
||||||
|
countTo('sav2', 6924, 1500);
|
||||||
|
countTo('sav3', 2004, 1500);
|
||||||
|
}, 700);
|
||||||
|
}, 3000);
|
||||||
observer.disconnect();
|
observer.disconnect();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -493,7 +575,10 @@
|
|||||||
|
|
||||||
<div class="relative max-w-[1200px] mx-auto px-6">
|
<div class="relative max-w-[1200px] mx-auto px-6">
|
||||||
<div class="max-w-2xl mb-10">
|
<div class="max-w-2xl mb-10">
|
||||||
<p class="eyebrow text-amber-600 mb-4">⚠ CADRE RÉGLEMENTAIRE</p>
|
<p class="eyebrow text-amber-600 mb-4 inline-flex items-center gap-1.5">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" class="w-3.5 h-3.5" 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>
|
||||||
|
CADRE RÉGLEMENTAIRE
|
||||||
|
</p>
|
||||||
<h2 id="cycle-title" class="text-[clamp(2rem,3vw,2.75rem)] font-black mb-3 text-brand-navy">
|
<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>
|
Trois options. <span class="text-brand-navy/30">Une seule est conforme.</span>
|
||||||
</h2>
|
</h2>
|
||||||
@@ -519,23 +604,25 @@
|
|||||||
</template>
|
</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>
|
<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>
|
<span class="text-[11px] tracking-wide text-brand-navy/55">Réunion en cours — données confidentielles</span>
|
||||||
|
<span class="cycle-live-dot inline-block w-1.5 h-1.5 rounded-full bg-red-500 shrink-0" aria-hidden="true"></span>
|
||||||
|
<span class="font-mono font-bold text-[8px] tracking-[0.22em] uppercase text-red-500/85">Live</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{# Lignes de connexion SVG — de la source vers les 3 colonnes #}
|
{# Lignes de connexion SVG — de la source vers les 3 colonnes (round 4 : dash flow continu) #}
|
||||||
<div class="relative h-10">
|
<div class="relative h-10">
|
||||||
<svg class="w-full h-full" viewBox="0 0 100 10" preserveAspectRatio="none" aria-hidden="true">
|
<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' : ''"
|
<line class="cycle-line cycle-line-flow" :class="phase >= 1 ? 'is-visible' : ''"
|
||||||
x1="50" y1="0" x2="14" y2="10"
|
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" />
|
stroke="rgba(148,163,184,0.55)" stroke-width="0.3" />
|
||||||
<line class="cycle-line" :class="phase >= 1 ? 'is-visible' : ''"
|
<line class="cycle-line cycle-line-flow" :class="phase >= 1 ? 'is-visible' : ''"
|
||||||
x1="50" y1="0" x2="50" y2="10"
|
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"
|
stroke="rgba(239,68,68,0.55)" stroke-width="0.3"
|
||||||
style="transition-delay: 80ms;" />
|
style="transition-delay: 80ms; animation-delay: 0.4s;" />
|
||||||
<line class="cycle-line" :class="phase >= 1 ? 'is-visible' : ''"
|
<line class="cycle-line cycle-line-flow" :class="phase >= 1 ? 'is-visible' : ''"
|
||||||
x1="50" y1="0" x2="86" y2="10"
|
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"
|
stroke="rgba(34,197,94,0.6)" stroke-width="0.3"
|
||||||
style="transition-delay: 160ms;" />
|
style="transition-delay: 160ms; animation-delay: 0.8s;" />
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -576,8 +663,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="px-5 pb-5 pt-4 border-t border-brand-border">
|
<div class="px-5 pb-5 pt-4 border-t border-brand-border">
|
||||||
<div class="flex items-baseline gap-1.5 mb-2">
|
<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="font-black text-3xl leading-none text-brand-navy/65 tabular-nums" x-text="priceHumain">315</span>
|
||||||
<span class="text-xs text-brand-navy/45">$ / réunion</span>
|
<span class="text-xs text-brand-navy/45">$ / réunion</span>
|
||||||
</div>
|
</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="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="w-1 h-1 rounded-full bg-red-400"></span>
|
||||||
@@ -587,18 +674,20 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{# COL 2 — IA cloud américaine #}
|
{# 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"
|
<div class="cycle-reveal-up cycle-col-flash relative flex flex-col rounded border border-red-200 bg-red-50/30 overflow-hidden"
|
||||||
:class="phase >= 2 ? 'is-visible' : ''"
|
:class="phase >= 2 ? 'is-visible' : ''"
|
||||||
style="transition-delay: 120ms;">
|
style="transition-delay: 120ms;">
|
||||||
{# Overlay légal NON CONFORME (phase 3) #}
|
{# Overlay légal NON CONFORME (phase 3) — round 4 : STAMP huissier qui claque #}
|
||||||
<div class="absolute inset-0 z-30 flex flex-col items-center justify-center pointer-events-none cycle-reveal-up"
|
<div class="absolute inset-0 z-30 flex flex-col items-center justify-center pointer-events-none"
|
||||||
:class="phase >= 3 ? 'is-visible' : ''"
|
:class="phase >= 3 ? 'opacity-100' : 'opacity-0'"
|
||||||
style="backdrop-filter: blur(6px); background: rgba(255,255,255,0.78);">
|
style="transition: opacity 220ms ease-out; 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">
|
<div class="cycle-stamp absolute top-1/2 left-1/2 flex flex-col items-center gap-3 px-6 py-5 rounded bg-white border-[3px] border-red-500 shadow-[0_8px_30px_-6px_rgba(239,68,68,0.55)]"
|
||||||
|
:class="phase >= 3 ? 'is-visible' : ''"
|
||||||
|
style="transform: translate(-50%,-50%) scale(0); opacity: 0;">
|
||||||
<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>
|
<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="text-center">
|
||||||
<div class="font-mono font-bold tracking-[0.14em] text-sm uppercase text-red-600">NON CONFORME</div>
|
<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 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>
|
||||||
</div>
|
</div>
|
||||||
@@ -612,10 +701,10 @@
|
|||||||
<div class="relative flex flex-col items-center gap-2">
|
<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">
|
<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>
|
<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 #}
|
{# Particules de fuite (round 4 : 10 particules, plus dense) #}
|
||||||
{% for i in range(6) %}
|
{% for i in range(10) %}
|
||||||
<span class="cycle-leak-particle absolute w-[3px] h-[3px] rounded-full bg-red-400 pointer-events-none"
|
<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>
|
style="left: 50%; top: 50%; --lx: {{ (-32 + i*8) }}px; --ly: -{{ 12 + (i % 4) * 7 }}px; animation-delay: {{ i * 0.16 }}s; box-shadow: 0 0 4px rgba(239,68,68,0.55);"></span>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
<div class="px-2 py-0.5 rounded bg-red-50 border border-red-100">
|
<div class="px-2 py-0.5 rounded bg-red-50 border border-red-100">
|
||||||
@@ -660,11 +749,18 @@
|
|||||||
style="background: radial-gradient(ellipse 80% 35% at 50% 0%, rgba(0,189,216,0.12) 0%, transparent 65%);"></div>
|
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 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">
|
{# Numéro 03 → checkmark vert (round 4) #}
|
||||||
<div class="absolute inset-0 rounded-full bg-brand-b1/15 border border-brand-b1/30"></div>
|
<span class="relative w-5 h-5 rounded-full flex items-center justify-center shrink-0 bg-emerald-500/15 border border-emerald-500/45" aria-hidden="true">
|
||||||
<div class="w-1.5 h-1.5 rounded-full bg-brand-b1"></div>
|
<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="cycle-check-svg w-3 h-3 text-emerald-600"
|
||||||
</div>
|
:class="phase >= 4 ? 'is-visible' : ''"><path d="M5 13l4 4L19 7"/></svg>
|
||||||
|
</span>
|
||||||
<span class="text-[11px] uppercase tracking-[0.22em] font-semibold text-brand-b1/80">Solution</span>
|
<span class="text-[11px] uppercase tracking-[0.22em] font-semibold text-brand-b1/80">Solution</span>
|
||||||
|
{# Badge top-right : Loi 25 conforme (round 4) #}
|
||||||
|
<span class="cycle-conforme-badge ml-auto inline-flex items-center gap-1 rounded-full px-2 py-0.5 bg-emerald-500/12 border border-emerald-500/40"
|
||||||
|
:class="phase >= 4 ? 'is-visible' : ''" aria-label="Loi 25 conforme">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" class="w-2.5 h-2.5 text-emerald-600" aria-hidden="true"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg>
|
||||||
|
<span class="font-mono font-bold text-[8px] tracking-[0.16em] uppercase text-emerald-700">Loi 25 conforme</span>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="relative flex-1 flex flex-col items-center justify-center px-5 py-7 gap-5">
|
<div class="relative flex-1 flex flex-col items-center justify-center px-5 py-7 gap-5">
|
||||||
@@ -725,20 +821,46 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{# Barre d'économies — apparaît avec phase 4 #}
|
{# Section "Économies annuelles · 25 utilisateurs" — round 4 : 3 cards avec counter animation #}
|
||||||
<div class="cycle-reveal-up mt-10 flex flex-wrap items-center gap-x-8 gap-y-3 justify-center"
|
<div class="cycle-reveal-up mt-12"
|
||||||
:class="phase >= 4 ? 'is-visible' : ''"
|
:class="phase >= 4 ? 'is-visible' : ''"
|
||||||
style="transition-delay: 700ms;">
|
style="transition-delay: 700ms;">
|
||||||
<div class="flex items-center gap-2 text-[12px] text-brand-navy/55">
|
<div class="flex items-center justify-center gap-2.5 mb-5">
|
||||||
<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>
|
<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 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
|
<span class="font-mono font-bold text-[10px] uppercase tracking-[0.22em] text-brand-navy/65">
|
||||||
|
Économies annuelles · 25 utilisateurs
|
||||||
|
</span>
|
||||||
|
<span class="flex-1 h-px bg-brand-border max-w-[60px]" aria-hidden="true"></span>
|
||||||
</div>
|
</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">
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-3" role="list" aria-label="Trois comparaisons d'économies annuelles">
|
||||||
<span class="font-black text-xl text-emerald-600">{{ sav[0] | safe }}</span>
|
{% for sav in [
|
||||||
<span class="text-xs text-brand-navy/55">{{ sav[1] }}</span>
|
{'icon': 'otter', 'val_prop': 'sav1', 'val_static': '3 924', 'label': 'vs Otter.ai', 'sub': 'IA cloud US'},
|
||||||
|
{'icon': 'teams', 'val_prop': 'sav2', 'val_static': '6 924', 'label': 'vs MS Teams', 'sub': 'Copilot premium'},
|
||||||
|
{'icon': 'scribe','val_prop': 'sav3', 'val_static': '2 004', 'label': 'vs Sténographe','sub': 'Service humain'}
|
||||||
|
] %}
|
||||||
|
<div class="cycle-savings-card flex items-center gap-3 px-4 py-4 rounded border border-brand-border bg-white" role="listitem">
|
||||||
|
<span class="shrink-0 w-10 h-10 rounded flex items-center justify-center bg-emerald-50 border border-emerald-100 text-emerald-600" aria-hidden="true">
|
||||||
|
{% if sav.icon == 'otter' %}
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" class="w-5 h-5"><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"/><circle cx="12" cy="12" r="3"/></svg>
|
||||||
|
{% elif sav.icon == 'teams' %}
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" class="w-5 h-5"><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"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg>
|
||||||
|
{% else %}
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" class="w-5 h-5"><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="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/></svg>
|
||||||
|
{% endif %}
|
||||||
|
</span>
|
||||||
|
<div class="flex-1 min-w-0">
|
||||||
|
<div class="flex items-baseline">
|
||||||
|
{# OQLF NBSP entre nombre et $ — préservé en placeholder statique pour SEO/no-JS, JS écrase via x-html #}
|
||||||
|
<span class="font-black text-2xl leading-none text-emerald-600 tabular-nums"
|
||||||
|
x-html="fmt({{ sav.val_prop }}) + ' <span class="text-sm text-emerald-600/70 font-bold">$</span>'">{{ sav.val_static | safe }} $</span>
|
||||||
|
</div>
|
||||||
|
<div class="text-[11px] font-semibold text-brand-navy/85 mt-1">{{ sav.label }}</div>
|
||||||
|
<div class="text-[10px] text-brand-navy/45">{{ sav.sub }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1768,11 +1890,13 @@
|
|||||||
|
|
||||||
{# ===== CADRE RÉGLEMENTAIRE — Moniteur d'Interception ===== #}
|
{# ===== CADRE RÉGLEMENTAIRE — Moniteur d'Interception ===== #}
|
||||||
{# Source canonique : InnovA-AI/Website-Sanity/components/sections/dictai-contraste.tsx (REGS + MoniteurInterception)
|
{# 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
|
Round 4 cinématique : radar sweep continu, 6 paquets data en flight QC→US (offset-path bezier),
|
||||||
Cartographie 6 textes : Loi 25, Loi 96, Cloud Act, Guide IA Barreau, Cadre IA MCN, CAI #}
|
typewriter 3 lignes char-par-char, REGS reveal cascadé + glow rouge hover, verdict pulse + scan-line,
|
||||||
|
grid pattern bg console. #}
|
||||||
<style>
|
<style>
|
||||||
/* Cadre — folder qui glisse de QC vers US */
|
/* Cadre — RADAR SWEEP circulaire continu (round 4) */
|
||||||
.cadre-folder { transition: left 1.4s cubic-bezier(0.4,0,0.2,1), color 0.4s, filter 0.4s; }
|
@keyframes cadre-radar-sweep { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
|
||||||
|
.cadre-radar { animation: cadre-radar-sweep 4s linear infinite; transform-origin: center; }
|
||||||
|
|
||||||
/* Cadre — pulse halo autour du folder en alerte */
|
/* Cadre — pulse halo autour du folder en alerte */
|
||||||
@keyframes cadre-folder-halo {
|
@keyframes cadre-folder-halo {
|
||||||
@@ -1781,10 +1905,38 @@
|
|||||||
}
|
}
|
||||||
.cadre-halo { animation: cadre-folder-halo 1.2s ease-out infinite; }
|
.cadre-halo { animation: cadre-folder-halo 1.2s ease-out infinite; }
|
||||||
|
|
||||||
|
/* Cadre — DATA PACKET flight QC→US via offset-path bezier (round 4) */
|
||||||
|
@keyframes cadre-packet-fly {
|
||||||
|
0% { offset-distance: 0%; opacity: 0; transform: scale(0.7); }
|
||||||
|
8% { opacity: 1; transform: scale(1); }
|
||||||
|
85% { opacity: 1; transform: scale(1); }
|
||||||
|
100% { offset-distance: 100%; opacity: 0; transform: scale(0.4); }
|
||||||
|
}
|
||||||
|
.cadre-packet {
|
||||||
|
offset-path: path('M 0 24 Q 100 -24, 200 24');
|
||||||
|
offset-rotate: auto;
|
||||||
|
animation: cadre-packet-fly 2.6s ease-in-out infinite;
|
||||||
|
will-change: offset-distance, opacity, transform;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Cadre — packet trail (traînée fade rouge à l'arrivée) */
|
||||||
|
@keyframes cadre-packet-arrive {
|
||||||
|
0%, 70% { opacity: 0; transform: translate(-50%,-50%) scale(0.5); }
|
||||||
|
80% { opacity: 0.85; transform: translate(-50%,-50%) scale(1.4); }
|
||||||
|
100% { opacity: 0; transform: translate(-50%,-50%) scale(2.2); }
|
||||||
|
}
|
||||||
|
.cadre-packet-burst { animation: cadre-packet-arrive 2.6s ease-in-out infinite; }
|
||||||
|
|
||||||
/* Cadre — REGS row flash amber pendant le sweep */
|
/* Cadre — REGS row flash amber pendant le sweep */
|
||||||
.cadre-reg { transition: border-color 180ms, background-color 180ms; }
|
.cadre-reg { transition: border-color 220ms, background-color 220ms, box-shadow 220ms, transform 220ms; }
|
||||||
.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 { 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-reg.is-flash .cadre-reg-label { color: #d97706 !important; }
|
||||||
|
/* Hover red glow (round 4) */
|
||||||
|
.cadre-reg:hover { box-shadow: 0 0 18px rgba(239,68,68,0.22); border-left-width: 3px !important; border-left-color: rgba(239,68,68,0.65) !important; transform: translateX(2px); }
|
||||||
|
|
||||||
|
/* Cadre — REGS reveal cascadé (round 4 : IntersectionObserver via Alpine `revealRegs`) */
|
||||||
|
.cadre-reg-item { opacity: 0; transform: translateX(12px); transition: opacity 420ms ease-out, transform 420ms ease-out; }
|
||||||
|
.cadre-reg-item.is-visible { opacity: 1; transform: translateX(0); }
|
||||||
|
|
||||||
/* Cadre — alerte clignotante footer */
|
/* Cadre — alerte clignotante footer */
|
||||||
@keyframes cadre-blink {
|
@keyframes cadre-blink {
|
||||||
@@ -1800,10 +1952,37 @@
|
|||||||
}
|
}
|
||||||
.cadre-caret { animation: cadre-caret 0.7s ease-in-out infinite; }
|
.cadre-caret { animation: cadre-caret 0.7s ease-in-out infinite; }
|
||||||
|
|
||||||
|
/* Cadre — VERDICT NON CONFORME pulse glow (round 4) */
|
||||||
|
@keyframes cadre-verdict-pulse {
|
||||||
|
0%, 100% { box-shadow: 0 0 0 0 rgba(239,68,68,0.50), inset 0 0 0 1px rgba(239,68,68,0.45); }
|
||||||
|
50% { box-shadow: 0 0 18px 3px rgba(239,68,68,0.30), inset 0 0 0 1px rgba(239,68,68,0.85); }
|
||||||
|
}
|
||||||
|
.cadre-verdict-active { animation: cadre-verdict-pulse 2s ease-in-out infinite; }
|
||||||
|
|
||||||
|
/* Cadre — VERDICT scan-line traversante (round 4) */
|
||||||
|
@keyframes cadre-scan-line {
|
||||||
|
0% { transform: translateX(-110%); opacity: 0; }
|
||||||
|
20% { opacity: 0.85; }
|
||||||
|
80% { opacity: 0.85; }
|
||||||
|
100% { transform: translateX(110%); opacity: 0; }
|
||||||
|
}
|
||||||
|
.cadre-scan-line { animation: cadre-scan-line 3s linear infinite; }
|
||||||
|
|
||||||
|
/* Cadre — typewriter caret (3e ligne en rouge avec glow) */
|
||||||
|
.cadre-hud-line-3 { color: #ff5252 !important; text-shadow: 0 0 8px rgba(239,68,68,0.55); }
|
||||||
|
|
||||||
|
/* Mobile — désactiver radar + packets (CPU-intensive) */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.cadre-radar, .cadre-packet, .cadre-packet-burst { display: none; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Reduced motion — TOUT figé */
|
||||||
@media (prefers-reduced-motion: reduce) {
|
@media (prefers-reduced-motion: reduce) {
|
||||||
.cadre-folder, .cadre-halo, .cadre-reg, .cadre-blink, .cadre-caret {
|
.cadre-radar, .cadre-halo, .cadre-blink, .cadre-caret, .cadre-packet,
|
||||||
animation: none !important; transition: none !important;
|
.cadre-packet-burst, .cadre-verdict-active, .cadre-scan-line {
|
||||||
|
animation: none !important;
|
||||||
}
|
}
|
||||||
|
.cadre-reg, .cadre-reg-item { transition: none !important; opacity: 1 !important; transform: none !important; }
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<section
|
<section
|
||||||
@@ -1811,32 +1990,62 @@
|
|||||||
aria-labelledby="cadre-title"
|
aria-labelledby="cadre-title"
|
||||||
x-data="{
|
x-data="{
|
||||||
step: 0,
|
step: 0,
|
||||||
hudLines: [],
|
/* hudTyped[i] = sous-chaîne progressive de HUD[i] (typewriter char-by-char) */
|
||||||
|
hudTyped: ['','',''],
|
||||||
|
hudCursorLine: -1,
|
||||||
flashIdx: -1,
|
flashIdx: -1,
|
||||||
|
revealedRegs: [],
|
||||||
timers: [],
|
timers: [],
|
||||||
observer: null,
|
observer: null,
|
||||||
HUD: ['> Interception IA détectée.', '> Données utilisées pour entraînement.', '> Statut : Violation Légale.'],
|
HUD: ['> Interception IA détectée.', '> Données utilisées pour entraînement.', '> Statut : Violation Légale.'],
|
||||||
|
typeLine(idx, cb) {
|
||||||
|
const full = this.HUD[idx];
|
||||||
|
const reduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
||||||
|
if (reduced) { this.hudTyped[idx] = full; if (cb) cb(); return; }
|
||||||
|
this.hudCursorLine = idx;
|
||||||
|
let i = 0;
|
||||||
|
const tick = () => {
|
||||||
|
if (i > full.length) { if (cb) cb(); return; }
|
||||||
|
this.hudTyped[idx] = full.slice(0, i);
|
||||||
|
i++;
|
||||||
|
this.timers.push(setTimeout(tick, 28));
|
||||||
|
};
|
||||||
|
tick();
|
||||||
|
},
|
||||||
runCycle() {
|
runCycle() {
|
||||||
this.step = 0; this.hudLines = []; this.flashIdx = -1;
|
this.step = 0; this.hudTyped = ['','','']; this.hudCursorLine = -1; this.flashIdx = -1;
|
||||||
this.timers.push(setTimeout(() => this.step = 1, 900));
|
this.timers.push(setTimeout(() => this.step = 1, 900));
|
||||||
this.timers.push(setTimeout(() => this.step = 2, 2300));
|
this.timers.push(setTimeout(() => this.step = 2, 2300));
|
||||||
this.timers.push(setTimeout(() => {
|
this.timers.push(setTimeout(() => {
|
||||||
this.step = 3;
|
this.step = 3;
|
||||||
this.HUD.forEach((line, i) => {
|
/* Typewriter chain : ligne 0 → 1 → 2 puis reset cursor */
|
||||||
this.timers.push(setTimeout(() => this.hudLines.push(line), i * 650));
|
this.typeLine(0, () => {
|
||||||
|
this.timers.push(setTimeout(() => this.typeLine(1, () => {
|
||||||
|
this.timers.push(setTimeout(() => this.typeLine(2, () => {
|
||||||
|
this.hudCursorLine = -1;
|
||||||
|
}), 280));
|
||||||
|
}), 280));
|
||||||
});
|
});
|
||||||
}, 2800));
|
}, 2800));
|
||||||
for (let i = 0; i < 6; i++) {
|
for (let i = 0; i < 6; i++) {
|
||||||
this.timers.push(setTimeout(() => this.flashIdx = i, 4700 + i * 200));
|
this.timers.push(setTimeout(() => this.flashIdx = i, 5400 + i * 200));
|
||||||
}
|
}
|
||||||
this.timers.push(setTimeout(() => this.flashIdx = -1, 4700 + 6 * 200 + 500));
|
this.timers.push(setTimeout(() => this.flashIdx = -1, 5400 + 6 * 200 + 500));
|
||||||
this.timers.push(setTimeout(() => this.runCycle(), 8500));
|
this.timers.push(setTimeout(() => this.runCycle(), 9500));
|
||||||
|
},
|
||||||
|
revealRegsCascade() {
|
||||||
|
[0,1,2,3,4,5].forEach((i) => {
|
||||||
|
this.timers.push(setTimeout(() => {
|
||||||
|
if (!this.revealedRegs.includes(i)) this.revealedRegs.push(i);
|
||||||
|
}, i * 120));
|
||||||
|
});
|
||||||
},
|
},
|
||||||
init() {
|
init() {
|
||||||
this.observer = new IntersectionObserver((entries) => {
|
this.observer = new IntersectionObserver((entries) => {
|
||||||
entries.forEach(e => {
|
entries.forEach(e => {
|
||||||
if (e.isIntersecting && this.step === 0 && this.timers.length === 0) {
|
if (e.isIntersecting && this.step === 0 && this.timers.length === 0) {
|
||||||
this.timers.push(setTimeout(() => this.runCycle(), 300));
|
this.timers.push(setTimeout(() => this.runCycle(), 300));
|
||||||
|
this.timers.push(setTimeout(() => this.revealRegsCascade(), 500));
|
||||||
this.observer.disconnect();
|
this.observer.disconnect();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -1845,12 +2054,16 @@
|
|||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
|
{# Round 4 : decorative grid pattern bg console-style 40×40 #}
|
||||||
<div class="absolute inset-0 pointer-events-none" aria-hidden="true"
|
<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>
|
style="background-image: linear-gradient(rgba(255,255,255,0.03) 1px, transparent 1px), linear-gradient(90deg, rgba(255,255,255,0.03) 1px, transparent 1px), radial-gradient(circle, rgba(11,15,26,0.045) 1px, transparent 1px); background-size: 40px 40px, 40px 40px, 28px 28px;"></div>
|
||||||
|
|
||||||
<div class="relative max-w-[1200px] mx-auto px-6">
|
<div class="relative max-w-[1200px] mx-auto px-6">
|
||||||
<div class="text-center max-w-2xl mx-auto mb-12">
|
<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>
|
<p class="eyebrow text-amber-600 mb-4 inline-flex items-center gap-1.5">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" class="w-3.5 h-3.5" 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>
|
||||||
|
CADRE RÉGLEMENTAIRE QUÉBEC
|
||||||
|
</p>
|
||||||
<h2 id="cadre-title" class="text-[clamp(2rem,3vw,2.75rem)] font-black mb-4 text-brand-navy">
|
<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>.
|
Ce que vos outils actuels enfreignent <span class="grad-text">en secret</span>.
|
||||||
</h2>
|
</h2>
|
||||||
@@ -1872,20 +2085,45 @@
|
|||||||
Moniteur d'Interception
|
Moniteur d'Interception
|
||||||
</span>
|
</span>
|
||||||
<span x-show="step >= 2"
|
<span x-show="step >= 2"
|
||||||
class="ml-auto font-bold text-[9px] uppercase tracking-[0.18em] text-amber-600 cadre-blink"
|
class="ml-auto inline-flex items-center gap-1 font-bold text-[9px] uppercase tracking-[0.18em] text-amber-600 cadre-blink"
|
||||||
x-cloak>
|
x-cloak>
|
||||||
⚠ Alerte Active
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round" class="w-2.5 h-2.5" 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>
|
||||||
|
Alerte Active
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{# Body — 2 colonnes #}
|
{# Body — 2 colonnes #}
|
||||||
<div class="flex flex-col md:flex-row">
|
<div class="flex flex-col md:flex-row">
|
||||||
|
|
||||||
{# LEFT — animation track (~42%) #}
|
{# LEFT — animation track (~42%) — round 4 : radar + data packets en flight #}
|
||||||
<div class="relative md:w-[42%] px-5 py-5 overflow-hidden bg-brand-bg" style="min-height: 200px;">
|
<div class="relative md:w-[42%] px-5 py-5 overflow-hidden bg-brand-bg" style="min-height: 240px;" role="img" aria-label="Visualisation : radar de surveillance et paquets de données voix transférés du Québec aux États-Unis">
|
||||||
{# Grille bg #}
|
{# Grille bg console #}
|
||||||
<div class="absolute inset-0 pointer-events-none" aria-hidden="true"
|
<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>
|
style="background-image: linear-gradient(rgba(11,15,26,0.045) 1px, transparent 1px), linear-gradient(90deg, rgba(11,15,26,0.045) 1px, transparent 1px); background-size: 20px 20px;"></div>
|
||||||
|
|
||||||
|
{# Round 4 : Radar sweep — cercle vert avec ligne rotative balayant 360° #}
|
||||||
|
<div class="absolute -top-8 -right-8 w-40 h-40 pointer-events-none opacity-50" aria-hidden="true">
|
||||||
|
<svg viewBox="0 0 100 100" class="w-full h-full">
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="cadre-radar-grad" x1="0" y1="0" x2="1" y2="0">
|
||||||
|
<stop offset="0%" stop-color="rgba(34,197,94,0)"/>
|
||||||
|
<stop offset="80%" stop-color="rgba(34,197,94,0.55)"/>
|
||||||
|
<stop offset="100%" stop-color="rgba(34,197,94,0.85)"/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<circle cx="50" cy="50" r="48" fill="none" stroke="rgba(34,197,94,0.20)" stroke-width="0.5"/>
|
||||||
|
<circle cx="50" cy="50" r="32" fill="none" stroke="rgba(34,197,94,0.15)" stroke-width="0.4"/>
|
||||||
|
<circle cx="50" cy="50" r="16" fill="none" stroke="rgba(34,197,94,0.12)" stroke-width="0.4"/>
|
||||||
|
<line x1="2" y1="50" x2="98" y2="50" stroke="rgba(34,197,94,0.10)" stroke-width="0.3"/>
|
||||||
|
<line x1="50" y1="2" x2="50" y2="98" stroke="rgba(34,197,94,0.10)" stroke-width="0.3"/>
|
||||||
|
{# Sweep arm #}
|
||||||
|
<g class="cadre-radar" style="transform-origin: 50px 50px;">
|
||||||
|
<path d="M 50 50 L 98 50 A 48 48 0 0 0 84 16 Z" fill="url(#cadre-radar-grad)" opacity="0.45"/>
|
||||||
|
<line x1="50" y1="50" x2="98" y2="50" stroke="rgba(34,197,94,0.85)" stroke-width="0.6"/>
|
||||||
|
</g>
|
||||||
|
<circle cx="50" cy="50" r="1.6" fill="rgba(34,197,94,0.85)"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
|
||||||
{# Labels QC / US #}
|
{# Labels QC / US #}
|
||||||
<div class="relative flex items-center justify-between mb-3">
|
<div class="relative flex items-center justify-between mb-3">
|
||||||
@@ -1898,14 +2136,15 @@
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{# Track #}
|
{# Track + folder + DATA PACKETS en flight (round 4) #}
|
||||||
<div class="relative h-10">
|
<div class="relative h-12">
|
||||||
<div class="absolute top-0 bottom-0 transition-colors duration-300"
|
<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)'};`"
|
: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>
|
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%'}`">
|
{# Folder source (QC) #}
|
||||||
<span x-show="step >= 2" class="cadre-halo absolute inset-[-8px] rounded-full bg-amber-500/15" x-cloak></span>
|
<div class="absolute top-1/2 -translate-y-1/2 -translate-x-1/2"
|
||||||
|
style="left: 6%; transition: color 0.4s, filter 0.4s;">
|
||||||
<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"
|
<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="relative w-5 h-5 transition-colors duration-300"
|
||||||
:class="step >= 2 ? 'text-amber-500' : 'text-brand-b1'"
|
:class="step >= 2 ? 'text-amber-500' : 'text-brand-b1'"
|
||||||
@@ -1914,20 +2153,55 @@
|
|||||||
<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"/>
|
<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>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{# Folder destination (US) avec halo alerte #}
|
||||||
|
<div class="absolute top-1/2 -translate-y-1/2 -translate-x-1/2"
|
||||||
|
style="left: 94%;">
|
||||||
|
<span x-show="step >= 2" class="cadre-halo absolute inset-[-8px] rounded-full bg-red-500/20" x-cloak aria-hidden="true"></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-red-500' : 'text-brand-navy/40'"
|
||||||
|
:style="`filter: drop-shadow(0 0 ${step >= 2 ? '6px rgba(239,68,68,0.7)' : '0'})`"
|
||||||
|
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>
|
||||||
|
{# Burst rouge à l'arrivée des paquets #}
|
||||||
|
<span x-show="step >= 2" class="cadre-packet-burst absolute top-1/2 left-1/2 w-4 h-4 rounded-full bg-red-500/40 pointer-events-none" x-cloak aria-hidden="true"></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{# 6 PAQUETS DATA en flight QC→US (round 4 : offset-path bezier) #}
|
||||||
|
<div x-show="step >= 1" class="absolute inset-0 pointer-events-none" x-cloak aria-hidden="true">
|
||||||
|
{% for pi in range(6) %}
|
||||||
|
<span class="cadre-packet absolute top-0 left-[6%] inline-flex items-center gap-1 px-1 py-0.5 rounded bg-red-500/85 text-white font-mono text-[8px] tracking-tighter shadow-[0_0_6px_rgba(239,68,68,0.6)]"
|
||||||
|
style="animation-delay: {{ pi * 0.42 }}s;">
|
||||||
|
<span class="w-1 h-1 rounded-full bg-white/80"></span>
|
||||||
|
voice.wav
|
||||||
|
</span>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{# HUD panel #}
|
{# HUD panel — typewriter char-by-char (round 4) #}
|
||||||
<div x-show="step >= 3"
|
<div x-show="step >= 3"
|
||||||
x-transition:enter="transition ease-out duration-300"
|
x-transition:enter="transition ease-out duration-300"
|
||||||
x-transition:enter-start="opacity-0 translate-y-2"
|
x-transition:enter-start="opacity-0 translate-y-2"
|
||||||
x-transition:enter-end="opacity-100 translate-y-0"
|
x-transition:enter-end="opacity-100 translate-y-0"
|
||||||
class="mt-3 rounded px-3 py-2.5 font-mono"
|
class="relative 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;"
|
style="background: rgba(11,15,26,0.94); border: 1px solid rgba(245,158,11,0.30); font-size: 10px; color: #F59E0B; line-height: 1.65; min-height: 70px;"
|
||||||
|
role="log" aria-live="polite"
|
||||||
x-cloak>
|
x-cloak>
|
||||||
<template x-for="(line, i) in hudLines" :key="i">
|
<div class="flex items-baseline">
|
||||||
<div x-html="line"></div>
|
<span x-html="hudTyped[0]"></span>
|
||||||
</template>
|
<span x-show="hudCursorLine === 0" class="cadre-caret ml-0.5">▮</span>
|
||||||
<span x-show="hudLines.length < HUD.length" class="cadre-caret">▌</span>
|
</div>
|
||||||
|
<div class="flex items-baseline" x-show="hudTyped[1].length > 0 || hudCursorLine === 1">
|
||||||
|
<span x-html="hudTyped[1]"></span>
|
||||||
|
<span x-show="hudCursorLine === 1" class="cadre-caret ml-0.5">▮</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-baseline cadre-hud-line-3" x-show="hudTyped[2].length > 0 || hudCursorLine === 2">
|
||||||
|
<span x-html="hudTyped[2]"></span>
|
||||||
|
<span x-show="hudCursorLine === 2" class="cadre-caret ml-0.5">▮</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -1945,7 +2219,9 @@
|
|||||||
{'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': '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}
|
{'label': 'CAI', 'detail': 'Commission d\'accès à l\'information — application active', 'href': 'https://www.cai.gouv.qc.ca/', 'risk': False}
|
||||||
] %}
|
] %}
|
||||||
<li role="listitem">
|
<li role="listitem" class="cadre-reg-item"
|
||||||
|
:class="revealedRegs.includes({{ loop.index0 }}) ? 'is-visible' : ''"
|
||||||
|
style="transition-delay: {{ loop.index0 * 30 }}ms;">
|
||||||
<a href="{{ reg.href }}" target="_blank" rel="noopener noreferrer"
|
<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="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' : ''"
|
:class="flashIdx === {{ loop.index0 }} ? 'is-flash' : ''"
|
||||||
@@ -1972,18 +2248,26 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{# Footer — verdict #}
|
{# Footer — verdict (round 4 : pulse glow rouge + scan-line traversante) #}
|
||||||
<div class="px-5 py-3 flex items-center gap-2.5 border-t border-brand-border bg-white">
|
<div class="relative px-5 py-3 border-t border-brand-border bg-white overflow-hidden">
|
||||||
<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"
|
<div class="relative flex items-center gap-2.5 px-3 py-2 rounded transition-all duration-300"
|
||||||
class="w-4 h-4 transition-colors"
|
:class="step >= 2 ? 'cadre-verdict-active bg-red-50/40' : ''"
|
||||||
:class="step >= 2 ? 'text-amber-500 cadre-blink' : 'text-brand-navy/35'"
|
style="border-radius: 4px;">
|
||||||
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>
|
<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"
|
||||||
<span class="font-bold text-[10px] uppercase tracking-[0.20em] transition-colors"
|
class="w-4 h-4 transition-colors"
|
||||||
:class="step >= 2 ? 'text-amber-600 cadre-blink' : 'text-brand-navy/35'"
|
:class="step >= 2 ? 'text-red-500 cadre-blink' : 'text-brand-navy/35'"
|
||||||
style="text-shadow: 0 0 6px rgba(245,158,11,0.20);"
|
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>
|
||||||
aria-live="polite">
|
<span class="font-bold text-[10px] uppercase tracking-[0.20em] transition-colors"
|
||||||
<span x-text="step >= 2 ? 'NON CONFORME — Loi 25 · Cloud Act' : 'Surveillance active...'"></span>
|
:class="step >= 2 ? 'text-red-600' : 'text-brand-navy/35'"
|
||||||
</span>
|
style="text-shadow: 0 0 6px rgba(239,68,68,0.30);"
|
||||||
|
aria-live="polite">
|
||||||
|
<span x-text="step >= 2 ? 'NON CONFORME — Loi 25 · Cloud Act' : 'Surveillance active...'"></span>
|
||||||
|
</span>
|
||||||
|
{# Scan-line horizontale (round 4) #}
|
||||||
|
<span x-show="step >= 2" x-cloak class="cadre-scan-line absolute inset-y-0 left-0 w-1/3 pointer-events-none"
|
||||||
|
style="background: linear-gradient(90deg, transparent 0%, rgba(239,68,68,0.18) 50%, transparent 100%);"
|
||||||
|
aria-hidden="true"></span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -848,6 +848,104 @@ def test_round2_cadre_reglementaire_section_present():
|
|||||||
assert 'runCycle' in body
|
assert 'runCycle' in body
|
||||||
|
|
||||||
|
|
||||||
|
def test_round4_cadre_cinematic_features():
|
||||||
|
"""Round 4 — Cadre Moniteur d'Interception cinematic upgrades.
|
||||||
|
|
||||||
|
- Radar sweep circulaire continu en background HUD
|
||||||
|
- 6 paquets data 'voice.wav' en flight QC→US (offset-path bezier)
|
||||||
|
- Console typewriter char-by-char (3 lignes via hudTyped + caret blink)
|
||||||
|
- 6 REGS reveal cascadé (revealedRegs IntersectionObserver)
|
||||||
|
- Verdict 'NON CONFORME' pulse glow + scan-line traversante
|
||||||
|
- eyebrow ⚠ remplacé par SVG warning-triangle
|
||||||
|
"""
|
||||||
|
client = app.test_client()
|
||||||
|
body = client.get('/').data.decode('utf-8')
|
||||||
|
# Radar sweep
|
||||||
|
assert 'cadre-radar-sweep' in body, "Round 4 radar sweep keyframe missing"
|
||||||
|
assert 'cadre-radar' in body
|
||||||
|
# 6 data packets en flight (voice.wav répété 6×)
|
||||||
|
assert body.count('voice.wav') >= 6, "Round 4 must have 6 'voice.wav' packets in flight"
|
||||||
|
assert 'cadre-packet' in body
|
||||||
|
assert 'offset-path' in body, "Round 4 packets must use offset-path for bezier flight"
|
||||||
|
# Typewriter
|
||||||
|
assert 'hudTyped' in body, "Round 4 typewriter state missing"
|
||||||
|
assert 'typeLine' in body, "Round 4 typewriter function missing"
|
||||||
|
# REGS cascade reveal
|
||||||
|
assert 'revealRegsCascade' in body or 'revealedRegs' in body
|
||||||
|
assert 'cadre-reg-item' in body
|
||||||
|
# Verdict pulse glow + scan line
|
||||||
|
assert 'cadre-verdict-active' in body
|
||||||
|
assert 'cadre-scan-line' in body
|
||||||
|
# ⚠ remplacé par SVG (le mot WARNING ne doit plus apparaître entouré de ⚠ dans l'eyebrow Cadre)
|
||||||
|
assert '⚠ CADRE RÉGLEMENTAIRE QUÉBEC' not in body, "⚠ literal must be replaced by SVG"
|
||||||
|
|
||||||
|
|
||||||
|
def test_round4_cycle_cinematic_features():
|
||||||
|
"""Round 4 — Cycle (Trois options) cinematic upgrades.
|
||||||
|
|
||||||
|
- Phase reveal séquentiel + price counter Col 1 (priceHumain 0→315)
|
||||||
|
- Stamp 'NON CONFORME' impact (cycle-stamp keyframes)
|
||||||
|
- Col 3 checkmark draw (cycle-check-svg stroke-dashoffset)
|
||||||
|
- Col 3 glow vert (cycle-conforme-glow)
|
||||||
|
- Badge 'Loi 25 conforme' pulse (cycle-conforme-badge)
|
||||||
|
- Section 'Économies annuelles · 25 utilisateurs' avec 3 counters (sav1/sav2/sav3)
|
||||||
|
- Connecting line dash flow (cycle-line-flow)
|
||||||
|
- eyebrow ⚠ remplacé par SVG
|
||||||
|
"""
|
||||||
|
client = app.test_client()
|
||||||
|
body = client.get('/').data.decode('utf-8')
|
||||||
|
# Price counter
|
||||||
|
assert 'priceHumain' in body, "Round 4 price counter state missing"
|
||||||
|
assert 'countTo' in body, "Round 4 counter helper missing"
|
||||||
|
# Stamp impact + flash
|
||||||
|
assert 'cycle-stamp' in body
|
||||||
|
assert 'cycle-stamp-impact' in body or '@keyframes cycle-stamp-impact' in body
|
||||||
|
assert 'cycle-col-flash' in body
|
||||||
|
# Checkmark draw
|
||||||
|
assert 'cycle-check-svg' in body
|
||||||
|
# Conforme badge + glow
|
||||||
|
assert 'cycle-conforme-badge' in body
|
||||||
|
assert 'cycle-conforme-glow' in body
|
||||||
|
assert 'Loi 25 conforme' in body or 'Loi 25 conforme' in body
|
||||||
|
# Économies annuelles avec 3 counters
|
||||||
|
assert 'Économies annuelles' in body
|
||||||
|
assert 'sav1' in body and 'sav2' in body and 'sav3' in body
|
||||||
|
assert 'cycle-savings-card' in body
|
||||||
|
# Live dot "Réunion en cours"
|
||||||
|
assert 'cycle-live-dot' in body
|
||||||
|
# Dash flow
|
||||||
|
assert 'cycle-line-flow' in body
|
||||||
|
# ⚠ remplacé
|
||||||
|
assert '⚠ CADRE RÉGLEMENTAIRE</p>' not in body, "Cycle eyebrow ⚠ literal must be replaced by SVG"
|
||||||
|
|
||||||
|
|
||||||
|
def test_round4_no_emoji_warning_triangle():
|
||||||
|
"""Round 4 — aucun ⚠ littéral ne doit subsister dans le HTML rendu."""
|
||||||
|
client = app.test_client()
|
||||||
|
body = client.get('/').data.decode('utf-8')
|
||||||
|
# Le caractère ⚠ U+26A0 ne doit plus apparaître nulle part dans landing.html
|
||||||
|
# (sauf dans les keyframes/CSS comments qui sont absents)
|
||||||
|
assert '⚠' not in body, "Round 4 must not contain literal ⚠ character anywhere on landing"
|
||||||
|
|
||||||
|
|
||||||
|
def test_round4_reduced_motion_disables_all_cinematics():
|
||||||
|
"""Round 4 — prefers-reduced-motion media query must disable ALL new cinematics."""
|
||||||
|
client = app.test_client()
|
||||||
|
body = client.get('/').data.decode('utf-8')
|
||||||
|
# Le bloc @media (prefers-reduced-motion: reduce) doit explicitement neutraliser :
|
||||||
|
# - radar (cadre-radar)
|
||||||
|
# - packets (cadre-packet)
|
||||||
|
# - typewriter (typeLine has reduced-motion shortcut)
|
||||||
|
# - stamp (cycle-stamp)
|
||||||
|
# - conforme glow + badge
|
||||||
|
assert 'prefers-reduced-motion: reduce' in body
|
||||||
|
assert 'cadre-radar' in body and 'cadre-packet' in body
|
||||||
|
# Round 4 reduced-motion must disable cycle-stamp + cycle-conforme-badge animations
|
||||||
|
assert 'cycle-stamp' in body and 'cycle-conforme-badge' in body
|
||||||
|
# Counter helper has explicit reduced-motion guard
|
||||||
|
assert "matchMedia('(prefers-reduced-motion: reduce)')" in body
|
||||||
|
|
||||||
|
|
||||||
def test_round2_no_external_js_libs_added():
|
def test_round2_no_external_js_libs_added():
|
||||||
"""Round 2 must NOT add Framer Motion / GSAP / canvas-confetti / etc."""
|
"""Round 2 must NOT add Framer Motion / GSAP / canvas-confetti / etc."""
|
||||||
client = app.test_client()
|
client = app.test_client()
|
||||||
|
|||||||
Reference in New Issue
Block a user