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_