From 15025b41885230f54a2e50053a72b5aec3584d01 Mon Sep 17 00:00:00 2001 From: Disco DeDisco Date: Mon, 25 May 2026 02:16:53 -0400 Subject: [PATCH] =?UTF-8?q?A.7-polish=20my=5Fsea=20slot=20image-rendering?= =?UTF-8?q?=20(server-saved=20+=20mid-draw=20JS)=20+=20non-polarized=20sin?= =?UTF-8?q?gle-deck-stack=20collapse=20=E2=80=94=20TDD.=20End-of-session?= =?UTF-8?q?=20wrap-up=202026-05-25=20PM.=20Three=20changes=20covering=20my?= =?UTF-8?q?=5Fsea.html=20surfaces=20that=20weren't=20in=20A.5/A.7's=20cent?= =?UTF-8?q?ral-sig-only=20scope:=20(1)=20saved-hand=20slot=20rendering=20(?= =?UTF-8?q?`=5Fmy=5Fsea=5Fslot.html`=20server-rendered=20partial=20fired?= =?UTF-8?q?=20when=20a=20draw=20is=20resumed=20via=20refresh);=20(2)=20mid?= =?UTF-8?q?-draw=20slot=20fill=20(sea.js's=20`=5FfillSlot`=20writes=20slot?= =?UTF-8?q?.innerHTML=20on=20each=20card-deposit=20click;=20previously=20r?= =?UTF-8?q?endered=20corner-rank=20+=20suit-icon=20only,=20NOW=20renders?= =?UTF-8?q?=20=20when=20card.image=5Furl=20is=20non-empty);=20(3)=20d?= =?UTF-8?q?eck-stack=20collapse=20for=20non-polarized=20decks=20(Minchiate?= =?UTF-8?q?=20today)=20=E2=80=94=20the=20bottom-right=20of=20my=5Fsea.html?= =?UTF-8?q?=20showed=20two=20side-by-side=20GRAVITY=20+=20LEVITY=20stacks?= =?UTF-8?q?=20regardless=20of=20equipped-deck=20polarization;=20for=20non-?= =?UTF-8?q?polarized=20decks=20polarity=20has=20no=20meaning=20so=20the=20?= =?UTF-8?q?dual=20layout=20misleads.=20**Critical=20lock**:=20collapse=20i?= =?UTF-8?q?s=20my=5Fsea-ONLY.=20room.html=20keeps=20the=20dual=20stacks=20?= =?UTF-8?q?since=20multiple=20gamers=20contribute=20(each=20might=20bring?= =?UTF-8?q?=20a=20different=20polarization).=20Server-side=20template=20br?= =?UTF-8?q?anches=20`{%=20if=20request.user.equipped=5Fdeck.is=5Fpolarized?= =?UTF-8?q?=20%}`=20to=20pick=20dual=20vs.=20single=20rendering;=20the=20`?= =?UTF-8?q?--single`=20stack=20carries=20the=20actual=20deck=20back-image?= =?UTF-8?q?=20via=20``=20(object-fit:?= =?UTF-8?q?=20cover)=20when=20has=5Fcard=5Fimages=3DTrue.=20Sub-changes:?= =?UTF-8?q?=20`card=5Fdict()`=20in=20`apps/epic/utils.py`=20now=20includes?= =?UTF-8?q?=20`image=5Furl`=20+=20`arcana=5Fkey`=20fields=20so=20the=20pic?= =?UTF-8?q?ker=20grid's=20JSON=20payload=20carries=20the=20data=20the=20JS?= =?UTF-8?q?=20fill-handler=20needs=20(single=20source=20of=20truth=20share?= =?UTF-8?q?d=20w.=20the=20gameroom=20sea=5Fdeck=20endpoint=20=E2=80=94=20`?= =?UTF-8?q?apps/gameboard/views.py`'s=20`saved=5Fby=5Fposition`=20dict=20g?= =?UTF-8?q?ets=20the=20parallel=20additions=20for=20server-rendered=20save?= =?UTF-8?q?d=20hand).=20SCSS:=20extended=20the=20shared=20image-mode=20rul?= =?UTF-8?q?e's=20comma-list=20selector=20in=20`=5Fcard-deck.scss`=20to=20i?= =?UTF-8?q?nclude=20`.sea-card-slot.sea-card-slot--image`=20so=20the=20con?= =?UTF-8?q?tour=20stroke=20+=20depth=20shadow=20apply=20to=20both=20saved?= =?UTF-8?q?=20+=20mid-draw=20slots=20from=20a=20single=20rule=20definition?= =?UTF-8?q?.=20Also=20added=20`.sea-deck-stack--single=20.sea-stack-face`?= =?UTF-8?q?=20block=20w.=20neutral=20--priUser/--terUser=20palette=20(vs.?= =?UTF-8?q?=20gravity's=20--quiUser/--quaUser=20+=20levity's=20--terUser/-?= =?UTF-8?q?-ninUser)=20+=20the=20corresponding=20hover/active=20glow=20rul?= =?UTF-8?q?e=20positioned=20AFTER=20the=20`$=5Fsea-shadow`=20SCSS=20variab?= =?UTF-8?q?le=20definition=20at=20line=201808=20(initial=20draft=20hit=20a?= =?UTF-8?q?=20compile=20error:=20`Undefined=20variable:=20"$=5Fsea-shadow"?= =?UTF-8?q?`=20because=20the=20hover=20rule=20was=20placed=20before=20the?= =?UTF-8?q?=20variable=20was=20defined;=20SCSS=20variables=20are=20scope/o?= =?UTF-8?q?rder-dependent).=20JS:=20`=5FfillSlot`=20in=20`sea.js`=20branch?= =?UTF-8?q?es=20on=20`card.image=5Furl`=20=E2=80=94=20when=20non-empty,=20?= =?UTF-8?q?write=20``=20+=20add=20`.se?= =?UTF-8?q?a-card-slot--image`=20marker=20+=20`data-arcana-key`=20attr;=20?= =?UTF-8?q?otherwise=20legacy=20corner-rank=20+=20suit-icon.=20innerHTML?= =?UTF-8?q?=20alt-attribute=20properly=20escapes=20`"`=20to=20`"`=20s?= =?UTF-8?q?o=20card=20names=20w.=20quotes=20(none=20today,=20but=20defensi?= =?UTF-8?q?ve)=20don't=20break=20HTML.=20Existing=20JS=20that=20activates?= =?UTF-8?q?=20a=20clicked=20stack=20(`=5FactiveStack`=20flow=20+=20`=5Fsho?= =?UTF-8?q?wOk`=20/=20`=5FhideOk`)=20works=20unchanged=20w.=20the=20single?= =?UTF-8?q?-stack=20variant=20since=20the=20selector=20`.sea-deck-stack`?= =?UTF-8?q?=20matches=20all=20variants=20regardless=20of=20polarity=20suff?= =?UTF-8?q?ix;=20the=20`isLevity=20=3D=20stack.classList.contains('sea-dec?= =?UTF-8?q?k-stack--levity')`=20check=20at=20the=20deposit=20moment=20retu?= =?UTF-8?q?rns=20false=20for=20`--single`=20=E2=86=92=20defaults=20to=20gr?= =?UTF-8?q?avity=20polarity=20assignment,=20which=20is=20fine=20for=20non-?= =?UTF-8?q?polarized=20decks=20(polarity=20field=20has=20no=20card-content?= =?UTF-8?q?=20effect).=20Memory=20updated:=20`project=5Fimage=5Fbased=5Fde?= =?UTF-8?q?ck=5Fface=5Frendering.md`=20now=20lists=20A.0-A.7=20done=20+=20?= =?UTF-8?q?this=20polish=20+=20room.html=20(A.8)=20as=20the=20sole=20remai?= =?UTF-8?q?ning=20surface=20for=20tomorrow.=20The=206-surface=20scope=20sh?= =?UTF-8?q?eet=20shows=20A.8=20as=20the=20last=20red=20box;=20everything?= =?UTF-8?q?=20else=20green.=20Tests:=201306/1306=20IT+UT=20total=20green?= =?UTF-8?q?=20(73s).=20No=20new=20ITs=20in=20this=20commit=20=E2=80=94=20t?= =?UTF-8?q?he=20saved-slot=20render=20touch=20was=20an=20extension=20of=20?= =?UTF-8?q?existing=20`saved=5Fby=5Fposition`=20view=20context=20shape=20(?= =?UTF-8?q?covered=20by=20existing=20slot-render=20tests'=20implicit=20inv?= =?UTF-8?q?ariance);=20the=20JS=20change=20is=20hard=20to=20test=20via=20D?= =?UTF-8?q?jango=20ITs=20(would=20need=20Jasmine=20spec=20or=20FT,=20defer?= =?UTF-8?q?red);=20the=20deck-stack=20collapse=20is=20a=20template=20branc?= =?UTF-8?q?h=20(visual;=20user=20verified=20live=20in=20browser=20this=20s?= =?UTF-8?q?ession).=20Tomorrow:=20A.8=20room.html=20image-rendering=20(mul?= =?UTF-8?q?ti-user=20surface=20via=20Channels=20WebSocket=20payload=20+=20?= =?UTF-8?q?same=20template=20branch=20pattern;=20keep=20dual=20gravity/lev?= =?UTF-8?q?ity=20stacks=20per=20user=20spec)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.7 (1M context) --- src/apps/epic/static/apps/epic/sea.js | 23 ++++++++++++-- src/apps/epic/utils.py | 9 ++++++ src/apps/gameboard/views.py | 9 ++++++ src/static_src/scss/_card-deck.scss | 31 ++++++++++++++++++- .../gameboard/_partials/_my_sea_slot.html | 16 +++++++--- src/templates/apps/gameboard/my_sea.html | 25 +++++++++++++++ 6 files changed, 105 insertions(+), 8 deletions(-) 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 %}