A.4 cont.: deck back-image renders inside card-stack icon + kit-bag dialog Deck section adopts the icon + size+pattern polish — TDD. Three follow-up improvements after user browser-verified A.4's first cut: (1) image-equipped decks (Minchiate today, future Earthman) now render the deck's actual <deck-slug>-back.png as the card-stack icon's visible faces instead of the placeholder --priUser solid fill — feels like a real deck, not a generic stand-in. (2) The kit-bag dialog Deck section (#id_kit_bag_dialog .kit-bag-deck) gets the same new card-stack icon (was still showing the old fa-regular fa-id-badge), with (×2) tooltip decoration on polarized decks for consistency w. the gameboard applet. (3) Visual polish: icon bumped 1.5× (1.5rem → 2.25rem width; 2.4rem → 3.6rem height, 5:8 aspect preserved); SVG <pattern> switched from patternUnits=userSpaceOnUse (which painted the image at fixed user-space coordinates and let the rect slide out from under it on hover, reading as "low opacity" to the user) to patternUnits=objectBoundingBox + patternContentUnits=objectBoundingBox (transform-aware — image tracks the rect through rest-state offsets + hover fan-out). New DeckVariant.back_image_url property mirrors A.2's TarotCard.image_url pattern: returns full static-asset URL for <deck-slug>-back.png when has_card_images=True, else empty string. Template partial _deck_stack_icon.html extended w. conditional <defs><pattern> block that renders only when deck.has_card_images is true; each of the 3 card rects then carries an inline style="fill: url(#deck-back-<short_key>)" overriding the SCSS default fill: rgba(--priUser, 1) (inline style beats CSS, the only way to opt out of the cascade default per-element). When no deck is passed (kit-bag placeholder branch) or deck has no images (Earthman + RWS), the partial falls through to the placeholder fill — single template handles both modes. _kit_bag_panel.html Deck section: equipped-deck branch swaps <i class="fa-regular fa-id-badge"> for {% include _deck_stack_icon.html with deck=equipped_deck %} + adds (×2) span in --terUser for equipped_deck.is_polarized; placeholder branch swaps for the same include without deck= so the partial's conditional falls through. SCSS reorg: lifted the .deck-stack-icon base rules out of the #id_applet_game_kit nest (they were scoped to gameboard's Game Kit applet only) into top-level scope so the same SCSS applies in the kit-bag dialog context too. Hover/active/focus trigger selector list broadened to cover .deck-stack-icon itself + .token.deck-variant wrapper + .kit-bag-deck wrapper. 4 new ITs total: 2 in GameboardViewTest (image-equipped Minchiate's <pattern> defines + inline fill style on all 3 rects + asset URL ref; non-image Earthman has NEITHER pattern nor inline fill); 2 in dashboard.KitBagViewTest (kit-bag Deck section renders svg.deck-stack-icon + lacks fa-id-badge; polarized equipped deck tooltip carries .tt-x2 — element-presence assertion since literal "×2" character had encoding issues in the dashboard test file vs the gameboard one, which is fine since the template-side rendering of the literal × is exercised by the parent template). Tests: 4 new green; 1297/1297 IT+UT total green (69s; +4 from A.4's 1293). Visual verify pending: refresh /gameboard/ → Minchiate icon should show 3 stacked Minchiate card-backs at 1.5× size, fan out on hover w. back image tracking; refresh kit-bag dialog → same icon visible in Deck section w. (×2) on Earthman tooltip

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Disco DeDisco
2026-05-25 01:01:05 -04:00
parent b9bb73db69
commit d26c45bf77
6 changed files with 181 additions and 58 deletions

View File

@@ -1,20 +1,46 @@
{# Sprint A.4 — card-deck stack SVG icon. Replaces fa-regular fa-id-badge #}
{# wherever a deck is represented by an icon (currently game_kit applet's #}
{# .token.deck-variant; future: room.html pile + deck-bag). #}
{# wherever a deck is represented by an icon (game_kit applet's #}
{# .token.deck-variant + kit-bag dialog's .kit-bag-deck; future: room.html #}
{# pile + deck-bag). #}
{# #}
{# 3 rect-cards stacked tightly at rest; .deck-stack-icon SCSS rotates the #}
{# whole stack 5° CW + drives the fan-out on hover/active/focus of the #}
{# parent .token (or whatever wraps it). The card-back design here is #}
{# placeholder (solid --priUser fill + currentColor stroke). Detailed art #}
{# (Earthman planet-impact illustration, future custom decks) drops in later #}
{# per [[project-card-deck-icon]]. #}
{# whole stack 5° CW + drives the fan-out on hover/active/focus. When `deck` #}
{# is image-equipped (has_card_images=True), the rect fills are overridden #}
{# via inline `style` to use an SVG <pattern> referencing the deck's actual #}
{# card-back PNG (per [[reference-card-image-naming-convention]] back slot, #}
{# `<deck-slug>-back.png`). When no deck is passed (kit-bag deck placeholder) #}
{# or the deck has no images (Earthman until art lands, RWS), rects fall #}
{# through to the SCSS default `fill: rgba(--priUser, 1)` placeholder. #}
<svg class="deck-stack-icon" viewBox="0 0 32 48" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false">
{% if deck.has_card_images %}
<defs>
{# Both patternUnits + patternContentUnits = objectBoundingBox so the #}
{# image is rendered relative to each card rect's bounding box rather #}
{# than in fixed user-space. This makes the image TRACK the rect's #}
{# transforms (rest-state micro-offsets + hover fan-out) instead of #}
{# staying put while the rect slides out from under it — the bug #}
{# that read as low-opacity to the user 2026-05-25 PM. width=height=1 #}
{# normalizes the pattern to fill the bbox; image x/y/width/height #}
{# = 0/0/1/1 fills the pattern. preserveAspectRatio=xMidYMid slice #}
{# crops the image to the 5:8 card aspect (vs the back PNG's native #}
{# 620×1024 ≈ 5:8.25 — minimal crop). #}
<pattern id="deck-back-{{ deck.short_key }}"
patternUnits="objectBoundingBox" patternContentUnits="objectBoundingBox"
width="1" height="1">
<image href="{{ deck.back_image_url }}" x="0" y="0" width="1" height="1"
preserveAspectRatio="xMidYMid slice" />
</pattern>
</defs>
{% endif %}
<g class="deck-stack-icon__stack">
<rect class="deck-stack-icon__card deck-stack-icon__card--3"
x="6" y="8" width="20" height="32" rx="2.5" />
x="6" y="8" width="20" height="32" rx="2.5"
{% if deck.has_card_images %}style="fill: url(#deck-back-{{ deck.short_key }});"{% endif %} />
<rect class="deck-stack-icon__card deck-stack-icon__card--2"
x="6" y="8" width="20" height="32" rx="2.5" />
x="6" y="8" width="20" height="32" rx="2.5"
{% if deck.has_card_images %}style="fill: url(#deck-back-{{ deck.short_key }});"{% endif %} />
<rect class="deck-stack-icon__card deck-stack-icon__card--1"
x="6" y="8" width="20" height="32" rx="2.5" />
x="6" y="8" width="20" height="32" rx="2.5"
{% if deck.has_card_images %}style="fill: url(#deck-back-{{ deck.short_key }});"{% endif %} />
</g>
</svg>