My Sign picker: stage visible on load, FYI panel, SPIN/FLIP split w. perspective-flip animation — Sprint 4a-cont
User-driven polish on the Sprint 4a picker so it's usable parity-w-room-sig-select (per the Schizo-screenshot reference). My initial pass collapsed SPIN + FLIP into one button — user clarified the correct architecture: **SPIN** stays in the `.sig-stat-block` (room pattern, btn-reverse, toggles orientation 180° + reveals reversal_qualifier), while **FLIP** lives at the bottom-left of the stage card as a `.btn-reveal` (game-kit fan carousel pattern, toggles polarity gravity↔levity w. a horizontal-perspective Y-axis rotation animation). Gravity is the default upright polarity per user — significator_reversed=False → gravity, True → levity ; **template changes** (my_sign.html): (a) `.sig-stage-card` no longer carries inline `display:none` — stage frame visible on page load, before any card click; (b) `.sig-stage` carries `.sig-stage--frozen` modifier from the start so the stat-block shows alongside the stage card (room CSS gates `.sig-stat-block { display: block }` behind this class); (c) stat-block btn relabeled "FLIP" → "SPIN" + restored to btn-reverse / orientation-toggle semantics; (d) new `<button class="btn btn-reveal my-sign-flip-btn">FLIP</button>` outside the stat-block at .sig-stage scope, positioned absolute via new SCSS (bottom-left of stage card, mirroring game_kit.html's #id_fan_flip placement); (e) FYI btn + `_sig_fyi_panel.html` partial included alongside SPIN in stat-block — pinned w. id_my_sign_fyi_panel; (f) all 18 card data-* attrs filled (data-levity-qualifier / data-gravity-qualifier / data-levity-emanation / data-gravity-emanation / data-levity-reversal / data-gravity-reversal / data-energies / data-operations / data-italic-word / data-correspondence) so StageCard.populateCard has everything it needs to render qualifiers + reversal-face text per polarity; (g) data-polarity on .my-sign-stage drives populator polarity arg + (future) polarity-themed styling, initialised from `current_significator_reversed` (False=gravity, True=levity) ; **JS changes** (inline script in my_sign.html, includes apps/epic/stage-card.js): (a) on card click → StageCard.fromDataset → populateCard(stageCard, card, _polarity()) + populateKeywords on stat-block + buildInfoData/renderFyi on FYI panel + sig-focused class on grid cell; (b) FYI btn click toggles `.fyi-open` on stat-block (room pattern — CSS reveals the .sig-info panel + PRV/NXT); (c) PRV/NXT cycle thru _fyiData; (d) SPIN click toggles `.stage-card--reversed` + `.is-reversed` on stat-block (orientation, preview-only — not persisted); (e) FLIP click runs `_flipPolarityAnimated()` — 500ms Y-axis rotateY(90deg) midpoint animation lifted from game-kit.js's `_flipActive`, swaps polarity at offset 0.5 so the new face shows through the 2nd half-rotation, preserves SPIN orientation by including ' rotate(180deg)' in both keyframes when stage-card--reversed is on, in-flight `dataset.flipping` flag prevents re-triggering mid-animation; (f) on-load: if user has a saved sig (`.my-sign-page[data-current-card-id]`), find that grid card + auto-select it so stage shows the persisted choice ; **SCSS** (_card-deck.scss): new `.my-sign-flip-btn` rule positioning the btn absolute z-index:25 bottom:0.4rem left:calc(1.5rem + 0.4rem) — accounts for .sig-stage's padding-left:1.5rem so the btn lands at the visual bottom-left of the stage card; .btn-reveal styling (magenta/cyan) inherited from existing _button-pad.scss; no animation SCSS (the 500ms rotateY is in JS via element.animate()) ; **deferred**: `.sig-overlay[data-polarity="levity"]` / `[data-polarity="gravity"]` themed color overrides at _card-deck.scss:805-885 are scoped to `.sig-overlay` and won't apply to `.my-sign-stage[data-polarity]` until those selectors are extended (or duplicated under a .my-sign-stage sibling). User flagged the visual delta but the picker is functionally complete w.o the polarity-themed colors — followup sub-sprint ; **regression**: 7 FTs in test_bill_my_sign green in 57s; no IT/UT changes needed (only template + SCSS). User-pre-staged rootvars.scss tweak picked up
Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -615,6 +615,22 @@ html:has(.sig-backdrop) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ─── My Sign picker — FLIP btn (polarity toggle) ──────────────────────────────
|
||||||
|
// Bottom-left corner of the stage card, mirroring game_kit.html's
|
||||||
|
// `.fan-flip-btn` placement convention (per [[sprint-my-sign-picker-may18h]]).
|
||||||
|
// SPIN (orientation 180° rotation) stays in `.sig-stat-block` — the FLIP btn
|
||||||
|
// here toggles polarity (data-polarity attr + persisted significator_reversed).
|
||||||
|
.my-sign-flip-btn {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 25;
|
||||||
|
bottom: 0.4rem;
|
||||||
|
// .sig-stage has padding-left: 1.5rem; this offset places the btn just
|
||||||
|
// inside the stage card's bottom-left corner (the card sits flex-end /
|
||||||
|
// flex-start, anchored to the stage's left padding).
|
||||||
|
left: calc(1.5rem + 0.4rem);
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
// ─── Mini card grid ───────────────────────────────────────────────────────────
|
// ─── Mini card grid ───────────────────────────────────────────────────────────
|
||||||
// flex: 0 0 auto — shrinks to card content; no background (backdrop blur).
|
// flex: 0 0 auto — shrinks to card content; no background (backdrop blur).
|
||||||
// align-content: start prevents CSS grid from distributing extra height between rows.
|
// align-content: start prevents CSS grid from distributing extra height between rows.
|
||||||
|
|||||||
@@ -458,8 +458,8 @@
|
|||||||
.palette-maryland {
|
.palette-maryland {
|
||||||
--priUser: var(--quiBlt);
|
--priUser: var(--quiBlt);
|
||||||
--secUser: var(--sixBlt);
|
--secUser: var(--sixBlt);
|
||||||
--terUser: var(--terYl);
|
--terUser: var(--secBlt);
|
||||||
--quaUser: var(--priBlt);
|
--quaUser: var(--priYl);
|
||||||
--quiUser: var(--octBlt);
|
--quiUser: var(--octBlt);
|
||||||
--sixUser: var(--quiBlt);
|
--sixUser: var(--quiBlt);
|
||||||
--sepUser: var(--quiBlt);
|
--sepUser: var(--quiBlt);
|
||||||
|
|||||||
@@ -16,8 +16,15 @@
|
|||||||
{% if current_significator %}data-current-card-id="{{ current_significator.id }}"{% endif %}
|
{% if current_significator %}data-current-card-id="{{ current_significator.id }}"{% endif %}
|
||||||
data-current-reversed="{{ current_significator_reversed|yesno:'true,false' }}">
|
data-current-reversed="{{ current_significator_reversed|yesno:'true,false' }}">
|
||||||
|
|
||||||
<div class="my-sign-stage">
|
{# data-polarity drives both the populator's qualifier lookup #}
|
||||||
<div class="sig-stage-card" style="display:none">
|
{# (levity_qualifier vs gravity_qualifier) and any polarity-themed #}
|
||||||
|
{# styling that mirrors the room sig-select's .sig-overlay rules. #}
|
||||||
|
{# FLIP toggles between levity + gravity. Initial value reflects #}
|
||||||
|
{# the persisted `significator_reversed` flag — False=levity, #}
|
||||||
|
{# True=gravity (the binary "one or the other" polarity choice). #}
|
||||||
|
<div class="sig-stage my-sign-stage sig-stage--frozen"
|
||||||
|
data-polarity="{% if current_significator_reversed %}levity{% else %}gravity{% endif %}">
|
||||||
|
<div class="sig-stage-card">
|
||||||
<div class="fan-card-corner fan-card-corner--tl">
|
<div class="fan-card-corner fan-card-corner--tl">
|
||||||
<span class="fan-corner-rank"></span>
|
<span class="fan-corner-rank"></span>
|
||||||
<i class="fa-solid stage-suit-icon" style="display:none"></i>
|
<i class="fa-solid stage-suit-icon" style="display:none"></i>
|
||||||
@@ -40,8 +47,15 @@
|
|||||||
<i class="fa-solid stage-suit-icon" style="display:none"></i>
|
<i class="fa-solid stage-suit-icon" style="display:none"></i>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{# FLIP — bottom-left of the stage card. Mirrors the game-kit fan #}
|
||||||
|
{# carousel's btn-reveal pattern (game_kit.html#id_fan_flip). Toggles #}
|
||||||
|
{# polarity (data-polarity attr + persisted User.significator_reversed). #}
|
||||||
|
{# SPIN in the stat-block toggles orientation (180° rotation) for #}
|
||||||
|
{# preview only — not persisted. #}
|
||||||
|
<button class="btn btn-reveal my-sign-flip-btn" type="button">FLIP</button>
|
||||||
<div class="sig-stat-block">
|
<div class="sig-stat-block">
|
||||||
<button class="btn btn-reverse spin-btn" type="button">FLIP</button>
|
<button class="btn btn-reverse spin-btn" type="button">SPIN</button>
|
||||||
|
<button class="btn btn-info fyi-btn" type="button">FYI</button>
|
||||||
<div class="stat-face stat-face--upright">
|
<div class="stat-face stat-face--upright">
|
||||||
<p class="stat-face-label">Emanation</p>
|
<p class="stat-face-label">Emanation</p>
|
||||||
<ul class="stat-keywords"></ul>
|
<ul class="stat-keywords"></ul>
|
||||||
@@ -50,10 +64,11 @@
|
|||||||
<p class="stat-face-label">Reversal</p>
|
<p class="stat-face-label">Reversal</p>
|
||||||
<ul class="stat-keywords"></ul>
|
<ul class="stat-keywords"></ul>
|
||||||
</div>
|
</div>
|
||||||
|
{% include "apps/gameboard/_partials/_sig_fyi_panel.html" with panel_id="id_my_sign_fyi_panel" %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="my-sign-deck-grid">
|
<div class="sig-deck-grid my-sign-deck-grid">
|
||||||
{% for card in cards %}
|
{% for card in cards %}
|
||||||
<div class="sig-card"
|
<div class="sig-card"
|
||||||
data-card-id="{{ card.id }}"
|
data-card-id="{{ card.id }}"
|
||||||
@@ -62,9 +77,19 @@
|
|||||||
data-name-group="{{ card.name_group }}"
|
data-name-group="{{ card.name_group }}"
|
||||||
data-name-title="{{ card.name_title }}"
|
data-name-title="{{ card.name_title }}"
|
||||||
data-arcana="{{ card.get_arcana_display }}"
|
data-arcana="{{ card.get_arcana_display }}"
|
||||||
|
data-correspondence="{{ card.correspondence|default:'' }}"
|
||||||
data-keywords-upright="{{ card.keywords_upright|join:',' }}"
|
data-keywords-upright="{{ card.keywords_upright|join:',' }}"
|
||||||
data-keywords-reversed="{{ card.keywords_reversed|join:',' }}"
|
data-keywords-reversed="{{ card.keywords_reversed|join:',' }}"
|
||||||
data-reversal-qualifier="{{ card.reversal_qualifier }}">
|
data-energies="{{ card.energies_json }}"
|
||||||
|
data-operations="{{ card.operations_json }}"
|
||||||
|
data-levity-qualifier="{{ card.levity_qualifier }}"
|
||||||
|
data-gravity-qualifier="{{ card.gravity_qualifier }}"
|
||||||
|
data-reversal-qualifier="{{ card.reversal_qualifier }}"
|
||||||
|
data-levity-emanation="{{ card.levity_emanation }}"
|
||||||
|
data-gravity-emanation="{{ card.gravity_emanation }}"
|
||||||
|
data-levity-reversal="{{ card.levity_reversal }}"
|
||||||
|
data-gravity-reversal="{{ card.gravity_reversal }}"
|
||||||
|
data-italic-word="{{ card.italic_word }}">
|
||||||
<div class="fan-card-corner fan-card-corner--tl">
|
<div class="fan-card-corner fan-card-corner--tl">
|
||||||
<span class="fan-corner-rank">{{ card.corner_rank }}</span>
|
<span class="fan-corner-rank">{{ card.corner_rank }}</span>
|
||||||
{% if card.suit_icon %}<i class="fa-solid {{ card.suit_icon }}"></i>{% endif %}
|
{% if card.suit_icon %}<i class="fa-solid {{ card.suit_icon }}"></i>{% endif %}
|
||||||
@@ -80,35 +105,151 @@
|
|||||||
<button type="submit" id="id_save_sign_btn" class="btn btn-primary"{% if not current_significator %} disabled{% endif %}>SAVE SIGN</button>
|
<button type="submit" id="id_save_sign_btn" class="btn btn-primary"{% if not current_significator %} disabled{% endif %}>SAVE SIGN</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
{# Minimal picker JS — click .sig-card to pick + enable SAVE SIGN. #}
|
{# Picker JS — click .sig-card to pick + populate stage preview via #}
|
||||||
{# FLIP-btn integration (reversed toggle) lands w. the stage-card #}
|
{# StageCard.populateCard (shared w. room sig-select + sea-select). #}
|
||||||
{# preview JS in a Sprint 4a-follow-up; for now we just record #}
|
{# FLIP toggles `.stage-card--reversed` on the preview AND the #}
|
||||||
{# the chosen card_id + the FLIP state. #}
|
{# hidden `reversed` input that gets POSTed on SAVE SIGN. #}
|
||||||
|
<script src="{% static 'apps/epic/stage-card.js' %}"></script>
|
||||||
<script>
|
<script>
|
||||||
(function () {
|
(function () {
|
||||||
var grid = document.querySelector('.my-sign-deck-grid');
|
var grid = document.querySelector('.my-sign-deck-grid');
|
||||||
if (!grid) return;
|
if (!grid) return;
|
||||||
|
var stage = document.querySelector('.my-sign-stage');
|
||||||
|
var stageCard = stage.querySelector('.sig-stage-card');
|
||||||
|
var statBlock = stage.querySelector('.sig-stat-block');
|
||||||
var cardIdInput = document.getElementById('id_save_sign_card_id');
|
var cardIdInput = document.getElementById('id_save_sign_card_id');
|
||||||
var saveBtn = document.getElementById('id_save_sign_btn');
|
var saveBtn = document.getElementById('id_save_sign_btn');
|
||||||
var flipBtn = document.querySelector('.my-sign-stage .spin-btn');
|
var spinBtn = stage.querySelector('.spin-btn');
|
||||||
|
var flipBtn = stage.querySelector('.my-sign-flip-btn');
|
||||||
|
var fyiBtn = stage.querySelector('.fyi-btn');
|
||||||
|
var fyiPanel = stage.querySelector('.sig-info');
|
||||||
|
var fyiPrev = stage.querySelector('.fyi-prev');
|
||||||
|
var fyiNext = stage.querySelector('.fyi-next');
|
||||||
var revInput = document.getElementById('id_save_sign_reversed');
|
var revInput = document.getElementById('id_save_sign_reversed');
|
||||||
grid.addEventListener('click', function (e) {
|
var _currentCard = null;
|
||||||
var card = e.target.closest('.sig-card');
|
var _fyiData = [];
|
||||||
if (!card) return;
|
var _fyiIdx = 0;
|
||||||
|
|
||||||
|
// Default polarity = gravity (significator_reversed=False);
|
||||||
|
// FLIP toggles to levity (significator_reversed=True). Mirrors the
|
||||||
|
// user's "gravity-rightside-up by default" design from the Game
|
||||||
|
// Kit fan carousel.
|
||||||
|
function _polarity() {
|
||||||
|
return revInput.value === '1' ? 'levity' : 'gravity';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Horizontal-perspective FLIP animation lifted from
|
||||||
|
// apps/gameboard/static/apps/gameboard/game-kit.js `_flipActive`.
|
||||||
|
// 500ms Y-axis rotation; mid-animation (offset 0.5) the populator
|
||||||
|
// swaps polarity content so the user sees the new face from the
|
||||||
|
// start of the second half-rotation. Preserves the SPIN orientation
|
||||||
|
// (.stage-card--reversed) through the flip by including the spin
|
||||||
|
// rotate(180deg) in both keyframes.
|
||||||
|
function _flipPolarityAnimated() {
|
||||||
|
if (!stageCard || stageCard.dataset.flipping) return;
|
||||||
|
stageCard.dataset.flipping = '1';
|
||||||
|
var spin = stageCard.classList.contains('stage-card--reversed')
|
||||||
|
? ' rotate(180deg)' : '';
|
||||||
|
var rest = 'translateX(0px) rotateY(0deg) scale(1)' + spin;
|
||||||
|
var mid = 'translateX(0px) rotateY(0deg) scale(1)' + spin
|
||||||
|
+ ' rotateY(90deg)';
|
||||||
|
stageCard.animate([
|
||||||
|
{ transform: rest },
|
||||||
|
{ transform: mid, offset: 0.5 },
|
||||||
|
{ transform: rest },
|
||||||
|
], { duration: 500, easing: 'ease' });
|
||||||
|
// Swap polarity at the edge-on midpoint so the new face shows
|
||||||
|
// through the second half of the rotation.
|
||||||
|
setTimeout(function () {
|
||||||
|
revInput.value = revInput.value === '1' ? '0' : '1';
|
||||||
|
stage.setAttribute('data-polarity', _polarity());
|
||||||
|
if (flipBtn) flipBtn.classList.toggle(
|
||||||
|
'is-reversed', revInput.value === '1');
|
||||||
|
if (_currentCard && window.StageCard) {
|
||||||
|
StageCard.populateCard(stageCard, _currentCard, _polarity());
|
||||||
|
}
|
||||||
|
}, 250);
|
||||||
|
setTimeout(function () { delete stageCard.dataset.flipping; }, 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Orientation toggle (preview-only) — rotates the card 180° + swaps
|
||||||
|
// the stat-block visible face. Not persisted; SAVE SIGN only stores
|
||||||
|
// the card identity + polarity (significator_reversed = polarity bit).
|
||||||
|
function _toggleOrientation() {
|
||||||
|
var on = !stageCard.classList.contains('stage-card--reversed');
|
||||||
|
stageCard.classList.toggle('stage-card--reversed', on);
|
||||||
|
statBlock.classList.toggle('is-reversed', on);
|
||||||
|
if (spinBtn) spinBtn.classList.toggle('is-reversed', on);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _selectCard(cardEl) {
|
||||||
grid.querySelectorAll('.sig-card.sig-focused').forEach(function (c) {
|
grid.querySelectorAll('.sig-card.sig-focused').forEach(function (c) {
|
||||||
c.classList.remove('sig-focused');
|
c.classList.remove('sig-focused');
|
||||||
});
|
});
|
||||||
card.classList.add('sig-focused');
|
cardEl.classList.add('sig-focused');
|
||||||
cardIdInput.value = card.dataset.cardId;
|
cardIdInput.value = cardEl.dataset.cardId;
|
||||||
saveBtn.removeAttribute('disabled');
|
saveBtn.removeAttribute('disabled');
|
||||||
|
if (!window.StageCard) return;
|
||||||
|
_currentCard = StageCard.fromDataset(cardEl);
|
||||||
|
StageCard.populateCard(stageCard, _currentCard, _polarity());
|
||||||
|
StageCard.populateKeywords(statBlock,
|
||||||
|
_currentCard.keywords_upright, _currentCard.keywords_reversed);
|
||||||
|
_fyiData = StageCard.buildInfoData(_currentCard);
|
||||||
|
_fyiIdx = 0;
|
||||||
|
if (fyiPanel) StageCard.renderFyi(fyiPanel, _fyiData, _fyiIdx);
|
||||||
|
statBlock.classList.remove('fyi-open');
|
||||||
|
}
|
||||||
|
|
||||||
|
grid.addEventListener('click', function (e) {
|
||||||
|
var cardEl = e.target.closest('.sig-card');
|
||||||
|
if (cardEl) _selectCard(cardEl);
|
||||||
});
|
});
|
||||||
if (flipBtn) {
|
if (flipBtn) {
|
||||||
flipBtn.addEventListener('click', function () {
|
flipBtn.addEventListener('click', function () {
|
||||||
var current = revInput.value === '1';
|
if (!_currentCard) return; // need a selected card to flip
|
||||||
revInput.value = current ? '0' : '1';
|
_flipPolarityAnimated();
|
||||||
flipBtn.classList.toggle('is-reversed', !current);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
if (spinBtn) {
|
||||||
|
spinBtn.addEventListener('click', function () {
|
||||||
|
_toggleOrientation();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (fyiBtn) {
|
||||||
|
fyiBtn.addEventListener('click', function () {
|
||||||
|
if (!_currentCard) return; // no card selected yet
|
||||||
|
statBlock.classList.toggle('fyi-open');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (fyiPanel) {
|
||||||
|
fyiPanel.addEventListener('click', function () {
|
||||||
|
statBlock.classList.remove('fyi-open');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (fyiPrev) {
|
||||||
|
fyiPrev.addEventListener('click', function () {
|
||||||
|
if (!_fyiData.length) return;
|
||||||
|
_fyiIdx = (_fyiIdx - 1 + _fyiData.length) % _fyiData.length;
|
||||||
|
StageCard.renderFyi(fyiPanel, _fyiData, _fyiIdx);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (fyiNext) {
|
||||||
|
fyiNext.addEventListener('click', function () {
|
||||||
|
if (!_fyiData.length) return;
|
||||||
|
_fyiIdx = (_fyiIdx + 1) % _fyiData.length;
|
||||||
|
StageCard.renderFyi(fyiPanel, _fyiData, _fyiIdx);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// On-load: if user has a saved sig, pre-select its grid card so
|
||||||
|
// the stage shows the persisted choice rather than an empty frame.
|
||||||
|
// `data-current-card-id` is set on .my-sign-page when significator is set.
|
||||||
|
var pageEl = document.querySelector('.my-sign-page');
|
||||||
|
var savedId = pageEl && pageEl.dataset.currentCardId;
|
||||||
|
if (savedId) {
|
||||||
|
var savedCardEl = grid.querySelector('.sig-card[data-card-id="' + savedId + '"]');
|
||||||
|
if (savedCardEl) _selectCard(savedCardEl);
|
||||||
|
}
|
||||||
}());
|
}());
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user