diff --git a/src/apps/epic/static/apps/epic/sig-select.js b/src/apps/epic/static/apps/epic/sig-select.js index f9c88bb..6fc49be 100644 --- a/src/apps/epic/static/apps/epic/sig-select.js +++ b/src/apps/epic/static/apps/epic/sig-select.js @@ -127,8 +127,13 @@ var SigSelect = (function () { stageCard.querySelector('.sig-qualifier-above').textContent = isMajor ? '' : qualifier; stageCard.querySelector('.sig-qualifier-below').textContent = isMajor ? qualifier : ''; + // Reversed face — same qualifier, polarity-resolved reversal title + stageCard.querySelector('.fan-card-reversal-qualifier').textContent = qualifier; + stageCard.querySelector('.fan-card-reversal-name').textContent = cardEl.dataset.reversal || ''; + // Populate stat block keyword faces and reset to upright statBlock.classList.remove('is-reversed'); + stageCard.classList.remove('stage-card--reversed'); _populateKeywordList( statBlock.querySelector('#id_stat_keywords_upright'), cardEl.dataset.keywordsUpright @@ -613,6 +618,7 @@ var SigSelect = (function () { _flipBtn.addEventListener('click', function () { if (_flipBtn.classList.contains('btn-disabled')) return; statBlock.classList.toggle('is-reversed'); + stageCard.classList.toggle('stage-card--reversed'); }); cautionEl = stage.querySelector('.sig-caution-tooltip'); diff --git a/src/static/tests/SigSelectSpec.js b/src/static/tests/SigSelectSpec.js index 33065c8..a93d148 100644 --- a/src/static/tests/SigSelectSpec.js +++ b/src/static/tests/SigSelectSpec.js @@ -22,9 +22,11 @@ describe("SigSelect", () => {

+

+

- +

Upright

@@ -60,7 +62,8 @@ describe("SigSelect", () => { data-keywords-reversed="no direction,disregard for consequences" data-cautions="${cardCautions.replace(/"/g, '"')}" data-levity-qualifier="Elevated" - data-gravity-qualifier="Graven"> + data-gravity-qualifier="Graven" + data-reversal="Territoriality">
K
@@ -321,7 +324,7 @@ describe("SigSelect", () => { expect(cautionEffect.innerHTML).toContain("First"); }); - it("opening caution adds .btn-disabled and swaps labels to ×", () => { + it("opening caution adds .btn-disabled and swaps SPIN/FYI labels to ×", () => { openCaution(); var flipBtn = testDiv.querySelector(".sig-flip-btn"); expect(flipBtn.classList.contains("btn-disabled")).toBe(true); @@ -330,7 +333,7 @@ describe("SigSelect", () => { expect(cautionBtn.textContent).toBe("\u00D7"); }); - it("closing caution removes .btn-disabled and restores original labels", () => { + it("closing caution removes .btn-disabled and restores SPIN/FYI labels", () => { var flipBtn = testDiv.querySelector(".sig-flip-btn"); var origFlip = flipBtn.textContent; var origCaution = cautionBtn.textContent; @@ -348,7 +351,7 @@ describe("SigSelect", () => { expect(testDiv.querySelector(".sig-stage").classList.contains("sig-caution-open")).toBe(false); }); - it("FLIP click when caution open (btn-disabled) does nothing", () => { + it("SPIN click when caution open (btn-disabled) does nothing", () => { openCaution(); var flipBtn = testDiv.querySelector(".sig-flip-btn"); flipBtn.dispatchEvent(new MouseEvent("click", { bubbles: true })); @@ -357,9 +360,9 @@ describe("SigSelect", () => { }); }); - // ── Stat block: keyword population and FLIP toggle ────────────────── // + // ── Stat block: keyword population and SPIN toggle ────────────────── // - describe("stat block and FLIP", () => { + describe("stat block and SPIN", () => { beforeEach(() => makeFixture()); it("populates upright keywords when a card is hovered", () => { @@ -379,14 +382,14 @@ describe("SigSelect", () => { expect(items[1].textContent).toBe("disregard for consequences"); }); - it("FLIP click adds .is-reversed to the stat block", () => { + it("SPIN click adds .is-reversed to the stat block", () => { card.dispatchEvent(new MouseEvent("mouseenter", { bubbles: true })); var flipBtn = statBlock.querySelector(".sig-flip-btn"); flipBtn.dispatchEvent(new MouseEvent("click", { bubbles: true })); expect(statBlock.classList.contains("is-reversed")).toBe(true); }); - it("second FLIP click removes .is-reversed", () => { + it("second SPIN click removes .is-reversed", () => { card.dispatchEvent(new MouseEvent("mouseenter", { bubbles: true })); var flipBtn = statBlock.querySelector(".sig-flip-btn"); flipBtn.dispatchEvent(new MouseEvent("click", { bubbles: true })); @@ -416,6 +419,76 @@ describe("SigSelect", () => { }); }); + // ── SPIN card animation — stage-card reversed state ──────────────────── // + + describe("SPIN card animation", () => { + function hover() { + card.dispatchEvent(new MouseEvent("mouseenter", { bubbles: true })); + } + + it("SPIN click adds .stage-card--reversed to the stage card", () => { + makeFixture(); + hover(); + statBlock.querySelector(".sig-flip-btn") + .dispatchEvent(new MouseEvent("click", { bubbles: true })); + expect(stageCard.classList.contains("stage-card--reversed")).toBe(true); + }); + + it("second SPIN click removes .stage-card--reversed", () => { + makeFixture(); + hover(); + var flipBtn = statBlock.querySelector(".sig-flip-btn"); + flipBtn.dispatchEvent(new MouseEvent("click", { bubbles: true })); + flipBtn.dispatchEvent(new MouseEvent("click", { bubbles: true })); + expect(stageCard.classList.contains("stage-card--reversed")).toBe(false); + }); + + it("hovering a new card resets .stage-card--reversed", () => { + makeFixture(); + hover(); + statBlock.querySelector(".sig-flip-btn") + .dispatchEvent(new MouseEvent("click", { bubbles: true })); + expect(stageCard.classList.contains("stage-card--reversed")).toBe(true); + card.dispatchEvent(new MouseEvent("mouseleave", { bubbles: true })); + card.dispatchEvent(new MouseEvent("mouseenter", { bubbles: true })); + expect(stageCard.classList.contains("stage-card--reversed")).toBe(false); + }); + + it("updateStage() populates fan-card-reversal-name from data-reversal", () => { + makeFixture(); + card.dataset.reversal = "Territoriality"; + hover(); + expect(stageCard.querySelector(".fan-card-reversal-name").textContent) + .toBe("Territoriality"); + }); + + it("updateStage() populates fan-card-reversal-qualifier with levity qualifier", () => { + makeFixture({ polarity: "levity", userRole: "PC" }); + hover(); + expect(stageCard.querySelector(".fan-card-reversal-qualifier").textContent) + .toBe("Elevated"); + }); + + it("updateStage() populates fan-card-reversal-qualifier with gravity qualifier", () => { + makeFixture({ polarity: "gravity", userRole: "BC" }); + hover(); + expect(stageCard.querySelector(".fan-card-reversal-qualifier").textContent) + .toBe("Graven"); + }); + + it("hovering a card without data-reversal clears the reversal name", () => { + makeFixture(); + card.dataset.reversal = "Territoriality"; + hover(); + card.dispatchEvent(new MouseEvent("mouseleave", { bubbles: true })); + delete card.dataset.reversal; + card.dispatchEvent(new MouseEvent("mouseenter", { bubbles: true })); + // reversal-name clears because data-reversal is gone; + // reversal-qualifier stays (it always mirrors the polarity qualifier) + expect(stageCard.querySelector(".fan-card-reversal-name").textContent).toBe(""); + }); + }); + // ── WS cursor hover (applyHover) ──────────────────────────────────────── // // // Fixture polarity = levity, userRole = PC. diff --git a/src/static_src/scss/_button-pad.scss b/src/static_src/scss/_button-pad.scss index 0c83eb4..9d5e7be 100644 --- a/src/static_src/scss/_button-pad.scss +++ b/src/static_src/scss/_button-pad.scss @@ -24,6 +24,7 @@ border: 0.18rem solid rgba(var(--priUser), 1); } + // BIG btn &.btn-primary { width: 4rem; height: 4rem; @@ -70,6 +71,7 @@ } } + // BYE btn &.btn-abandon { color: rgba(var(--priBl), 1); border-color: rgba(var(--priBl), 1); @@ -105,6 +107,7 @@ } } + // DEL btn &.btn-cancel { color: rgba(var(--priOr), 1); border-color: rgba(var(--priOr), 1); @@ -139,6 +142,7 @@ } } + // FYI btn &.btn-caution { color: rgba(var(--priYl), 1); border-color: rgba(var(--priYl), 1); @@ -174,6 +178,7 @@ } } + // OK btn &.btn-confirm { color: rgba(var(--priGn), 1); border-color: rgba(var(--priGn), 1); @@ -209,6 +214,7 @@ } } + // DEL btn &.btn-danger { color: rgba(var(--priRd), 1); background-color: rgba(var(--terRd), 1); @@ -286,6 +292,7 @@ font-size: 0.75rem; // 0.63rem × 1.2 } + // PRV btn &.btn-nav-left { color: rgba(var(--priFs), 1); border-color: rgba(var(--priFs), 1); @@ -321,6 +328,7 @@ } } + // NXT btn &.btn-nav-right { color: rgba(var(--priLm), 1); border-color: rgba(var(--priLm), 1); @@ -356,6 +364,7 @@ } } + // DON btn &.btn-equip { color: rgba(var(--priTk), 1); border-color: rgba(var(--priTk), 1); @@ -391,6 +400,7 @@ } } + // DOFF btn &.btn-unequip { color: rgba(var(--priMe), 1); border-color: rgba(var(--priMe), 1); @@ -426,7 +436,8 @@ } } - &.btn-reverse { + // FLIP btn + &.btn-reveal { color: rgba(var(--priCy), 1); border-color: rgba(var(--priCy), 1); background-color: rgba(var(--terCy), 1); @@ -461,37 +472,38 @@ } } - &.btn-tip { - color: rgba(var(--priLm), 1); - border-color: rgba(var(--priLm), 1); - background-color: rgba(var(--terLm), 1); + // SPIN btn + &.btn-reverse { + color: rgba(var(--priCy), 1); + border-color: rgba(var(--priCy), 1); + background-color: rgba(var(--terCy), 1); box-shadow: - 0.1rem 0.1rem 0.12rem rgba(var(--terLm), 0.25), + 0.1rem 0.1rem 0.12rem rgba(var(--terCy), 0.25), 0.12rem 0.12rem 0.25rem rgba(0, 0, 0, 0.25), - 0.25rem 0.25rem 0.25rem rgba(var(--terLm), 0.12) + 0.25rem 0.25rem 0.25rem rgba(var(--terCy), 0.12) ; &:hover { text-shadow: 0.1rem 0.1rem 0.1rem rgba(0, 0, 0, 0.25), - 0 0 1rem rgba(var(--priLm), 1) + 0 0 1rem rgba(var(--priCy), 1) ; box-shadow: 0.12rem 0.12rem 0.5rem rgba(0, 0, 0, 0.25), - 0 0 0.5rem rgba(var(--priLm), 0.12) + 0 0 0.5rem rgba(var(--priCy), 0.12) ; } &:active { - border: 0.18rem solid rgba(var(--priLm), 1); + border: 0.18rem solid rgba(var(--priCy), 1); text-shadow: -0.1rem -0.1rem 0.25rem rgba(0, 0, 0, 0.25), - 0 0 0.12rem rgba(var(--priLm), 1) + 0 0 0.12rem rgba(var(--priCy), 1) ; box-shadow: - -0.1rem -0.1rem 0.12rem rgba(var(--terLm), 0.25), + -0.1rem -0.1rem 0.12rem rgba(var(--terCy), 0.25), -0.1rem -0.1rem 0.12rem rgba(0, 0, 0, 0.25), - 0 0 0.5rem rgba(var(--priLm), 0.12) + 0 0 0.5rem rgba(var(--priCy), 0.12) ; } } diff --git a/src/static_src/tests/SigSelectSpec.js b/src/static_src/tests/SigSelectSpec.js index 33065c8..a93d148 100644 --- a/src/static_src/tests/SigSelectSpec.js +++ b/src/static_src/tests/SigSelectSpec.js @@ -22,9 +22,11 @@ describe("SigSelect", () => {

+

+

- +

Upright

@@ -60,7 +62,8 @@ describe("SigSelect", () => { data-keywords-reversed="no direction,disregard for consequences" data-cautions="${cardCautions.replace(/"/g, '"')}" data-levity-qualifier="Elevated" - data-gravity-qualifier="Graven"> + data-gravity-qualifier="Graven" + data-reversal="Territoriality">
K
@@ -321,7 +324,7 @@ describe("SigSelect", () => { expect(cautionEffect.innerHTML).toContain("First"); }); - it("opening caution adds .btn-disabled and swaps labels to ×", () => { + it("opening caution adds .btn-disabled and swaps SPIN/FYI labels to ×", () => { openCaution(); var flipBtn = testDiv.querySelector(".sig-flip-btn"); expect(flipBtn.classList.contains("btn-disabled")).toBe(true); @@ -330,7 +333,7 @@ describe("SigSelect", () => { expect(cautionBtn.textContent).toBe("\u00D7"); }); - it("closing caution removes .btn-disabled and restores original labels", () => { + it("closing caution removes .btn-disabled and restores SPIN/FYI labels", () => { var flipBtn = testDiv.querySelector(".sig-flip-btn"); var origFlip = flipBtn.textContent; var origCaution = cautionBtn.textContent; @@ -348,7 +351,7 @@ describe("SigSelect", () => { expect(testDiv.querySelector(".sig-stage").classList.contains("sig-caution-open")).toBe(false); }); - it("FLIP click when caution open (btn-disabled) does nothing", () => { + it("SPIN click when caution open (btn-disabled) does nothing", () => { openCaution(); var flipBtn = testDiv.querySelector(".sig-flip-btn"); flipBtn.dispatchEvent(new MouseEvent("click", { bubbles: true })); @@ -357,9 +360,9 @@ describe("SigSelect", () => { }); }); - // ── Stat block: keyword population and FLIP toggle ────────────────── // + // ── Stat block: keyword population and SPIN toggle ────────────────── // - describe("stat block and FLIP", () => { + describe("stat block and SPIN", () => { beforeEach(() => makeFixture()); it("populates upright keywords when a card is hovered", () => { @@ -379,14 +382,14 @@ describe("SigSelect", () => { expect(items[1].textContent).toBe("disregard for consequences"); }); - it("FLIP click adds .is-reversed to the stat block", () => { + it("SPIN click adds .is-reversed to the stat block", () => { card.dispatchEvent(new MouseEvent("mouseenter", { bubbles: true })); var flipBtn = statBlock.querySelector(".sig-flip-btn"); flipBtn.dispatchEvent(new MouseEvent("click", { bubbles: true })); expect(statBlock.classList.contains("is-reversed")).toBe(true); }); - it("second FLIP click removes .is-reversed", () => { + it("second SPIN click removes .is-reversed", () => { card.dispatchEvent(new MouseEvent("mouseenter", { bubbles: true })); var flipBtn = statBlock.querySelector(".sig-flip-btn"); flipBtn.dispatchEvent(new MouseEvent("click", { bubbles: true })); @@ -416,6 +419,76 @@ describe("SigSelect", () => { }); }); + // ── SPIN card animation — stage-card reversed state ──────────────────── // + + describe("SPIN card animation", () => { + function hover() { + card.dispatchEvent(new MouseEvent("mouseenter", { bubbles: true })); + } + + it("SPIN click adds .stage-card--reversed to the stage card", () => { + makeFixture(); + hover(); + statBlock.querySelector(".sig-flip-btn") + .dispatchEvent(new MouseEvent("click", { bubbles: true })); + expect(stageCard.classList.contains("stage-card--reversed")).toBe(true); + }); + + it("second SPIN click removes .stage-card--reversed", () => { + makeFixture(); + hover(); + var flipBtn = statBlock.querySelector(".sig-flip-btn"); + flipBtn.dispatchEvent(new MouseEvent("click", { bubbles: true })); + flipBtn.dispatchEvent(new MouseEvent("click", { bubbles: true })); + expect(stageCard.classList.contains("stage-card--reversed")).toBe(false); + }); + + it("hovering a new card resets .stage-card--reversed", () => { + makeFixture(); + hover(); + statBlock.querySelector(".sig-flip-btn") + .dispatchEvent(new MouseEvent("click", { bubbles: true })); + expect(stageCard.classList.contains("stage-card--reversed")).toBe(true); + card.dispatchEvent(new MouseEvent("mouseleave", { bubbles: true })); + card.dispatchEvent(new MouseEvent("mouseenter", { bubbles: true })); + expect(stageCard.classList.contains("stage-card--reversed")).toBe(false); + }); + + it("updateStage() populates fan-card-reversal-name from data-reversal", () => { + makeFixture(); + card.dataset.reversal = "Territoriality"; + hover(); + expect(stageCard.querySelector(".fan-card-reversal-name").textContent) + .toBe("Territoriality"); + }); + + it("updateStage() populates fan-card-reversal-qualifier with levity qualifier", () => { + makeFixture({ polarity: "levity", userRole: "PC" }); + hover(); + expect(stageCard.querySelector(".fan-card-reversal-qualifier").textContent) + .toBe("Elevated"); + }); + + it("updateStage() populates fan-card-reversal-qualifier with gravity qualifier", () => { + makeFixture({ polarity: "gravity", userRole: "BC" }); + hover(); + expect(stageCard.querySelector(".fan-card-reversal-qualifier").textContent) + .toBe("Graven"); + }); + + it("hovering a card without data-reversal clears the reversal name", () => { + makeFixture(); + card.dataset.reversal = "Territoriality"; + hover(); + card.dispatchEvent(new MouseEvent("mouseleave", { bubbles: true })); + delete card.dataset.reversal; + card.dispatchEvent(new MouseEvent("mouseenter", { bubbles: true })); + // reversal-name clears because data-reversal is gone; + // reversal-qualifier stays (it always mirrors the polarity qualifier) + expect(stageCard.querySelector(".fan-card-reversal-name").textContent).toBe(""); + }); + }); + // ── WS cursor hover (applyHover) ──────────────────────────────────────── // // // Fixture polarity = levity, userRole = PC. diff --git a/src/templates/apps/gameboard/_partials/_sig_select_overlay.html b/src/templates/apps/gameboard/_partials/_sig_select_overlay.html index b51c501..e20e40b 100644 --- a/src/templates/apps/gameboard/_partials/_sig_select_overlay.html +++ b/src/templates/apps/gameboard/_partials/_sig_select_overlay.html @@ -36,7 +36,7 @@ Context: sig_cards, user_polarity, user_seat, sig_reserve_url, sig_reservations_
- +

Upright