+ Ce que vos outils actuels enfreignent en secret.
+
+
+ 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.
+
+
+
+ {# Carte principale — header + body 2 colonnes (animation à gauche, REGS à droite) #}
+
diff --git a/tests/test_marketing_landing_template.py b/tests/test_marketing_landing_template.py
index 8933eac..11f8af0 100644
--- a/tests/test_marketing_landing_template.py
+++ b/tests/test_marketing_landing_template.py
@@ -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