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:
@@ -848,6 +848,104 @@ def test_round2_cadre_reglementaire_section_present():
|
||||
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():
|
||||
"""Round 2 must NOT add Framer Motion / GSAP / canvas-confetti / etc."""
|
||||
client = app.test_client()
|
||||
|
||||
Reference in New Issue
Block a user