diff --git a/static/css/marketing.css b/static/css/marketing.css index 820b94f..72b6535 100644 --- a/static/css/marketing.css +++ b/static/css/marketing.css @@ -497,6 +497,9 @@ .z-\[9999\] { z-index: 9999; } + .col-span-1 { + grid-column: span 1 / span 1; + } .container { width: 100%; @media (width >= 40rem) { @@ -3669,6 +3672,11 @@ opacity: 50%; } } + .sm\:col-span-2 { + @media (width >= 40rem) { + grid-column: span 2 / span 2; + } + } .sm\:-mx-6 { @media (width >= 40rem) { margin-inline: calc(var(--spacing) * -6); @@ -4000,6 +4008,11 @@ } } } + .md\:col-span-3 { + @media (width >= 48rem) { + grid-column: span 3 / span 3; + } + } .md\:mb-2 { @media (width >= 48rem) { margin-bottom: calc(var(--spacing) * 2); diff --git a/templates/macros/bento.html b/templates/macros/bento.html index 3823e80..d4cd692 100644 --- a/templates/macros/bento.html +++ b/templates/macros/bento.html @@ -1,12 +1,14 @@ -{# Reusable bento card macro. FlexiHub style: dark navy2 surface, decorative watermark number, gradient icon corner. #} +{# Reusable bento card macro. FlexiHub style: dark navy2 surface, decorative watermark number, gradient icon corner. + `span` controls column span via a static lookup table (Tailwind's content scanner only sees literal class strings, + so dynamic `col-span-{{ span }}` would produce dead classes — the lookup keeps the utilities discoverable). #} {% macro bento_card(number, title, description, icon='✦', span='1') %} -
+{%- set span_classes = {'1': 'col-span-1', '2': 'sm:col-span-2', '3': 'sm:col-span-2 md:col-span-3'} -%} +
{{ icon }}
-

{{ title }}

-

{{ description }}

+

{{ title | safe }}

+

{{ description | safe }}

{% endmacro %} diff --git a/tests/test_marketing_landing_template.py b/tests/test_marketing_landing_template.py index 8049332..7af2430 100644 --- a/tests/test_marketing_landing_template.py +++ b/tests/test_marketing_landing_template.py @@ -255,6 +255,8 @@ def test_bento_has_6_features(): # Watermark numbers 01..06 for n in ['01', '02', '03', '04', '05', '06']: assert f'>{n}<' in body, f"Missing bento watermark number {n}" + # Card 04 must use French Q&R, not English Q&A — primary identifier check + assert 'Q&R' in body or 'Q&R' in body, "Card 04 must use French Q&R, not Q&A" def test_bento_uses_flexihub_styling(): @@ -281,3 +283,20 @@ def test_bento_uses_wcag_safe_text_on_dark(): client = app.test_client() body = client.get('/').data.decode('utf-8') assert 'text-white/70' in body, "Missing WCAG-safe /70 text opacity on dark cards" + + +def test_bento_renders_nbsp_entities_not_escaped(): + """Card 01 '95 %+' NBSP must render as a non-breaking space, not as literal ' ' text. + + Regression guard: if the bento macro stops piping description through `| safe`, + Jinja autoescape will double-escape ' ' to '&nbsp;' and users see the + raw entity. The HTML response must contain the literal '95 %+' once + (single escape), never '95&nbsp;%+'. + """ + client = app.test_client() + body = client.get('/').data.decode('utf-8') + assert '95 %+' in body, "NBSP entity should appear single-escaped in card 01" + assert '95&nbsp;' not in body, "NBSP entity must not be double-escaped (missing | safe?)" + # Q&R card title: French ampersand must survive as & in HTML, not &amp; + assert 'Q&R' in body, "Q&R title should appear single-escaped" + assert 'Q&amp;R' not in body, "Q&R title must not be double-escaped"