diff --git a/src/apps/epic/static/apps/epic/sea.js b/src/apps/epic/static/apps/epic/sea.js index 677cf51..a332d0f 100644 --- a/src/apps/epic/static/apps/epic/sea.js +++ b/src/apps/epic/static/apps/epic/sea.js @@ -94,9 +94,26 @@ var SeaDeal = (function () { // partial also stamps `data-pos-key="{{ position }}"` raw. // Standardize so both code paths agree (2026-05-21 fix). slot.dataset.posKey = _posName(posSelector); - slot.innerHTML = - '' + card.corner_rank + '' + - (card.suit_icon ? '' : ''); + // Sprint A.7-polish — image-mode branch. When the drawn card carries + // an `image_url` (deck has_card_images=True, Minchiate today), render + // an + add the `.sea-card-slot--image` marker class so the + // shared `_card-deck.scss` comma-list rule applies the contour + // stroke + depth shadow. Mirrors the server-rendered branch in + // `_my_sea_slot.html` for saved-hand parity. Otherwise fall through + // to the legacy corner-rank + suit-icon render. + if (card.image_url) { + slot.classList.add('sea-card-slot--image'); + if (card.arcana_key) slot.dataset.arcanaKey = card.arcana_key; + slot.innerHTML = + '' + (card.name || '').replace(/'; + } else { + slot.classList.remove('sea-card-slot--image'); + slot.removeAttribute('data-arcana-key'); + slot.innerHTML = + '' + card.corner_rank + '' + + (card.suit_icon ? '' : ''); + } } // ── Show / hide stage ───────────────────────────────────────────────────── diff --git a/src/apps/epic/utils.py b/src/apps/epic/utils.py index db7b324..f9571dc 100644 --- a/src/apps/epic/utils.py +++ b/src/apps/epic/utils.py @@ -67,6 +67,15 @@ def card_dict(card, reversal_prob=STACK_REVERSAL_PROBABILITY): 'energies': card.energies, 'operations': card.operations, 'reversed': _random.random() < reversal_prob, + # Sprint A.7-polish — image-mode payload for the my-sea picker's + # JS-driven `_fillSlot` (which writes slot.innerHTML on draw). Empty + # strings for legacy text-only decks (Earthman, RWS); non-empty when + # `deck.has_card_images=True` (Minchiate today). JS branches on + # `image_url` to render an instead of corner-rank + suit-icon + # so the mid-draw slot fill matches the server-rendered saved-hand + # `_my_sea_slot.html` partial branch. + 'image_url': card.image_url, + 'arcana_key': card.arcana, } diff --git a/src/apps/gameboard/views.py b/src/apps/gameboard/views.py index 9ff7b6a..471cd82 100644 --- a/src/apps/gameboard/views.py +++ b/src/apps/gameboard/views.py @@ -281,6 +281,15 @@ def my_sea(request): "polarity": entry.get("polarity", "gravity"), "corner_rank": c.corner_rank if c else "", "suit_icon": c.suit_icon if c else "", + # Sprint A.7-polish: extra fields for image-mode slot render + # in `_my_sea_slot.html`. Empty strings when the card's deck + # has no images (legacy text-only); template branches on + # `has_card_images` to pick render mode. + "has_card_images": (c.deck_variant.has_card_images + if c and c.deck_variant else False), + "image_url": c.image_url if c else "", + "arcana": c.arcana if c else "", + "name": c.name if c else "", } return render(request, "apps/gameboard/my_sea.html", { diff --git a/src/static_src/scss/_card-deck.scss b/src/static_src/scss/_card-deck.scss index 3387ca8..d15ebf8 100644 --- a/src/static_src/scss/_card-deck.scss +++ b/src/static_src/scss/_card-deck.scss @@ -665,7 +665,8 @@ html:has(.sig-backdrop) { // [[project-image-based-deck-face-rendering]]'s Q2 lock. .sig-stage-card.sig-stage-card--image, .my-sign-applet-card.my-sign-applet-card--image, -.my-sea-slot.my-sea-slot--image { +.my-sea-slot.my-sea-slot--image, +.sea-card-slot.sea-card-slot--image { --img-stroke-color: rgba(var(--quiUser), 1); background: transparent; border: 0; @@ -1819,6 +1820,27 @@ $_glow-gravity: 0 0 0.8rem 0.15rem rgba(var(--quaUser), 0.6); border-color: rgba(var(--quaUser), 0.65); box-shadow: $_sea-shadow; } +// Sprint A.7-polish — single (non-polarized) deck stack for my_sea.html +// when the equipped deck has no polarity (Minchiate today). Neutral palette +// vs. gravity/levity polarity colors. When the deck also has card images, +// the actual back-image overlays the face via .sea-stack-face-img (object- +// fit: cover so the PNG fills the rect cleanly; positioned absolute so the +// FLIP btn on top still sits center). +.sea-deck-stack--single .sea-stack-face { + background: rgba(var(--priUser), 0.88); + border-color: rgba(var(--terUser), 0.65); + box-shadow: $_sea-shadow; + overflow: hidden; // clip the back-img to the rounded-rect face +} +.sea-stack-face-img { + position: absolute; + inset: 0; + width: 100%; + height: 100%; + object-fit: cover; + border-radius: 0.15rem; // inner radius matches the face's outer + z-index: 0; +} // Glow on hover, :active, and while OK is showing (--active class set by JS) .sea-deck-stack--levity:hover .sea-stack-face, @@ -1827,6 +1849,13 @@ $_glow-gravity: 0 0 0.8rem 0.15rem rgba(var(--quaUser), 0.6); .sea-deck-stack--gravity:hover .sea-stack-face, .sea-deck-stack--gravity:active .sea-stack-face, .sea-deck-stack--gravity.sea-deck-stack--active .sea-stack-face { box-shadow: $_sea-shadow, $_glow-gravity; } +// Single-stack hover/active glow — neutral --terUser tone vs. gravity's +// --quaUser + levity's --ninUser, matching the face border color. +.sea-deck-stack--single:hover .sea-stack-face, +.sea-deck-stack--single:active .sea-stack-face, +.sea-deck-stack--single.sea-deck-stack--active .sea-stack-face { + box-shadow: $_sea-shadow, 0 0 0.8rem 0.15rem rgba(var(--terUser), 0.6); +} // Form action row — LOCK HAND + DEL side by side at the bottom .sea-form-actions { diff --git a/src/templates/apps/gameboard/_partials/_my_sea_slot.html b/src/templates/apps/gameboard/_partials/_my_sea_slot.html index dabc38a..a97b0e4 100644 --- a/src/templates/apps/gameboard/_partials/_my_sea_slot.html +++ b/src/templates/apps/gameboard/_partials/_my_sea_slot.html @@ -8,11 +8,19 @@ {# crossing — bool; pass True for the cross slot (gets the #} {# `.sea-card-slot--crossing` modifier in iter-4a HTML) #} {% if saved %} -
- {{ saved.corner_rank }} - {% if saved.suit_icon %}{% endif %} + data-pos-key="{{ position }}" + {% if saved.has_card_images %}data-arcana-key="{{ saved.arcana }}"{% endif %}> + {% if saved.has_card_images %} + {# Sprint A.7-polish — image-mode slot for saved-hand bypass. #} + {# Shares `.sig-stage-card-img` filter chain via the comma-list #} + {# selector extension in `_card-deck.scss` (.sea-card-slot.--image). #} + {{ saved.name }} + {% else %} + {{ saved.corner_rank }} + {% if saved.suit_icon %}{% endif %} + {% endif %}
{% else %}
diff --git a/src/templates/apps/gameboard/my_sea.html b/src/templates/apps/gameboard/my_sea.html index d0da289..b10ae65 100644 --- a/src/templates/apps/gameboard/my_sea.html +++ b/src/templates/apps/gameboard/my_sea.html @@ -210,6 +210,18 @@ + {# Sprint A.7-polish — deck-stack rendering depends on #} + {# the equipped deck's polarization. Polarized decks #} + {# (Earthman) keep the dual gravity + levity stacks + #} + {# named labels — those are the two halves of a 6- #} + {# segment deck. Non-polarized decks (Minchiate, RWS) #} + {# collapse to a single unnamed stack (DECKS → DECK): #} + {# polarity has no meaning at the deck level, so the #} + {# split labels would mislead. user spec 2026-05-25 PM. #} + {# Critical: this collapse is my_sea-ONLY — room.html #} + {# keeps the dual layout since multiple gamers contribute #} + {# (each might bring a different polarization). #} + {% if request.user.equipped_deck.is_polarized %}
DECKS
@@ -225,6 +237,19 @@ Levity
+ {% else %} +
+ DECK +
+
+ {% if request.user.equipped_deck.has_card_images %} + + {% endif %} + +
+
+
+ {% endif %}