From bdf6a251f431275f3e09f25d7942a4c39b1e8d81 Mon Sep 17 00:00:00 2001 From: Disco DeDisco Date: Mon, 25 May 2026 01:35:47 -0400 Subject: [PATCH] =?UTF-8?q?A.5-polish-2=20FLIP-to-back=20bug=20fixes=20?= =?UTF-8?q?=E2=80=94=20TDD.=20Two=20user-reported=20bugs=20from=20the=20fi?= =?UTF-8?q?rst=20FLIP-to-back=20commit=20(1963ad4):=20(1)=20FLIPping=20mad?= =?UTF-8?q?e=20the=20entire=20card=20disappear=20instead=20of=20showing=20?= =?UTF-8?q?the=20back;=20(2)=20the=20FLIP=20btn=20stayed=20visible=20durin?= =?UTF-8?q?g=20the=20animation=20when=20it=20should=20hide=20just=20like?= =?UTF-8?q?=20the=20tarot-fan=20view's=20flip=20btn=20does.=20Root=20cause?= =?UTF-8?q?=20of=20(1):=20the=20back-image=20element=20was=20rendered=20wi?= =?UTF-8?q?th=20inline=20`style=3D"display:none"`=20in=20the=20template.?= =?UTF-8?q?=20Inline=20styles=20beat=20CSS=20class=20rules=20in=20the=20ca?= =?UTF-8?q?scade=20=E2=80=94=20my=20`.sig-stage-card.is-flipped-to-back=20?= =?UTF-8?q?.sig-stage-card-back-img=20{=20display:=20block=20}`=20rule=20w?= =?UTF-8?q?as=20the=20right=20specificity=20but=20couldn't=20override=20an?= =?UTF-8?q?=20inline=20`style`=20attribute.=20So=20the=20toggle=20hid=20th?= =?UTF-8?q?e=20front=20(CSS-controlled,=20no=20inline=20override)=20but=20?= =?UTF-8?q?failed=20to=20show=20the=20back=20(CSS=20blocked=20by=20inline)?= =?UTF-8?q?.=20Net:=20empty=20stage.=20Fix:=20removed=20the=20inline=20sty?= =?UTF-8?q?le=20on=20the=20back-img=20element;=20default=20`.sig-stage-car?= =?UTF-8?q?d-back-img=20{=20display:=20none=20}`=20rule=20(already=20prese?= =?UTF-8?q?nt=20in=20SCSS)=20handles=20the=20hidden=20default,=20and=20the?= =?UTF-8?q?=20`.is-flipped-to-back`=20toggle=20now=20flips=20visibility=20?= =?UTF-8?q?cleanly.=20Both=20rules=20are=20pure-CSS=20so=20they=20cascade?= =?UTF-8?q?=20as=20expected.=20Root=20cause=20of=20(2):=20the=20non-polari?= =?UTF-8?q?zed=20FLIP=20handler=20was=20a=20bare=20class=20toggle=20(no=20?= =?UTF-8?q?animation,=20no=20`data-flipping`=20attr),=20so=20there=20was?= =?UTF-8?q?=20no=20SCSS=20hook=20to=20hide=20the=20btn.=20Plus=20there=20w?= =?UTF-8?q?as=20no=20equivalent=20SCSS=20rule=20even=20for=20the=20polariz?= =?UTF-8?q?ed=20`=5FflipPolarityAnimated`=20flow=20which=20DID=20set=20`da?= =?UTF-8?q?ta-flipping`=20=E2=80=94=20the=20polarized=20flip=20just=20anim?= =?UTF-8?q?ated=20without=20hiding=20the=20btn=20either.=20Fix:=20(a)=20ad?= =?UTF-8?q?ded=20`=5FflipToBackAnimated()`=20JS=20function=20mirroring=20`?= =?UTF-8?q?=5FflipPolarityAnimated`'s=20shape=20=E2=80=94=20rotateY=200?= =?UTF-8?q?=E2=86=9290=E2=86=920=20at=20500ms=20ease,=20swap=20visual=20co?= =?UTF-8?q?ntent=20at=20the=20halfway=20point=20(here:=20class=20toggle=20?= =?UTF-8?q?instead=20of=20revInput/polarity=20flip),=20set=20`stageCard.da?= =?UTF-8?q?taset.flipping=20=3D=20'1'`=20for=20the=20duration=20so=20SCSS?= =?UTF-8?q?=20has=20a=20hook.=20(b)=20New=20SCSS=20rule=20`.my-sign-stage:?= =?UTF-8?q?has(.sig-stage-card[data-flipping])=20.my-sign-flip-btn=20{=20o?= =?UTF-8?q?pacity:=200;=20pointer-events:=20none=20}`=20mirrors=20the=20ta?= =?UTF-8?q?rot-fan=20view's=20pattern=20(`=5Fcard-deck.scss:459`=20?= =?UTF-8?q?=E2=80=94=20`.tarot-fan-wrap:has(.fan-card[data-flipping])=20.f?= =?UTF-8?q?an-flip-btn`).=20The=20:has()=20selector=20covers=20BOTH=20the?= =?UTF-8?q?=20polarized=20animation=20(which=20already=20sets=20data-flipp?= =?UTF-8?q?ing)=20AND=20the=20new=20non-polarized=20animation,=20so=20the?= =?UTF-8?q?=20btn=20hide-during-flip=20behavior=20now=20lands=20consistent?= =?UTF-8?q?ly=20across=20both=20flip=20modes=20=E2=80=94=20fixes=20a=20lat?= =?UTF-8?q?ent=20polished-flow=20gap=20not=20just=20the=20new=20code=20pat?= =?UTF-8?q?h.=20No=20new=20tests=20=E2=80=94=20the=20existing=203=20ITs=20?= =?UTF-8?q?from=201963ad4=20already=20verify=20the=20template/scaffold=20c?= =?UTF-8?q?ontract=20(data-deck-polarized=20attr=20+=20back-img=20element?= =?UTF-8?q?=20conditional=20render);=20the=20bug=20fixes=20here=20are=20CS?= =?UTF-8?q?S/JS-level=20behavior=20best=20caught=20by=20visual=20verify=20?= =?UTF-8?q?(no=20automated=20test=20would=20have=20caught=20the=20inline-s?= =?UTF-8?q?tyle=20cascade=20issue=20since=20the=20IT=20asserted=20on=20ele?= =?UTF-8?q?ment=20presence,=20not=20display=20state).=201303/1303=20IT+UT?= =?UTF-8?q?=20total=20green=20(71s,=20unchanged=20from=201963ad4=20since?= =?UTF-8?q?=20no=20new=20tests=20in=20this=20commit)?= 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/static_src/scss/_card-deck.scss | 12 +++++++ src/templates/apps/billboard/my_sign.html | 44 ++++++++++++++++------- 2 files changed, 44 insertions(+), 12 deletions(-) diff --git a/src/static_src/scss/_card-deck.scss b/src/static_src/scss/_card-deck.scss index e1a021e..ef618e6 100644 --- a/src/static_src/scss/_card-deck.scss +++ b/src/static_src/scss/_card-deck.scss @@ -887,6 +887,18 @@ html:has(.sig-backdrop) { display: inline-flex; } +// Sprint A.5 — hide FLIP btn during the flip animation. `data-flipping="1"` +// is set on .sig-stage-card by _flipPolarityAnimated (polarized) AND +// _flipToBackAnimated (non-polarized) for the 500ms animation duration; CSS +// :has() selects the parent .my-sign-stage when any child carries that attr +// and zeros the btn so it doesn't visually interfere w. the rotateY mid-spin. +// Mirrors the tarot-fan view's pattern (`_card-deck.scss:459` — +// `.tarot-fan-wrap:has(.fan-card[data-flipping]) .fan-flip-btn`). +.my-sign-stage:has(.sig-stage-card[data-flipping]) .my-sign-flip-btn { + opacity: 0; + pointer-events: none; +} + // ─── Mini card grid ─────────────────────────────────────────────────────────── // flex: 0 0 auto — shrinks to card content; no background (backdrop blur). // align-content: start prevents CSS grid from distributing extra height between rows. diff --git a/src/templates/apps/billboard/my_sign.html b/src/templates/apps/billboard/my_sign.html index 1e14e01..19e7bd8 100644 --- a/src/templates/apps/billboard/my_sign.html +++ b/src/templates/apps/billboard/my_sign.html @@ -42,9 +42,11 @@ {# non-polarized decks). Pre-rendered back-image element; CSS #} {# toggles visibility via `.sig-stage-card.is-flipped-to-back`. #} {% if request.user.equipped_deck.has_card_images and not request.user.equipped_deck.is_polarized %} + {# No inline style — `.sig-stage-card-back-img` defaults to #} + {# `display: none` via SCSS; `.sig-stage-card.is-flipped-to-back` #} + {# overrides to `display: block`. Inline would beat the toggle. #} + src="{{ request.user.equipped_deck.back_image_url }}"> {% endif %}
@@ -359,20 +361,38 @@ }); } + // Sprint A.5 — non-polarized FLIP animation. Mirrors the polarized + // `_flipPolarityAnimated` shape (rotateY-90 mid-spin + dataset.flipping + // for the duration so SCSS can hide the btn) but swaps a class + // toggle for the polarity revInput-flip at the halfway point. + function _flipToBackAnimated() { + if (!stageCard || stageCard.dataset.flipping) return; + stageCard.dataset.flipping = '1'; + var rest = 'translateX(0px) rotateY(0deg) scale(1)'; + var mid = 'translateX(0px) rotateY(90deg) scale(1)'; + stageCard.animate([ + { transform: rest }, + { transform: mid, offset: 0.5 }, + { transform: rest }, + ], { duration: 500, easing: 'ease' }); + setTimeout(function () { + stageCard.classList.toggle('is-flipped-to-back'); + if (flipBtn) flipBtn.classList.toggle( + 'is-reversed', + stageCard.classList.contains('is-flipped-to-back') + ); + }, 250); + setTimeout(function () { delete stageCard.dataset.flipping; }, 500); + } + if (flipBtn) { flipBtn.addEventListener('click', function () { if (!_currentCard) return; - // Sprint A.5 — non-polarized decks (Minchiate, RWS): FLIP - // shows the deck card-back instead of cycling polarity (no - // gravity/levity to toggle on these decks). Stat block stays - // unchanged — user spec 2026-05-25 PM. Polarized decks - // (Earthman) keep the existing polarity-flip animation. + // Non-polarized decks (Minchiate, RWS): FLIP shows the deck + // card-back; stat block stays unchanged — user spec 2026-05-25 PM. + // Polarized decks (Earthman) keep the existing polarity cycle. if (pageEl.dataset.deckPolarized === 'false') { - stageCard.classList.toggle('is-flipped-to-back'); - if (flipBtn) flipBtn.classList.toggle( - 'is-reversed', - stageCard.classList.contains('is-flipped-to-back') - ); + _flipToBackAnimated(); } else { _flipPolarityAnimated(); }