From 2b3eeb98e0d17d6bb29a29ac6f496d6405f1b0ed Mon Sep 17 00:00:00 2001 From: Allison Date: Mon, 27 Apr 2026 20:34:53 -0400 Subject: [PATCH] fix(marketing): A-2.7b WCAG 2.2 AA polish + JSON-LD test hardening MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Drop role="region" from FAQ panels (had no accessible name — axe-core violation; disclosure pattern with button + aria-controls + aria-expanded is sufficient per WAI-APG accordion guidance) - Add focus-visible:outline-2 outline-brand-b1 outline-offset-2 to FAQ buttons (WCAG 2.2 AA 2.4.7 Focus Not Obscured + 2.4.11 Focus Appearance — Safari default focus indicator is unreliable) - Sweep pre-existing text-white/50 on Hero social proof microcopy → /70 (branch-wide WCAG floor; recurring landmine flagged at A-2.7a review) - Strengthen test_faq_jsonld_schema_present to json.loads() the extracted block and validate the FAQPage schema shape (regression guard for future content edits with unescaped backslashes/quotes) --- static/css/marketing.css | 16 ++++++++++++++++ templates/marketing/landing.html | 7 +++---- tests/test_marketing_landing_template.py | 17 +++++++++++++++++ 3 files changed, 36 insertions(+), 4 deletions(-) diff --git a/static/css/marketing.css b/static/css/marketing.css index f9d6bee..7e6ba40 100644 --- a/static/css/marketing.css +++ b/static/css/marketing.css @@ -3701,6 +3701,22 @@ outline-style: none; } } + .focus-visible\:outline-2 { + &:focus-visible { + outline-style: var(--tw-outline-style); + outline-width: 2px; + } + } + .focus-visible\:outline-offset-2 { + &:focus-visible { + outline-offset: 2px; + } + } + .focus-visible\:outline-brand-b1 { + &:focus-visible { + outline-color: #0062ff; + } + } .active\:scale-95 { &:active { --tw-scale-x: 95%; diff --git a/templates/marketing/landing.html b/templates/marketing/landing.html index 0891706..d83ff7e 100644 --- a/templates/marketing/landing.html +++ b/templates/marketing/landing.html @@ -51,7 +51,7 @@ {# Social proof microcopy — defensible: refers to pre-launch waitlist + factual ordres pros count #} -

+

+ x-transition.opacity.duration.200ms>

{{ item.a | safe }}

diff --git a/tests/test_marketing_landing_template.py b/tests/test_marketing_landing_template.py index 3738cd4..af55b5e 100644 --- a/tests/test_marketing_landing_template.py +++ b/tests/test_marketing_landing_template.py @@ -696,6 +696,23 @@ def test_faq_jsonld_schema_present(): assert ' ' not in body[body.find('"FAQPage"'):body.find('', body.find('"FAQPage"'))], \ "JSON-LD must not contain raw ' ' entities — strip them server-side" + # M-1 hardening: actually parse the JSON-LD to catch malformed JSON regressions + import json + import re + match = re.search(r'', + body, re.DOTALL) + assert match, "JSON-LD block not found" + parsed = json.loads(match.group(1)) + assert parsed['@context'] == 'https://schema.org' + assert parsed['@type'] == 'FAQPage' + assert isinstance(parsed['mainEntity'], list) + assert len(parsed['mainEntity']) == 7, "FAQPage must contain exactly 7 questions" + for q in parsed['mainEntity']: + assert q['@type'] == 'Question' + assert q['acceptedAnswer']['@type'] == 'Answer' + assert q['name'].strip(), "Question name must not be empty" + assert q['acceptedAnswer']['text'].strip(), "Answer text must not be empty" + def test_cta_final_section(): """CTA final section with mailto pré-inscription + ghost button to #tarifs."""