A.7.5-polish-6 FLIP btn everywhere — applet gate dropped + sea_stage modal gets FLIP. User-spec 2026-05-25 PM ("If it's interfering to have bespoke rules, just allow the FLIP btn everywhere, including in my_sea.html") follow-up to polish-5 (1e2041e).

**(1) `_applet-my-sign.html`** — FLIP btn moved OUTSIDE the `{% if card.deck_variant.has_card_images %}` + nested `{% if not card.deck_variant.is_polarized %}` gates. Now renders as a direct child of `.my-sign-applet-card` for ALL cards regardless of mode/polarity. Back-img element stays gated (back-img is meaningless for polarized decks or text-mode — would render an empty src). JS handler in the same template ungated too (was wrapped in matching `{% if %}` blocks); now always wires + gracefully no-ops on click when no `.sig-stage-card-back-img` sibling exists. Card-element selector broadened from `.my-sign-applet-card--image` (image-mode only) to `.my-sign-applet-card` (any mode).

**(2) `_sea_stage.html`** — added `<img class="sig-stage-card-back-img">` (gated on `request.user.equipped_deck.has_card_images and not is_polarized` — same condition as my_sign.html's main page back-img) + `<button class="sea-stage-flip-btn">` (unconditional). Both nested INSIDE the `.sig-stage-card.sea-stage-card` for card-relative positioning. Multi-user gameroom is a known limitation here — the back-img src is the room viewer's deck-back, not the drawing gamer's, which is wrong when different gamers' decks have different backs. Parked for a future multi-user polish pass (called out in template comment).

**(3) `_card-deck.scss`** — extended the polish-5 shared FLIP-btn rule trio (positioning + hover-reveal + mid-flip-hide) to include `.sea-stage-flip-btn` across all 3 declarations. Now all 4 surfaces (my_sign main / applet / sea_stage / fan carousel) share the same opacity-0-default + hover-reveal + display:none-mid-flip behavior — single source of truth.

**(4) `sea.js`** — added FLIP btn click handler in the init() function next to the existing SPIN/FYI handlers. Mirrors the `_flipToBackAnimated` shape from my_sign.html / _applet-my-sign.html: rotateY 0→90→0 over 500ms, toggle `.is-flipped-to-back` at midpoint, `[data-flipping]` attr for SCSS mid-flip-hide. Same defensive no-op pattern as the applet — bails when no `.sig-stage-card-back-img` sibling exists. Behavior for polarized text-mode decks (no back-img rendered): click is a no-op. Polarized image-mode (future Earthman art): also no-op since back-img is server-gated to non-polarized. Non-polarized image-mode (Minchiate today): flips between front + back.

**Why ungate the FLIP btn rendering rather than render it conditionally per surface:** user-spec was "just allow the FLIP btn everywhere" + the prior bespoke per-surface gating was causing both visual quirks (missing FLIP btn in applet earlier) + maintenance complexity. The unified "always render, JS picks behavior by sibling existence" pattern eliminates the per-surface conditional templates. The btn is always visible-on-hover, always click-handles cleanly, gracefully no-ops where it has nothing to flip to — minimal surprise, maximal consistency.

**JS handlers not unified into a shared module** (yet): each of the 3 surfaces (my_sign main inline script, applet inline script, sea.js init()) carries its own copy of the ~15-line FLIP-to-back animate-and-toggle dance. Could be DRY'd into a `StageCard.flipToBack(card, btn)` helper at some point, but the call sites differ enough in setup (different parent DOM selectors, different surrounding state — frozen-gate for my_sign, no gate for applet/sea_stage) that the helper would mostly be the animate+setTimeout block. Deferred — flagged in [[project-image-based-deck-face-rendering]] follow-ups if it accretes.

Tests: 1314/1314 IT+UT total green (71s). No new tests — JS handler change is pure DOM augmentation; template changes just relax server-side gates (no new conditionals to test). Visual verify 2026-05-25 PM via Claudezilla on /billboard/: applet FLIP btn present (opacity:0 at rest, hover-reveals); shared `.my-sign-applet-card:hover .my-sign-applet-flip-btn` CSS rule confirmed in computed stylesheet; my_sign main page FLIP behavior unchanged (still works per user 2026-05-25 PM "Works well in my_sign.html tho").

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Disco DeDisco
2026-05-25 19:31:45 -04:00
parent 1e2041ed9f
commit b308115fcf
4 changed files with 109 additions and 43 deletions

View File

@@ -18,23 +18,26 @@
data-card-id="{{ card.id }}"
data-arcana-key="{{ card.arcana }}">
{% if card.deck_variant.has_card_images %}
{# Sprint A.6 — image-mode render mirrors my_sign.html's #}
{# .sig-stage-card--image treatment. Shares the SCSS rule #}
{# (comma-list selector) so the contour stroke + tray-card #}
{# silhouette black depth shadow + arcana stroke-color #}
{# come for free. #}
{% comment %}
Sprint A.6 — image-mode render mirrors my_sign.html's
.sig-stage-card--image treatment. Shares the SCSS rule
(comma-list selector) so the contour stroke + tray-card
silhouette black depth shadow + arcana stroke-color
come for free.
{% endcomment %}
<img class="sig-stage-card-img" src="{{ card.image_url }}" alt="{{ card.name }}">
{% if not card.deck_variant.is_polarized %}
{# Non-polarized image deck: FLIP btn shows the deck back #}
{# image (same behavior as my_sign.html main page). Both #}
{# the back-img + flip-btn nest INSIDE the card so the #}
{# absolute-positioned FLIP btn anchors to the card's #}
{# bounds (card is position: relative in --image mode). #}
{% comment %}
Non-polarized image deck: back-img element renders the
deck-back PNG that FLIP toggles to. Back-img stays gated
(back-img is meaningless for polarized decks or text-mode);
the FLIP btn itself moved OUT of this gate in polish-6 so
it renders for every card (per user-spec "allow the FLIP
btn everywhere"). The JS click handler is a no-op when no
back-img sibling exists.
{% endcomment %}
<img class="sig-stage-card-back-img" alt=""
src="{{ card.deck_variant.back_image_url }}">
<button class="btn btn-reveal my-sign-applet-flip-btn"
type="button"
aria-label="Flip to deck back">FLIP</button>
{% endif %}
{% else %}
<div class="fan-card-corner fan-card-corner--tl">
@@ -42,15 +45,17 @@
{% if card.suit_icon %}<i class="fa-solid {{ card.suit_icon }}"></i>{% endif %}
</div>
<div class="fan-card-face">
{# `request.user.sig_face` is the rendering payload from #}
{# `TarotCard.applet_face()` — mirrors `populateCard` in #}
{# `stage-card.js:135-144`: #}
{# • Polarity-split (cards 48-49, trumps 19-21): #}
{# single-line title, qualifier blank. #}
{# • Major + qualifier: title carries a trailing #}
{# comma + qualifier renders BELOW. #}
{# • Non-Major (middle court, Schizo / Nomad w. no #}
{# qualifier): qualifier renders ABOVE the title. #}
{% comment %}
`request.user.sig_face` is the rendering payload from
`TarotCard.applet_face()` — mirrors `populateCard` in
`stage-card.js:135-144`:
• Polarity-split (cards 48-49, trumps 19-21):
single-line title, qualifier blank.
• Major + qualifier: title carries a trailing
comma + qualifier renders BELOW.
• Non-Major (middle court, Schizo / Nomad w. no
qualifier): qualifier renders ABOVE the title.
{% endcomment %}
{% with face=request.user.sig_face %}
{% if face.qualifier_first %}
<p class="fan-card-qualifier">{{ face.qualifier }}</p>
@@ -67,6 +72,16 @@
{% if card.suit_icon %}<i class="fa-solid {{ card.suit_icon }}"></i>{% endif %}
</div>
{% endif %}
{% comment %}
Polish-6 — FLIP btn rendered UNCONDITIONALLY (was gated on
`has_card_images and not is_polarized`). Per user-spec
2026-05-25 PM "just allow the FLIP btn everywhere". JS
handler below picks behavior by sibling existence: back-img
present → flip-to-back animation; absent → no-op.
{% endcomment %}
<button class="btn btn-reveal my-sign-applet-flip-btn"
type="button"
aria-label="Flip card">FLIP</button>
</div>
{# Stat block — same shape as my_sign.html's `.sig-stat-block` #}
{# (Emanation face label + keyword list) but no SPIN/FYI btns #}
@@ -82,25 +97,28 @@
{% endcomment %}
{% include "core/_partials/_stat_face.html" with face_modifier="upright" label_text="Emanation" card=card %}
</div>
{# Sprint A.6 — applet FLIP btn handler. Mirrors my_sign.html's #}
{# `_flipToBackAnimated()` shape (rotateY 0→90→0 over 500ms, class #}
{# toggle at halfway, `data-flipping` attr for SCSS to hide the #}
{# btn). Self-contained inline script — no shared module needed #}
{# since the applet is the only consumer outside the main page #}
{# (which has its own copy). Script wrapped inside the sig-present #}
{# branch AND inside `{% with card %}` scope so `card` references #}
{# resolve + the JS selector strings don't leak into the no-sig #}
{# DOM (which would trip substring-matching tests). #}
{% if card.deck_variant.has_card_images and not card.deck_variant.is_polarized %}
{% comment %}
Polish-6 — applet FLIP btn handler. Mirrors my_sign.html's
`_flipToBackAnimated()` (rotateY 0→90→0 over 500ms, class
toggle at halfway, `data-flipping` attr for SCSS to hide the
btn during animation). Script is now UNGATED (was conditional
on `has_card_images and not is_polarized` like the FLIP btn
itself); per user-spec "allow the FLIP btn everywhere", the
handler always wires + gracefully no-ops when there's no
`.sig-stage-card-back-img` sibling to toggle.
{% endcomment %}
<script>
(function () {
var applet = document.getElementById('id_applet_my_sign');
if (!applet) return;
var c = applet.querySelector('.my-sign-applet-card--image');
var c = applet.querySelector('.my-sign-applet-card');
var b = applet.querySelector('.my-sign-applet-flip-btn');
if (!c || !b) return;
b.addEventListener('click', function () {
if (c.dataset.flipping) return;
// No-op when no back-img to toggle (text-mode +
// polarized image-mode decks render no back-img).
if (!c.querySelector('.sig-stage-card-back-img')) return;
c.dataset.flipping = '1';
var rest = 'rotateY(0deg)';
var mid = 'rotateY(90deg)';
@@ -120,7 +138,6 @@
});
}());
</script>
{% endif %}
{% endwith %}
{% else %}
<p class="my-sign-applet-empty">No sign chosen yet.</p>

View File

@@ -41,6 +41,24 @@
<span class="fan-corner-rank"></span>
<i class="fa-solid stage-suit-icon" style="display:none"></i>
</div>
{% comment %}
Polish-6 — back-img + FLIP btn. The back-img mirrors the
my_sign.html / _applet-my-sign.html pattern: only renders
for non-polarized image-equipped decks (since back-img is
meaningless otherwise; src derives from user's equipped
deck). FLIP btn renders UNCONDITIONALLY per user-spec "allow
the FLIP btn everywhere"; sea.js's click handler no-ops
when no back-img sibling exists. Multi-user gameroom is a
known limitation here — the back-img src is the room
viewer's deck-back, not the drawing gamer's, which is wrong
when different gamers' decks have different backs. Parked
for a future multi-user polish pass.
{% endcomment %}
{% if request.user.is_authenticated and request.user.equipped_deck.has_card_images and not request.user.equipped_deck.is_polarized %}
<img class="sig-stage-card-back-img" alt=""
src="{{ request.user.equipped_deck.back_image_url }}">
{% endif %}
<button class="btn btn-reveal sea-stage-flip-btn" type="button" aria-label="Flip card">FLIP</button>
</div>
<div class="sig-stat-block sea-stat-block">
<button class="btn btn-reverse spin-btn" type="button">SPIN</button>