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

@@ -227,6 +227,31 @@ var SeaDeal = (function () {
});
}
// Polish-6 — FLIP btn (rotateY 0→90→0 over 500ms, toggle
// `.is-flipped-to-back` at midpoint). Renders unconditionally per
// user-spec "allow the FLIP btn everywhere"; no-ops when the card
// has no `.sig-stage-card-back-img` sibling (back-img only renders
// server-side for non-polarized image-equipped decks; text-mode +
// polarized image decks render no back-img + the FLIP is inert).
// Mirrors `_flipToBackAnimated` shape from my_sign.html / applet.
var flipBtn = stage.querySelector('.sea-stage-flip-btn');
if (flipBtn) {
flipBtn.addEventListener('click', function () {
if (stageCard.dataset.flipping) return;
if (!stageCard.querySelector('.sig-stage-card-back-img')) return;
stageCard.dataset.flipping = '1';
stageCard.animate([
{ transform: 'rotateY(0deg)' },
{ transform: 'rotateY(90deg)', offset: 0.5 },
{ transform: 'rotateY(0deg)' },
], { duration: 500, easing: 'ease' });
setTimeout(function () {
stageCard.classList.toggle('is-flipped-to-back');
}, 250);
setTimeout(function () { delete stageCard.dataset.flipping; }, 500);
});
}
// Clicking the FYI panel itself dismisses it (same as sig-select caution)
if (fyiPanel) {
fyiPanel.addEventListener('click', function (e) {

View File

@@ -989,12 +989,15 @@ html:has(.sig-backdrop) {
background: rgba(var(--duoUser), 1);
}
// Polish-5: my_sign main + applet FLIP btns share one positioning rule.
// Both live INSIDE their card (`.sig-stage-card` / `.my-sign-applet-card`,
// both `position: relative`) so `bottom: 0.6rem; left: 0.6rem` anchors
// universally to card-bottom-left w/o needing surface-specific calc().
// Polish-5: my_sign main + applet + sea_stage FLIP btns share one positioning
// rule. All live INSIDE their card (`.sig-stage-card` / `.my-sign-applet-card`
// / `.sig-stage-card.sea-stage-card`, all `position: relative`) so `bottom:
// 0.6rem; left: 0.6rem` anchors universally to card-bottom-left w/o needing
// surface-specific calc(). Polish-6 added sea-stage-flip-btn for the sea_stage
// modal per user-spec "allow the FLIP btn everywhere".
.my-sign-flip-btn,
.my-sign-applet-flip-btn {
.my-sign-applet-flip-btn,
.sea-stage-flip-btn {
@include flip-btn-base;
z-index: 25;
bottom: 0.6rem;
@@ -1012,19 +1015,22 @@ html:has(.sig-backdrop) {
.my-sign-stage.sig-stage--frozen .sig-stage-card:hover .my-sign-flip-btn,
.my-sign-stage.sig-stage--frozen .sig-stage-card:has(.my-sign-flip-btn:hover) .my-sign-flip-btn,
.my-sign-applet-card:hover .my-sign-applet-flip-btn,
.my-sign-applet-card:has(.my-sign-applet-flip-btn:hover) .my-sign-applet-flip-btn {
.my-sign-applet-card:has(.my-sign-applet-flip-btn:hover) .my-sign-applet-flip-btn,
.sea-stage-card:hover .sea-stage-flip-btn,
.sea-stage-card:has(.sea-stage-flip-btn:hover) .sea-stage-flip-btn {
@extend %flip-btn-revealed;
}
// Unified mid-flip-hide across all 3 surfaces. `[data-flipping]="1"` is set on
// Unified mid-flip-hide across all 4 surfaces. `[data-flipping]="1"` is set on
// the card by each surface's FLIP handler for the 500ms rotation duration;
// `%flip-btn-mid-flip`'s `display: none` makes the btn vanish INSTANTLY (per
// user spec — no ease-out logic competing w. the click). Selector chains
// differ per surface because the btn-to-card DOM relationship varies (btn is
// INSIDE the card on my_sign + applet post-polish-5; sibling under .tarot-
// fan-wrap for the fan).
// INSIDE the card on my_sign + applet + sea_stage; sibling under .tarot-fan-
// wrap for the fan carousel).
.sig-stage-card[data-flipping] .my-sign-flip-btn,
.my-sign-applet-card[data-flipping] .my-sign-applet-flip-btn,
.sea-stage-card[data-flipping] .sea-stage-flip-btn,
.tarot-fan-wrap:has(.fan-card[data-flipping]) .fan-flip-btn {
@extend %flip-btn-mid-flip;
}

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>