diff --git a/src/apps/epic/static/apps/epic/sig-select.js b/src/apps/epic/static/apps/epic/sig-select.js index 5a68a49..3224e43 100644 --- a/src/apps/epic/static/apps/epic/sig-select.js +++ b/src/apps/epic/static/apps/epic/sig-select.js @@ -102,7 +102,7 @@ var SigSelect = (function () { // (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 // stayed blank. - StageCard.populateStatExtras(statBlock, card); + StageCard.populateStatExtras(statBlock, card, { polarity: userPolarity }); stageCard.style.display = ''; stage.classList.add('sig-stage--active'); diff --git a/src/apps/epic/static/apps/epic/stage-card.js b/src/apps/epic/static/apps/epic/stage-card.js index 625db0e..24a2aa8 100644 --- a/src/apps/epic/static/apps/epic/stage-card.js +++ b/src/apps/epic/static/apps/epic/stage-card.js @@ -287,8 +287,38 @@ var StageCard = (function () { var rawTitle = card.name_title || card.name || ''; var title = rawTitle.split(',')[0].trim(); 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) { - 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) { el.textContent = arcana; diff --git a/src/functional_tests/test_game_room_select_sig.py b/src/functional_tests/test_game_room_select_sig.py index 8c9a95e..ec795be 100644 --- a/src/functional_tests/test_game_room_select_sig.py +++ b/src/functional_tests/test_game_room_select_sig.py @@ -1,5 +1,4 @@ import os -from unittest import skip from django.conf import settings as django_settings from django.test import tag @@ -256,71 +255,80 @@ class SigSelectThemeTest(FunctionalTest): ActionChains(self.browser).move_to_element(card).perform() 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 ──────────────────────────────────── # - @skip( - "Qualifier (Elevated/Enlightened/Graven) now belongs on the always-" - "visible stat-block, not the card face: the Earthman deck renders in " - "image-mode (has_card_images defaults True) so the text card-face is " - "display:none. Unskip + repoint to the stat-block next." - ) + # Qualifier asserts target the frozen stat-block (.stat-face--upright), not + # the text card-face: image-mode decks hide .fan-card-face, and the qualifier + # is moving to the always-present stat-block. It mirrors the card-face + # above/below placement — non-major puts the qualifier ABOVE the title, major + # 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): - """Hovering a non-major card in the levity overlay shows 'Elevated' in - qualifier-above and nothing in qualifier-below.""" + """Selecting (OK) a non-major card shows 'Elevated' in the frozen + stat-block qualifier-above and nothing in qualifier-below.""" room = self._setup_sig_room() self.create_pre_authenticated_session("founder@test.io") # PC = levity 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._hover_card('.sig-card[data-arcana="Middle Arcana"]') + self._select_card_and_freeze('.sig-card[data-arcana="Middle Arcana"]') - above = self.wait_for( - lambda: self.browser.find_element(By.CSS_SELECTOR, ".sig-qualifier-above") - ) - self.assertEqual(above.text, "Elevated") - below = self.browser.find_element(By.CSS_SELECTOR, ".sig-qualifier-below") + self.wait_for(lambda: self.assertEqual( + self.browser.find_element(By.CSS_SELECTOR, self.UPRIGHT_QUAL_ABOVE).text, + "Elevated", + )) + below = self.browser.find_element(By.CSS_SELECTOR, self.UPRIGHT_QUAL_BELOW) 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): - """Hovering a major arcana card in the levity overlay shows 'Enlightened' in - qualifier-below and nothing in qualifier-above.""" + """Selecting (OK) a major arcana card shows 'Enlightened' in the frozen + stat-block qualifier-below and nothing in qualifier-above.""" room = self._setup_sig_room() self.create_pre_authenticated_session("founder@test.io") # PC = levity 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._hover_card('.sig-card[data-arcana="Major Arcana"]') + self._select_card_and_freeze('.sig-card[data-arcana="Major Arcana"]') - below = self.wait_for( - lambda: self.browser.find_element(By.CSS_SELECTOR, ".sig-qualifier-below") - ) - self.assertEqual(below.text, "Enlightened") - above = self.browser.find_element(By.CSS_SELECTOR, ".sig-qualifier-above") + self.wait_for(lambda: self.assertEqual( + self.browser.find_element(By.CSS_SELECTOR, self.UPRIGHT_QUAL_BELOW).text, + "Enlightened", + )) + above = self.browser.find_element(By.CSS_SELECTOR, self.UPRIGHT_QUAL_ABOVE) self.assertEqual(above.text, "") # ── 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): - """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() self.create_pre_authenticated_session("bud@test.io") # EC = gravity 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._hover_card('.sig-card[data-arcana="Middle Arcana"]') + self._select_card_and_freeze('.sig-card[data-arcana="Middle Arcana"]') - above = self.wait_for( - lambda: self.browser.find_element(By.CSS_SELECTOR, ".sig-qualifier-above") - ) - self.assertEqual(above.text, "Graven") + self.wait_for(lambda: self.assertEqual( + self.browser.find_element(By.CSS_SELECTOR, self.UPRIGHT_QUAL_ABOVE).text, + "Graven", + )) # ── ST3: Correspondence not shown ─────────────────────────────────────── # diff --git a/src/static/tests/SigSelectSpec.js b/src/static/tests/SigSelectSpec.js index 330de47..d24cea3 100644 --- a/src/static/tests/SigSelectSpec.js +++ b/src/static/tests/SigSelectSpec.js @@ -34,10 +34,18 @@ describe("SigSelect", () => {
Emanation
+ + + +Reversal
+ + + +Emanation
+ + + +Reversal
+ + + +{{ label_text }}
+ {# 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. #} +{% if card %}{{ card.name }}{% endif %}
+{% if card %}{{ card.get_arcana_display }}{% endif %}