{# 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 %}