diff --git a/src/apps/gameboard/tests/integrated/test_views.py b/src/apps/gameboard/tests/integrated/test_views.py index ffbd7b3..a10d20c 100644 --- a/src/apps/gameboard/tests/integrated/test_views.py +++ b/src/apps/gameboard/tests/integrated/test_views.py @@ -969,6 +969,64 @@ class MySeaPickerPhaseTemplateTest(TestCase): self.assertContains(response, "sea-pos-leave") self.assertContains(response, "sea-pos-loom") + def test_sea_sig_card_renders_image_when_deck_has_card_images(self): + """Sprint A.5 — central sig card on /gameboard/my-sea/ carries the + `.sig-stage-card--image` marker class + an + child pointing at the deck's image asset when the user's equipped + deck is image-equipped (Minchiate today). Mirrors A.3's my_sign.html + image-mode treatment so the central sig + the Sea Stage modal render + with consistent visual identity.""" + from apps.epic.models import DeckVariant, TarotCard + minchiate = DeckVariant.objects.get(slug="minchiate-fiorentine-1860-1890") + # Override the auto-equipped Earthman w. Minchiate + pick Il Matto as + # the user's sig (it's MAJOR rank 0 → permitted by personal_sig_cards + # IF user has the super-nomad Note unlock; superuser auto-gets it). + self.user.is_superuser = True + self.user.save() + # Re-run the post_save Note grants for the now-superuser by manually + # granting (signal only fires on initial create). + from apps.drama.models import Note + Note.grant_if_new(self.user, "super-nomad") + Note.grant_if_new(self.user, "super-schizo") + self.user.unlocked_decks.add(minchiate) + self.user.equipped_deck = minchiate + il_matto = TarotCard.objects.get(deck_variant=minchiate, slug="il-matto") + self.user.significator = il_matto + self.user.save(update_fields=["equipped_deck", "significator"]) + + import lxml.html + response = self.client.get(reverse("my_sea")) + parsed = lxml.html.fromstring(response.content) + [sig_card] = parsed.cssselect(".sea-sig-card") + self.assertIn( + "sig-stage-card--image", sig_card.get("class", ""), + "Sig card must carry --image marker class for Minchiate-equipped user", + ) + [img] = sig_card.cssselect("img.sig-stage-card-img") + self.assertIn( + "minchiate-fiorentine-1860-1890-trumps-00-il-matto.png", + img.get("src", ""), + "Image src must point at the v2-convention Il Matto asset", + ) + + def test_sea_sig_card_renders_text_when_deck_has_no_images(self): + """Earthman (has_card_images=False) keeps the existing corner-rank + + suit-icon text render — the image branch only applies to image-decks.""" + # Default setUp leaves the user on auto-equipped Earthman. + import lxml.html + response = self.client.get(reverse("my_sea")) + parsed = lxml.html.fromstring(response.content) + [sig_card] = parsed.cssselect(".sea-sig-card") + self.assertNotIn("sig-stage-card--image", sig_card.get("class", "")) + self.assertEqual( + len(sig_card.cssselect("img.sig-stage-card-img")), 0, + "Non-image deck must not render the in the sig card", + ) + self.assertEqual( + len(sig_card.cssselect(".fan-corner-rank")), 1, + "Non-image deck falls through to corner-rank text render", + ) + def test_picker_renders_six_card_only_positions_for_spread_switch(self): # Crown / lay / cross sit in the DOM unconditionally so iter 3's # SPREAD dropdown can reveal them via CSS attribute swap (data- diff --git a/src/static_src/scss/_card-deck.scss b/src/static_src/scss/_card-deck.scss index caacc67..8ed3be7 100644 --- a/src/static_src/scss/_card-deck.scss +++ b/src/static_src/scss/_card-deck.scss @@ -619,61 +619,6 @@ html:has(.sig-backdrop) { .sig-qualifier-below { opacity: 0.25; } } - // Sprint A.3 — image-rendering mode for decks w. DeckVariant.has_card_images=True - // (Minchiate Fiorentine 1860-1890 today; future image-equipped decks - // flip the flag to opt in). When `.sig-stage-card--image` is set by - // stage-card.js _setImageMode, the text scaffold (fan-card-* children) - // hides and an renders inside the same shell. - // Card bg + border go away — the transparent PNG carries its own - // irregular outline; we stack four cardinal-direction drop-shadows on - // the itself to render a stroke-like outline that FOLLOWS the - // alpha contour (per user spec 2026-05-25 PM — NOT a rectangular border - // around the bounding box). Color is arcana-driven: `--quiUser` (cream) - // for minor + middle, `--terUser` (gold) for major per - // [[project-image-based-deck-face-rendering]]'s Q2 lock. - &.sig-stage-card--image { - --img-stroke-color: rgba(var(--quiUser), 1); - background: transparent; - border: 0; - padding: 0; - overflow: visible; - - &[data-arcana-key="MAJOR"] { - --img-stroke-color: rgba(var(--terUser), 1); - } - - .fan-card-corner, - .fan-card-face { - display: none; - } - - .sig-stage-card-img { - display: block; - width: 100%; - height: 100%; - object-fit: contain; - // Filter chain (order matters — each drop-shadow operates on - // the prior result): - // 1-4: 4 cardinal-direction drop-shadows at 0.2rem (~3.2px) - // each → contour-following stroke. Combined apparent width - // ~6.4px. Bump to 8-direction stack if we ever go past - // ~0.5rem so curved edges stay even. - // 5: down-right black 1,1 offset 2px-blur drop-shadow - // matches the silhouette shadow `.tray-cell > img` carries - // (`_tray.scss:272`) — "lifted off the felt" depth cue. - // Comes AFTER the strokes so it traces the stroked - // silhouette, not just the original PNG alpha. - // Mobile-safe: filter on raster images works fine cross-browser - // (the [[feedback-mobile-svg-glow]] dead-end was specifically - // SVG glow, not raster drop-shadow). - filter: - drop-shadow( 0.2rem 0 0 var(--img-stroke-color)) - drop-shadow(-0.2rem 0 0 var(--img-stroke-color)) - drop-shadow( 0 0.2rem 0 var(--img-stroke-color)) - drop-shadow( 0 -0.2rem 0 var(--img-stroke-color)) - drop-shadow( 1px 1px 2px rgba(0, 0, 0, 1)); - } - } } // Stat block — same dimensions as the preview card (width × 5:8 aspect). @@ -702,6 +647,68 @@ html:has(.sig-backdrop) { } } +// Sprint A.3 / A.5 — image-rendering mode for decks w. DeckVariant.has_card_images=True +// (Minchiate Fiorentine 1860-1890 today; future image-equipped decks flip +// the flag to opt in). LIFTED OUT of the `.sig-stage` nest in A.5 polish so +// the same rule applies wherever `.sig-stage-card.sig-stage-card--image` +// renders — my_sign.html's stage card (inside .sig-stage), my_sea.html's +// central sig card (.sea-sig-card inside .sea-pos-core, NOT in .sig-stage), +// future surface drops. When `.sig-stage-card--image` is set (either by +// stage-card.js _setImageMode or server-side template branch), the text +// scaffold (fan-card-* + .fan-corner-rank text children) hides and an +// renders inside the same shell. Card bg + border +// go away — the transparent PNG carries its own irregular outline; four +// cardinal-direction drop-shadows on the render a stroke-like outline +// that FOLLOWS the alpha contour (user spec 2026-05-25 PM — NOT a rectangular +// border around the bounding box). Color is arcana-driven: `--quiUser` (cream) +// for minor + middle, `--terUser` (gold) for major per +// [[project-image-based-deck-face-rendering]]'s Q2 lock. +.sig-stage-card.sig-stage-card--image { + --img-stroke-color: rgba(var(--quiUser), 1); + background: transparent; + border: 0; + padding: 0; + overflow: visible; + + &[data-arcana-key="MAJOR"] { + --img-stroke-color: rgba(var(--terUser), 1); + } + + .fan-card-corner, + .fan-card-face, + .fan-corner-rank, + > i.fa-solid { + display: none; + } + + .sig-stage-card-img { + display: block; + width: 100%; + height: 100%; + object-fit: contain; + // Filter chain (order matters — each drop-shadow operates on + // the prior result): + // 1-4: 4 cardinal-direction drop-shadows at 0.2rem (~3.2px) + // each → contour-following stroke. Combined apparent width + // ~6.4px. Bump to 8-direction stack if we ever go past + // ~0.5rem so curved edges stay even. + // 5: down-right black 1,1 offset 2px-blur drop-shadow + // matches the silhouette shadow `.tray-cell > img` carries + // (`_tray.scss:272`) — "lifted off the felt" depth cue. + // Comes AFTER the strokes so it traces the stroked + // silhouette, not just the original PNG alpha. + // Mobile-safe: filter on raster images works fine cross-browser + // (the [[feedback-mobile-svg-glow]] dead-end was specifically + // SVG glow, not raster drop-shadow). + filter: + drop-shadow( 0.2rem 0 0 var(--img-stroke-color)) + drop-shadow(-0.2rem 0 0 var(--img-stroke-color)) + drop-shadow( 0 0.2rem 0 var(--img-stroke-color)) + drop-shadow( 0 -0.2rem 0 var(--img-stroke-color)) + drop-shadow( 1px 1px 2px rgba(0, 0, 0, 1)); + } +} + // ─── My Sign picker — sizing + state-gated reveal ──────────────────────────── // Two-phase layout: landing (DRY 1-chair hex w. SCAN SIGN center) → picker // (sig-card grid below an always-present stage frame). SAVE SIGN rides diff --git a/src/templates/apps/gameboard/my_sea.html b/src/templates/apps/gameboard/my_sea.html index caac204..d0da289 100644 --- a/src/templates/apps/gameboard/my_sea.html +++ b/src/templates/apps/gameboard/my_sea.html @@ -130,10 +130,21 @@ {% include "apps/gameboard/_partials/_my_sea_slot.html" with position="leave" saved=saved_by_position.leave crossing=False %}
-
- {{ significator.corner_rank }} - {% if significator.suit_icon %}{% endif %} + {# Sprint A.5 — central sig card mirrors my_sign.html's image-mode #} + {# render: when the user's deck has card images (Minchiate today, #} + {# future Earthman), show the transparent-PNG w. contour #} + {# stroke + depth shadow per A.3's `.sig-stage-card--image` rule. #} + {# Otherwise fall through to the existing corner-rank + suit-icon #} + {# text render (Earthman, RWS). #} +
+ {% if significator.deck_variant.has_card_images %} + {{ significator.name }} + {% else %} + {{ significator.corner_rank }} + {% if significator.suit_icon %}{% endif %} + {% endif %}
Action