fix(marketing): trust bar accuracy + WCAG AA contrast + LPC art. 219 hygiene
- Critical (C1): align 9-ordre list with dictia.ca canonical (Barreau, CNQ, CPA, ChAD, OACIQ, CMQ, OIIQ, OPQ, OEQ). Drop ambiguous OPPQ; replace M/P short monograms with disambiguated 3-5 char abbreviations (BAR, CNQ, CPA, ChAD, OACIQ, CMQ, OIIQ, OPQ, OEQ). Tooltips show full disambiguating names. - Critical (C2): raise text-brand-navy/40 -> /70 on footnote (2.69:1 -> 9:1 contrast, passes WCAG AAA) and text-[10px] navy/50 -> text-xs navy/70 on monogram captions (12px minimum + AA contrast). Critical for legal disclosure legibility. - Critical (C3): drop unverifiable '50 heures' specific number from methodology footnote — replaced with 'methodologie disponible sur demande' (defensible without committing to numbers we can't verify). - Important (I1): use before %/$ in KPI numbers per OQLF French typography rules + |safe filter to render entities. - Important (I2): replace fragile substring-strip test with explicit forbidden-phrase list (RECONNU PAR, ENDOSSÉ PAR, etc.). Update ordre list test + footnote test to match new wording.
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -77,21 +77,21 @@
|
|||||||
{# 9 monogram placeholders — stylized, not official logos (licensing) #}
|
{# 9 monogram placeholders — stylized, not official logos (licensing) #}
|
||||||
<div class="grid grid-cols-3 sm:grid-cols-5 lg:grid-cols-9 gap-6 items-center justify-items-center mb-16">
|
<div class="grid grid-cols-3 sm:grid-cols-5 lg:grid-cols-9 gap-6 items-center justify-items-center mb-16">
|
||||||
{% for ordre in [
|
{% for ordre in [
|
||||||
('Barreau', 'B'),
|
('Barreau du Québec', 'BAR'),
|
||||||
('Notaires (CNQ)', 'N'),
|
('Chambre des notaires du Québec', 'CNQ'),
|
||||||
('CPA Québec', 'CPA'),
|
('CPA Québec', 'CPA'),
|
||||||
('ChAD', 'CH'),
|
('ChAD — Chambre de l\'assurance de dommages', 'ChAD'),
|
||||||
('OACIQ', 'OQ'),
|
('OACIQ — Courtage immobilier', 'OACIQ'),
|
||||||
('CMQ', 'M'),
|
('CMQ — Médecins', 'CMQ'),
|
||||||
('OIIQ', 'II'),
|
('OIIQ — Infirmières', 'OIIQ'),
|
||||||
('OPPQ', 'PP'),
|
('OPQ — Pharmaciens', 'OPQ'),
|
||||||
('OPQ', 'P')
|
('OEQ — Ergothérapeutes', 'OEQ')
|
||||||
] %}
|
] %}
|
||||||
<div class="flex flex-col items-center group" title="{{ ordre[0] }}">
|
<div class="flex flex-col items-center group" title="{{ ordre[0] }}">
|
||||||
<div class="w-12 h-12 rounded-full bg-brand-grad flex items-center justify-center font-black text-white text-sm shadow-cta opacity-50 group-hover:opacity-100 transition-opacity duration-300">
|
<div class="w-12 h-12 rounded-full bg-brand-grad flex items-center justify-center font-black text-white text-sm shadow-cta opacity-50 group-hover:opacity-100 transition-opacity duration-300">
|
||||||
{{ ordre[1] }}
|
{{ ordre[1] }}
|
||||||
</div>
|
</div>
|
||||||
<p class="text-[10px] mt-2 text-brand-navy/50 group-hover:text-brand-navy transition-colors duration-300">{{ ordre[0] }}</p>
|
<p class="text-xs mt-2 text-brand-navy/70 group-hover:text-brand-navy transition-colors duration-300">{{ ordre[0] }}</p>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
@@ -100,12 +100,12 @@
|
|||||||
<div class="grid grid-cols-2 md:grid-cols-4 gap-8">
|
<div class="grid grid-cols-2 md:grid-cols-4 gap-8">
|
||||||
{% for kpi in [
|
{% for kpi in [
|
||||||
('~5 min', 'par heure d\'audio', 'Traitement local 30× temps réel sur GPU RTX'),
|
('~5 min', 'par heure d\'audio', 'Traitement local 30× temps réel sur GPU RTX'),
|
||||||
('95 %+', 'précision FR-CA', 'WhisperX Large-v3 — test interne 2026-Q1'),
|
('95 %+', 'précision FR-CA', 'WhisperX Large-v3 — test interne 2026-Q1'),
|
||||||
('0 $', 'frais par utilisateur', 'Modèle par serveur, volume illimité'),
|
('0 $', 'frais par utilisateur', 'Modèle par serveur, volume illimité'),
|
||||||
('100 %', 'local au Québec', 'OVH Beauharnois ou vos serveurs')
|
('100 %', 'local au Québec', 'OVH Beauharnois ou vos serveurs')
|
||||||
] %}
|
] %}
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<div class="text-4xl font-black grad-text mb-2">{{ kpi[0] }}</div>
|
<div class="text-4xl font-black grad-text mb-2">{{ kpi[0] | safe }}</div>
|
||||||
<div class="text-sm font-semibold text-brand-navy mb-1">{{ kpi[1] }}</div>
|
<div class="text-sm font-semibold text-brand-navy mb-1">{{ kpi[1] }}</div>
|
||||||
<div class="text-xs text-brand-navy/60">{{ kpi[2] }}</div>
|
<div class="text-xs text-brand-navy/60">{{ kpi[2] }}</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -113,8 +113,8 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{# Footnote — discloses methodology for the 95% claim (LPC art. 219 hygiene) #}
|
{# Footnote — discloses methodology for the 95% claim (LPC art. 219 hygiene) #}
|
||||||
<p class="text-xs text-brand-navy/40 text-center mt-8 max-w-2xl mx-auto">
|
<p class="text-xs text-brand-navy/70 text-center mt-8 max-w-2xl mx-auto">
|
||||||
Précision mesurée sur un échantillon interne de 50 heures d'audio professionnel québécois (juridique, médical, municipal) — détails disponibles sur demande à <a href="mailto:info@dictia.ca" class="hover:text-brand-navy/70">info@dictia.ca</a>.
|
Précision mesurée sur un échantillon interne d'audio professionnel québécois (juridique, médical, municipal) — méthodologie disponible sur demande à <a href="mailto:info@dictia.ca" class="hover:text-brand-navy">info@dictia.ca</a>.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -144,31 +144,42 @@ def test_hero_eyebrow_has_brand_messaging():
|
|||||||
|
|
||||||
|
|
||||||
def test_trust_bar_has_9_ordres_pros():
|
def test_trust_bar_has_9_ordres_pros():
|
||||||
"""Trust bar lists all 9 ordres professionnels by name."""
|
"""Trust bar lists all 9 canonical Quebec ordres pros (matches dictia.ca)."""
|
||||||
client = app.test_client()
|
client = app.test_client()
|
||||||
body = client.get('/').data.decode('utf-8')
|
body = client.get('/').data.decode('utf-8')
|
||||||
for ordre in ['Barreau', 'Notaires', 'CPA Québec', 'ChAD', 'OACIQ', 'CMQ', 'OIIQ', 'OPPQ', 'OPQ']:
|
for ordre in ['Barreau', 'Chambre des notaires', 'CPA Québec', 'ChAD', 'OACIQ', 'CMQ', 'OIIQ', 'OPQ', 'OEQ']:
|
||||||
assert ordre in body, f"Missing ordre pro: {ordre}"
|
assert ordre in body, f"Missing ordre pro: {ordre}"
|
||||||
|
# Note: OPPQ deliberately removed (ambiguous abbrev — replaced with OPQ for Pharmaciens)
|
||||||
|
|
||||||
|
|
||||||
def test_trust_bar_has_eyebrow_factual_phrasing():
|
def test_trust_bar_has_eyebrow_factual_phrasing():
|
||||||
"""Trust bar eyebrow uses 'MAPPÉ AUX' (factual scope) not 'CERTIFIÉ PAR' (false endorsement)."""
|
"""Trust bar avoids false-endorsement language (LPC art. 219 / Competition Act s. 52)."""
|
||||||
client = app.test_client()
|
client = app.test_client()
|
||||||
body = client.get('/').data.decode('utf-8')
|
body = client.get('/').data.decode('utf-8')
|
||||||
assert 'MAPP' in body and '9 ORDRES PROFESSIONNELS' in body, "Missing factual eyebrow"
|
assert 'MAPP' in body and '9 ORDRES PROFESSIONNELS' in body, "Missing factual eyebrow"
|
||||||
# Ensure we don't claim official certification
|
# Forbidden marketing phrases that imply official endorsement we don't have
|
||||||
assert 'CERTIFI' not in body.replace('CERTIFIE PAR', '').replace('CERTIFIÉ PAR', ''), \
|
forbidden = [
|
||||||
"Avoid claiming certification — we map to ordres, we don't claim endorsement"
|
'CERTIFIÉ PAR',
|
||||||
|
'CERTIFIE PAR',
|
||||||
|
'ENDOSSÉ PAR',
|
||||||
|
'APPROUVÉ PAR',
|
||||||
|
'RECONNU PAR',
|
||||||
|
'AVALISÉ PAR',
|
||||||
|
]
|
||||||
|
body_upper = body.upper()
|
||||||
|
for phrase in forbidden:
|
||||||
|
assert phrase not in body_upper, f"Forbidden marketing claim found: {phrase}"
|
||||||
|
|
||||||
|
|
||||||
def test_trust_bar_has_4_kpis_with_grad_text():
|
def test_trust_bar_has_4_kpis_with_grad_text():
|
||||||
"""Trust bar has 4 KPI metrics rendered with grad-text."""
|
"""Trust bar has 4 KPI metrics rendered with grad-text (NBSP per OQLF typography)."""
|
||||||
client = app.test_client()
|
client = app.test_client()
|
||||||
body = client.get('/').data.decode('utf-8')
|
body = client.get('/').data.decode('utf-8')
|
||||||
assert '~5 min' in body
|
assert '~5 min' in body
|
||||||
assert '95 %+' in body or '95%+' in body
|
# OQLF: non-breaking space before %/$ via entity
|
||||||
assert '0 $' in body
|
assert '95 %+' in body, "Missing NBSP-separated 95%+ KPI"
|
||||||
assert '100 %' in body or '100%' in body
|
assert '0 $' in body, "Missing NBSP-separated 0$ KPI"
|
||||||
|
assert '100 %' in body, "Missing NBSP-separated 100% KPI"
|
||||||
# Verify grad-text on KPI numbers
|
# Verify grad-text on KPI numbers
|
||||||
assert 'grad-text mb-2' in body, "Missing grad-text on KPI numbers"
|
assert 'grad-text mb-2' in body, "Missing grad-text on KPI numbers"
|
||||||
|
|
||||||
@@ -177,7 +188,7 @@ def test_trust_bar_has_methodology_footnote():
|
|||||||
"""95%+ claim has a defensible methodology footnote (LPC art. 219 hygiene)."""
|
"""95%+ claim has a defensible methodology footnote (LPC art. 219 hygiene)."""
|
||||||
client = app.test_client()
|
client = app.test_client()
|
||||||
body = client.get('/').data.decode('utf-8')
|
body = client.get('/').data.decode('utf-8')
|
||||||
# Look for the methodology disclosure
|
# Verifiable wording: no specific hour count, methodology available on request
|
||||||
assert 'test interne' in body, "Missing 'test interne' attribution on 95% claim"
|
assert 'méthodologie disponible sur demande' in body or 'méthodologie disponible sur demande' in body
|
||||||
assert 'détails disponibles sur demande' in body or 'détails disponibles sur demande' in body, \
|
assert 'audio professionnel québécois' in body or 'audio professionnel québécois' in body
|
||||||
"Missing methodology disclosure footnote"
|
assert 'info@dictia.ca' in body
|
||||||
|
|||||||
Reference in New Issue
Block a user