{# Shared stack card macro — visual parity with marketplace.html .mp-card. Vertical tile (mirrors mp-card pixel-for-pixel): ┌──────────────────────────────────────┐ │ │ │ SA │ ← 120px photo banner, │ (26px bold initials) │ gradient bg, centered │ │ ├──────────────────────────────────────┤ │ Sales bundle │ ← title (15px bold, clamp 2) │ 12 tables │ ← meta (11px secondary) │ Orders + line items … │ ← description (clamp 2) │ [keboola] [bigquery] │ ← optional tags ├──────────────────────────────────────┤ │ Open → [+ Add] │ ← footer └──────────────────────────────────────┘ The 120px coloured banner with 2-letter initials replaces the old tiny inline icon-box. This matches marketplace.html .mp-card .photo exactly so /catalog, /memory and /marketplace read as one product. Initials default to the first letter of the first two whitespace- separated words ("Sales bundle" → "SB"); single-word names fall back to name[:2]. When `entry.icon` is provided AND is a single emoji glyph the admin set explicitly, we render it instead — at 30px so it doesn't dwarf the rest of the card. States (dual-encoded: color border + text badge): .stack-card.is-required → amber border + "Required" badge top-right .stack-card.is-in-stack → amber border + "In stack" badge top-right default → neutral border + "+ Add to stack" button `entry` is a dict-like with keys: id, name, description, icon, color, requirement ("available"|"required"), in_stack (bool), meta (str), meta_html (str — server-built, trusted, wins over `meta` for inline CTAs), tags (list[str]), drilldown_url (str), footer_left (str). #} {% macro card(entry) %} {% set _cls_required = ' is-required' if entry.requirement == 'required' else '' %} {% set _cls_instack = ' is-in-stack' if entry.in_stack else '' %} {% set _name = (entry.name or '?')|string %} {% set _name_parts = _name.split() %} {% if _name_parts|length >= 2 %} {% set _initials = (_name_parts[0][0] ~ _name_parts[1][0])|upper %} {% else %} {% set _initials = _name[:2]|upper %} {% endif %} {# Per design review (2 independent assessments): emoji glyphs on cards feel childish in a B2B context. Always render the 2-letter initials — matches marketplace.html PL/SK/AG fallback exactly. The admin's `entry.icon` is preserved on disk (admin/* edit screens still show it) but not rendered on cards. Future polish: swap initials for Lucide SVG icons keyed by `entry.icon`. #} {% set _has_glyph = False %} {# v51 status pill — only the non-default values surface as a visible pill. 'prod' is the assumed-good default and would just be noise on every card. Map drives both the visible label and a per-status CSS class for tint. #} {% set _status = entry.status|default('prod') %} {% set _status_meta = { 'poc': {'label': 'POC', 'cls': 'stack-card__status-pill--poc'}, 'coming-soon': {'label': 'Coming soon', 'cls': 'stack-card__status-pill--coming'}, 'draft': {'label': 'Draft', 'cls': 'stack-card__status-pill--draft'} }.get(_status) %}