(1) **Card title color unified to --quaUser** — shared `stat-block-shared` mixin's `.stat-face-title` was `--quiUser` (cream-purple) for non-major arcana, but the My Sign applet's bespoke override at `_billboard.scss:642` had it as `--quaUser` (bright yellow-gold). User-observed inconsistency 2026-05-25 PM: "only the My Sign applet has --quaUser as a font color; the rest are --quiUser. Let's change the latter to match the former". Mixin default flipped — applet's bespoke override stays (was always --quaUser, the new universal value).
(2) **Stat-face top-pin** — `.stat-face` top padding collapsed from `0.37 * card-w` (which mid-vertically centered the arcana label) to `0.1 * card-w` (uniform w. bottom) so the chip + EMANATION/REVERSAL header pin at the actual top edge + title/arcana/keywords cascade DOWN naturally. User-spec 2026-05-25 PM: "pin the number/alphanumeric at the top and the rest of the content cascades down from it, instead of pinning the arcana type in the center and stacking the rest of the content atop it".
(3) **Chip layout restructured** — header is now a 2-row vertical stack (was a 1-row flex w. chip-pill + label inline). Row 1: `.stat-chip-rank` on its OWN line (room for long Roman numerals like XXVIII without squeezing the label). Row 2: `.stat-chip-tag` flex-row holding `<i class="stat-chip-icon">` + `<p class="stat-face-label">` — the icon is always 1 char so it never crowds the label. Border-bottom on the whole `.stat-face-header` (0.05rem solid --secUser at 0.4 alpha) underscores both rows as one header unit, replacing the prior per-`.stat-face-label` `text-decoration: underline` (dropped). Per user spec 2026-05-25 PM: "allow EMANATION/REVERSAL to remain inline with the <i> el below the alphanumeric, which will more predictably only ever be one character long. Then we should extend the underline as a thin line underscoring them both (not merely underlined text)". Template-side: 4 stat-block surfaces (`my_sign.html` / `_applet-my-sign.html` / `_sea_stage.html` / `game_kit.html`) updated to the new 2-row HTML structure — `.stat-face-chip` wrapper dropped entirely; rank is a direct child of header; icon + label live in `.stat-chip-tag`. 4 ITs adjusted to match the new DOM.
(4) **SPIN animation restored for image-mode via CSS transition** — A.7.5 had gated the 180° card rotation behind `!.fan-card--image` (per the prior "monodecks shouldn't have polarity" spec), leaving image-mode cards static on SPIN while only the stat-block face toggled. User-spec 2026-05-25 PM: "reintroduce the SPIN animation". First attempt used a layered `Element.animate(0→180→0)` keyframe; user reported "card rotates back the other way even quicker". Second attempt continued past 180° to 360° for single-direction spin; user reported "now it does three! Upside down, rightside up, and upside down again!" — root cause was the layered `Element.animate` racing the existing `.fan-card { transition: transform 0.18s ease-out }` set in updateFan, producing double/triple-firing. User suggestion 2026-05-25 PM: "Why can't we just resort to the CSS transition". Final fix: drop the special-case image-mode `Element.animate` block entirely; image-mode + text-mode now share the same SPIN handler — toggle `.stage-card--reversed` + set inline `style.transform` w. the rotate(180deg) appended. The existing CSS transition handles the rotation in a single mechanism, no layering. Persistent state via `.stage-card--reversed` continues to be read by `updateFan()` so post-SPIN nav re-renders the rotation correctly.
(5) **sea-sig-card image-mode bg artifact fix** — User-reported 2026-05-25 PM: "Looks like we still have an artifact card bg behind this version of the card preview img in my_sea.html". The central sig card in `my_sea.html`'s picker was showing a beige card-shape behind the transparent-PNG art. Root cause: `.sig-stage-card.sea-sig-card` (`_card-deck.scss:1684`, specificity 0,2,0) matches the shared `.sig-stage-card.sig-stage-card--image` comma-list rule's specificity exactly but appears LATER in source order — so its `background: rgba(var(--priUser), 1)` + `border: 0.15rem solid ...` + `padding: 0.25rem` overrode the image-mode rule's `background: transparent; border: 0; padding: 0`. Fix: add a `&.sig-stage-card--image { background: transparent; border: 0; padding: 0; }` override INSIDE the bespoke rule (specificity 0,3,0 — wins both source-order against the comma-list AND beats the levity-polarity rule at line 1299). Parallel override added to `.my-sea-page[data-polarity="levity"] .sig-stage-card.sea-sig-card` (0,3,0) for the same reason — under levity the polarity rule re-clothes the sea-sig-card w. --secUser bg even in image mode; the nested `&.sig-stage-card--image` override at 0,4,0 wins. Other 3 image-mode surfaces audited: `.my-sea-slot` + `.sea-card-slot` + `.fan-card` base rules are 0,1,0 and lose to the 0,2,0 comma-list naturally; no parallel fix needed for them.
Tests: 1314/1314 IT+UT total green (73s). 4 ITs updated to match the new chip DOM structure (`.stat-face-chip` wrapper dropped; rank now direct child of header; icon + label inside `.stat-chip-tag`): BillboardMySignViewTest.test_stat_block_renders_rank_suit_chip_per_face + BillboardAppletMySignTest.test_applet_stat_block_renders_server_side_chip + MySeaViewTest.test_sea_stage_stat_block_renders_rank_suit_chip_per_face + GameKitViewTest.test_fan_stage_block_renders_rank_suit_chip_per_face. Visual verify 2026-05-25 PM via Claudezilla: chip restructure renders correctly across game_kit carousel (XXVIII Il Capricorno + Il Matto trumps); sea-sig-card bg artifact gone (computed bg `rgba(0, 0, 0, 0)`, border 0, padding 0); SPIN animation smooth in both image-mode + text-mode. No FT runs per [[feedback-ft-run-discipline]]. DRY partial split for the duplicated stat-face header markup deferred to a follow-up commit per user request 2026-05-25 PM ("hold it for a separate commit").
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
141 lines
8.5 KiB
HTML
141 lines
8.5 KiB
HTML
<section
|
|
id="id_applet_my_sign"
|
|
style="--applet-cols: {{ entry.applet.grid_cols }}; --applet-rows: {{ entry.applet.grid_rows }};"
|
|
>
|
|
<h2><a href="{% url 'billboard:my_sign' %}">My Sign</a></h2>
|
|
<div class="my-sign-applet-body"
|
|
data-polarity="{% if request.user.significator_reversed %}levity{% else %}gravity{% endif %}">
|
|
{% if request.user.significator %}
|
|
{% with card=request.user.significator %}
|
|
{# Mirrors the my_sign.html `.sig-stage-card` layout — corner #}
|
|
{# top-left, name + polarity qualifier in the face, mirror #}
|
|
{# corner bottom-right (pre-rotated). Sized to fill the #}
|
|
{# applet's vertical aperture via container queries in #}
|
|
{# `_billboard.scss`. `significator_reversed` is the POLARITY #}
|
|
{# axis (True ↔ levity), so the saved sig is always upright #}
|
|
{# in its polarity — no `.stage-card--reversed` rotation. #}
|
|
<div class="my-sign-applet-card my-sign-applet-card--{% if request.user.significator_reversed %}levity{% else %}gravity{% endif %}{% if card.deck_variant.has_card_images %} my-sign-applet-card--image{% endif %}"
|
|
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. #}
|
|
<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). #}
|
|
<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">
|
|
<span class="fan-corner-rank">{{ card.corner_rank }}</span>
|
|
{% 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. #}
|
|
{% with face=request.user.sig_face %}
|
|
{% if face.qualifier_first %}
|
|
<p class="fan-card-qualifier">{{ face.qualifier }}</p>
|
|
<p class="fan-card-name">{{ face.title }}</p>
|
|
{% else %}
|
|
<p class="fan-card-name">{{ face.title }}</p>
|
|
<p class="fan-card-qualifier">{{ face.qualifier }}</p>
|
|
{% endif %}
|
|
{% endwith %}
|
|
<p class="fan-card-arcana">{{ card.get_arcana_display }}</p>
|
|
</div>
|
|
<div class="fan-card-corner fan-card-corner--br">
|
|
<span class="fan-corner-rank">{{ card.corner_rank }}</span>
|
|
{% if card.suit_icon %}<i class="fa-solid {{ card.suit_icon }}"></i>{% endif %}
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
{# Stat block — same shape as my_sign.html's `.sig-stat-block` #}
|
|
{# (Emanation face label + keyword list) but no SPIN/FYI btns #}
|
|
{# since the applet is a read-only preview. Saved sigs persist #}
|
|
{# only the polarity axis (FLIP), never the orientation axis #}
|
|
{# (SPIN), so always render the upright/emanation face. #}
|
|
<div class="my-sign-applet-stat-block" data-arcana-key="{{ card.arcana }}">
|
|
{# Sprint A.7.5 — `.stat-face-header` wraps the rank+suit chip #}
|
|
{# inline w. EMANATION per [[project-image-based-deck-face- #}
|
|
{# rendering]]'s A.3 Q3 spec. Server-rendered (read-only #}
|
|
{# applet — no JS populate path). #}
|
|
<div class="stat-face-header">
|
|
<span class="stat-chip-rank">{{ card.corner_rank }}</span>
|
|
<div class="stat-chip-tag">
|
|
{% if card.suit_icon %}<i class="fa-solid {{ card.suit_icon }} stat-chip-icon"></i>{% endif %}
|
|
<p class="stat-face-label">Emanation</p>
|
|
</div>
|
|
</div>
|
|
<p class="stat-face-title">{{ card.name }}</p>
|
|
<p class="stat-face-arcana">{{ card.get_arcana_display }}</p>
|
|
<ul class="stat-keywords">
|
|
{% for kw in card.keywords_upright %}
|
|
<li>{{ kw }}</li>
|
|
{% endfor %}
|
|
</ul>
|
|
</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 %}
|
|
<script>
|
|
(function () {
|
|
var applet = document.getElementById('id_applet_my_sign');
|
|
if (!applet) return;
|
|
var c = applet.querySelector('.my-sign-applet-card--image');
|
|
var b = applet.querySelector('.my-sign-applet-flip-btn');
|
|
if (!c || !b) return;
|
|
b.addEventListener('click', function () {
|
|
if (c.dataset.flipping) return;
|
|
c.dataset.flipping = '1';
|
|
var rest = 'rotateY(0deg)';
|
|
var mid = 'rotateY(90deg)';
|
|
c.animate([
|
|
{ transform: rest },
|
|
{ transform: mid, offset: 0.5 },
|
|
{ transform: rest },
|
|
], { duration: 500, easing: 'ease' });
|
|
setTimeout(function () {
|
|
c.classList.toggle('is-flipped-to-back');
|
|
b.classList.toggle(
|
|
'is-reversed',
|
|
c.classList.contains('is-flipped-to-back')
|
|
);
|
|
}, 250);
|
|
setTimeout(function () { delete c.dataset.flipping; }, 500);
|
|
});
|
|
}());
|
|
</script>
|
|
{% endif %}
|
|
{% endwith %}
|
|
{% else %}
|
|
<p class="my-sign-applet-empty">No sign chosen yet.</p>
|
|
{% endif %}
|
|
</div>
|
|
</section>
|