From 750fef890e69825c1e58bca61898365085605cc7 Mon Sep 17 00:00:00 2001 From: Disco DeDisco Date: Mon, 25 May 2026 01:07:24 -0400 Subject: [PATCH] =?UTF-8?q?A.4=20cont.:=20card-deck=20icon=20placeholder?= =?UTF-8?q?=20mode=20(no-deck-equipped)=20styled=20like=20empty=20dice=20s?= =?UTF-8?q?lot=20=E2=80=94=20TDD.=20User=20polish=202026-05-25=20PM=20afte?= =?UTF-8?q?r=20browser-verifying=20the=20kit-bag=20dialog:=20when=20no=20d?= =?UTF-8?q?eck=20is=20equipped=20(kit-bag-placeholder=20branch=20fires)=20?= =?UTF-8?q?the=20new=20card-stack=20icon=20should=20not=20animate=20+=20sh?= =?UTF-8?q?ould=20render=20w.=20the=20same=20dimmed=20`rgba(--quaUser,=200?= =?UTF-8?q?.x)`=20palette=20as=20the=20existing=20fa-dice=20empty=20slot?= =?UTF-8?q?=20next=20to=20it,=20not=20the=20bright=20--terUser=20stroke=20?= =?UTF-8?q?/=20--priUser=20fill=20of=20an=20equipped-deck=20icon.=20Two=20?= =?UTF-8?q?SCSS=20changes:=20(1)=20Removed=20`.deck-stack-icon:hover`=20+?= =?UTF-8?q?=20`.deck-stack-icon:active`=20from=20the=20splay-trigger=20sel?= =?UTF-8?q?ector=20list=20=E2=80=94=20only=20wrapper-based=20selectors=20(?= =?UTF-8?q?.token.deck-variant,=20.kit-bag-deck)=20fire=20the=20fan-out=20?= =?UTF-8?q?now.=20Placeholder=20icons=20land=20inside=20.kit-bag-placehold?= =?UTF-8?q?er=20(or=20game=5Fkit=20applet's=20.kit-item=20empty-state)=20w?= =?UTF-8?q?hich=20aren't=20in=20the=20trigger=20list,=20so=20they=20stay?= =?UTF-8?q?=20static=20no=20matter=20where=20the=20user=20hovers.=20(2)=20?= =?UTF-8?q?New=20`.kit-bag-placeholder=20.deck-stack-icon`=20+=20`.kit-ite?= =?UTF-8?q?m=20.deck-stack-icon`=20descendant-selector=20rule:=20drops=20c?= =?UTF-8?q?olor=20(=3D=20stroke=20via=20currentColor)=20to=20`rgba(--quaUs?= =?UTF-8?q?er,=200.3)`=20matching=20the=20existing=20.kit-bag-placeholder?= =?UTF-8?q?=20color=20(`=5Fgame-kit.scss:143`);=20drops=20.deck-stack-icon?= =?UTF-8?q?=5F=5Fcard=20fill=20to=20`rgba(--quaUser,=200.15)`=20(lower=20t?= =?UTF-8?q?han=20the=20stroke=20alpha=20=E2=80=94=20user-dialed=202026-05-?= =?UTF-8?q?25=20PM=20for=20a=20subtler=20look,=20the=20cards=20read=20as?= =?UTF-8?q?=20faintly-present=20"absence"=20rather=20than=20bright-but-gre?= =?UTF-8?q?y).=201=20new=20IT=20in=20`KitBagViewTest`=20(clears=20equipped?= =?UTF-8?q?=5Fdeck=20=E2=86=92=20asserts=20.kit-bag-deck=20absent,=20.kit-?= =?UTF-8?q?bag-placeholder=20present=20+=20carries=20svg.deck-stack-icon?= =?UTF-8?q?=20+=20lacks=20fa-id-badge).=20Tests:=201=20new=20green;=201298?= =?UTF-8?q?/1298=20IT+UT=20total=20green=20(71s;=20+1=20from=20d26c45b's?= =?UTF-8?q?=201297).=20Visual=20verify=20pending:=20refresh=20/gameboard/?= =?UTF-8?q?=20+=20clear=20equipped=20deck=20=E2=86=92=20kit-bag=20dialog?= =?UTF-8?q?=20Deck=20slot=20should=20show=20a=20dimmed=20static=20card-sta?= =?UTF-8?q?ck=20icon=20matching=20the=20dice=20slot's=20washed-out=20look?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.7 (1M context) --- .../dashboard/tests/integrated/test_views.py | 28 +++++++++++++++++ src/static_src/scss/_gameboard.scss | 30 ++++++++++++------- 2 files changed, 48 insertions(+), 10 deletions(-) diff --git a/src/apps/dashboard/tests/integrated/test_views.py b/src/apps/dashboard/tests/integrated/test_views.py index f6e1583..bcf305b 100644 --- a/src/apps/dashboard/tests/integrated/test_views.py +++ b/src/apps/dashboard/tests/integrated/test_views.py @@ -517,6 +517,34 @@ class KitBagViewTest(TestCase): "fa-regular fa-id-badge must be gone from kit-bag-deck", ) + def test_no_deck_equipped_renders_placeholder_card_stack_icon(self): + """Sprint A.4 follow-up — when the user has no equipped_deck, the + kit-bag Deck section renders the same .deck-stack-icon SVG inside a + .kit-bag-placeholder wrapper (no fan-out trigger; CSS dims it to + --quaUser at 0.3 alpha to match the empty dice slot).""" + import lxml.html + # Clear the auto-equipped Earthman to land in the placeholder branch. + self.user.equipped_deck = None + self.user.save(update_fields=["equipped_deck"]) + response = self.client.get(self.url) + parsed = lxml.html.fromstring(response.content) + # equipped-deck branch is skipped → placeholder branch fires. + self.assertEqual( + len(parsed.cssselect(".kit-bag-deck")), 0, + "No equipped deck → no .kit-bag-deck wrapper", + ) + placeholders = parsed.cssselect(".kit-bag-section .kit-bag-placeholder") + # First placeholder is the Deck slot (Dice + Trinket slots also use + # .kit-bag-placeholder when empty); assert at least one carries the + # card-stack SVG. + deck_placeholder = placeholders[0] + [_] = deck_placeholder.cssselect("svg.deck-stack-icon") + # Old fa-id-badge must be gone. + self.assertEqual( + len(deck_placeholder.cssselect("i.fa-id-badge")), 0, + "fa-regular fa-id-badge must be gone from kit-bag-placeholder", + ) + def test_polarized_equipped_deck_tooltip_has_x2_decoration(self): """Same (x2) decoration as the gameboard applet — polarized decks signal segment-doubling in the tooltip card-count line via --terUser diff --git a/src/static_src/scss/_gameboard.scss b/src/static_src/scss/_gameboard.scss index e8d9334..cba5742 100644 --- a/src/static_src/scss/_gameboard.scss +++ b/src/static_src/scss/_gameboard.scss @@ -123,16 +123,14 @@ body.page-gameboard { .deck-stack-icon__card--3 { transform: translateY( 0.4px); } } -// Sprint A.4 — fan-out trigger lives at the icon level (not nested in -// #id_applet_game_kit) so the same `.deck-stack-icon` works wherever it's -// dropped: gameboard's .token.deck-variant, kit-bag dialog's .kit-bag-deck, -// future room.html pile + deck-bag. Hover/active/focus on the icon itself -// OR any of its known wrappers triggers the splay; cards 2 + 3 fan out from -// under card 1, card 1 stays put. Tooltip portal is wired to the same -// `.token:hover` / `.kit-bag-deck:hover` triggers via JS so the splay + -// tooltip-appearance co-activate. -.deck-stack-icon:hover, -.deck-stack-icon:active, +// Sprint A.4 — fan-out trigger is wrapper-only (NOT self-triggered on the +// SVG itself) so placeholder-mode icons inside .kit-bag-placeholder stay +// static + dim like the empty dice slot. Real-deck wrappers (.token.deck- +// variant on gameboard, .kit-bag-deck in kit-bag dialog, future room.html +// pile + deck-bag) drive the splay; cards 2 + 3 fan out from under card 1, +// card 1 stays put. Tooltip portal is wired to the same `.token:hover` / +// `.kit-bag-deck:hover` triggers via JS so splay + tooltip-appearance +// co-activate. .token.deck-variant:hover .deck-stack-icon, .token.deck-variant:active .deck-stack-icon, .token.deck-variant:focus .deck-stack-icon, @@ -143,6 +141,18 @@ body.page-gameboard { .deck-stack-icon__card--3 { transform: translate( 5px, -2px) rotate( 12deg); } } +// Sprint A.4 — placeholder-mode dim styling. When the icon is inside a +// .kit-bag-placeholder (no deck equipped) or game_kit applet's empty-state +// .kit-item (no decks unlocked), color + fill drop to `--quaUser` at 0.3 +// alpha to match the existing empty-slot treatment (`.kit-bag-placeholder` +// at `_game-kit.scss:143`, `.kit-item { opacity: 0.6 }` here). No animation +// since the wrapper isn't in the splay-trigger list above. +.kit-bag-placeholder .deck-stack-icon, +.kit-item .deck-stack-icon { + color: rgba(var(--quaUser), 0.15); + .deck-stack-icon__card { fill: rgba(var(--quiUser), 0.15); } +} + #id_applet_new_game { display: flex; flex-direction: column;