Game Kit fan stage + FLIP/SPIN; sig/sea/fan refactor — TDD
- fan modal: stage block w. idle-reveal/careen-out; carousel shifts left so focused card sits left-of-center; SPIN rotates whole card via Element.animate(); FLIP toggles polarity (Levity ↔ Gravity) via perspective rotateY w. mid-flip repaint; SPIN state retained across FLIP; FLIP btn hover-revealed only when focused card or btn is hovered (:has) - mobile breakpoints: --fan-card-w / --fan-card-h / --fan-stage-shift / --fan-carousel-step lifted to CSS vars on .tarot-fan-wrap; portrait ≤ 480px @ 150×230, landscape ≤ 500h @ 150×235; corners + face text/padding scale w. card width - shared StageCard JS module (apps/epic/stage-card.js): fromDataset, populateCard, populateKeywords, buildInfoData, renderFyi — sig/sea/fan all delegate; ~150 lines de-duplicated - shared @mixin stat-block-shared (SCSS) lifts duplicated stat-face / stat-keywords / sig-info rules; @mixin stage-card-polarity unifies sea-stage--levity/--gravity + fan[data-polarity] coloring - model rename: TarotCard.reversal → reversal_qualifier (migration 0014); render-time fallback to current polarity's qualifier when blank - class unification: .sig-info-open / .sea-info-open / .fyi-open → .fyi-open (on stat block); .sig-flip-btn / .sea-spin-btn / .fan-spin-btn → .spin-btn; same for .fyi-btn / .fyi-prev / .fyi-next - custom combobox (apps/epic/combobox.js) replaces native <select> for PICK SEA spread picker — keyboard nav, click-outside-close, aria roles; Firefox/Chrome OS-rendered <option> ignored CSS - Jasmine: FanStageSpec.js w. idle-reveal / population / SPIN / FYI / FLIP specs; sig + sea fixtures + IT view assertions updated for renamed classes - 748 ITs + Jasmine green Code architected by Disco DeDisco <discodedisco@outlook.com> Git commit message Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -30,8 +30,8 @@ describe("SigSelect", () => {
|
||||
</div>
|
||||
</div>
|
||||
<div class="sig-stat-block">
|
||||
<button class="btn btn-reverse sig-flip-btn" type="button">SPIN</button>
|
||||
<button class="btn btn-info sig-info-btn" type="button">FYI</button>
|
||||
<button class="btn btn-reverse spin-btn" type="button">SPIN</button>
|
||||
<button class="btn btn-info fyi-btn" type="button">FYI</button>
|
||||
<div class="stat-face stat-face--upright">
|
||||
<p class="stat-face-label">Emanation</p>
|
||||
<ul class="stat-keywords" id="id_stat_keywords_upright"></ul>
|
||||
@@ -40,8 +40,8 @@ describe("SigSelect", () => {
|
||||
<p class="stat-face-label">Reversal</p>
|
||||
<ul class="stat-keywords" id="id_stat_keywords_reversed"></ul>
|
||||
</div>
|
||||
<button class="btn btn-nav-left sig-info-prev" type="button">◀</button>
|
||||
<button class="btn btn-nav-right sig-info-next" type="button">▶</button>
|
||||
<button class="btn btn-nav-left fyi-prev" type="button">◀</button>
|
||||
<button class="btn btn-nav-right fyi-next" type="button">▶</button>
|
||||
<div class="sig-info" id="id_sig_info">
|
||||
<div class="sig-info-header">
|
||||
<h4 class="sig-info-title"></h4>
|
||||
@@ -67,7 +67,7 @@ describe("SigSelect", () => {
|
||||
data-operations="[]"
|
||||
data-levity-qualifier="Elevated"
|
||||
data-gravity-qualifier="Graven"
|
||||
data-reversal="">
|
||||
data-reversal-qualifier="">
|
||||
<div class="fan-card-corner fan-card-corner--tl">
|
||||
<span class="fan-corner-rank">K</span>
|
||||
</div>
|
||||
@@ -211,24 +211,24 @@ describe("SigSelect", () => {
|
||||
infoTitle = testDiv.querySelector(".sig-info-title");
|
||||
infoType = testDiv.querySelector(".sig-info-type");
|
||||
infoIndex = testDiv.querySelector(".sig-info-index");
|
||||
infoPrev = testDiv.querySelector(".sig-info-prev");
|
||||
infoNext = testDiv.querySelector(".sig-info-next");
|
||||
infoBtn = testDiv.querySelector(".sig-info-btn");
|
||||
infoPrev = testDiv.querySelector(".fyi-prev");
|
||||
infoNext = testDiv.querySelector(".fyi-next");
|
||||
infoBtn = testDiv.querySelector(".fyi-btn");
|
||||
});
|
||||
|
||||
function hover() { card.dispatchEvent(new MouseEvent("mouseenter", { bubbles: true })); }
|
||||
function openFYI() { hover(); infoBtn.dispatchEvent(new MouseEvent("click", { bubbles: true })); }
|
||||
|
||||
it("FYI click adds .sig-info-open to the stage", () => {
|
||||
it("FYI click adds .fyi-open to the stat block", () => {
|
||||
openFYI();
|
||||
expect(testDiv.querySelector(".sig-stage").classList.contains("sig-info-open")).toBe(true);
|
||||
expect(testDiv.querySelector(".sig-stat-block").classList.contains("fyi-open")).toBe(true);
|
||||
});
|
||||
|
||||
it("FYI click when btn-disabled does not toggle", () => {
|
||||
openFYI();
|
||||
expect(infoBtn.classList.contains("btn-disabled")).toBe(true);
|
||||
infoBtn.dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
||||
expect(testDiv.querySelector(".sig-stage").classList.contains("sig-info-open")).toBe(true);
|
||||
expect(testDiv.querySelector(".sig-stat-block").classList.contains("fyi-open")).toBe(true);
|
||||
});
|
||||
|
||||
it("shows placeholder when both energies and operations are empty", () => {
|
||||
@@ -370,9 +370,9 @@ describe("SigSelect", () => {
|
||||
|
||||
it("card mouseleave closes the info panel", () => {
|
||||
openFYI();
|
||||
expect(testDiv.querySelector(".sig-stage").classList.contains("sig-info-open")).toBe(true);
|
||||
expect(testDiv.querySelector(".sig-stat-block").classList.contains("fyi-open")).toBe(true);
|
||||
card.dispatchEvent(new MouseEvent("mouseleave", { bubbles: true }));
|
||||
expect(testDiv.querySelector(".sig-stage").classList.contains("sig-info-open")).toBe(false);
|
||||
expect(testDiv.querySelector(".sig-stat-block").classList.contains("fyi-open")).toBe(false);
|
||||
});
|
||||
|
||||
it("opening again resets to first entry", () => {
|
||||
@@ -389,7 +389,7 @@ describe("SigSelect", () => {
|
||||
|
||||
it("opening info panel adds .btn-disabled and swaps SPIN/FYI labels to ×", () => {
|
||||
openFYI();
|
||||
var flipBtn = testDiv.querySelector(".sig-flip-btn");
|
||||
var flipBtn = testDiv.querySelector(".spin-btn");
|
||||
expect(flipBtn.classList.contains("btn-disabled")).toBe(true);
|
||||
expect(infoBtn.classList.contains("btn-disabled")).toBe(true);
|
||||
expect(flipBtn.textContent).toBe("×");
|
||||
@@ -397,7 +397,7 @@ describe("SigSelect", () => {
|
||||
});
|
||||
|
||||
it("closing info panel removes .btn-disabled and restores SPIN/FYI labels", () => {
|
||||
var flipBtn = testDiv.querySelector(".sig-flip-btn");
|
||||
var flipBtn = testDiv.querySelector(".spin-btn");
|
||||
var origFlip = flipBtn.textContent;
|
||||
var origInfo = infoBtn.textContent;
|
||||
openFYI();
|
||||
@@ -411,14 +411,14 @@ describe("SigSelect", () => {
|
||||
it("clicking the info panel closes it", () => {
|
||||
openFYI();
|
||||
infoEffect.dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
||||
expect(testDiv.querySelector(".sig-stage").classList.contains("sig-info-open")).toBe(false);
|
||||
expect(testDiv.querySelector(".sig-stat-block").classList.contains("fyi-open")).toBe(false);
|
||||
});
|
||||
|
||||
it("SPIN click when info open (btn-disabled) does nothing", () => {
|
||||
openFYI();
|
||||
var flipBtn = testDiv.querySelector(".sig-flip-btn");
|
||||
var flipBtn = testDiv.querySelector(".spin-btn");
|
||||
flipBtn.dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
||||
expect(testDiv.querySelector(".sig-stage").classList.contains("sig-info-open")).toBe(true);
|
||||
expect(testDiv.querySelector(".sig-stat-block").classList.contains("fyi-open")).toBe(true);
|
||||
expect(statBlock.classList.contains("is-reversed")).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -447,14 +447,14 @@ describe("SigSelect", () => {
|
||||
|
||||
it("SPIN click adds .is-reversed to the stat block", () => {
|
||||
card.dispatchEvent(new MouseEvent("mouseenter", { bubbles: true }));
|
||||
var flipBtn = statBlock.querySelector(".sig-flip-btn");
|
||||
var flipBtn = statBlock.querySelector(".spin-btn");
|
||||
flipBtn.dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
||||
expect(statBlock.classList.contains("is-reversed")).toBe(true);
|
||||
});
|
||||
|
||||
it("second SPIN click removes .is-reversed", () => {
|
||||
card.dispatchEvent(new MouseEvent("mouseenter", { bubbles: true }));
|
||||
var flipBtn = statBlock.querySelector(".sig-flip-btn");
|
||||
var flipBtn = statBlock.querySelector(".spin-btn");
|
||||
flipBtn.dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
||||
flipBtn.dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
||||
expect(statBlock.classList.contains("is-reversed")).toBe(false);
|
||||
@@ -462,7 +462,7 @@ describe("SigSelect", () => {
|
||||
|
||||
it("hovering a new card resets .is-reversed", () => {
|
||||
card.dispatchEvent(new MouseEvent("mouseenter", { bubbles: true }));
|
||||
statBlock.querySelector(".sig-flip-btn").dispatchEvent(
|
||||
statBlock.querySelector(".spin-btn").dispatchEvent(
|
||||
new MouseEvent("click", { bubbles: true })
|
||||
);
|
||||
expect(statBlock.classList.contains("is-reversed")).toBe(true);
|
||||
@@ -491,7 +491,7 @@ describe("SigSelect", () => {
|
||||
it("SPIN click adds .stage-card--reversed to the stage card", () => {
|
||||
makeFixture();
|
||||
hover();
|
||||
statBlock.querySelector(".sig-flip-btn")
|
||||
statBlock.querySelector(".spin-btn")
|
||||
.dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
||||
expect(stageCard.classList.contains("stage-card--reversed")).toBe(true);
|
||||
});
|
||||
@@ -499,7 +499,7 @@ describe("SigSelect", () => {
|
||||
it("second SPIN click removes .stage-card--reversed", () => {
|
||||
makeFixture();
|
||||
hover();
|
||||
var flipBtn = statBlock.querySelector(".sig-flip-btn");
|
||||
var flipBtn = statBlock.querySelector(".spin-btn");
|
||||
flipBtn.dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
||||
flipBtn.dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
||||
expect(stageCard.classList.contains("stage-card--reversed")).toBe(false);
|
||||
@@ -508,7 +508,7 @@ describe("SigSelect", () => {
|
||||
it("hovering a new card resets .stage-card--reversed", () => {
|
||||
makeFixture();
|
||||
hover();
|
||||
statBlock.querySelector(".sig-flip-btn")
|
||||
statBlock.querySelector(".spin-btn")
|
||||
.dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
||||
expect(stageCard.classList.contains("stage-card--reversed")).toBe(true);
|
||||
card.dispatchEvent(new MouseEvent("mouseleave", { bubbles: true }));
|
||||
@@ -516,9 +516,9 @@ describe("SigSelect", () => {
|
||||
expect(stageCard.classList.contains("stage-card--reversed")).toBe(false);
|
||||
});
|
||||
|
||||
it("non-major with data-reversal: reversal-qualifier = suit word, reversal-name = card name", () => {
|
||||
it("non-major with data-reversal-qualifier: reversal-qualifier = suit word, reversal-name = card name", () => {
|
||||
makeFixture();
|
||||
card.dataset.reversal = "Nervous";
|
||||
card.dataset.reversalQualifier = "Nervous";
|
||||
hover();
|
||||
expect(stageCard.querySelector(".fan-card-reversal-qualifier").textContent).toBe("Nervous");
|
||||
expect(stageCard.querySelector(".fan-card-reversal-name").textContent)
|
||||
@@ -539,9 +539,9 @@ describe("SigSelect", () => {
|
||||
.toBe("Graven");
|
||||
});
|
||||
|
||||
it("non-major with data-reversal: suit qualifier on own line, upright name repeated below", () => {
|
||||
it("non-major with data-reversal-qualifier: suit qualifier on own line, upright name repeated below", () => {
|
||||
makeFixture({ polarity: "levity", userRole: "PC" });
|
||||
card.dataset.reversal = "Vacant";
|
||||
card.dataset.reversalQualifier = "Vacant";
|
||||
hover();
|
||||
expect(stageCard.querySelector(".fan-card-reversal-qualifier").textContent).toBe("Vacant");
|
||||
expect(stageCard.querySelector(".fan-card-reversal-name").textContent)
|
||||
@@ -558,7 +558,7 @@ describe("SigSelect", () => {
|
||||
expect(stageCard.querySelector(".fan-card-reversal-name").textContent).toBe("Elevated");
|
||||
});
|
||||
|
||||
it("non-major without data-reversal: qualifier mirrors polarity, name repeats card title", () => {
|
||||
it("non-major without data-reversal-qualifier: qualifier mirrors polarity, name repeats card title", () => {
|
||||
makeFixture({ polarity: "levity", userRole: "PC" });
|
||||
// fixture default: Minor Arcana, no reversal word
|
||||
hover();
|
||||
|
||||
Reference in New Issue
Block a user