A.7.5-polish-5 DRY _stat_face.html partial + FLIP-btn SCSS unification + my_sign FLIP DOM move-into-card + universal hover-reveal + instant mid-flip vanish + sea-stage-card image-mode bg fix + multi-line comment syntax cleanup. User-spec 2026-05-25 PM bundle of 5 cleanup threads atop polish-4 (4554c71).
**(1) `_stat_face.html` partial** — extracted to `templates/core/_partials/_stat_face.html` per user 2026-05-25 PM: "Why are there so many individual instances of this feature? Couldn't we call the same DRY partial for each?". One partial covers all 4 stat-block surfaces (sig-stat-block / sea-stat-block / fan-stage-block / my-sign-applet-stat-block) — ~80 lines of duplicated markup collapse to 7 `{% include %}` sites (3 surfaces × 2 faces + applet × 1 face). Args: `face_modifier` (required: "upright"|"reversed"), `label_text` (required: "Emanation"|"Reversal"), `card` (optional TarotCard for applet's server-render path), `keywords_ul_id` (optional id attr on the keyword `<ul>` — sea_stage + fan need `id_sea_stat_upright/reversed` + `id_fan_stat_upright/reversed` for stage-card.js's `populateKeywords` surface-specific selector overrides). The `.stat-face` wrapper that the partial introduces is a no-op for the applet — applet's bespoke `.my-sign-applet-stat-block` rule doesn't `@include stat-block-shared` so `.stat-face` inherits no padding / display-none from the shared mixin.
**(2) FLIP-btn `@mixin flip-btn-base` + `%flip-btn-revealed` + `%flip-btn-mid-flip` primitives** — `_card-deck.scss` head per user 2026-05-25 PM: "unify the many disparate calculations we use for when we allow that FLIP btn to appear and where it appears". Each surface's flip-btn declaration now `@include`s the base (position absolute + zero margin + hidden default opacity 0 + 0.3s transition) and `@extend`s `%flip-btn-revealed` on its surface-specific reveal trigger + `%flip-btn-mid-flip` on its surface-specific `[data-flipping]` selector chain. ~30 lines of duplication collapsed to 6 lines of mixin/placeholder + 3 `@include` + 4 `@extend` calls.
**(3) my_sign FLIP btn moved INSIDE `.sig-stage-card`** + `.my-sign-flip-btn` + `.my-sign-applet-flip-btn` share one positioning rule (`bottom: 0.6rem; left: 0.6rem`) — was a sibling under `.my-sign-stage` positioned via stage-padding-relative `calc(1.5rem + 0.4rem)`. Polish-5 nests it INSIDE the card so positioning is naturally card-relative + the separate `.my-sign-page[data-current-card-id]` centered-mode geometric override (re-deriving offsets from the centred-row layout) is DROPPED entirely. The applet was already inside-card positioned; same `bottom: 0.6rem; left: 0.6rem` rule combines both surfaces in a single `_card-deck.scss` declaration. The applet's `_billboard.scss` flip-btn rule is now just a shim `@include` + `@extend` (the positioning got DRY'd up to the shared rule).
**(4) Hover-reveal everywhere** + instant mid-flip vanish — user-spec 2026-05-25 PM: "The .btn-reveal behavior here should now (1) disappear much earlier, so no independent ease-in/-out logic needed on clicking FLIP; (2) calculate its position more dynamically; be mirrored in the gameboard's My Sign applet. In all places does the hover-to-reveal-FLIP-.btn-reveal effect abate while the card is finishing a FLIP". my_sign main flipped from `display: none → display: inline-flex` (frozen-gated) to opacity-based hover-reveal on `.sig-stage-card:hover` (still gated by `.sig-stage--frozen`). Applet flipped from always-visible to opacity-based hover-reveal on `.my-sign-applet-card:hover`. Fan kept its existing hover-reveal. Mid-flip-hide changed from `opacity: 0 + pointer-events: none` (faded out over the 0.3s transition, which competed w. the click) to `display: none` — INSTANT vanish, no ease-out animation. All 3 surfaces consolidated into one combined `[data-flipping] -> flip-btn` selector list extending `%flip-btn-mid-flip`. The `:has(.flip-btn:hover)` self-pin clause (already present on fan) added to my_sign + applet too — keeps the btn visible while the cursor is on it, otherwise the btn (z-index 25, on top of the card) steals `:hover` from the card the moment the cursor moves onto it + retracts the reveal mid-click.
**(5) `.sea-stage--levity .sea-stage-card` image-mode bg fix** — user-reported 2026-05-25 PM: "the card preview stage in my_sea.html still sports the old card bg (the --secUser here) behind the card img (with the --quiUser box-shadow border)". Same source-order collision pattern as the sea-sig-card fix in polish-4: `.sea-stage--levity .sea-stage-card`'s `@include stage-card-polarity($invert-frame: true)` sets `background: rgba(var(--secUser), 1) + border-color: rgba(var(--priUser), 1)` at specificity 0,2,0 — matches the shared `.sig-stage-card.sig-stage-card--image` comma-list rule's specificity but source-loses to it (levity rule lives at line 2150, comma-list at line 705). Fix: add a `&.sig-stage-card--image { background: transparent; border: 0; }` nested override (0,3,0 specificity) — re-states the transparency under the levity polarity branch so image-mode drawn cards (Minchiate today) don't show a beige card-shape behind the PNG art. The gravity branch was already fine (its mixin call doesn't pass `$invert-frame`).
**(6) Multi-line `{# #}` comment syntax cleanup** — user-spotted 2026-05-25 PM after my polish-5 partial extraction caused visible comment text to leak into rendered HTML on 4 templates (per [[feedback-django-multiline-comments]] / [[feedback-django-comments-single-line-only]] traps the user has flagged before). All multi-line block comments I added in this polish converted to `{% comment %}...{% endcomment %}` form — covers the `_stat_face.html` partial header + 4 template include sites (my_sign.html × 2 blocks, _applet-my-sign.html, _sea_stage.html, game_kit.html).
Tests: 1314/1314 IT+UT total green (72s). No new tests — existing chip-presence + image-mode ITs from polish-4 still pass through the partial extraction. Visual verify 2026-05-25 PM via Claudezilla: my_sign main page (Queen of Coins) renders cleanly via partial w. card+stat-block; applet renders cleanly w. server-filled chip + title; carousel + sea_stage modal work via JS-populated partial includes; my_sign FLIP btn moved into card + hover-reveals + vanishes instantly on FLIP click; sea-stage-card no longer shows --secUser bg behind image-mode PNG art under levity. DRY partial extraction was held out of polish-4 as user-requested separate concern: "hold it for a separate commit, but fold the FLIP btn unification into it as the styling cleanup part" — done.
**Follow-up parked for next sprint**: user-flagged 2026-05-25 PM "If it's interfering to have bespoke rules, just allow the FLIP btn everywhere, including in my_sea.html". This needs (a) dropping the `not card.deck_variant.is_polarized` server-render gate in the applet template, (b) adding a FLIP btn + back-img element to the `_sea_stage.html` modal scaffold, (c) wiring a JS handler in sea.js (currently has no FLIP behavior for drawn-card stage). Out of scope for the polish-5 commit since it's template + JS scope; will pick up as polish-6 or a fresh sprint.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -582,21 +582,21 @@ body.page-billposts {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Sprint A.6 — FLIP btn for non-polarized image-equipped decks in the
|
// Sprint A.6 — FLIP btn for non-polarized image-equipped decks in the
|
||||||
// applet. Nested INSIDE the .my-sign-applet-card.--image (which has
|
// applet. Nested INSIDE .my-sign-applet-card.--image (which has
|
||||||
// position: relative) so absolute positioning anchors to the card bounds.
|
// position: relative) so absolute positioning anchors to the card.
|
||||||
// Hidden during the rotateY animation via the [data-flipping] hook on
|
// Polish-5: shares `@include flip-btn-base` + `%flip-btn-mid-flip` w.
|
||||||
// the parent card — same pattern as the my_sign page (`_card-deck.scss:889`)
|
// the other 2 surfaces (my_sign main + game-kit fan) via `_card-deck.scss`.
|
||||||
// and the tarot-fan view (`_card-deck.scss:459`).
|
|
||||||
.my-sign-applet-card .my-sign-applet-flip-btn {
|
.my-sign-applet-card .my-sign-applet-flip-btn {
|
||||||
position: absolute;
|
@include flip-btn-base;
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
bottom: 0.6rem;
|
bottom: 0.6rem;
|
||||||
left: 0.6rem;
|
left: 0.6rem;
|
||||||
margin: 0;
|
|
||||||
}
|
}
|
||||||
|
// Btn is nested INSIDE the card, so the [data-flipping] hook is a direct
|
||||||
|
// ancestor — no `:has()` needed (unlike the my_sign + fan surfaces where
|
||||||
|
// the btn is a sibling of the flipping card under a common parent).
|
||||||
.my-sign-applet-card[data-flipping] .my-sign-applet-flip-btn {
|
.my-sign-applet-card[data-flipping] .my-sign-applet-flip-btn {
|
||||||
opacity: 0;
|
@extend %flip-btn-mid-flip;
|
||||||
pointer-events: none;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stat block — mirrors the stage card's footprint (same 5:8 aspect +
|
// Stat block — mirrors the stage card's footprint (same 5:8 aspect +
|
||||||
|
|||||||
@@ -3,6 +3,47 @@
|
|||||||
// Shared card display classes (.fan-card, .fan-card-corner, .fan-card-face, .fan-nav)
|
// Shared card display classes (.fan-card, .fan-card-corner, .fan-card-face, .fan-nav)
|
||||||
// extracted from _game-kit.scss; sig-select overlay extracted from _room.scss.
|
// extracted from _game-kit.scss; sig-select overlay extracted from _room.scss.
|
||||||
|
|
||||||
|
// ── Shared FLIP-btn primitives ───────────────────────────────────────────────
|
||||||
|
//
|
||||||
|
// The FLIP btn lives on 3 surfaces (my_sign main stage, my_sign applet, game
|
||||||
|
// kit fan). Polish-5 2026-05-25 PM unification per user spec: hover-reveal
|
||||||
|
// everywhere (was display-toggle on my_sign + always-visible on applet);
|
||||||
|
// `display: none` mid-flip for INSTANT vanish (was opacity-fade — felt sluggish
|
||||||
|
// since clicks happen faster than the 0.3s ease). The reveal-on-hover transition
|
||||||
|
// stays smooth for everyday hover, but the moment a FLIP starts the btn pops
|
||||||
|
// out of layout entirely so it can't visually interfere w. the rotateY mid-spin.
|
||||||
|
//
|
||||||
|
// @mixin flip-btn-base Position absolute + zero margin + hidden default
|
||||||
|
// (opacity:0 + pointer-events:none) + 0.3s opacity
|
||||||
|
// transition for the smooth hover-reveal. Surfaces
|
||||||
|
// add z-index + position offsets (most use bottom-
|
||||||
|
// left-of-card; fan uses transform-based carousel-
|
||||||
|
// shift since its btn is at wrap-level).
|
||||||
|
// %flip-btn-revealed opacity:1 + pointer-events:auto — applied by
|
||||||
|
// each surface's hover-trigger selector chain.
|
||||||
|
// %flip-btn-mid-flip display:none — applied by each surface's
|
||||||
|
// `[data-flipping]` selector chain. Instant vanish
|
||||||
|
// (display isn't animatable); supersedes the
|
||||||
|
// hover-reveal entirely while rotation is in
|
||||||
|
// flight. Each surface's selector chain differs
|
||||||
|
// because the btn-to-card DOM relationship varies
|
||||||
|
// (inside the card on my_sign + applet post-polish-
|
||||||
|
// 5; sibling under common wrap for the fan).
|
||||||
|
@mixin flip-btn-base {
|
||||||
|
position: absolute;
|
||||||
|
margin: 0;
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
transition: opacity 0.3s ease;
|
||||||
|
}
|
||||||
|
%flip-btn-revealed {
|
||||||
|
opacity: 1;
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
%flip-btn-mid-flip {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
// ── Shared stage-card polarity rules ─────────────────────────────────────────
|
// ── Shared stage-card polarity rules ─────────────────────────────────────────
|
||||||
//
|
//
|
||||||
// Used by .sea-stage-card (Sea Select polarity is fixed by the deck-stack the
|
// Used by .sea-stage-card (Sea Select polarity is fixed by the deck-stack the
|
||||||
@@ -519,34 +560,32 @@
|
|||||||
// Positioned at bottom-left of the focused card slot (carousel-shifted, so its
|
// Positioned at bottom-left of the focused card slot (carousel-shifted, so its
|
||||||
// translateX matches .tarot-fan's leftward shift).
|
// translateX matches .tarot-fan's leftward shift).
|
||||||
.fan-flip-btn {
|
.fan-flip-btn {
|
||||||
position: absolute;
|
@include flip-btn-base;
|
||||||
z-index: 25;
|
z-index: 25;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
|
// Carousel-shifted bottom-left-of-focused-card. The btn sits at wrap level
|
||||||
|
// (not inside any card) so positioning has to derive from the carousel
|
||||||
|
// geometry vars rather than the bottom-left-of-card pattern the other 2
|
||||||
|
// surfaces use. --fan-stage-shift + --fan-card-w/h scale w. breakpoint
|
||||||
|
// via the parent `.tarot-fan-wrap`.
|
||||||
transform: translate(calc(-50% - var(--fan-stage-shift) - var(--fan-card-w) / 2 + 1.5rem),
|
transform: translate(calc(-50% - var(--fan-stage-shift) - var(--fan-card-w) / 2 + 1.5rem),
|
||||||
calc(-50% + var(--fan-card-h) / 2 - 1.5rem));
|
calc(-50% + var(--fan-card-h) / 2 - 1.5rem));
|
||||||
margin: 0;
|
|
||||||
opacity: 0;
|
|
||||||
pointer-events: none;
|
|
||||||
transition: opacity 0.3s ease;
|
|
||||||
}
|
}
|
||||||
// Reveal when the focused card OR the FLIP button itself is hovered. Without
|
// Hover-reveal. The `.fan-flip-btn:hover` clause pins the btn visible while
|
||||||
// the `.fan-flip-btn:hover` clause the button (z-index 25, sitting on top of
|
// the cursor is on it — without it, the btn (z-index 25, on top of the card)
|
||||||
// the card) steals :hover from the card the moment the cursor moves onto it,
|
// steals :hover from the card the moment the cursor moves onto it, retracting
|
||||||
// flipping :has() false, fading the button to opacity:0 + pointer-events:none,
|
// the reveal + letting the in-flight click pass through to the dialog backdrop
|
||||||
// and letting the in-flight click pass through to the dialog backdrop (which
|
// (which closes the modal). `.fan-touch-revealed` is the touch-device fallback
|
||||||
// closes the modal). Keeping the button in the trigger list pins it visible
|
// (no :hover on touchscreens) — game-kit.js toggles it on first card tap.
|
||||||
// while the cursor is on it.
|
|
||||||
.tarot-fan-wrap:has(.fan-card--active:hover) .fan-flip-btn,
|
.tarot-fan-wrap:has(.fan-card--active:hover) .fan-flip-btn,
|
||||||
.tarot-fan-wrap:has(.fan-flip-btn:hover) .fan-flip-btn,
|
.tarot-fan-wrap:has(.fan-flip-btn:hover) .fan-flip-btn,
|
||||||
.tarot-fan-wrap.fan-touch-revealed .fan-flip-btn {
|
.tarot-fan-wrap.fan-touch-revealed .fan-flip-btn {
|
||||||
opacity: 1;
|
@extend %flip-btn-revealed;
|
||||||
pointer-events: auto;
|
|
||||||
}
|
|
||||||
.tarot-fan-wrap:has(.fan-card[data-flipping]) .fan-flip-btn {
|
|
||||||
opacity: 0;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
}
|
||||||
|
// Mid-flip-hide selector for the fan is consolidated into the unified
|
||||||
|
// 3-surface rule below the `.my-sign-flip-btn` / `.my-sign-applet-flip-btn`
|
||||||
|
// declarations. See `_card-deck.scss` polish-5 FLIP-btn block.
|
||||||
|
|
||||||
.fan-nav {
|
.fan-nav {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@@ -867,23 +906,10 @@ html:has(.sig-backdrop) {
|
|||||||
// pair aligned in the centred row.
|
// pair aligned in the centred row.
|
||||||
.sig-stat-block { align-self: center; }
|
.sig-stat-block { align-self: center; }
|
||||||
|
|
||||||
// FLIP was positioned via `left: calc(1.5rem + 0.4rem)` (default
|
// Polish-5: FLIP-btn centered-mode offset override DROPPED. The btn is
|
||||||
// rule below) assuming the card sat flush against the stage's
|
// now positioned INSIDE the card (`.sig-stage-card { position: relative }`
|
||||||
// padded-left edge — true on the picker's left-anchored layout but
|
// + btn `bottom: 0.6rem; left: 0.6rem`), so it follows the card naturally
|
||||||
// wrong here w. `justify-content: center` (the card moves to
|
// wherever the stage positions it — no per-layout-mode geometric calc.
|
||||||
// wherever the group's left edge lands).
|
|
||||||
// Re-derive FLIP's offsets from the centred geometry:
|
|
||||||
// group width = card + gap + stat = 2 * --sig-card-w + 0.75rem
|
|
||||||
// card's left edge (in stage) = (100% - group width) / 2
|
|
||||||
// card's bottom edge (in stage) = 50% - (cardHeight / 2)
|
|
||||||
// = 50% - --sig-card-w * 0.8
|
|
||||||
// (cardHeight = w × 8/5 = w × 1.6)
|
|
||||||
// The +0.4rem on each lands FLIP just inside the card's bottom-left
|
|
||||||
// corner, matching the picker-side positioning intent.
|
|
||||||
.my-sign-flip-btn {
|
|
||||||
left: calc((100% - 2 * var(--sig-card-w) - 0.75rem) / 2 + 0.4rem);
|
|
||||||
bottom: calc(50% - var(--sig-card-w) * 0.8 + 0.4rem);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Landing collapses since the hex is server-side gone — just DEL is
|
// Landing collapses since the hex is server-side gone — just DEL is
|
||||||
// left + that's `position: absolute`. `position: static` here drops
|
// left + that's `position: absolute`. `position: static` here drops
|
||||||
@@ -963,34 +989,44 @@ html:has(.sig-backdrop) {
|
|||||||
background: rgba(var(--duoUser), 1);
|
background: rgba(var(--duoUser), 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.my-sign-flip-btn {
|
// Polish-5: my_sign main + applet FLIP btns share one positioning rule.
|
||||||
position: absolute;
|
// 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().
|
||||||
|
.my-sign-flip-btn,
|
||||||
|
.my-sign-applet-flip-btn {
|
||||||
|
@include flip-btn-base;
|
||||||
z-index: 25;
|
z-index: 25;
|
||||||
bottom: 0.4rem;
|
bottom: 0.6rem;
|
||||||
// .sig-stage has padding-left: 1.5rem; this offset places the btn just
|
left: 0.6rem;
|
||||||
// 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;
|
|
||||||
display: none;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// FLIP btn appears only when the stage is frozen (post-OK confirm). Hover-only
|
// Hover-reveal on the parent card. `:has(.flip-btn:hover)` pins the btn
|
||||||
// previews don't reveal the polarity toggle — the user hasn't committed yet.
|
// visible while the cursor is on it — without this clause, the btn (z-index
|
||||||
.my-sign-stage.sig-stage--frozen .my-sign-flip-btn {
|
// 25, on top of the card) steals :hover from the card the moment the cursor
|
||||||
display: inline-flex;
|
// moves onto it, retracting the reveal + breaking the click flow. Same
|
||||||
|
// pattern the fan carousel uses.
|
||||||
|
//
|
||||||
|
// my_sign main still gates on `.sig-stage--frozen` — hover-only previews
|
||||||
|
// don't reveal the polarity toggle until the user has committed a sig.
|
||||||
|
.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 {
|
||||||
|
@extend %flip-btn-revealed;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sprint A.5 — hide FLIP btn during the flip animation. `data-flipping="1"`
|
// Unified mid-flip-hide across all 3 surfaces. `[data-flipping]="1"` is set on
|
||||||
// is set on .sig-stage-card by _flipPolarityAnimated (polarized) AND
|
// the card by each surface's FLIP handler for the 500ms rotation duration;
|
||||||
// _flipToBackAnimated (non-polarized) for the 500ms animation duration; CSS
|
// `%flip-btn-mid-flip`'s `display: none` makes the btn vanish INSTANTLY (per
|
||||||
// :has() selects the parent .my-sign-stage when any child carries that attr
|
// user spec — no ease-out logic competing w. the click). Selector chains
|
||||||
// and zeros the btn so it doesn't visually interfere w. the rotateY mid-spin.
|
// differ per surface because the btn-to-card DOM relationship varies (btn is
|
||||||
// Mirrors the tarot-fan view's pattern (`_card-deck.scss:459` —
|
// INSIDE the card on my_sign + applet post-polish-5; sibling under .tarot-
|
||||||
// `.tarot-fan-wrap:has(.fan-card[data-flipping]) .fan-flip-btn`).
|
// fan-wrap for the fan).
|
||||||
.my-sign-stage:has(.sig-stage-card[data-flipping]) .my-sign-flip-btn {
|
.sig-stage-card[data-flipping] .my-sign-flip-btn,
|
||||||
opacity: 0;
|
.my-sign-applet-card[data-flipping] .my-sign-applet-flip-btn,
|
||||||
pointer-events: none;
|
.tarot-fan-wrap:has(.fan-card[data-flipping]) .fan-flip-btn {
|
||||||
|
@extend %flip-btn-mid-flip;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── Mini card grid ───────────────────────────────────────────────────────────
|
// ─── Mini card grid ───────────────────────────────────────────────────────────
|
||||||
@@ -2120,6 +2156,17 @@ $_sea-title-els: '.fan-card-name, .sig-qualifier-above, .sig-qualifier-below, .f
|
|||||||
color: rgba(var(--priUser), 1);
|
color: rgba(var(--priUser), 1);
|
||||||
.fan-card-arcana,
|
.fan-card-arcana,
|
||||||
.fan-card-corner { color: rgba(var(--priUser), 1); }
|
.fan-card-corner { color: rgba(var(--priUser), 1); }
|
||||||
|
|
||||||
|
// Polish-5: image-mode override mirrors the sea-sig-card pattern. The
|
||||||
|
// `$invert-frame: true` arg above sets `--secUser` bg + `--priUser`
|
||||||
|
// border, which then OUT-CASCADES the shared image-mode comma-list rule
|
||||||
|
// (same 0,2,0 specificity but later in source). Re-state transparency
|
||||||
|
// here so image-mode drawn cards (Minchiate today) don't show a beige
|
||||||
|
// card-shape behind the PNG art.
|
||||||
|
&.sig-stage-card--image {
|
||||||
|
background: transparent;
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.sea-stage--gravity .sea-stage-card {
|
.sea-stage--gravity .sea-stage-card {
|
||||||
@include stage-card-polarity(
|
@include stage-card-polarity(
|
||||||
|
|||||||
@@ -74,24 +74,13 @@
|
|||||||
{# only the polarity axis (FLIP), never the orientation axis #}
|
{# only the polarity axis (FLIP), never the orientation axis #}
|
||||||
{# (SPIN), so always render the upright/emanation face. #}
|
{# (SPIN), so always render the upright/emanation face. #}
|
||||||
<div class="my-sign-applet-stat-block" data-arcana-key="{{ card.arcana }}">
|
<div class="my-sign-applet-stat-block" data-arcana-key="{{ card.arcana }}">
|
||||||
{# Sprint A.7.5 — `.stat-face-header` wraps the rank+suit chip #}
|
{% comment %}
|
||||||
{# inline w. EMANATION per [[project-image-based-deck-face- #}
|
DRY stat-face — see `core/_partials/_stat_face.html`. The
|
||||||
{# rendering]]'s A.3 Q3 spec. Server-rendered (read-only #}
|
applet is the only server-render consumer (no SPIN, single
|
||||||
{# applet — no JS populate path). #}
|
emanation face); passes `card` so chip + title + arcana +
|
||||||
<div class="stat-face-header">
|
keywords are filled from `card.*` at render time.
|
||||||
<span class="stat-chip-rank">{{ card.corner_rank }}</span>
|
{% endcomment %}
|
||||||
<div class="stat-chip-tag">
|
{% include "core/_partials/_stat_face.html" with face_modifier="upright" label_text="Emanation" card=card %}
|
||||||
{% 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>
|
</div>
|
||||||
{# Sprint A.6 — applet FLIP btn handler. Mirrors my_sign.html's #}
|
{# Sprint A.6 — applet FLIP btn handler. Mirrors my_sign.html's #}
|
||||||
{# `_flipToBackAnimated()` shape (rotateY 0→90→0 over 500ms, class #}
|
{# `_flipToBackAnimated()` shape (rotateY 0→90→0 over 500ms, class #}
|
||||||
|
|||||||
@@ -5,15 +5,17 @@
|
|||||||
{% block header_text %}<span>Game</span><span>Sign</span>{% endblock header_text %}
|
{% block header_text %}<span>Game</span><span>Sign</span>{% endblock header_text %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{# Two-phase picker. Landing renders the DRY table hex (1-chair) w. a #}
|
{% comment %}
|
||||||
{# central SCAN SIGN btn; the stage frame above previews the user's #}
|
Two-phase picker. Landing renders the DRY table hex (1-chair) w. a
|
||||||
{# saved sig if any. Clicking SCAN SIGN swaps to picker phase: the hex #}
|
central SCAN SIGN btn; the stage frame above previews the user's
|
||||||
{# hides, the card grid appears below the stage. Selection is a two-step #}
|
saved sig if any. Clicking SCAN SIGN swaps to picker phase: the hex
|
||||||
{# click on the thumbnail itself (matching room sig-select): click thumb #}
|
hides, the card grid appears below the stage. Selection is a two-step
|
||||||
{# → OK btn appears; click OK → lock (stat block + FLIP + SAVE SIGN #}
|
click on the thumbnail itself (matching room sig-select): click thumb
|
||||||
{# enable + NVM appears for deselect). #}
|
→ OK btn appears; click OK → lock (stat block + FLIP + SAVE SIGN
|
||||||
{# "Significator" is preserved at the storage layer (User.significator); #}
|
enable + NVM appears for deselect).
|
||||||
{# this billboard surface re-brands to "Sign". #}
|
"Significator" is preserved at the storage layer (User.significator);
|
||||||
|
this billboard surface re-brands to "Sign".
|
||||||
|
{% endcomment %}
|
||||||
<div class="my-sign-page"
|
<div class="my-sign-page"
|
||||||
data-phase="landing"
|
data-phase="landing"
|
||||||
data-save-url="{% url 'billboard:save_sign' %}"
|
data-save-url="{% url 'billboard:save_sign' %}"
|
||||||
@@ -69,10 +71,19 @@
|
|||||||
<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>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{% comment %}
|
||||||
{# FLIP — bottom-left of the stage card. Visible only after lock #}
|
Sprint A.7.5-polish-5 — FLIP btn moved INSIDE .sig-stage-card
|
||||||
{# (.sig-stage--frozen). #}
|
(was sibling under .my-sign-stage). Positioning is now relative
|
||||||
|
to the card itself: card-bottom-left via `bottom: 0.6rem;
|
||||||
|
left: 0.6rem;` (shared w. the applet + future fan move per
|
||||||
|
`.my-sign-flip-btn, .my-sign-applet-flip-btn` combined rule in
|
||||||
|
`_card-deck.scss`). Drops the prior stage-relative calc + the
|
||||||
|
centered-mode geometric override. Hover-reveal on the card
|
||||||
|
itself (`:hover` + `:has(.my-sign-flip-btn:hover)`) matches
|
||||||
|
the fan carousel's progressive-disclosure pattern.
|
||||||
|
{% endcomment %}
|
||||||
<button class="btn btn-reveal my-sign-flip-btn" type="button">FLIP</button>
|
<button class="btn btn-reveal my-sign-flip-btn" type="button">FLIP</button>
|
||||||
|
</div>
|
||||||
<div class="sig-stat-block">
|
<div class="sig-stat-block">
|
||||||
<button class="btn btn-reverse spin-btn" type="button">SPIN</button>
|
<button class="btn btn-reverse spin-btn" type="button">SPIN</button>
|
||||||
<button class="btn btn-info fyi-btn" type="button">FYI</button>
|
<button class="btn btn-info fyi-btn" type="button">FYI</button>
|
||||||
@@ -83,35 +94,15 @@
|
|||||||
{# `.stat-face-title` + `.stat-face-arcana` empty by default, #}
|
{# `.stat-face-title` + `.stat-face-arcana` empty by default, #}
|
||||||
{# populated by stage-card.js `populateStatBlock` from the card #}
|
{# populated by stage-card.js `populateStatBlock` from the card #}
|
||||||
{# data flow on focus / save. #}
|
{# data flow on focus / save. #}
|
||||||
{# Sprint A.7.5 — `.stat-face-header` wraps the new top-left #}
|
{% comment %}
|
||||||
{# rank+suit chip inline w. the underlined EMANATION/REVERSAL #}
|
DRY stat-face — see `core/_partials/_stat_face.html` for the
|
||||||
{# label per [[project-image-based-deck-face-rendering]]'s A.3 #}
|
header (chip + EMANATION/REVERSAL label) + title + arcana +
|
||||||
{# Q3 spec. Chip elements empty by default; stage-card.js's #}
|
keywords structure. JS-populated surface; no `card` arg, no
|
||||||
{# populateStatExtras fills `.stat-chip-rank` + `.stat-chip-icon`.#}
|
`keywords_ul_id` arg — stage-card.js's populateStatExtras +
|
||||||
<div class="stat-face stat-face--upright">
|
populateKeywords fill the empty placeholders at runtime.
|
||||||
<div class="stat-face-header">
|
{% endcomment %}
|
||||||
<span class="stat-chip-rank"></span>
|
{% include "core/_partials/_stat_face.html" with face_modifier="upright" label_text="Emanation" %}
|
||||||
<div class="stat-chip-tag">
|
{% include "core/_partials/_stat_face.html" with face_modifier="reversed" label_text="Reversal" %}
|
||||||
<i class="fa-solid stat-chip-icon" style="display:none"></i>
|
|
||||||
<p class="stat-face-label">Emanation</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<p class="stat-face-title"></p>
|
|
||||||
<p class="stat-face-arcana"></p>
|
|
||||||
<ul class="stat-keywords"></ul>
|
|
||||||
</div>
|
|
||||||
<div class="stat-face stat-face--reversed">
|
|
||||||
<div class="stat-face-header">
|
|
||||||
<span class="stat-chip-rank"></span>
|
|
||||||
<div class="stat-chip-tag">
|
|
||||||
<i class="fa-solid stat-chip-icon" style="display:none"></i>
|
|
||||||
<p class="stat-face-label">Reversal</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<p class="stat-face-title"></p>
|
|
||||||
<p class="stat-face-arcana"></p>
|
|
||||||
<ul class="stat-keywords"></ul>
|
|
||||||
</div>
|
|
||||||
{% include "apps/gameboard/_partials/_sig_fyi_panel.html" with panel_id="id_my_sign_fyi_panel" %}
|
{% include "apps/gameboard/_partials/_sig_fyi_panel.html" with panel_id="id_my_sign_fyi_panel" %}
|
||||||
</div>
|
</div>
|
||||||
{# SAVE SIGN + NVM form lives inside the stage so the layout #}
|
{# SAVE SIGN + NVM form lives inside the stage so the layout #}
|
||||||
|
|||||||
@@ -45,34 +45,13 @@
|
|||||||
<div class="sig-stat-block sea-stat-block">
|
<div class="sig-stat-block sea-stat-block">
|
||||||
<button class="btn btn-reverse spin-btn" type="button">SPIN</button>
|
<button class="btn btn-reverse spin-btn" type="button">SPIN</button>
|
||||||
<button class="btn btn-info fyi-btn" type="button">FYI</button>
|
<button class="btn btn-info fyi-btn" type="button">FYI</button>
|
||||||
{# Sprint A.7.5 — `.stat-face-header` wraps the rank+suit chip #}
|
{% comment %}
|
||||||
{# inline w. EMANATION/REVERSAL per [[project-image-based-deck- #}
|
DRY stat-face — see `core/_partials/_stat_face.html`. JS-
|
||||||
{# face-rendering]]'s A.3 Q3 spec. Chip empty by default; stage- #}
|
populated; keyword ul carries id for stage-card.js's surface-
|
||||||
{# card.js populateStatExtras fills both faces' chips identically.#}
|
specific selector overrides (populateKeywords opts).
|
||||||
<div class="stat-face stat-face--upright">
|
{% endcomment %}
|
||||||
<div class="stat-face-header">
|
{% include "core/_partials/_stat_face.html" with face_modifier="upright" label_text="Emanation" keywords_ul_id="id_sea_stat_upright" %}
|
||||||
<span class="stat-chip-rank"></span>
|
{% include "core/_partials/_stat_face.html" with face_modifier="reversed" label_text="Reversal" keywords_ul_id="id_sea_stat_reversed" %}
|
||||||
<div class="stat-chip-tag">
|
|
||||||
<i class="fa-solid stat-chip-icon" style="display:none"></i>
|
|
||||||
<p class="stat-face-label">Emanation</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<p class="stat-face-title"></p>
|
|
||||||
<p class="stat-face-arcana"></p>
|
|
||||||
<ul class="stat-keywords" id="id_sea_stat_upright"></ul>
|
|
||||||
</div>
|
|
||||||
<div class="stat-face stat-face--reversed">
|
|
||||||
<div class="stat-face-header">
|
|
||||||
<span class="stat-chip-rank"></span>
|
|
||||||
<div class="stat-chip-tag">
|
|
||||||
<i class="fa-solid stat-chip-icon" style="display:none"></i>
|
|
||||||
<p class="stat-face-label">Reversal</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<p class="stat-face-title"></p>
|
|
||||||
<p class="stat-face-arcana"></p>
|
|
||||||
<ul class="stat-keywords" id="id_sea_stat_reversed"></ul>
|
|
||||||
</div>
|
|
||||||
{% include "apps/gameboard/_partials/_sig_fyi_panel.html" with panel_id="id_sea_fyi_panel" panel_extra_attrs='style="display:none"' %}
|
{% include "apps/gameboard/_partials/_sig_fyi_panel.html" with panel_id="id_sea_fyi_panel" panel_extra_attrs='style="display:none"' %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -21,37 +21,14 @@
|
|||||||
<div class="fan-stage-block sig-stat-block" id="id_fan_stage_block">
|
<div class="fan-stage-block sig-stat-block" id="id_fan_stage_block">
|
||||||
<button class="btn btn-reverse spin-btn" type="button">SPIN</button>
|
<button class="btn btn-reverse spin-btn" type="button">SPIN</button>
|
||||||
<button class="btn btn-info fyi-btn" type="button">FYI</button>
|
<button class="btn btn-info fyi-btn" type="button">FYI</button>
|
||||||
{# Sprint A.7.5 — match the my_sign / sea_stage stat-block shape: #}
|
{% comment %}
|
||||||
{# `.stat-face-header` w. chip + EMANATION/REVERSAL label, then #}
|
DRY stat-face — see `core/_partials/_stat_face.html`. JS-
|
||||||
{# `.stat-face-title` + `.stat-face-arcana` (the carousel stat #}
|
populated; keyword ul carries id for stage-card.js's
|
||||||
{# block previously had only the keyword list; for image-mode #}
|
populateKeywords opts override (game-kit.js passes
|
||||||
{# decks the stat block is the only home for textual metadata). #}
|
uprightSel/reversedSel pointing at these IDs).
|
||||||
{# All four — chip rank, chip icon, title, arcana — populated by #}
|
{% endcomment %}
|
||||||
{# stage-card.js's populateStatExtras on each card focus change. #}
|
{% include "core/_partials/_stat_face.html" with face_modifier="upright" label_text="Emanation" keywords_ul_id="id_fan_stat_upright" %}
|
||||||
<div class="stat-face stat-face--upright">
|
{% include "core/_partials/_stat_face.html" with face_modifier="reversed" label_text="Reversal" keywords_ul_id="id_fan_stat_reversed" %}
|
||||||
<div class="stat-face-header">
|
|
||||||
<span class="stat-chip-rank"></span>
|
|
||||||
<div class="stat-chip-tag">
|
|
||||||
<i class="fa-solid stat-chip-icon" style="display:none"></i>
|
|
||||||
<p class="stat-face-label">Emanation</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<p class="stat-face-title"></p>
|
|
||||||
<p class="stat-face-arcana"></p>
|
|
||||||
<ul class="stat-keywords" id="id_fan_stat_upright"></ul>
|
|
||||||
</div>
|
|
||||||
<div class="stat-face stat-face--reversed">
|
|
||||||
<div class="stat-face-header">
|
|
||||||
<span class="stat-chip-rank"></span>
|
|
||||||
<div class="stat-chip-tag">
|
|
||||||
<i class="fa-solid stat-chip-icon" style="display:none"></i>
|
|
||||||
<p class="stat-face-label">Reversal</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<p class="stat-face-title"></p>
|
|
||||||
<p class="stat-face-arcana"></p>
|
|
||||||
<ul class="stat-keywords" id="id_fan_stat_reversed"></ul>
|
|
||||||
</div>
|
|
||||||
{% include "apps/gameboard/_partials/_sig_fyi_panel.html" with panel_id="id_fan_fyi_panel" panel_extra_attrs='style="display:none"' %}
|
{% include "apps/gameboard/_partials/_sig_fyi_panel.html" with panel_id="id_fan_fyi_panel" panel_extra_attrs='style="display:none"' %}
|
||||||
</div>
|
</div>
|
||||||
<button id="id_fan_next" class="fan-nav fan-nav--next" aria-label="Next card">›</button>
|
<button id="id_fan_next" class="fan-nav fan-nav--next" aria-label="Next card">›</button>
|
||||||
|
|||||||
38
src/templates/core/_partials/_stat_face.html
Normal file
38
src/templates/core/_partials/_stat_face.html
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
{% comment %}
|
||||||
|
DRY stat-face partial — used by all 4 stat-block surfaces:
|
||||||
|
sig-stat-block (my_sign main + sig-overlay), sea-stat-block (sea_stage
|
||||||
|
modal), fan-stage-block (game_kit carousel), my-sign-applet-stat-block
|
||||||
|
(billboard applet). Sprint A.7.5-polish-5 DRY extraction 2026-05-25 PM
|
||||||
|
per user request "Why are there so many individual instances of this
|
||||||
|
feature? Couldn't we call the same DRY partial for each?".
|
||||||
|
|
||||||
|
Args:
|
||||||
|
face_modifier Required. "upright" or "reversed" — appended to .stat-face
|
||||||
|
BEM modifier class. The 3 SPIN-capable surfaces (my_sign /
|
||||||
|
sea_stage / fan) call this partial TWICE (once per face)
|
||||||
|
so .is-reversed on the parent stat-block can swap which
|
||||||
|
face displays. The applet calls it ONCE w. "upright".
|
||||||
|
label_text Required. "Emanation" or "Reversal" — static label text.
|
||||||
|
card Optional. TarotCard instance for server-render mode (the
|
||||||
|
applet's sole consumption pattern). When present: chip
|
||||||
|
rank/icon + title + arcana + keywords are server-filled
|
||||||
|
from card.*; when None: fields render empty for stage-
|
||||||
|
card.js's `populateStatExtras` to populate at runtime
|
||||||
|
(sig / sea_stage / fan stage).
|
||||||
|
keywords_ul_id Optional. Adds `id="..."` to the keyword `<ul>`. Used by
|
||||||
|
sea_stage (`id_sea_stat_upright/reversed`) + fan stage
|
||||||
|
(`id_fan_stat_upright/reversed`) for stage-card.js's
|
||||||
|
`populateKeywords` surface-specific selector overrides.
|
||||||
|
{% endcomment %}
|
||||||
|
<div class="stat-face stat-face--{{ face_modifier }}">
|
||||||
|
<div class="stat-face-header">
|
||||||
|
<span class="stat-chip-rank">{% if card %}{{ card.corner_rank }}{% endif %}</span>
|
||||||
|
<div class="stat-chip-tag">
|
||||||
|
{% if card and card.suit_icon %}<i class="fa-solid {{ card.suit_icon }} stat-chip-icon"></i>{% else %}<i class="fa-solid stat-chip-icon" style="display:none"></i>{% endif %}
|
||||||
|
<p class="stat-face-label">{{ label_text }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p class="stat-face-title">{% if card %}{{ card.name }}{% endif %}</p>
|
||||||
|
<p class="stat-face-arcana">{% if card %}{{ card.get_arcana_display }}{% endif %}</p>
|
||||||
|
<ul class="stat-keywords"{% if keywords_ul_id %} id="{{ keywords_ul_id }}"{% endif %}>{% if card %}{% for kw in card.keywords_upright %}<li>{{ kw }}</li>{% endfor %}{% endif %}</ul>
|
||||||
|
</div>
|
||||||
Reference in New Issue
Block a user