Sig Select qualifier on the stat-block — green the 3 theme FTs
The qualifier (Elevated/Enlightened/Graven) now renders on the frozen stat-block
stat-face, not just the hover-only text card-face (which image-mode decks hide).
populateStatExtras takes opts.polarity and fills new .stat-face-qualifier--above
/--below slots, mirroring the card-face placement: non-major above the title,
major below it (title gets a trailing comma). Qualifier shares the title's style
per request ("same style as 'Jack of Crowns' below it").
The 3 theme FTs were asserting the qualifier on hover, but the stat-block is
display:none until a card is OK'd (.sig-stage--frozen) — only the card preview
shows on hover. Repointed them to select→OK→freeze, then read the stat-block.
Threaded polarity through the sig + my_sign callers; added 4 Jasmine specs.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -102,7 +102,7 @@ var SigSelect = (function () {
|
|||||||
// (unified w. my_sign 2026-06-03) — previously the sig stat-block was a
|
// (unified w. my_sign 2026-06-03) — previously the sig stat-block was a
|
||||||
// reduced label-only copy, so this call was absent and those fields
|
// reduced label-only copy, so this call was absent and those fields
|
||||||
// stayed blank.
|
// stayed blank.
|
||||||
StageCard.populateStatExtras(statBlock, card);
|
StageCard.populateStatExtras(statBlock, card, { polarity: userPolarity });
|
||||||
|
|
||||||
stageCard.style.display = '';
|
stageCard.style.display = '';
|
||||||
stage.classList.add('sig-stage--active');
|
stage.classList.add('sig-stage--active');
|
||||||
|
|||||||
@@ -287,8 +287,38 @@ var StageCard = (function () {
|
|||||||
var rawTitle = card.name_title || card.name || '';
|
var rawTitle = card.name_title || card.name || '';
|
||||||
var title = rawTitle.split(',')[0].trim();
|
var title = rawTitle.split(',')[0].trim();
|
||||||
var arcana = _arcanaDisplay(card);
|
var arcana = _arcanaDisplay(card);
|
||||||
|
var isMajor = _isMajor(card);
|
||||||
|
|
||||||
|
// Polarity qualifier (Elevated/Enlightened/Graven) on the stat-block —
|
||||||
|
// the always-visible home now that image-mode decks hide the text card
|
||||||
|
// face. Mirrors populateCard's above/below placement: non-major above
|
||||||
|
// the title, major below it (title carries a trailing comma so the
|
||||||
|
// qualifier reads as a subtitle). Only filled when a polarity is
|
||||||
|
// supplied (sig / my_sign / sea); the kit fan omits it. Cards 48-49 +
|
||||||
|
// trumps 19-21 carry a polarity-split emanation override whose title
|
||||||
|
// already incorporates the qualifier inline → no separate slot.
|
||||||
|
var polarity = opts.polarity || '';
|
||||||
|
var qualifier = polarity === 'levity' ? (card.levity_qualifier || '')
|
||||||
|
: polarity === 'gravity' ? (card.gravity_qualifier || '')
|
||||||
|
: '';
|
||||||
|
var emanationOverride = polarity === 'levity' ? (card.levity_emanation || '')
|
||||||
|
: polarity === 'gravity' ? (card.gravity_emanation || '')
|
||||||
|
: '';
|
||||||
|
if (emanationOverride) {
|
||||||
|
title = emanationOverride;
|
||||||
|
qualifier = '';
|
||||||
|
}
|
||||||
|
var qAbove = qualifier && !isMajor ? qualifier : '';
|
||||||
|
var qBelow = qualifier && isMajor ? qualifier : '';
|
||||||
|
|
||||||
statBlock.querySelectorAll('.stat-face-title').forEach(function (el) {
|
statBlock.querySelectorAll('.stat-face-title').forEach(function (el) {
|
||||||
el.textContent = title;
|
el.textContent = qBelow ? title + ',' : title;
|
||||||
|
});
|
||||||
|
statBlock.querySelectorAll('.stat-face-qualifier--above').forEach(function (el) {
|
||||||
|
el.textContent = qAbove;
|
||||||
|
});
|
||||||
|
statBlock.querySelectorAll('.stat-face-qualifier--below').forEach(function (el) {
|
||||||
|
el.textContent = qBelow;
|
||||||
});
|
});
|
||||||
statBlock.querySelectorAll('.stat-face-arcana').forEach(function (el) {
|
statBlock.querySelectorAll('.stat-face-arcana').forEach(function (el) {
|
||||||
el.textContent = arcana;
|
el.textContent = arcana;
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import os
|
import os
|
||||||
from unittest import skip
|
|
||||||
|
|
||||||
from django.conf import settings as django_settings
|
from django.conf import settings as django_settings
|
||||||
from django.test import tag
|
from django.test import tag
|
||||||
@@ -256,71 +255,80 @@ class SigSelectThemeTest(FunctionalTest):
|
|||||||
ActionChains(self.browser).move_to_element(card).perform()
|
ActionChains(self.browser).move_to_element(card).perform()
|
||||||
return card
|
return card
|
||||||
|
|
||||||
|
def _select_card_and_freeze(self, css):
|
||||||
|
"""Click a card → OK → wait for the stage to freeze. The stat-block is
|
||||||
|
`display:none` until `.sig-stage--frozen` (only the card preview shows
|
||||||
|
on hover); OK'ing a card reveals it. The qualifier lives on the frozen
|
||||||
|
stat-block now (the hover-only text card-face is going away as decks
|
||||||
|
gain images), so assertions need the frozen state."""
|
||||||
|
card = self.browser.find_element(By.CSS_SELECTOR, css)
|
||||||
|
card.click()
|
||||||
|
ok = self.wait_for(
|
||||||
|
lambda: self.browser.find_element(By.CSS_SELECTOR, ".sig-focused .sig-ok-btn")
|
||||||
|
)
|
||||||
|
ok.click()
|
||||||
|
self.wait_for(lambda: self.browser.find_element(By.CSS_SELECTOR, ".sig-stage--frozen"))
|
||||||
|
|
||||||
# ── ST1: Levity (Leavened) qualifier ──────────────────────────────────── #
|
# ── ST1: Levity (Leavened) qualifier ──────────────────────────────────── #
|
||||||
|
|
||||||
@skip(
|
# Qualifier asserts target the frozen stat-block (.stat-face--upright), not
|
||||||
"Qualifier (Elevated/Enlightened/Graven) now belongs on the always-"
|
# the text card-face: image-mode decks hide .fan-card-face, and the qualifier
|
||||||
"visible stat-block, not the card face: the Earthman deck renders in "
|
# is moving to the always-present stat-block. It mirrors the card-face
|
||||||
"image-mode (has_card_images defaults True) so the text card-face is "
|
# above/below placement — non-major puts the qualifier ABOVE the title, major
|
||||||
"display:none. Unskip + repoint to the stat-block next."
|
# BELOW it. See stage-card.js populateStatExtras + _stat_face.html.
|
||||||
)
|
UPRIGHT_QUAL_ABOVE = ".sig-stat-block .stat-face--upright .stat-face-qualifier--above"
|
||||||
|
UPRIGHT_QUAL_BELOW = ".sig-stat-block .stat-face--upright .stat-face-qualifier--below"
|
||||||
|
|
||||||
def test_levity_non_major_card_shows_elevated_above(self):
|
def test_levity_non_major_card_shows_elevated_above(self):
|
||||||
"""Hovering a non-major card in the levity overlay shows 'Elevated' in
|
"""Selecting (OK) a non-major card shows 'Elevated' in the frozen
|
||||||
qualifier-above and nothing in qualifier-below."""
|
stat-block qualifier-above and nothing in qualifier-below."""
|
||||||
room = self._setup_sig_room()
|
room = self._setup_sig_room()
|
||||||
self.create_pre_authenticated_session("founder@test.io") # PC = levity
|
self.create_pre_authenticated_session("founder@test.io") # PC = levity
|
||||||
self.browser.get(self.live_server_url + f"/gameboard/room/{room.pk}/")
|
self.browser.get(self.live_server_url + f"/gameboard/room/{room.pk}/")
|
||||||
self.wait_for(lambda: self.browser.find_element(By.CSS_SELECTOR, ".sig-overlay"))
|
self.wait_for(lambda: self.browser.find_element(By.CSS_SELECTOR, ".sig-overlay"))
|
||||||
|
|
||||||
self._hover_card('.sig-card[data-arcana="Middle Arcana"]')
|
self._select_card_and_freeze('.sig-card[data-arcana="Middle Arcana"]')
|
||||||
|
|
||||||
above = self.wait_for(
|
self.wait_for(lambda: self.assertEqual(
|
||||||
lambda: self.browser.find_element(By.CSS_SELECTOR, ".sig-qualifier-above")
|
self.browser.find_element(By.CSS_SELECTOR, self.UPRIGHT_QUAL_ABOVE).text,
|
||||||
)
|
"Elevated",
|
||||||
self.assertEqual(above.text, "Elevated")
|
))
|
||||||
below = self.browser.find_element(By.CSS_SELECTOR, ".sig-qualifier-below")
|
below = self.browser.find_element(By.CSS_SELECTOR, self.UPRIGHT_QUAL_BELOW)
|
||||||
self.assertEqual(below.text, "")
|
self.assertEqual(below.text, "")
|
||||||
|
|
||||||
@skip(
|
|
||||||
"Qualifier now belongs on the always-visible stat-block, not the card "
|
|
||||||
"face (image-mode hides .fan-card-face). Unskip + repoint next."
|
|
||||||
)
|
|
||||||
def test_levity_major_card_shows_enlightened_below(self):
|
def test_levity_major_card_shows_enlightened_below(self):
|
||||||
"""Hovering a major arcana card in the levity overlay shows 'Enlightened' in
|
"""Selecting (OK) a major arcana card shows 'Enlightened' in the frozen
|
||||||
qualifier-below and nothing in qualifier-above."""
|
stat-block qualifier-below and nothing in qualifier-above."""
|
||||||
room = self._setup_sig_room()
|
room = self._setup_sig_room()
|
||||||
self.create_pre_authenticated_session("founder@test.io") # PC = levity
|
self.create_pre_authenticated_session("founder@test.io") # PC = levity
|
||||||
self.browser.get(self.live_server_url + f"/gameboard/room/{room.pk}/")
|
self.browser.get(self.live_server_url + f"/gameboard/room/{room.pk}/")
|
||||||
self.wait_for(lambda: self.browser.find_element(By.CSS_SELECTOR, ".sig-overlay"))
|
self.wait_for(lambda: self.browser.find_element(By.CSS_SELECTOR, ".sig-overlay"))
|
||||||
|
|
||||||
self._hover_card('.sig-card[data-arcana="Major Arcana"]')
|
self._select_card_and_freeze('.sig-card[data-arcana="Major Arcana"]')
|
||||||
|
|
||||||
below = self.wait_for(
|
self.wait_for(lambda: self.assertEqual(
|
||||||
lambda: self.browser.find_element(By.CSS_SELECTOR, ".sig-qualifier-below")
|
self.browser.find_element(By.CSS_SELECTOR, self.UPRIGHT_QUAL_BELOW).text,
|
||||||
)
|
"Enlightened",
|
||||||
self.assertEqual(below.text, "Enlightened")
|
))
|
||||||
above = self.browser.find_element(By.CSS_SELECTOR, ".sig-qualifier-above")
|
above = self.browser.find_element(By.CSS_SELECTOR, self.UPRIGHT_QUAL_ABOVE)
|
||||||
self.assertEqual(above.text, "")
|
self.assertEqual(above.text, "")
|
||||||
|
|
||||||
# ── ST2: Gravity (Graven) qualifier ───────────────────────────────────── #
|
# ── ST2: Gravity (Graven) qualifier ───────────────────────────────────── #
|
||||||
|
|
||||||
@skip(
|
|
||||||
"Qualifier now belongs on the always-visible stat-block, not the card "
|
|
||||||
"face (image-mode hides .fan-card-face). Unskip + repoint next."
|
|
||||||
)
|
|
||||||
def test_gravity_non_major_card_shows_graven_above(self):
|
def test_gravity_non_major_card_shows_graven_above(self):
|
||||||
"""EC (bud) sees the gravity overlay; hovering a non-major card shows 'Graven'."""
|
"""EC (bud) sees the gravity overlay; selecting (OK) a non-major card
|
||||||
|
shows 'Graven' in the frozen stat-block qualifier-above."""
|
||||||
room = self._setup_sig_room()
|
room = self._setup_sig_room()
|
||||||
self.create_pre_authenticated_session("bud@test.io") # EC = gravity
|
self.create_pre_authenticated_session("bud@test.io") # EC = gravity
|
||||||
self.browser.get(self.live_server_url + f"/gameboard/room/{room.pk}/")
|
self.browser.get(self.live_server_url + f"/gameboard/room/{room.pk}/")
|
||||||
self.wait_for(lambda: self.browser.find_element(By.CSS_SELECTOR, ".sig-overlay"))
|
self.wait_for(lambda: self.browser.find_element(By.CSS_SELECTOR, ".sig-overlay"))
|
||||||
|
|
||||||
self._hover_card('.sig-card[data-arcana="Middle Arcana"]')
|
self._select_card_and_freeze('.sig-card[data-arcana="Middle Arcana"]')
|
||||||
|
|
||||||
above = self.wait_for(
|
self.wait_for(lambda: self.assertEqual(
|
||||||
lambda: self.browser.find_element(By.CSS_SELECTOR, ".sig-qualifier-above")
|
self.browser.find_element(By.CSS_SELECTOR, self.UPRIGHT_QUAL_ABOVE).text,
|
||||||
)
|
"Graven",
|
||||||
self.assertEqual(above.text, "Graven")
|
))
|
||||||
|
|
||||||
# ── ST3: Correspondence not shown ─────────────────────────────────────── #
|
# ── ST3: Correspondence not shown ─────────────────────────────────────── #
|
||||||
|
|
||||||
|
|||||||
@@ -34,10 +34,18 @@ describe("SigSelect", () => {
|
|||||||
<button class="btn btn-info fyi-btn" type="button">FYI</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>
|
||||||
|
<p class="stat-face-qualifier stat-face-qualifier--above"></p>
|
||||||
|
<p class="stat-face-title"></p>
|
||||||
|
<p class="stat-face-qualifier stat-face-qualifier--below"></p>
|
||||||
|
<p class="stat-face-arcana"></p>
|
||||||
<ul class="stat-keywords" id="id_stat_keywords_upright"></ul>
|
<ul class="stat-keywords" id="id_stat_keywords_upright"></ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-face stat-face--reversed">
|
<div class="stat-face stat-face--reversed">
|
||||||
<p class="stat-face-label">Reversal</p>
|
<p class="stat-face-label">Reversal</p>
|
||||||
|
<p class="stat-face-qualifier stat-face-qualifier--above"></p>
|
||||||
|
<p class="stat-face-title"></p>
|
||||||
|
<p class="stat-face-qualifier stat-face-qualifier--below"></p>
|
||||||
|
<p class="stat-face-arcana"></p>
|
||||||
<ul class="stat-keywords" id="id_stat_keywords_reversed"></ul>
|
<ul class="stat-keywords" id="id_stat_keywords_reversed"></ul>
|
||||||
</div>
|
</div>
|
||||||
<button class="btn btn-nav-left fyi-prev" type="button">◀</button>
|
<button class="btn btn-nav-left fyi-prev" type="button">◀</button>
|
||||||
@@ -742,6 +750,51 @@ describe("SigSelect", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ── Polarity theming — stat-block qualifier ────────────────────────────── //
|
||||||
|
//
|
||||||
|
// The qualifier's always-visible home: image-mode decks hide the text card
|
||||||
|
// face, so the stat-block (.stat-face--upright) carries Elevated/Enlightened/
|
||||||
|
// Graven now. Mirrors the card-face above/below placement (non-major above
|
||||||
|
// the title, major below it w. a trailing-comma title).
|
||||||
|
|
||||||
|
describe("polarity theming — stat-block qualifier", () => {
|
||||||
|
const UPRIGHT = ".stat-face--upright";
|
||||||
|
|
||||||
|
it("levity non-major puts qualifier ABOVE the stat-block title, below empty", () => {
|
||||||
|
makeFixture({ polarity: 'levity', userRole: 'PC' }); // fixture card = Minor Arcana
|
||||||
|
card.dispatchEvent(new MouseEvent("mouseenter", { bubbles: true }));
|
||||||
|
expect(statBlock.querySelector(UPRIGHT + " .stat-face-qualifier--above").textContent).toBe("Elevated");
|
||||||
|
expect(statBlock.querySelector(UPRIGHT + " .stat-face-qualifier--below").textContent).toBe("");
|
||||||
|
expect(statBlock.querySelector(UPRIGHT + " .stat-face-title").textContent).toBe("King of Pentacles");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("levity major puts qualifier BELOW the stat-block title, above empty, title gets comma", () => {
|
||||||
|
makeFixture({ polarity: 'levity', userRole: 'PC' });
|
||||||
|
card.dataset.arcana = "Major Arcana";
|
||||||
|
card.dispatchEvent(new MouseEvent("mouseenter", { bubbles: true }));
|
||||||
|
expect(statBlock.querySelector(UPRIGHT + " .stat-face-qualifier--above").textContent).toBe("");
|
||||||
|
expect(statBlock.querySelector(UPRIGHT + " .stat-face-qualifier--below").textContent).toBe("Elevated");
|
||||||
|
expect(statBlock.querySelector(UPRIGHT + " .stat-face-title").textContent).toBe("King of Pentacles,");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("gravity non-major puts 'Graven' ABOVE the stat-block title", () => {
|
||||||
|
makeFixture({ polarity: 'gravity', userRole: 'BC' });
|
||||||
|
card.dispatchEvent(new MouseEvent("mouseenter", { bubbles: true }));
|
||||||
|
expect(statBlock.querySelector(UPRIGHT + " .stat-face-qualifier--above").textContent).toBe("Graven");
|
||||||
|
expect(statBlock.querySelector(UPRIGHT + " .stat-face-qualifier--below").textContent).toBe("");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("re-hovering a different arcana re-places the stat-block qualifier slot", () => {
|
||||||
|
makeFixture({ polarity: 'levity', userRole: 'PC' });
|
||||||
|
card.dispatchEvent(new MouseEvent("mouseenter", { bubbles: true })); // non-major → above
|
||||||
|
card.dataset.arcana = "Major Arcana";
|
||||||
|
card.dispatchEvent(new MouseEvent("mouseleave", { bubbles: true }));
|
||||||
|
card.dispatchEvent(new MouseEvent("mouseenter", { bubbles: true })); // major → below
|
||||||
|
expect(statBlock.querySelector(UPRIGHT + " .stat-face-qualifier--above").textContent).toBe("");
|
||||||
|
expect(statBlock.querySelector(UPRIGHT + " .stat-face-qualifier--below").textContent).toBe("Elevated");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// ── WAIT NVM glow pulse ────────────────────────────────────────────────────── //
|
// ── WAIT NVM glow pulse ────────────────────────────────────────────────────── //
|
||||||
|
|
||||||
describe("WAIT NVM glow pulse", () => {
|
describe("WAIT NVM glow pulse", () => {
|
||||||
|
|||||||
@@ -176,7 +176,12 @@
|
|||||||
// the rest are --quiUser. Let's change the latter to match the
|
// the rest are --quiUser. Let's change the latter to match the
|
||||||
// former" — applet's `.stat-face-title` was already --quaUser;
|
// former" — applet's `.stat-face-title` was already --quaUser;
|
||||||
// shared mixin now matches so all 4 stat-block surfaces unify).
|
// shared mixin now matches so all 4 stat-block surfaces unify).
|
||||||
.stat-face-title {
|
// Title + polarity qualifier share one style (user-spec 2026-06-03 —
|
||||||
|
// "'Elevated' needs to be the same style as 'Jack of Crowns' below it").
|
||||||
|
// Qualifier is the subtitle hugging the title (above for non-major, below
|
||||||
|
// for major); both pick up the arcana-keyed color override below.
|
||||||
|
.stat-face-title,
|
||||||
|
.stat-face-qualifier {
|
||||||
font-size: calc(var(--sig-card-w, 120px) * 0.105);
|
font-size: calc(var(--sig-card-w, 120px) * 0.105);
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
line-height: 1.15;
|
line-height: 1.15;
|
||||||
@@ -184,7 +189,8 @@
|
|||||||
text-wrap: balance;
|
text-wrap: balance;
|
||||||
color: rgba(var(--quaUser), 1);
|
color: rgba(var(--quaUser), 1);
|
||||||
}
|
}
|
||||||
[data-arcana-key="MAJOR"] .stat-face-title {
|
[data-arcana-key="MAJOR"] .stat-face-title,
|
||||||
|
[data-arcana-key="MAJOR"] .stat-face-qualifier {
|
||||||
color: rgba(var(--terUser), 1);
|
color: rgba(var(--terUser), 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -195,10 +201,15 @@
|
|||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
margin: 0 0 calc(var(--sig-card-w, 120px) * 0.07);
|
margin: 0 0 calc(var(--sig-card-w, 120px) * 0.07);
|
||||||
}
|
}
|
||||||
// `:empty` rule hides title + arcana when stage-card.js hasn't populated
|
// Qualifier hugs the title — tighten the gap on the side that touches it
|
||||||
// them yet (rest state) — prevents zero-height paragraphs from inflating
|
// (below the above-slot, above the below-slot) so the two read as one unit.
|
||||||
// the stat block vertical layout.
|
.stat-face-qualifier--above { margin-bottom: calc(var(--sig-card-w, 120px) * 0.01); }
|
||||||
|
.stat-face-qualifier--below { margin-top: calc(var(--sig-card-w, 120px) * -0.01); }
|
||||||
|
// `:empty` rule hides title + arcana + qualifier slots when stage-card.js
|
||||||
|
// hasn't populated them yet (rest state) — prevents zero-height paragraphs
|
||||||
|
// from inflating the stat block vertical layout.
|
||||||
.stat-face-title:empty,
|
.stat-face-title:empty,
|
||||||
|
.stat-face-qualifier:empty,
|
||||||
.stat-face-arcana:empty { display: none; }
|
.stat-face-arcana:empty { display: none; }
|
||||||
|
|
||||||
.stat-keywords {
|
.stat-keywords {
|
||||||
|
|||||||
@@ -34,10 +34,18 @@ describe("SigSelect", () => {
|
|||||||
<button class="btn btn-info fyi-btn" type="button">FYI</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>
|
||||||
|
<p class="stat-face-qualifier stat-face-qualifier--above"></p>
|
||||||
|
<p class="stat-face-title"></p>
|
||||||
|
<p class="stat-face-qualifier stat-face-qualifier--below"></p>
|
||||||
|
<p class="stat-face-arcana"></p>
|
||||||
<ul class="stat-keywords" id="id_stat_keywords_upright"></ul>
|
<ul class="stat-keywords" id="id_stat_keywords_upright"></ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-face stat-face--reversed">
|
<div class="stat-face stat-face--reversed">
|
||||||
<p class="stat-face-label">Reversal</p>
|
<p class="stat-face-label">Reversal</p>
|
||||||
|
<p class="stat-face-qualifier stat-face-qualifier--above"></p>
|
||||||
|
<p class="stat-face-title"></p>
|
||||||
|
<p class="stat-face-qualifier stat-face-qualifier--below"></p>
|
||||||
|
<p class="stat-face-arcana"></p>
|
||||||
<ul class="stat-keywords" id="id_stat_keywords_reversed"></ul>
|
<ul class="stat-keywords" id="id_stat_keywords_reversed"></ul>
|
||||||
</div>
|
</div>
|
||||||
<button class="btn btn-nav-left fyi-prev" type="button">◀</button>
|
<button class="btn btn-nav-left fyi-prev" type="button">◀</button>
|
||||||
@@ -742,6 +750,51 @@ describe("SigSelect", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ── Polarity theming — stat-block qualifier ────────────────────────────── //
|
||||||
|
//
|
||||||
|
// The qualifier's always-visible home: image-mode decks hide the text card
|
||||||
|
// face, so the stat-block (.stat-face--upright) carries Elevated/Enlightened/
|
||||||
|
// Graven now. Mirrors the card-face above/below placement (non-major above
|
||||||
|
// the title, major below it w. a trailing-comma title).
|
||||||
|
|
||||||
|
describe("polarity theming — stat-block qualifier", () => {
|
||||||
|
const UPRIGHT = ".stat-face--upright";
|
||||||
|
|
||||||
|
it("levity non-major puts qualifier ABOVE the stat-block title, below empty", () => {
|
||||||
|
makeFixture({ polarity: 'levity', userRole: 'PC' }); // fixture card = Minor Arcana
|
||||||
|
card.dispatchEvent(new MouseEvent("mouseenter", { bubbles: true }));
|
||||||
|
expect(statBlock.querySelector(UPRIGHT + " .stat-face-qualifier--above").textContent).toBe("Elevated");
|
||||||
|
expect(statBlock.querySelector(UPRIGHT + " .stat-face-qualifier--below").textContent).toBe("");
|
||||||
|
expect(statBlock.querySelector(UPRIGHT + " .stat-face-title").textContent).toBe("King of Pentacles");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("levity major puts qualifier BELOW the stat-block title, above empty, title gets comma", () => {
|
||||||
|
makeFixture({ polarity: 'levity', userRole: 'PC' });
|
||||||
|
card.dataset.arcana = "Major Arcana";
|
||||||
|
card.dispatchEvent(new MouseEvent("mouseenter", { bubbles: true }));
|
||||||
|
expect(statBlock.querySelector(UPRIGHT + " .stat-face-qualifier--above").textContent).toBe("");
|
||||||
|
expect(statBlock.querySelector(UPRIGHT + " .stat-face-qualifier--below").textContent).toBe("Elevated");
|
||||||
|
expect(statBlock.querySelector(UPRIGHT + " .stat-face-title").textContent).toBe("King of Pentacles,");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("gravity non-major puts 'Graven' ABOVE the stat-block title", () => {
|
||||||
|
makeFixture({ polarity: 'gravity', userRole: 'BC' });
|
||||||
|
card.dispatchEvent(new MouseEvent("mouseenter", { bubbles: true }));
|
||||||
|
expect(statBlock.querySelector(UPRIGHT + " .stat-face-qualifier--above").textContent).toBe("Graven");
|
||||||
|
expect(statBlock.querySelector(UPRIGHT + " .stat-face-qualifier--below").textContent).toBe("");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("re-hovering a different arcana re-places the stat-block qualifier slot", () => {
|
||||||
|
makeFixture({ polarity: 'levity', userRole: 'PC' });
|
||||||
|
card.dispatchEvent(new MouseEvent("mouseenter", { bubbles: true })); // non-major → above
|
||||||
|
card.dataset.arcana = "Major Arcana";
|
||||||
|
card.dispatchEvent(new MouseEvent("mouseleave", { bubbles: true }));
|
||||||
|
card.dispatchEvent(new MouseEvent("mouseenter", { bubbles: true })); // major → below
|
||||||
|
expect(statBlock.querySelector(UPRIGHT + " .stat-face-qualifier--above").textContent).toBe("");
|
||||||
|
expect(statBlock.querySelector(UPRIGHT + " .stat-face-qualifier--below").textContent).toBe("Elevated");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// ── WAIT NVM glow pulse ────────────────────────────────────────────────────── //
|
// ── WAIT NVM glow pulse ────────────────────────────────────────────────────── //
|
||||||
|
|
||||||
describe("WAIT NVM glow pulse", () => {
|
describe("WAIT NVM glow pulse", () => {
|
||||||
|
|||||||
@@ -238,7 +238,7 @@ this billboard surface re-brands to "Sign".
|
|||||||
StageCard.populateCard(stageCard, _currentCard, _polarity());
|
StageCard.populateCard(stageCard, _currentCard, _polarity());
|
||||||
StageCard.populateKeywords(statBlock,
|
StageCard.populateKeywords(statBlock,
|
||||||
_currentCard.keywords_upright, _currentCard.keywords_reversed);
|
_currentCard.keywords_upright, _currentCard.keywords_reversed);
|
||||||
StageCard.populateStatExtras(statBlock, _currentCard);
|
StageCard.populateStatExtras(statBlock, _currentCard, { polarity: _polarity() });
|
||||||
_fyiData = StageCard.buildInfoData(_currentCard);
|
_fyiData = StageCard.buildInfoData(_currentCard);
|
||||||
_fyiIdx = 0;
|
_fyiIdx = 0;
|
||||||
if (fyiPanel) StageCard.renderFyi(fyiPanel, _fyiData, _fyiIdx);
|
if (fyiPanel) StageCard.renderFyi(fyiPanel, _fyiData, _fyiIdx);
|
||||||
|
|||||||
@@ -32,7 +32,15 @@ Args:
|
|||||||
<p class="stat-face-label">{{ label_text }}</p>
|
<p class="stat-face-label">{{ label_text }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{# Polarity qualifier (Elevated/Enlightened/Graven) — mirrors the card-face #}
|
||||||
|
{# above/below placement: non-major arcana puts it ABOVE the title, major #}
|
||||||
|
{# puts it BELOW (the title then carries a trailing comma). JS-only #}
|
||||||
|
{# (stage-card.js populateStatExtras w. opts.polarity); empty server-side #}
|
||||||
|
{# since the qualifier is polarity-dependent. The card-face copy is going #}
|
||||||
|
{# away as decks gain images, so the stat-block becomes its sole home. #}
|
||||||
|
<p class="stat-face-qualifier stat-face-qualifier--above"></p>
|
||||||
<p class="stat-face-title">{% if card %}{{ card.name }}{% endif %}</p>
|
<p class="stat-face-title">{% if card %}{{ card.name }}{% endif %}</p>
|
||||||
|
<p class="stat-face-qualifier stat-face-qualifier--below"></p>
|
||||||
<p class="stat-face-arcana">{% if card %}{{ card.get_arcana_display }}{% 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>
|
<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>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user