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 =
+ '
';
+ } 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 %}
-