SIG SELECT sprint 1+2: SPIN animation; Emanation/Reversal; Ally Interaction FYI — TDD
Sprint 1 (template + SCSS):
- Stage card gains .fan-card-reversal-name + .fan-card-reversal-qualifier elements
(pre-rotated 180° so they read forward after card spins); sig cards gain data-reversal attr
- _card-deck.scss: Z-axis rotate(180deg) spin on .stage-card--reversed; reversed elements
dim @ opacity 0.25 normally, flip to 1 when card is spun; upright content dims in return
- Stat face labels: Upright→Emanation, Reversed→Reversal
- Fixture updated: Emanation/Reversal labels; reversal elements + data-reversal attr
Sprint 2 (FYI from mechanisms + articulations):
- sig-select.js: _openCaution() now parses data-mechanisms + data-articulations (concat)
instead of data-cautions; _renderCaution() sets .sig-caution-title from entry.category,
.sig-caution-effect.innerHTML from entry.effect; empty fallback: "No ally interactions"
- TarotCard model: mechanisms_json + articulations_json @property (parallel to cautions_json)
- Template: data-cautions→data-mechanisms+data-articulations; "Caution!"→"" title (set by JS);
"Rival Interaction"→"Ally Interaction"; shoptalk <p> removed
- SigSelectSpec.js: all old caution tests migrated to {category,effect} dict format +
data-mechanisms; 7-spec "FYI from mechanisms + articulations" describe block; 242 specs 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:
@@ -341,6 +341,16 @@ class TarotCard(models.Model):
|
|||||||
import json
|
import json
|
||||||
return json.dumps(self.cautions)
|
return json.dumps(self.cautions)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def mechanisms_json(self):
|
||||||
|
import json
|
||||||
|
return json.dumps(self.mechanisms)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def articulations_json(self):
|
||||||
|
import json
|
||||||
|
return json.dumps(self.articulations)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ var SigSelect = (function () {
|
|||||||
};
|
};
|
||||||
|
|
||||||
var overlay, deckGrid, stage, stageCard, statBlock;
|
var overlay, deckGrid, stage, stageCard, statBlock;
|
||||||
var cautionEl, cautionEffect, cautionPrev, cautionNext, cautionIndexEl;
|
var cautionEl, cautionEffect, cautionTitle, cautionPrev, cautionNext, cautionIndexEl;
|
||||||
var _flipBtn, _cautionBtn, _flipOrigLabel, _cautionOrigLabel;
|
var _flipBtn, _cautionBtn, _flipOrigLabel, _cautionOrigLabel;
|
||||||
var reserveUrl, readyUrl, userRole, userPolarity;
|
var reserveUrl, readyUrl, userRole, userPolarity;
|
||||||
|
|
||||||
@@ -47,13 +47,16 @@ var SigSelect = (function () {
|
|||||||
|
|
||||||
function _renderCaution() {
|
function _renderCaution() {
|
||||||
if (_cautionData.length === 0) {
|
if (_cautionData.length === 0) {
|
||||||
cautionEffect.innerHTML = '<em>Rival interactions pending.</em>';
|
if (cautionTitle) cautionTitle.textContent = 'Ally Interaction';
|
||||||
|
cautionEffect.innerHTML = '<em>No ally interactions defined.</em>';
|
||||||
cautionPrev.disabled = true;
|
cautionPrev.disabled = true;
|
||||||
cautionNext.disabled = true;
|
cautionNext.disabled = true;
|
||||||
cautionIndexEl.textContent = '';
|
cautionIndexEl.textContent = '';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
cautionEffect.innerHTML = _cautionData[_cautionIdx];
|
var entry = _cautionData[_cautionIdx];
|
||||||
|
if (cautionTitle) cautionTitle.textContent = entry.category || '';
|
||||||
|
cautionEffect.innerHTML = entry.effect || '';
|
||||||
cautionPrev.disabled = (_cautionData.length <= 1);
|
cautionPrev.disabled = (_cautionData.length <= 1);
|
||||||
cautionNext.disabled = (_cautionData.length <= 1);
|
cautionNext.disabled = (_cautionData.length <= 1);
|
||||||
cautionIndexEl.textContent = _cautionData.length > 1
|
cautionIndexEl.textContent = _cautionData.length > 1
|
||||||
@@ -64,7 +67,9 @@ var SigSelect = (function () {
|
|||||||
function _openCaution() {
|
function _openCaution() {
|
||||||
if (!_focusedCardEl) return;
|
if (!_focusedCardEl) return;
|
||||||
try {
|
try {
|
||||||
_cautionData = JSON.parse(_focusedCardEl.dataset.cautions || '[]');
|
var mechanisms = JSON.parse(_focusedCardEl.dataset.mechanisms || '[]');
|
||||||
|
var articulations = JSON.parse(_focusedCardEl.dataset.articulations || '[]');
|
||||||
|
_cautionData = mechanisms.concat(articulations);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
_cautionData = [];
|
_cautionData = [];
|
||||||
}
|
}
|
||||||
@@ -623,6 +628,7 @@ var SigSelect = (function () {
|
|||||||
|
|
||||||
cautionEl = stage.querySelector('.sig-caution-tooltip');
|
cautionEl = stage.querySelector('.sig-caution-tooltip');
|
||||||
cautionEffect = cautionEl.querySelector('.sig-caution-effect');
|
cautionEffect = cautionEl.querySelector('.sig-caution-effect');
|
||||||
|
cautionTitle = cautionEl.querySelector('.sig-caution-title');
|
||||||
cautionPrev = statBlock.querySelector('.sig-caution-prev');
|
cautionPrev = statBlock.querySelector('.sig-caution-prev');
|
||||||
cautionNext = statBlock.querySelector('.sig-caution-next');
|
cautionNext = statBlock.querySelector('.sig-caution-next');
|
||||||
cautionIndexEl = cautionEl.querySelector('.sig-caution-index');
|
cautionIndexEl = cautionEl.querySelector('.sig-caution-index');
|
||||||
|
|||||||
@@ -29,21 +29,20 @@ describe("SigSelect", () => {
|
|||||||
<button class="btn btn-reverse sig-flip-btn" type="button">SPIN</button>
|
<button class="btn btn-reverse sig-flip-btn" type="button">SPIN</button>
|
||||||
<button class="btn btn-caution sig-caution-btn" type="button">!!</button>
|
<button class="btn btn-caution sig-caution-btn" type="button">!!</button>
|
||||||
<div class="stat-face stat-face--upright">
|
<div class="stat-face stat-face--upright">
|
||||||
<p class="stat-face-label">Upright</p>
|
<p class="stat-face-label">Emanation</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">Reversed</p>
|
<p class="stat-face-label">Reversal</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 sig-caution-prev" type="button">◀</button>
|
<button class="btn btn-nav-left sig-caution-prev" type="button">◀</button>
|
||||||
<button class="btn btn-nav-right sig-caution-next" type="button">▶</button>
|
<button class="btn btn-nav-right sig-caution-next" type="button">▶</button>
|
||||||
<div class="sig-caution-tooltip" id="id_sig_caution">
|
<div class="sig-caution-tooltip" id="id_sig_caution">
|
||||||
<div class="sig-caution-header">
|
<div class="sig-caution-header">
|
||||||
<h4 class="sig-caution-title">Caution!</h4>
|
<h4 class="sig-caution-title"></h4>
|
||||||
<span class="sig-caution-type">Rival Interaction</span>
|
<span class="sig-caution-type">Ally Interaction</span>
|
||||||
</div>
|
</div>
|
||||||
<p class="sig-caution-shoptalk">[Shoptalk forthcoming]</p>
|
|
||||||
<p class="sig-caution-effect"></p>
|
<p class="sig-caution-effect"></p>
|
||||||
<span class="sig-caution-index"></span>
|
<span class="sig-caution-index"></span>
|
||||||
</div>
|
</div>
|
||||||
@@ -61,6 +60,8 @@ describe("SigSelect", () => {
|
|||||||
data-keywords-upright="action,impulsiveness,ambition"
|
data-keywords-upright="action,impulsiveness,ambition"
|
||||||
data-keywords-reversed="no direction,disregard for consequences"
|
data-keywords-reversed="no direction,disregard for consequences"
|
||||||
data-cautions="${cardCautions.replace(/"/g, '"')}"
|
data-cautions="${cardCautions.replace(/"/g, '"')}"
|
||||||
|
data-mechanisms="[]"
|
||||||
|
data-articulations="[]"
|
||||||
data-levity-qualifier="Elevated"
|
data-levity-qualifier="Elevated"
|
||||||
data-gravity-qualifier="Graven"
|
data-gravity-qualifier="Graven"
|
||||||
data-reversal="Territoriality">
|
data-reversal="Territoriality">
|
||||||
@@ -238,71 +239,96 @@ describe("SigSelect", () => {
|
|||||||
expect(testDiv.querySelector(".sig-stage").classList.contains("sig-caution-open")).toBe(true);
|
expect(testDiv.querySelector(".sig-stage").classList.contains("sig-caution-open")).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("shows placeholder text when cautions list is empty", () => {
|
it("shows placeholder when both mechanisms and articulations are empty", () => {
|
||||||
card.dataset.cautions = "[]";
|
card.dataset.mechanisms = "[]";
|
||||||
|
card.dataset.articulations = "[]";
|
||||||
openCaution();
|
openCaution();
|
||||||
expect(cautionEffect.innerHTML).toContain("pending");
|
expect(cautionEffect.innerHTML).toContain("No ally interactions defined");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("renders first caution effect HTML including .card-ref spans", () => {
|
it("renders first mechanism effect HTML including .card-ref spans", () => {
|
||||||
card.dataset.cautions = JSON.stringify(['First <span class="card-ref">Card</span> effect.']);
|
card.dataset.mechanisms = JSON.stringify([
|
||||||
|
{ category: "Mechanism", effect: 'First <span class="card-ref">Card</span> effect.' }
|
||||||
|
]);
|
||||||
openCaution();
|
openCaution();
|
||||||
expect(cautionEffect.querySelector(".card-ref")).not.toBeNull();
|
expect(cautionEffect.querySelector(".card-ref")).not.toBeNull();
|
||||||
expect(cautionEffect.querySelector(".card-ref").textContent).toBe("Card");
|
expect(cautionEffect.querySelector(".card-ref").textContent).toBe("Card");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("with 1 caution both nav arrows are disabled", () => {
|
it("with 1 entry both nav arrows are disabled", () => {
|
||||||
card.dataset.cautions = JSON.stringify(["Single caution."]);
|
card.dataset.mechanisms = JSON.stringify([{ category: "Mechanism", effect: "Single." }]);
|
||||||
openCaution();
|
openCaution();
|
||||||
expect(cautionPrev.disabled).toBe(true);
|
expect(cautionPrev.disabled).toBe(true);
|
||||||
expect(cautionNext.disabled).toBe(true);
|
expect(cautionNext.disabled).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("with multiple cautions both nav arrows are always enabled", () => {
|
it("with multiple entries both nav arrows are always enabled", () => {
|
||||||
card.dataset.cautions = JSON.stringify(["C1", "C2", "C3", "C4"]);
|
card.dataset.mechanisms = JSON.stringify([
|
||||||
|
{ category: "Mechanism", effect: "C1" },
|
||||||
|
{ category: "Mechanism", effect: "C2" },
|
||||||
|
{ category: "Mechanism", effect: "C3" },
|
||||||
|
{ category: "Mechanism", effect: "C4" },
|
||||||
|
]);
|
||||||
openCaution();
|
openCaution();
|
||||||
expect(cautionPrev.disabled).toBe(false);
|
expect(cautionPrev.disabled).toBe(false);
|
||||||
expect(cautionNext.disabled).toBe(false);
|
expect(cautionNext.disabled).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("next click advances to second caution", () => {
|
it("next click advances to second entry", () => {
|
||||||
card.dataset.cautions = JSON.stringify(["First", "Second"]);
|
card.dataset.mechanisms = JSON.stringify([
|
||||||
|
{ category: "Mechanism", effect: "First" },
|
||||||
|
{ category: "Mechanism", effect: "Second" },
|
||||||
|
]);
|
||||||
openCaution();
|
openCaution();
|
||||||
cautionNext.dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
cautionNext.dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
||||||
expect(cautionEffect.innerHTML).toContain("Second");
|
expect(cautionEffect.innerHTML).toContain("Second");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("next wraps from last caution back to first", () => {
|
it("next wraps from last entry back to first", () => {
|
||||||
card.dataset.cautions = JSON.stringify(["First", "Last"]);
|
card.dataset.mechanisms = JSON.stringify([
|
||||||
|
{ category: "Mechanism", effect: "First" },
|
||||||
|
{ category: "Mechanism", effect: "Last" },
|
||||||
|
]);
|
||||||
openCaution();
|
openCaution();
|
||||||
cautionNext.dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
cautionNext.dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
||||||
cautionNext.dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
cautionNext.dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
||||||
expect(cautionEffect.innerHTML).toContain("First");
|
expect(cautionEffect.innerHTML).toContain("First");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("prev click goes back to first caution", () => {
|
it("prev click goes back to first entry", () => {
|
||||||
card.dataset.cautions = JSON.stringify(["First", "Second"]);
|
card.dataset.mechanisms = JSON.stringify([
|
||||||
|
{ category: "Mechanism", effect: "First" },
|
||||||
|
{ category: "Mechanism", effect: "Second" },
|
||||||
|
]);
|
||||||
openCaution();
|
openCaution();
|
||||||
cautionNext.dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
cautionNext.dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
||||||
cautionPrev.dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
cautionPrev.dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
||||||
expect(cautionEffect.innerHTML).toContain("First");
|
expect(cautionEffect.innerHTML).toContain("First");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("prev wraps from first caution to last", () => {
|
it("prev wraps from first entry to last", () => {
|
||||||
card.dataset.cautions = JSON.stringify(["First", "Middle", "Last"]);
|
card.dataset.mechanisms = JSON.stringify([
|
||||||
|
{ category: "Mechanism", effect: "First" },
|
||||||
|
{ category: "Mechanism", effect: "Middle" },
|
||||||
|
{ category: "Mechanism", effect: "Last" },
|
||||||
|
]);
|
||||||
openCaution();
|
openCaution();
|
||||||
cautionPrev.dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
cautionPrev.dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
||||||
expect(cautionEffect.innerHTML).toContain("Last");
|
expect(cautionEffect.innerHTML).toContain("Last");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("index label shows n / total when multiple cautions", () => {
|
it("index label shows n / total when multiple entries", () => {
|
||||||
card.dataset.cautions = JSON.stringify(["C1", "C2", "C3"]);
|
card.dataset.mechanisms = JSON.stringify([
|
||||||
|
{ category: "Mechanism", effect: "C1" },
|
||||||
|
{ category: "Mechanism", effect: "C2" },
|
||||||
|
{ category: "Mechanism", effect: "C3" },
|
||||||
|
]);
|
||||||
openCaution();
|
openCaution();
|
||||||
expect(testDiv.querySelector(".sig-caution-index").textContent).toBe("1 / 3");
|
expect(testDiv.querySelector(".sig-caution-index").textContent).toBe("1 / 3");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("index label is empty when only 1 caution", () => {
|
it("index label is empty when only 1 entry", () => {
|
||||||
card.dataset.cautions = JSON.stringify(["Only one."]);
|
card.dataset.mechanisms = JSON.stringify([{ category: "Mechanism", effect: "Only one." }]);
|
||||||
openCaution();
|
openCaution();
|
||||||
expect(testDiv.querySelector(".sig-caution-index").textContent).toBe("");
|
expect(testDiv.querySelector(".sig-caution-index").textContent).toBe("");
|
||||||
});
|
});
|
||||||
@@ -314,8 +340,11 @@ describe("SigSelect", () => {
|
|||||||
expect(testDiv.querySelector(".sig-stage").classList.contains("sig-caution-open")).toBe(false);
|
expect(testDiv.querySelector(".sig-stage").classList.contains("sig-caution-open")).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("opening again resets to first caution", () => {
|
it("opening again resets to first entry", () => {
|
||||||
card.dataset.cautions = JSON.stringify(["First", "Second"]);
|
card.dataset.mechanisms = JSON.stringify([
|
||||||
|
{ category: "Mechanism", effect: "First" },
|
||||||
|
{ category: "Mechanism", effect: "Second" },
|
||||||
|
]);
|
||||||
openCaution();
|
openCaution();
|
||||||
cautionNext.dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
cautionNext.dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
||||||
// Close and reopen
|
// Close and reopen
|
||||||
@@ -757,4 +786,87 @@ describe("SigSelect", () => {
|
|||||||
expect(takeSigBtn.classList.contains("btn-cancel")).toBe(false);
|
expect(takeSigBtn.classList.contains("btn-cancel")).toBe(false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ── FYI tooltip — mechanisms + articulations data source ──────────────── //
|
||||||
|
//
|
||||||
|
// Sprint 2: the caution tooltip is reworked to draw from data-mechanisms and
|
||||||
|
// data-articulations instead of data-cautions. Entries are {category, effect}
|
||||||
|
// dicts; the category label replaces the old "Caution!" title; the caution-type
|
||||||
|
// reads "Ally Interaction". Shoptalk is absent.
|
||||||
|
|
||||||
|
describe("FYI from mechanisms + articulations", () => {
|
||||||
|
var cautionEffect, cautionTitle, cautionType, cautionPrev, cautionNext, cautionBtn;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
makeFixture();
|
||||||
|
cautionEffect = testDiv.querySelector(".sig-caution-effect");
|
||||||
|
cautionTitle = testDiv.querySelector(".sig-caution-title");
|
||||||
|
cautionType = testDiv.querySelector(".sig-caution-type");
|
||||||
|
cautionPrev = testDiv.querySelector(".sig-caution-prev");
|
||||||
|
cautionNext = testDiv.querySelector(".sig-caution-next");
|
||||||
|
cautionBtn = testDiv.querySelector(".sig-caution-btn");
|
||||||
|
});
|
||||||
|
|
||||||
|
function hover() {
|
||||||
|
card.dispatchEvent(new MouseEvent("mouseenter", { bubbles: true }));
|
||||||
|
}
|
||||||
|
function openFYI() {
|
||||||
|
hover();
|
||||||
|
cautionBtn.dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
||||||
|
}
|
||||||
|
|
||||||
|
it("caution-type label reads 'Ally Interaction'", () => {
|
||||||
|
openFYI();
|
||||||
|
expect(cautionType.textContent).toBe("Ally Interaction");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("shows 'No ally interactions defined.' when both lists are empty", () => {
|
||||||
|
card.dataset.mechanisms = "[]";
|
||||||
|
card.dataset.articulations = "[]";
|
||||||
|
openFYI();
|
||||||
|
expect(cautionEffect.textContent).toContain("No ally interactions defined");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders first mechanism effect and sets title to its category", () => {
|
||||||
|
card.dataset.mechanisms = JSON.stringify([
|
||||||
|
{ category: "Mechanism", effect: "The card amplifies adjacent power." }
|
||||||
|
]);
|
||||||
|
openFYI();
|
||||||
|
expect(cautionTitle.textContent).toBe("Mechanism");
|
||||||
|
expect(cautionEffect.textContent).toContain("amplifies adjacent power");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("mechanisms come before articulations in the combined list", () => {
|
||||||
|
card.dataset.mechanisms = JSON.stringify([{ category: "Mechanism", effect: "First" }]);
|
||||||
|
card.dataset.articulations = JSON.stringify([{ category: "Articulation", effect: "Second" }]);
|
||||||
|
openFYI();
|
||||||
|
expect(cautionEffect.textContent).toContain("First");
|
||||||
|
cautionNext.dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
||||||
|
expect(cautionEffect.textContent).toContain("Second");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("articulation title is set from its category field", () => {
|
||||||
|
card.dataset.mechanisms = JSON.stringify([{ category: "Mechanism", effect: "M1" }]);
|
||||||
|
card.dataset.articulations = JSON.stringify([{ category: "Articulation", effect: "A1" }]);
|
||||||
|
openFYI();
|
||||||
|
cautionNext.dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
||||||
|
expect(cautionTitle.textContent).toBe("Articulation");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("effect HTML is injected (supports .card-ref spans)", () => {
|
||||||
|
card.dataset.mechanisms = JSON.stringify([
|
||||||
|
{ category: "Mechanism", effect: 'Draw <span class="card-ref">The Occultist</span>.' }
|
||||||
|
]);
|
||||||
|
openFYI();
|
||||||
|
expect(cautionEffect.querySelector(".card-ref")).not.toBeNull();
|
||||||
|
expect(cautionEffect.querySelector(".card-ref").textContent).toBe("The Occultist");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("shoptalk element is absent or empty", () => {
|
||||||
|
openFYI();
|
||||||
|
var shoptalk = testDiv.querySelector(".sig-caution-shoptalk");
|
||||||
|
// Either removed from DOM or has no visible content
|
||||||
|
expect(!shoptalk || shoptalk.textContent.trim() === "").toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -173,7 +173,6 @@ html:has(.sig-backdrop) {
|
|||||||
align-items: flex-end;
|
align-items: flex-end;
|
||||||
padding-left: 1.5rem;
|
padding-left: 1.5rem;
|
||||||
gap: 0.75rem;
|
gap: 0.75rem;
|
||||||
|
|
||||||
// Preview card — width driven by JS via --sig-card-w; aspect-ratio derives height.
|
// Preview card — width driven by JS via --sig-card-w; aspect-ratio derives height.
|
||||||
.sig-stage-card {
|
.sig-stage-card {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
@@ -188,6 +187,7 @@ html:has(.sig-backdrop) {
|
|||||||
position: relative;
|
position: relative;
|
||||||
padding: 0.25rem;
|
padding: 0.25rem;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
transition: transform 0.4s ease;
|
||||||
|
|
||||||
// game-kit sets .fan-card-corner { position: absolute; top/left offsets }
|
// game-kit sets .fan-card-corner { position: absolute; top/left offsets }
|
||||||
// so these just need display/font overrides; the corners land at the card edges.
|
// so these just need display/font overrides; the corners land at the card edges.
|
||||||
@@ -230,6 +230,26 @@ html:has(.sig-backdrop) {
|
|||||||
.fan-card-name { font-size: calc(var(--sig-card-w, 120px) * 0.093); font-weight: 600; }
|
.fan-card-name { font-size: calc(var(--sig-card-w, 120px) * 0.093); font-weight: 600; }
|
||||||
.fan-card-arcana { font-size: calc(var(--sig-card-w, 120px) * 0.067); text-transform: uppercase; letter-spacing: 0.06em; opacity: 0.5; }
|
.fan-card-arcana { font-size: calc(var(--sig-card-w, 120px) * 0.067); text-transform: uppercase; letter-spacing: 0.06em; opacity: 0.5; }
|
||||||
.fan-card-correspondence{ display: none; } // Minchiate equivalence shown in game-kit only
|
.fan-card-correspondence{ display: none; } // Minchiate equivalence shown in game-kit only
|
||||||
|
// Reversed face — counter-rotated so they read forward when card is spun
|
||||||
|
.fan-card-reversal-qualifier,
|
||||||
|
.fan-card-reversal-name {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
opacity: 0.25;
|
||||||
|
transition: opacity 0.2s;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
.fan-card-reversal-qualifier { font-size: calc(var(--sig-card-w, 120px) * 0.093); }
|
||||||
|
.fan-card-reversal-name { font-size: calc(var(--sig-card-w, 120px) * 0.073); opacity: 0.2; }
|
||||||
|
}
|
||||||
|
|
||||||
|
&.stage-card--reversed {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
|
||||||
|
.fan-card-reversal-qualifier,
|
||||||
|
.fan-card-reversal-name { opacity: 1; }
|
||||||
|
.fan-card-name,
|
||||||
|
.sig-qualifier-above,
|
||||||
|
.sig-qualifier-below { opacity: 0.25; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,21 +29,20 @@ describe("SigSelect", () => {
|
|||||||
<button class="btn btn-reverse sig-flip-btn" type="button">SPIN</button>
|
<button class="btn btn-reverse sig-flip-btn" type="button">SPIN</button>
|
||||||
<button class="btn btn-caution sig-caution-btn" type="button">!!</button>
|
<button class="btn btn-caution sig-caution-btn" type="button">!!</button>
|
||||||
<div class="stat-face stat-face--upright">
|
<div class="stat-face stat-face--upright">
|
||||||
<p class="stat-face-label">Upright</p>
|
<p class="stat-face-label">Emanation</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">Reversed</p>
|
<p class="stat-face-label">Reversal</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 sig-caution-prev" type="button">◀</button>
|
<button class="btn btn-nav-left sig-caution-prev" type="button">◀</button>
|
||||||
<button class="btn btn-nav-right sig-caution-next" type="button">▶</button>
|
<button class="btn btn-nav-right sig-caution-next" type="button">▶</button>
|
||||||
<div class="sig-caution-tooltip" id="id_sig_caution">
|
<div class="sig-caution-tooltip" id="id_sig_caution">
|
||||||
<div class="sig-caution-header">
|
<div class="sig-caution-header">
|
||||||
<h4 class="sig-caution-title">Caution!</h4>
|
<h4 class="sig-caution-title"></h4>
|
||||||
<span class="sig-caution-type">Rival Interaction</span>
|
<span class="sig-caution-type">Ally Interaction</span>
|
||||||
</div>
|
</div>
|
||||||
<p class="sig-caution-shoptalk">[Shoptalk forthcoming]</p>
|
|
||||||
<p class="sig-caution-effect"></p>
|
<p class="sig-caution-effect"></p>
|
||||||
<span class="sig-caution-index"></span>
|
<span class="sig-caution-index"></span>
|
||||||
</div>
|
</div>
|
||||||
@@ -61,6 +60,8 @@ describe("SigSelect", () => {
|
|||||||
data-keywords-upright="action,impulsiveness,ambition"
|
data-keywords-upright="action,impulsiveness,ambition"
|
||||||
data-keywords-reversed="no direction,disregard for consequences"
|
data-keywords-reversed="no direction,disregard for consequences"
|
||||||
data-cautions="${cardCautions.replace(/"/g, '"')}"
|
data-cautions="${cardCautions.replace(/"/g, '"')}"
|
||||||
|
data-mechanisms="[]"
|
||||||
|
data-articulations="[]"
|
||||||
data-levity-qualifier="Elevated"
|
data-levity-qualifier="Elevated"
|
||||||
data-gravity-qualifier="Graven"
|
data-gravity-qualifier="Graven"
|
||||||
data-reversal="Territoriality">
|
data-reversal="Territoriality">
|
||||||
@@ -238,71 +239,96 @@ describe("SigSelect", () => {
|
|||||||
expect(testDiv.querySelector(".sig-stage").classList.contains("sig-caution-open")).toBe(true);
|
expect(testDiv.querySelector(".sig-stage").classList.contains("sig-caution-open")).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("shows placeholder text when cautions list is empty", () => {
|
it("shows placeholder when both mechanisms and articulations are empty", () => {
|
||||||
card.dataset.cautions = "[]";
|
card.dataset.mechanisms = "[]";
|
||||||
|
card.dataset.articulations = "[]";
|
||||||
openCaution();
|
openCaution();
|
||||||
expect(cautionEffect.innerHTML).toContain("pending");
|
expect(cautionEffect.innerHTML).toContain("No ally interactions defined");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("renders first caution effect HTML including .card-ref spans", () => {
|
it("renders first mechanism effect HTML including .card-ref spans", () => {
|
||||||
card.dataset.cautions = JSON.stringify(['First <span class="card-ref">Card</span> effect.']);
|
card.dataset.mechanisms = JSON.stringify([
|
||||||
|
{ category: "Mechanism", effect: 'First <span class="card-ref">Card</span> effect.' }
|
||||||
|
]);
|
||||||
openCaution();
|
openCaution();
|
||||||
expect(cautionEffect.querySelector(".card-ref")).not.toBeNull();
|
expect(cautionEffect.querySelector(".card-ref")).not.toBeNull();
|
||||||
expect(cautionEffect.querySelector(".card-ref").textContent).toBe("Card");
|
expect(cautionEffect.querySelector(".card-ref").textContent).toBe("Card");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("with 1 caution both nav arrows are disabled", () => {
|
it("with 1 entry both nav arrows are disabled", () => {
|
||||||
card.dataset.cautions = JSON.stringify(["Single caution."]);
|
card.dataset.mechanisms = JSON.stringify([{ category: "Mechanism", effect: "Single." }]);
|
||||||
openCaution();
|
openCaution();
|
||||||
expect(cautionPrev.disabled).toBe(true);
|
expect(cautionPrev.disabled).toBe(true);
|
||||||
expect(cautionNext.disabled).toBe(true);
|
expect(cautionNext.disabled).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("with multiple cautions both nav arrows are always enabled", () => {
|
it("with multiple entries both nav arrows are always enabled", () => {
|
||||||
card.dataset.cautions = JSON.stringify(["C1", "C2", "C3", "C4"]);
|
card.dataset.mechanisms = JSON.stringify([
|
||||||
|
{ category: "Mechanism", effect: "C1" },
|
||||||
|
{ category: "Mechanism", effect: "C2" },
|
||||||
|
{ category: "Mechanism", effect: "C3" },
|
||||||
|
{ category: "Mechanism", effect: "C4" },
|
||||||
|
]);
|
||||||
openCaution();
|
openCaution();
|
||||||
expect(cautionPrev.disabled).toBe(false);
|
expect(cautionPrev.disabled).toBe(false);
|
||||||
expect(cautionNext.disabled).toBe(false);
|
expect(cautionNext.disabled).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("next click advances to second caution", () => {
|
it("next click advances to second entry", () => {
|
||||||
card.dataset.cautions = JSON.stringify(["First", "Second"]);
|
card.dataset.mechanisms = JSON.stringify([
|
||||||
|
{ category: "Mechanism", effect: "First" },
|
||||||
|
{ category: "Mechanism", effect: "Second" },
|
||||||
|
]);
|
||||||
openCaution();
|
openCaution();
|
||||||
cautionNext.dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
cautionNext.dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
||||||
expect(cautionEffect.innerHTML).toContain("Second");
|
expect(cautionEffect.innerHTML).toContain("Second");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("next wraps from last caution back to first", () => {
|
it("next wraps from last entry back to first", () => {
|
||||||
card.dataset.cautions = JSON.stringify(["First", "Last"]);
|
card.dataset.mechanisms = JSON.stringify([
|
||||||
|
{ category: "Mechanism", effect: "First" },
|
||||||
|
{ category: "Mechanism", effect: "Last" },
|
||||||
|
]);
|
||||||
openCaution();
|
openCaution();
|
||||||
cautionNext.dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
cautionNext.dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
||||||
cautionNext.dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
cautionNext.dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
||||||
expect(cautionEffect.innerHTML).toContain("First");
|
expect(cautionEffect.innerHTML).toContain("First");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("prev click goes back to first caution", () => {
|
it("prev click goes back to first entry", () => {
|
||||||
card.dataset.cautions = JSON.stringify(["First", "Second"]);
|
card.dataset.mechanisms = JSON.stringify([
|
||||||
|
{ category: "Mechanism", effect: "First" },
|
||||||
|
{ category: "Mechanism", effect: "Second" },
|
||||||
|
]);
|
||||||
openCaution();
|
openCaution();
|
||||||
cautionNext.dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
cautionNext.dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
||||||
cautionPrev.dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
cautionPrev.dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
||||||
expect(cautionEffect.innerHTML).toContain("First");
|
expect(cautionEffect.innerHTML).toContain("First");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("prev wraps from first caution to last", () => {
|
it("prev wraps from first entry to last", () => {
|
||||||
card.dataset.cautions = JSON.stringify(["First", "Middle", "Last"]);
|
card.dataset.mechanisms = JSON.stringify([
|
||||||
|
{ category: "Mechanism", effect: "First" },
|
||||||
|
{ category: "Mechanism", effect: "Middle" },
|
||||||
|
{ category: "Mechanism", effect: "Last" },
|
||||||
|
]);
|
||||||
openCaution();
|
openCaution();
|
||||||
cautionPrev.dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
cautionPrev.dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
||||||
expect(cautionEffect.innerHTML).toContain("Last");
|
expect(cautionEffect.innerHTML).toContain("Last");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("index label shows n / total when multiple cautions", () => {
|
it("index label shows n / total when multiple entries", () => {
|
||||||
card.dataset.cautions = JSON.stringify(["C1", "C2", "C3"]);
|
card.dataset.mechanisms = JSON.stringify([
|
||||||
|
{ category: "Mechanism", effect: "C1" },
|
||||||
|
{ category: "Mechanism", effect: "C2" },
|
||||||
|
{ category: "Mechanism", effect: "C3" },
|
||||||
|
]);
|
||||||
openCaution();
|
openCaution();
|
||||||
expect(testDiv.querySelector(".sig-caution-index").textContent).toBe("1 / 3");
|
expect(testDiv.querySelector(".sig-caution-index").textContent).toBe("1 / 3");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("index label is empty when only 1 caution", () => {
|
it("index label is empty when only 1 entry", () => {
|
||||||
card.dataset.cautions = JSON.stringify(["Only one."]);
|
card.dataset.mechanisms = JSON.stringify([{ category: "Mechanism", effect: "Only one." }]);
|
||||||
openCaution();
|
openCaution();
|
||||||
expect(testDiv.querySelector(".sig-caution-index").textContent).toBe("");
|
expect(testDiv.querySelector(".sig-caution-index").textContent).toBe("");
|
||||||
});
|
});
|
||||||
@@ -314,8 +340,11 @@ describe("SigSelect", () => {
|
|||||||
expect(testDiv.querySelector(".sig-stage").classList.contains("sig-caution-open")).toBe(false);
|
expect(testDiv.querySelector(".sig-stage").classList.contains("sig-caution-open")).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("opening again resets to first caution", () => {
|
it("opening again resets to first entry", () => {
|
||||||
card.dataset.cautions = JSON.stringify(["First", "Second"]);
|
card.dataset.mechanisms = JSON.stringify([
|
||||||
|
{ category: "Mechanism", effect: "First" },
|
||||||
|
{ category: "Mechanism", effect: "Second" },
|
||||||
|
]);
|
||||||
openCaution();
|
openCaution();
|
||||||
cautionNext.dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
cautionNext.dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
||||||
// Close and reopen
|
// Close and reopen
|
||||||
@@ -757,4 +786,87 @@ describe("SigSelect", () => {
|
|||||||
expect(takeSigBtn.classList.contains("btn-cancel")).toBe(false);
|
expect(takeSigBtn.classList.contains("btn-cancel")).toBe(false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ── FYI tooltip — mechanisms + articulations data source ──────────────── //
|
||||||
|
//
|
||||||
|
// Sprint 2: the caution tooltip is reworked to draw from data-mechanisms and
|
||||||
|
// data-articulations instead of data-cautions. Entries are {category, effect}
|
||||||
|
// dicts; the category label replaces the old "Caution!" title; the caution-type
|
||||||
|
// reads "Ally Interaction". Shoptalk is absent.
|
||||||
|
|
||||||
|
describe("FYI from mechanisms + articulations", () => {
|
||||||
|
var cautionEffect, cautionTitle, cautionType, cautionPrev, cautionNext, cautionBtn;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
makeFixture();
|
||||||
|
cautionEffect = testDiv.querySelector(".sig-caution-effect");
|
||||||
|
cautionTitle = testDiv.querySelector(".sig-caution-title");
|
||||||
|
cautionType = testDiv.querySelector(".sig-caution-type");
|
||||||
|
cautionPrev = testDiv.querySelector(".sig-caution-prev");
|
||||||
|
cautionNext = testDiv.querySelector(".sig-caution-next");
|
||||||
|
cautionBtn = testDiv.querySelector(".sig-caution-btn");
|
||||||
|
});
|
||||||
|
|
||||||
|
function hover() {
|
||||||
|
card.dispatchEvent(new MouseEvent("mouseenter", { bubbles: true }));
|
||||||
|
}
|
||||||
|
function openFYI() {
|
||||||
|
hover();
|
||||||
|
cautionBtn.dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
||||||
|
}
|
||||||
|
|
||||||
|
it("caution-type label reads 'Ally Interaction'", () => {
|
||||||
|
openFYI();
|
||||||
|
expect(cautionType.textContent).toBe("Ally Interaction");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("shows 'No ally interactions defined.' when both lists are empty", () => {
|
||||||
|
card.dataset.mechanisms = "[]";
|
||||||
|
card.dataset.articulations = "[]";
|
||||||
|
openFYI();
|
||||||
|
expect(cautionEffect.textContent).toContain("No ally interactions defined");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders first mechanism effect and sets title to its category", () => {
|
||||||
|
card.dataset.mechanisms = JSON.stringify([
|
||||||
|
{ category: "Mechanism", effect: "The card amplifies adjacent power." }
|
||||||
|
]);
|
||||||
|
openFYI();
|
||||||
|
expect(cautionTitle.textContent).toBe("Mechanism");
|
||||||
|
expect(cautionEffect.textContent).toContain("amplifies adjacent power");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("mechanisms come before articulations in the combined list", () => {
|
||||||
|
card.dataset.mechanisms = JSON.stringify([{ category: "Mechanism", effect: "First" }]);
|
||||||
|
card.dataset.articulations = JSON.stringify([{ category: "Articulation", effect: "Second" }]);
|
||||||
|
openFYI();
|
||||||
|
expect(cautionEffect.textContent).toContain("First");
|
||||||
|
cautionNext.dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
||||||
|
expect(cautionEffect.textContent).toContain("Second");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("articulation title is set from its category field", () => {
|
||||||
|
card.dataset.mechanisms = JSON.stringify([{ category: "Mechanism", effect: "M1" }]);
|
||||||
|
card.dataset.articulations = JSON.stringify([{ category: "Articulation", effect: "A1" }]);
|
||||||
|
openFYI();
|
||||||
|
cautionNext.dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
||||||
|
expect(cautionTitle.textContent).toBe("Articulation");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("effect HTML is injected (supports .card-ref spans)", () => {
|
||||||
|
card.dataset.mechanisms = JSON.stringify([
|
||||||
|
{ category: "Mechanism", effect: 'Draw <span class="card-ref">The Occultist</span>.' }
|
||||||
|
]);
|
||||||
|
openFYI();
|
||||||
|
expect(cautionEffect.querySelector(".card-ref")).not.toBeNull();
|
||||||
|
expect(cautionEffect.querySelector(".card-ref").textContent).toBe("The Occultist");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("shoptalk element is absent or empty", () => {
|
||||||
|
openFYI();
|
||||||
|
var shoptalk = testDiv.querySelector(".sig-caution-shoptalk");
|
||||||
|
// Either removed from DOM or has no visible content
|
||||||
|
expect(!shoptalk || shoptalk.textContent.trim() === "").toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -29,6 +29,8 @@ Context: sig_cards, user_polarity, user_seat, sig_reserve_url, sig_reservations_
|
|||||||
<p class="sig-qualifier-below"></p>
|
<p class="sig-qualifier-below"></p>
|
||||||
<p class="fan-card-arcana"></p>
|
<p class="fan-card-arcana"></p>
|
||||||
<p class="fan-card-correspondence"></p>{# not shown in sig-select — game-kit only #}
|
<p class="fan-card-correspondence"></p>{# not shown in sig-select — game-kit only #}
|
||||||
|
<p class="fan-card-reversal-qualifier"></p>
|
||||||
|
<p class="fan-card-reversal-name"></p>
|
||||||
</div>
|
</div>
|
||||||
<div class="fan-card-corner fan-card-corner--br">
|
<div class="fan-card-corner fan-card-corner--br">
|
||||||
<span class="fan-corner-rank"></span>
|
<span class="fan-corner-rank"></span>
|
||||||
@@ -39,19 +41,18 @@ Context: sig_cards, user_polarity, user_seat, sig_reserve_url, sig_reservations_
|
|||||||
<button class="btn btn-reverse sig-flip-btn" type="button">SPIN</button>
|
<button class="btn btn-reverse sig-flip-btn" type="button">SPIN</button>
|
||||||
<button class="btn btn-caution sig-caution-btn" type="button">FYI</button>
|
<button class="btn btn-caution sig-caution-btn" type="button">FYI</button>
|
||||||
<div class="stat-face stat-face--upright">
|
<div class="stat-face stat-face--upright">
|
||||||
<p class="stat-face-label">Upright</p>
|
<p class="stat-face-label">Emanation</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">Reversed</p>
|
<p class="stat-face-label">Reversal</p>
|
||||||
<ul class="stat-keywords" id="id_stat_keywords_reversed"></ul>
|
<ul class="stat-keywords" id="id_stat_keywords_reversed"></ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="sig-caution-tooltip" id="id_sig_caution">
|
<div class="sig-caution-tooltip" id="id_sig_caution">
|
||||||
<div class="sig-caution-header">
|
<div class="sig-caution-header">
|
||||||
<h4 class="sig-caution-title">Caution!</h4>
|
<h4 class="sig-caution-title"></h4>
|
||||||
<p class="sig-caution-type">Rival Interaction</p>
|
<p class="sig-caution-type">Ally Interaction</p>
|
||||||
</div>
|
</div>
|
||||||
<p class="sig-caution-shoptalk">[Shoptalk forthcoming]</p>
|
|
||||||
<p class="sig-caution-effect"></p>
|
<p class="sig-caution-effect"></p>
|
||||||
<span class="sig-caution-index"></span>
|
<span class="sig-caution-index"></span>
|
||||||
</div>
|
</div>
|
||||||
@@ -72,9 +73,11 @@ Context: sig_cards, user_polarity, user_seat, sig_reserve_url, sig_reservations_
|
|||||||
data-correspondence="{{ card.correspondence|default:'' }}"
|
data-correspondence="{{ card.correspondence|default:'' }}"
|
||||||
data-keywords-upright="{{ card.keywords_upright|join:',' }}"
|
data-keywords-upright="{{ card.keywords_upright|join:',' }}"
|
||||||
data-keywords-reversed="{{ card.keywords_reversed|join:',' }}"
|
data-keywords-reversed="{{ card.keywords_reversed|join:',' }}"
|
||||||
data-cautions="{{ card.cautions_json }}"
|
data-mechanisms="{{ card.mechanisms_json }}"
|
||||||
|
data-articulations="{{ card.articulations_json }}"
|
||||||
data-levity-qualifier="{{ card.levity_qualifier }}"
|
data-levity-qualifier="{{ card.levity_qualifier }}"
|
||||||
data-gravity-qualifier="{{ card.gravity_qualifier }}">
|
data-gravity-qualifier="{{ card.gravity_qualifier }}"
|
||||||
|
data-reversal="{{ card.reversal }}">
|
||||||
<div class="fan-card-corner fan-card-corner--tl">
|
<div class="fan-card-corner fan-card-corner--tl">
|
||||||
<span class="fan-corner-rank">{{ card.corner_rank }}</span>
|
<span class="fan-corner-rank">{{ card.corner_rank }}</span>
|
||||||
{% if card.suit_icon %}<i class="fa-solid {{ card.suit_icon }}"></i>{% endif %}
|
{% if card.suit_icon %}<i class="fa-solid {{ card.suit_icon }}"></i>{% endif %}
|
||||||
|
|||||||
Reference in New Issue
Block a user