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:
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user