+
+ {# ROI CALCULATOR — Alpine.js, hypotheses transparentes pour LPC art. 219 hygiene #}
+
+
CALCULATEUR ROI
+
Combien DictIA peut vous faire économiser ?
+
+
+
+
+
+
+
Économies estimées par an
+
+
+
+
+ Hypothèses : 80 % du temps de transcription manuelle économisé, 220 jours ouvrables/an, comparé à DictIA 16 (5 750 $ + 201 $/mois). Estimation à titre indicatif.
+
+
+
+
+{% endblock %}
+
+{% block scripts %}
+
{% endblock %}
diff --git a/tests/test_marketing_landing_template.py b/tests/test_marketing_landing_template.py
index 7af2430..67a3e78 100644
--- a/tests/test_marketing_landing_template.py
+++ b/tests/test_marketing_landing_template.py
@@ -300,3 +300,106 @@ def test_bento_renders_nbsp_entities_not_escaped():
# Q&R card title: French ampersand must survive as & in HTML, not &
assert 'Q&R' in body, "Q&R title should appear single-escaped"
assert 'Q&R' not in body, "Q&R title must not be double-escaped"
+
+
+def test_pricing_section_present():
+ """Pricing section is present after bento section, with eyebrow + H2 + tax disclaimer."""
+ client = app.test_client()
+ body = client.get('/').data.decode('utf-8')
+ assert 'pricing-title' in body, "Missing pricing section anchor"
+ assert 'Choisissez votre formule' in body, "Missing pricing H2"
+ # Tax disclaimer must be visible (LPC art. 219 — total cost transparency)
+ assert 'TPS' in body and 'TVQ' in body, "Missing tax disclaimer (TPS/TVQ)"
+
+
+def test_pricing_3_tiers_with_canonical_amounts():
+ """Pricing has 3 tiers: DictIA 8 (3450/173), DictIA 16 (5750/201), DictIA Cloud (0/369)."""
+ client = app.test_client()
+ body = client.get('/').data.decode('utf-8')
+ # Names
+ for name in ['DictIA 8', 'DictIA 16', 'DictIA Cloud']:
+ assert name in body, f"Missing pricing tier: {name}"
+ # Canonical prices with NBSP per OQLF
+ assert '3 450 $' in body, "Missing DictIA 8 setup price"
+ assert '173 $' in body, "Missing DictIA 8 monthly price"
+ assert '5 750 $' in body, "Missing DictIA 16 setup price"
+ assert '201 $' in body, "Missing DictIA 16 monthly price"
+ assert '369 $' in body, "Missing DictIA Cloud monthly price (canonical 369$)"
+
+
+def test_pricing_recommended_tier_is_dictia_16():
+ """DictIA 16 is the visually-recommended tier (RECOMMANDÉ badge + grad-bg frame)."""
+ client = app.test_client()
+ body = client.get('/').data.decode('utf-8')
+ assert 'RECOMMAND' in body, "Missing RECOMMANDÉ badge"
+ # The recommended tier wraps in grad-bg p-[1.5px] rounded-[20px] FlexiHub style
+ assert 'grad-bg p-[1.5px] rounded-[20px]' in body, "Missing FlexiHub gradient frame on recommended tier"
+
+
+def test_pricing_cta_uses_reserver_pre_launch_wording():
+ """CTAs say 'Réserver' not 'Choisir' — pre-launch LPC art. 219 hygiene."""
+ client = app.test_client()
+ body = client.get('/').data.decode('utf-8')
+ for slug in ['dictia-8', 'dictia-16', 'dictia-cloud']:
+ assert f'href="/checkout/{slug}"' in body, f"Missing checkout link for {slug}"
+ assert 'Réserver DictIA 8' in body or 'Réserver DictIA 8' in body, "CTA must use 'Réserver' wording (pre-launch)"
+
+
+def test_pricing_features_use_safe_filter_no_double_escape():
+ """Pricing card features piped through | safe — ' ' must render single-escaped, not double."""
+ client = app.test_client()
+ body = client.get('/').data.decode('utf-8')
+ # GPU sizes use NBSP
+ assert 'GPU 8 Go RTX' in body, "GPU 8 Go feature missing or NBSP double-escaped"
+ assert 'GPU 16 Go RTX' in body, "GPU 16 Go feature missing or NBSP double-escaped"
+ # Q&R card must use French Q&R, not English Q&A
+ assert 'Q&R' in body, "DictIA 16 must mention Q&R (French), not Q&A (English)"
+ assert 'Q&A' not in body, "Must use French Q&R consistently — no English Q&A"
+ # Loi 25 with NBSP
+ assert 'Conforme Loi 25' in body, "Conforme Loi 25 must use NBSP"
+ # SLA must be hedged ('visé') not absolute claim
+ assert 'SLA visé 99,9' in body, "SLA must be hedged 'visé' (pre-launch LPC art. 219 hygiene)"
+ # Negative: NO double-escape
+ assert ' ' not in body, "NBSP must not be double-escaped — | safe missing on pricing macro?"
+
+
+def test_pricing_uses_wcag_safe_text_on_white():
+ """Pricing card text uses text-brand-navy/70 or /80 minimum (WCAG AA on white)."""
+ client = app.test_client()
+ body = client.get('/').data.decode('utf-8')
+ # No regression to weak opacities like /40 or /50 in pricing area
+ assert 'text-brand-navy/70' in body
+ # The features list uses /80 in our impl
+ assert 'text-brand-navy/80' in body, "Feature text should use /80 for WCAG AA"
+
+
+def test_roi_calculator_present_with_alpine_bindings():
+ """ROI calculator section present with Alpine.js bindings + transparent hypotheses footnote."""
+ client = app.test_client()
+ body = client.get('/').data.decode('utf-8')
+ assert 'CALCULATEUR ROI' in body
+ assert 'roi-title' in body, "ROI calculator must have aria-labelledby anchor"
+ assert 'x-data="roiCalculator()"' in body
+ # Three sliders with x-model.number for type coercion
+ assert 'x-model.number="users"' in body
+ assert 'x-model.number="hours"' in body
+ assert 'x-model.number="rate"' in body
+ # Live output bindings
+ assert 'x-text="savings' in body
+ assert 'x-text="\'Payback' in body
+ # Transparent hypothesis footnote — LPC art. 219 hygiene
+ assert '80 %' in body and 'jours ouvrables' in body, "Missing transparent hypothesis footnote"
+ # Sliders accessible (aria-label on each input)
+ assert 'aria-label="Nombre d' in body
+
+
+def test_roi_calculator_script_loaded():
+ """roi_calculator.js loaded via {% block scripts %} (deferred after Alpine.js)."""
+ client = app.test_client()
+ body = client.get('/').data.decode('utf-8')
+ assert '/static/js/roi_calculator.js' in body, "ROI script must be referenced"
+ # Must come AFTER alpine.min.js in the document order
+ alpine_pos = body.find('alpine.min.js')
+ roi_pos = body.find('roi_calculator.js')
+ assert alpine_pos != -1 and roi_pos != -1
+ assert alpine_pos < roi_pos, "Alpine.js must load before roi_calculator.js"