SIG SELECT: non-major reversal display; face wrapper divs; middle arcana reversal seed — TDD
- sig-select.js: three-way reversal branch — major (qualifier + concept name), non-major w. reversal (suit qualifier word on own line + card title), non-major fallback (polarity qualifier only) - template: .fan-card-face-upright + .fan-card-face-reversal wrapper divs for compact centred text groups; arcana label sits between them - _card-deck.scss: wrapper divs display:flex; padding-top on reversal group equalises gap to MIDDLE ARCANA label on both sides; removes margin:auto overcorrect - migration 0007: populates reversal qualifier word per suit on Earthman Middle Arcana court cards (Seething/Gloomy/Nervous/Vacant); clears The Schizo's incorrectly inherited Territoriality reversal 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:
@@ -0,0 +1,73 @@
|
|||||||
|
"""Populate TarotCard.reversal for Earthman Middle Arcana court cards.
|
||||||
|
|
||||||
|
Each suit has a fixed reversal qualifier that replaces the polarity qualifier
|
||||||
|
(Elevated/Graven) when the card is spun to its reversed face:
|
||||||
|
Brands → Seething Grails → Gloomy Blades → Nervous Crowns → Vacant
|
||||||
|
|
||||||
|
Also clears the incorrectly inherited reversal on The Schizo (card 1), which
|
||||||
|
mistakenly carried 'Territoriality' from The Occultist (card 2).
|
||||||
|
"""
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
SUIT_REVERSAL_QUALIFIER = {
|
||||||
|
"BRANDS": "Seething",
|
||||||
|
"GRAILS": "Gloomy",
|
||||||
|
"BLADES": "Nervous",
|
||||||
|
"CROWNS": "Vacant",
|
||||||
|
}
|
||||||
|
|
||||||
|
RANK_NAMES = {11: "Maid", 12: "Jack", 13: "Queen", 14: "King"}
|
||||||
|
|
||||||
|
|
||||||
|
def populate_reversals(apps, schema_editor):
|
||||||
|
TarotCard = apps.get_model("epic", "TarotCard")
|
||||||
|
DeckVariant = apps.get_model("epic", "DeckVariant")
|
||||||
|
|
||||||
|
try:
|
||||||
|
earthman = DeckVariant.objects.get(slug="earthman")
|
||||||
|
except DeckVariant.DoesNotExist:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Middle Arcana court cards
|
||||||
|
for suit, qualifier in SUIT_REVERSAL_QUALIFIER.items():
|
||||||
|
TarotCard.objects.filter(
|
||||||
|
deck_variant=earthman,
|
||||||
|
arcana="MIDDLE",
|
||||||
|
suit=suit,
|
||||||
|
number__in=list(RANK_NAMES.keys()),
|
||||||
|
).update(reversal=qualifier)
|
||||||
|
|
||||||
|
# Clear The Schizo's incorrectly inherited reversal (belongs to The Occultist)
|
||||||
|
TarotCard.objects.filter(
|
||||||
|
deck_variant=earthman,
|
||||||
|
arcana="MAJOR",
|
||||||
|
number=1,
|
||||||
|
).update(reversal="")
|
||||||
|
|
||||||
|
|
||||||
|
def clear_reversals(apps, schema_editor):
|
||||||
|
TarotCard = apps.get_model("epic", "TarotCard")
|
||||||
|
DeckVariant = apps.get_model("epic", "DeckVariant")
|
||||||
|
try:
|
||||||
|
earthman = DeckVariant.objects.get(slug="earthman")
|
||||||
|
except DeckVariant.DoesNotExist:
|
||||||
|
return
|
||||||
|
TarotCard.objects.filter(
|
||||||
|
deck_variant=earthman, arcana="MIDDLE",
|
||||||
|
suit__in=list(SUIT_REVERSAL_QUALIFIER.keys()),
|
||||||
|
number__in=list(RANK_NAMES.keys()),
|
||||||
|
).update(reversal="")
|
||||||
|
TarotCard.objects.filter(deck_variant=earthman, arcana="MAJOR", number=1).update(
|
||||||
|
reversal="Territoriality"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("epic", "0006_add_deck_variant_to_tableseat"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RunPython(populate_reversals, reverse_code=clear_reversals),
|
||||||
|
]
|
||||||
@@ -132,9 +132,22 @@ var SigSelect = (function () {
|
|||||||
stageCard.querySelector('.sig-qualifier-above').textContent = isMajor ? '' : qualifier;
|
stageCard.querySelector('.sig-qualifier-above').textContent = isMajor ? '' : qualifier;
|
||||||
stageCard.querySelector('.sig-qualifier-below').textContent = isMajor ? qualifier : '';
|
stageCard.querySelector('.sig-qualifier-below').textContent = isMajor ? qualifier : '';
|
||||||
|
|
||||||
// Reversed face — same qualifier, polarity-resolved reversal title
|
// Reversed face.
|
||||||
|
// - Major arcana: polarity qualifier + reversal concept name
|
||||||
|
// - Non-major w. reversal: suit qualifier word replaces polarity qualifier;
|
||||||
|
// card name (title) stays the same — two separate lines
|
||||||
|
// - Non-major w/o reversal: fall back to mirroring the polarity qualifier
|
||||||
|
var reversal = cardEl.dataset.reversal || '';
|
||||||
|
if (isMajor) {
|
||||||
stageCard.querySelector('.fan-card-reversal-qualifier').textContent = qualifier;
|
stageCard.querySelector('.fan-card-reversal-qualifier').textContent = qualifier;
|
||||||
stageCard.querySelector('.fan-card-reversal-name').textContent = cardEl.dataset.reversal || '';
|
stageCard.querySelector('.fan-card-reversal-name').textContent = reversal;
|
||||||
|
} else if (reversal) {
|
||||||
|
stageCard.querySelector('.fan-card-reversal-qualifier').textContent = reversal;
|
||||||
|
stageCard.querySelector('.fan-card-reversal-name').textContent = title;
|
||||||
|
} else {
|
||||||
|
stageCard.querySelector('.fan-card-reversal-qualifier').textContent = qualifier;
|
||||||
|
stageCard.querySelector('.fan-card-reversal-name').textContent = '';
|
||||||
|
}
|
||||||
|
|
||||||
// Populate stat block keyword faces and reset to upright
|
// Populate stat block keyword faces and reset to upright
|
||||||
statBlock.classList.remove('is-reversed');
|
statBlock.classList.remove('is-reversed');
|
||||||
|
|||||||
@@ -22,8 +22,8 @@ describe("SigSelect", () => {
|
|||||||
<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>
|
<p class="fan-card-correspondence"></p>
|
||||||
<p class="fan-card-reversal-qualifier"></p>
|
|
||||||
<p class="fan-card-reversal-name"></p>
|
<p class="fan-card-reversal-name"></p>
|
||||||
|
<p class="fan-card-reversal-qualifier"></p>
|
||||||
</div>
|
</div>
|
||||||
<div class="sig-stat-block">
|
<div class="sig-stat-block">
|
||||||
<button class="btn btn-reverse sig-flip-btn" type="button">SPIN</button>
|
<button class="btn btn-reverse sig-flip-btn" type="button">SPIN</button>
|
||||||
@@ -64,7 +64,7 @@ describe("SigSelect", () => {
|
|||||||
data-articulations="[]"
|
data-articulations="[]"
|
||||||
data-levity-qualifier="Elevated"
|
data-levity-qualifier="Elevated"
|
||||||
data-gravity-qualifier="Graven"
|
data-gravity-qualifier="Graven"
|
||||||
data-reversal="Territoriality">
|
data-reversal="">
|
||||||
<div class="fan-card-corner fan-card-corner--tl">
|
<div class="fan-card-corner fan-card-corner--tl">
|
||||||
<span class="fan-corner-rank">K</span>
|
<span class="fan-corner-rank">K</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -483,12 +483,14 @@ describe("SigSelect", () => {
|
|||||||
expect(stageCard.classList.contains("stage-card--reversed")).toBe(false);
|
expect(stageCard.classList.contains("stage-card--reversed")).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("updateStage() populates fan-card-reversal-name from data-reversal", () => {
|
it("non-major with data-reversal: reversal-qualifier = suit word, reversal-name = card name", () => {
|
||||||
makeFixture();
|
makeFixture();
|
||||||
card.dataset.reversal = "Territoriality";
|
card.dataset.reversal = "Nervous";
|
||||||
hover();
|
hover();
|
||||||
|
// "Nervous" goes into qualifier slot (own line); upright name reused in name slot
|
||||||
|
expect(stageCard.querySelector(".fan-card-reversal-qualifier").textContent).toBe("Nervous");
|
||||||
expect(stageCard.querySelector(".fan-card-reversal-name").textContent)
|
expect(stageCard.querySelector(".fan-card-reversal-name").textContent)
|
||||||
.toBe("Territoriality");
|
.toBe(card.dataset.nameTitle);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("updateStage() populates fan-card-reversal-qualifier with levity qualifier", () => {
|
it("updateStage() populates fan-card-reversal-qualifier with levity qualifier", () => {
|
||||||
@@ -505,6 +507,24 @@ describe("SigSelect", () => {
|
|||||||
.toBe("Graven");
|
.toBe("Graven");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("non-major with data-reversal: suit qualifier on own line, upright name repeated below", () => {
|
||||||
|
makeFixture({ polarity: "levity", userRole: "PC" });
|
||||||
|
card.dataset.reversal = "Vacant";
|
||||||
|
hover();
|
||||||
|
expect(stageCard.querySelector(".fan-card-reversal-qualifier").textContent).toBe("Vacant");
|
||||||
|
expect(stageCard.querySelector(".fan-card-reversal-name").textContent)
|
||||||
|
.toBe(card.dataset.nameTitle);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("major arcana with data-reversal: polarity qualifier still shown alongside reversal name", () => {
|
||||||
|
makeFixture({ polarity: "levity", userRole: "PC" });
|
||||||
|
card.dataset.arcana = "Major Arcana";
|
||||||
|
card.dataset.reversal = "Territoriality";
|
||||||
|
hover();
|
||||||
|
expect(stageCard.querySelector(".fan-card-reversal-qualifier").textContent).toBe("Elevated");
|
||||||
|
expect(stageCard.querySelector(".fan-card-reversal-name").textContent).toBe("Territoriality");
|
||||||
|
});
|
||||||
|
|
||||||
it("hovering a card without data-reversal clears the reversal name", () => {
|
it("hovering a card without data-reversal clears the reversal name", () => {
|
||||||
makeFixture();
|
makeFixture();
|
||||||
card.dataset.reversal = "Territoriality";
|
card.dataset.reversal = "Territoriality";
|
||||||
|
|||||||
@@ -224,22 +224,23 @@ html:has(.sig-backdrop) {
|
|||||||
padding: 0.25rem 0.15rem;
|
padding: 0.25rem 0.15rem;
|
||||||
gap: 0.2rem;
|
gap: 0.2rem;
|
||||||
|
|
||||||
|
.fan-card-face-upright { display: flex; flex-direction: column; align-items: center; gap: 0.15rem; }
|
||||||
|
.fan-card-face-reversal { display: flex; flex-direction: column; align-items: center; gap: 0.15rem; padding-top: 0.1rem; }
|
||||||
.fan-card-name-group { font-size: calc(var(--sig-card-w, 120px) * 0.073); opacity: 0.6; }
|
.fan-card-name-group { font-size: calc(var(--sig-card-w, 120px) * 0.073); opacity: 0.6; }
|
||||||
|
// Upright qualifier + name share sizing/weight/color with their reversed counterparts
|
||||||
.sig-qualifier-above,
|
.sig-qualifier-above,
|
||||||
.sig-qualifier-below { font-size: calc(var(--sig-card-w, 120px) * 0.093); font-weight: 600; }
|
.sig-qualifier-below,
|
||||||
.fan-card-name { font-size: calc(var(--sig-card-w, 120px) * 0.093); font-weight: 600; }
|
.fan-card-reversal-qualifier { font-size: calc(var(--sig-card-w, 120px) * 0.093); font-weight: 600; color: rgba(var(--quiUser), 1); transition: opacity 0.2s; }
|
||||||
|
.fan-card-name,
|
||||||
|
.fan-card-reversal-name { font-size: calc(var(--sig-card-w, 120px) * 0.093); font-weight: 600; color: rgba(var(--quiUser), 1); transition: opacity 0.2s; }
|
||||||
.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
|
// Reversed face elements — pre-rotated so they read forward after card spins
|
||||||
.fan-card-reversal-qualifier,
|
.fan-card-reversal-qualifier,
|
||||||
.fan-card-reversal-name {
|
.fan-card-reversal-name {
|
||||||
transform: rotate(180deg);
|
transform: rotate(180deg);
|
||||||
opacity: 0.25;
|
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 {
|
&.stage-card--reversed {
|
||||||
|
|||||||
@@ -22,8 +22,8 @@ describe("SigSelect", () => {
|
|||||||
<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>
|
<p class="fan-card-correspondence"></p>
|
||||||
<p class="fan-card-reversal-qualifier"></p>
|
|
||||||
<p class="fan-card-reversal-name"></p>
|
<p class="fan-card-reversal-name"></p>
|
||||||
|
<p class="fan-card-reversal-qualifier"></p>
|
||||||
</div>
|
</div>
|
||||||
<div class="sig-stat-block">
|
<div class="sig-stat-block">
|
||||||
<button class="btn btn-reverse sig-flip-btn" type="button">SPIN</button>
|
<button class="btn btn-reverse sig-flip-btn" type="button">SPIN</button>
|
||||||
@@ -64,7 +64,7 @@ describe("SigSelect", () => {
|
|||||||
data-articulations="[]"
|
data-articulations="[]"
|
||||||
data-levity-qualifier="Elevated"
|
data-levity-qualifier="Elevated"
|
||||||
data-gravity-qualifier="Graven"
|
data-gravity-qualifier="Graven"
|
||||||
data-reversal="Territoriality">
|
data-reversal="">
|
||||||
<div class="fan-card-corner fan-card-corner--tl">
|
<div class="fan-card-corner fan-card-corner--tl">
|
||||||
<span class="fan-corner-rank">K</span>
|
<span class="fan-corner-rank">K</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -483,12 +483,14 @@ describe("SigSelect", () => {
|
|||||||
expect(stageCard.classList.contains("stage-card--reversed")).toBe(false);
|
expect(stageCard.classList.contains("stage-card--reversed")).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("updateStage() populates fan-card-reversal-name from data-reversal", () => {
|
it("non-major with data-reversal: reversal-qualifier = suit word, reversal-name = card name", () => {
|
||||||
makeFixture();
|
makeFixture();
|
||||||
card.dataset.reversal = "Territoriality";
|
card.dataset.reversal = "Nervous";
|
||||||
hover();
|
hover();
|
||||||
|
// "Nervous" goes into qualifier slot (own line); upright name reused in name slot
|
||||||
|
expect(stageCard.querySelector(".fan-card-reversal-qualifier").textContent).toBe("Nervous");
|
||||||
expect(stageCard.querySelector(".fan-card-reversal-name").textContent)
|
expect(stageCard.querySelector(".fan-card-reversal-name").textContent)
|
||||||
.toBe("Territoriality");
|
.toBe(card.dataset.nameTitle);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("updateStage() populates fan-card-reversal-qualifier with levity qualifier", () => {
|
it("updateStage() populates fan-card-reversal-qualifier with levity qualifier", () => {
|
||||||
@@ -505,6 +507,24 @@ describe("SigSelect", () => {
|
|||||||
.toBe("Graven");
|
.toBe("Graven");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("non-major with data-reversal: suit qualifier on own line, upright name repeated below", () => {
|
||||||
|
makeFixture({ polarity: "levity", userRole: "PC" });
|
||||||
|
card.dataset.reversal = "Vacant";
|
||||||
|
hover();
|
||||||
|
expect(stageCard.querySelector(".fan-card-reversal-qualifier").textContent).toBe("Vacant");
|
||||||
|
expect(stageCard.querySelector(".fan-card-reversal-name").textContent)
|
||||||
|
.toBe(card.dataset.nameTitle);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("major arcana with data-reversal: polarity qualifier still shown alongside reversal name", () => {
|
||||||
|
makeFixture({ polarity: "levity", userRole: "PC" });
|
||||||
|
card.dataset.arcana = "Major Arcana";
|
||||||
|
card.dataset.reversal = "Territoriality";
|
||||||
|
hover();
|
||||||
|
expect(stageCard.querySelector(".fan-card-reversal-qualifier").textContent).toBe("Elevated");
|
||||||
|
expect(stageCard.querySelector(".fan-card-reversal-name").textContent).toBe("Territoriality");
|
||||||
|
});
|
||||||
|
|
||||||
it("hovering a card without data-reversal clears the reversal name", () => {
|
it("hovering a card without data-reversal clears the reversal name", () => {
|
||||||
makeFixture();
|
makeFixture();
|
||||||
card.dataset.reversal = "Territoriality";
|
card.dataset.reversal = "Territoriality";
|
||||||
|
|||||||
@@ -23,14 +23,18 @@ Context: sig_cards, user_polarity, user_seat, sig_reserve_url, sig_reservations_
|
|||||||
<i class="fa-solid stage-suit-icon" style="display:none"></i>
|
<i class="fa-solid stage-suit-icon" style="display:none"></i>
|
||||||
</div>
|
</div>
|
||||||
<div class="fan-card-face">
|
<div class="fan-card-face">
|
||||||
|
<div class="fan-card-face-upright">
|
||||||
<p class="fan-card-name-group"></p>
|
<p class="fan-card-name-group"></p>
|
||||||
<p class="sig-qualifier-above"></p>
|
<p class="sig-qualifier-above"></p>
|
||||||
<h3 class="fan-card-name"></h3>
|
<h3 class="fan-card-name"></h3>
|
||||||
<p class="sig-qualifier-below"></p>
|
<p class="sig-qualifier-below"></p>
|
||||||
|
</div>
|
||||||
<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>
|
<div class="fan-card-face-reversal">
|
||||||
<p class="fan-card-reversal-name"></p>
|
<p class="fan-card-reversal-name"></p>
|
||||||
|
<p class="fan-card-reversal-qualifier"></p>
|
||||||
|
</div>
|
||||||
</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>
|
||||||
|
|||||||
Reference in New Issue
Block a user