From 2757ae855f9f26f3038fa26d430eb7c50c5e62e3 Mon Sep 17 00:00:00 2001 From: Disco DeDisco Date: Tue, 28 Apr 2026 20:09:23 -0400 Subject: [PATCH] =?UTF-8?q?SIG=20SELECT:=20non-major=20reversal=20display;?= =?UTF-8?q?=20face=20wrapper=20divs;=20middle=20arcana=20reversal=20seed?= =?UTF-8?q?=20=E2=80=94=20TDD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 Git commit message Co-Authored-By: Claude Sonnet 4.6 --- .../0007_populate_middle_arcana_reversals.py | 73 +++++++++++++++++++ src/apps/epic/static/apps/epic/sig-select.js | 19 ++++- src/static/tests/SigSelectSpec.js | 30 ++++++-- src/static_src/scss/_card-deck.scss | 15 ++-- src/static_src/tests/SigSelectSpec.js | 30 ++++++-- .../_partials/_sig_select_overlay.html | 16 ++-- 6 files changed, 157 insertions(+), 26 deletions(-) create mode 100644 src/apps/epic/migrations/0007_populate_middle_arcana_reversals.py diff --git a/src/apps/epic/migrations/0007_populate_middle_arcana_reversals.py b/src/apps/epic/migrations/0007_populate_middle_arcana_reversals.py new file mode 100644 index 0000000..26a3d26 --- /dev/null +++ b/src/apps/epic/migrations/0007_populate_middle_arcana_reversals.py @@ -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), + ] diff --git a/src/apps/epic/static/apps/epic/sig-select.js b/src/apps/epic/static/apps/epic/sig-select.js index 232b05c..ef277c3 100644 --- a/src/apps/epic/static/apps/epic/sig-select.js +++ b/src/apps/epic/static/apps/epic/sig-select.js @@ -132,9 +132,22 @@ 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 || ''; + // 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-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 statBlock.classList.remove('is-reversed'); diff --git a/src/static/tests/SigSelectSpec.js b/src/static/tests/SigSelectSpec.js index 15f1aef..cdfd2ec 100644 --- a/src/static/tests/SigSelectSpec.js +++ b/src/static/tests/SigSelectSpec.js @@ -22,8 +22,8 @@ describe("SigSelect", () => {

-

+

@@ -64,7 +64,7 @@ describe("SigSelect", () => { data-articulations="[]" data-levity-qualifier="Elevated" data-gravity-qualifier="Graven" - data-reversal="Territoriality"> + data-reversal="">
K
@@ -483,12 +483,14 @@ describe("SigSelect", () => { 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(); - card.dataset.reversal = "Territoriality"; + card.dataset.reversal = "Nervous"; 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) - .toBe("Territoriality"); + .toBe(card.dataset.nameTitle); }); it("updateStage() populates fan-card-reversal-qualifier with levity qualifier", () => { @@ -505,6 +507,24 @@ describe("SigSelect", () => { .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", () => { makeFixture(); card.dataset.reversal = "Territoriality"; diff --git a/src/static_src/scss/_card-deck.scss b/src/static_src/scss/_card-deck.scss index 09fcfb3..2d95063 100644 --- a/src/static_src/scss/_card-deck.scss +++ b/src/static_src/scss/_card-deck.scss @@ -224,22 +224,23 @@ html:has(.sig-backdrop) { padding: 0.25rem 0.15rem; 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; } + // Upright qualifier + name share sizing/weight/color with their reversed counterparts .sig-qualifier-above, - .sig-qualifier-below { 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; } + .sig-qualifier-below, + .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-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-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 { diff --git a/src/static_src/tests/SigSelectSpec.js b/src/static_src/tests/SigSelectSpec.js index 15f1aef..cdfd2ec 100644 --- a/src/static_src/tests/SigSelectSpec.js +++ b/src/static_src/tests/SigSelectSpec.js @@ -22,8 +22,8 @@ describe("SigSelect", () => {

-

+

@@ -64,7 +64,7 @@ describe("SigSelect", () => { data-articulations="[]" data-levity-qualifier="Elevated" data-gravity-qualifier="Graven" - data-reversal="Territoriality"> + data-reversal="">
K
@@ -483,12 +483,14 @@ describe("SigSelect", () => { 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(); - card.dataset.reversal = "Territoriality"; + card.dataset.reversal = "Nervous"; 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) - .toBe("Territoriality"); + .toBe(card.dataset.nameTitle); }); it("updateStage() populates fan-card-reversal-qualifier with levity qualifier", () => { @@ -505,6 +507,24 @@ describe("SigSelect", () => { .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", () => { makeFixture(); card.dataset.reversal = "Territoriality"; diff --git a/src/templates/apps/gameboard/_partials/_sig_select_overlay.html b/src/templates/apps/gameboard/_partials/_sig_select_overlay.html index a632548..f100a2f 100644 --- a/src/templates/apps/gameboard/_partials/_sig_select_overlay.html +++ b/src/templates/apps/gameboard/_partials/_sig_select_overlay.html @@ -23,14 +23,18 @@ Context: sig_cards, user_polarity, user_seat, sig_reserve_url, sig_reservations_
-

-

-

-

+
+

+

+

+

+

{# not shown in sig-select — game-kit only #} -

-

+
+

+

+