{# Shared cover renderer for list cards (feed + portfolio).
Single source of truth for the three-way image branch so the favicon
chip / og-image / glyph fallback never drifts between surfaces. Before
this macro the portfolio template lacked the favicon branch and stretched
a 32px /favicon.ico across the full cover, rendering it blurry.
Args:
image_url — tech_items.image_url (attacker-controllable; crawled).
category — CategoryType enum or None (tints the fallback gradient).
href — in-app reader URL for the card tap target.
source_url — used only to label the favicon chip with its domain.
#}
{% macro cover(image_url, category, href, source_url) %}
{%- set cat = (category.value if category else 'none')|lower -%}
{%- if image_url and image_url | is_favicon -%}
{# image_url is attacker-controllable (crawled): escaped
only, never CSS url(). #}
{{ source_url | domain }}
{%- elif image_url -%}
{# og:image is attacker-controllable (crawled): render via an HTML-escaped
rather than inline CSS url() to avoid CSS-context injection. #}
{%- else -%}
◎
{%- endif -%}
{% endmacro %}