btn-primary label renames + stage-card polarity color refinements — two interleaved threads from one session, committing together since both touch sig + sea stage cards ; LABEL RENAMES: PICK SIGS → SCAN SIGS (room.html #id_pick_sigs_btn), PICK SKY → CAST SKY (room.html #id_pick_sky_btn × 2), PICK SEA → DRAW SEA (room.html #id_pick_sea_btn), TAKE SIG → SAVE SIG (sig-select.js _takeSigBtn.textContent × 2 callsites + section comment) — Element IDs (id_pick_sky_btn etc.), URL names (epic:pick_sigs, epic:pick_sky), and Python state enums (TableStatus.PICK_SKY, PICK_SEA, SIG_SELECT) intentionally retained as stable identifiers; the renamed text is purely the .btn-primary user-facing label ; FT + IT mentions of the old labels swept in test_game_room_select_{sig,sky,sea,role}.py, test_billboard.py, setup_sea_session.py mgmt cmd, apps/epic/{views,utils,models,tasks,tests/integrated/test_views}.py, SigSelectSpec.js, sky_overlay/sea_overlay/dashboard/sky.html, _card-deck.scss, _sky.scss — all docstring/comment references updated for cascade-grep cleanliness ; STAGE-CARD COLOR + CLASS REFINEMENTS (earlier in session): sig-stage card text colour split per polarity — gravity gets --terUser on .fan-card-name + .fan-card-reversal-{name,qualifier} + .sig-qualifier-{above,below}, levity gets --quiUser on the same five slots; all selectors prefixed w. .sig-stage-card to match the 0,4,0 specificity of the default .sig-stage .sig-stage-card .fan-card-face .sig-qualifier-* rule (without the prefix the polarity overrides lose the cascade — .sig-qualifier-below was visibly stuck on the default --quiUser) ; .stat-face-label gets polarity-inverse colours — gravity stat-block bg is --secUser (opposite of card's --priUser) so the label takes --quiUser to stay legible; levity is the symmetric flip (label = --terUser on --priUser stat-block bg) ; levity card title/qualifier drop-shadow swapped from rgba(0,0,0,…) → rgba(255,255,255,…) — dark drop reads as harsh smudge against the inverted-frame levity --secUser bg; applied to both sig-overlay[data-polarity="levity"] stage card AND sea-stage--levity via $_sea-title-shadow-levity (former shared $_sea-title-shadow split into per-polarity {levity,gravity} variants) ; reversal-face class/content alignment so each .fan-card-reversal-* class always carries its semantic content — DOM order per arcana type controls visual layout after the 180° SPIN (DOM-second appears visually on top): Major → title in .fan-card-reversal-name @ DOM-second (visually top after spin), qualifier in .fan-card-reversal-qualifier @ DOM-first; Non-major → title in .fan-card-reversal-name @ DOM-first (visually bottom after spin), qualifier in .fan-card-reversal-qualifier @ DOM-second (preserves the original "qualifier word reads first after spin" layout for Middle/Minor arcana — e.g. "Relieving / Eight of Crowns" not "Eight of Crowns / Relieving") ; _tarot_fan.html renders per-arcana DOM order directly (Django template branches handle both layouts); sig + sea overlays render a fixed two-<p> skeleton (one DOM order) so stage-card.js's populator dynamically rewrites the two <p>s' className per arcana — Major/override branch flips DOM-second to .fan-card-reversal-name + content, DOM-first to .fan-card-reversal-qualifier; non-major branch keeps DOM-first as .fan-card-reversal-name + title, DOM-second as .fan-card-reversal-qualifier + reversalQualifier-or-polarity-fallback ; SigSelectSpec.js + SeaDealSpec.js fixtures + Major reversed-face assertion updated for the new semantic — TDD
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:
@@ -624,7 +624,7 @@ class Character(models.Model):
|
||||
"""A gamer's player-character for one seat in one game session.
|
||||
|
||||
Lifecycle:
|
||||
- Created (draft) when gamer opens PICK SKY overlay.
|
||||
- Created (draft) when gamer opens CAST SKY overlay.
|
||||
- confirmed_at set on confirm → locked.
|
||||
- retired_at set on retirement → archived (seat may hold a new Character).
|
||||
|
||||
@@ -647,7 +647,7 @@ class Character(models.Model):
|
||||
TableSeat, on_delete=models.CASCADE, related_name='characters',
|
||||
)
|
||||
|
||||
# ── significator (set at PICK SKY) ────────────────────────────────────
|
||||
# ── significator (set at CAST SKY) ────────────────────────────────────
|
||||
significator = models.ForeignKey(
|
||||
TarotCard, null=True, blank=True,
|
||||
on_delete=models.SET_NULL, related_name='character_significators',
|
||||
@@ -665,7 +665,7 @@ class Character(models.Model):
|
||||
# ── computed sky snapshot (full PySwiss response) ───────────────────
|
||||
chart_data = models.JSONField(null=True, blank=True)
|
||||
|
||||
# ── celtic cross spread (added at PICK SEA) ───────────────────────────
|
||||
# ── celtic cross spread (added at DRAW SEA) ───────────────────────────
|
||||
celtic_cross = models.JSONField(null=True, blank=True)
|
||||
|
||||
# ── lifecycle ─────────────────────────────────────────────────────────
|
||||
|
||||
@@ -317,7 +317,7 @@ var SigSelect = (function () {
|
||||
});
|
||||
}
|
||||
|
||||
// ── TAKE SIG / WAIT NVM button ─────────────────────────────────────────
|
||||
// ── SAVE SIG / WAIT NVM button ─────────────────────────────────────────
|
||||
|
||||
function _onTakeSigClick() {
|
||||
if (_isReady) {
|
||||
@@ -337,7 +337,7 @@ var SigSelect = (function () {
|
||||
_countdownTimer = null;
|
||||
if (_takeSigBtn) _takeSigBtn.style.fontSize = '';
|
||||
}
|
||||
if (_takeSigBtn) _takeSigBtn.textContent = 'TAKE SIG';
|
||||
if (_takeSigBtn) _takeSigBtn.textContent = 'SAVE SIG';
|
||||
_stopWaitNoGlow();
|
||||
_stopCountdownGlow();
|
||||
}
|
||||
@@ -367,7 +367,7 @@ var SigSelect = (function () {
|
||||
_takeSigBtn.id = 'id_take_sig_btn';
|
||||
_takeSigBtn.className = 'btn btn-primary sig-take-sig-btn';
|
||||
_takeSigBtn.type = 'button';
|
||||
_takeSigBtn.textContent = 'TAKE SIG';
|
||||
_takeSigBtn.textContent = 'SAVE SIG';
|
||||
_takeSigBtn.addEventListener('click', _onTakeSigClick);
|
||||
stage.appendChild(_takeSigBtn);
|
||||
}
|
||||
@@ -495,7 +495,7 @@ var SigSelect = (function () {
|
||||
function _showWaitingMsg(pendingPolarity) {
|
||||
if (document.getElementById('id_hex_waiting_msg')) return;
|
||||
// If the OTHER polarity finished before our tray sequence completed,
|
||||
// pick_sky_available will already have fired and revealed PICK SKY.
|
||||
// pick_sky_available will already have fired and revealed CAST SKY.
|
||||
// In that case skip the waiting msg so the two don't co-exist.
|
||||
var pickSkyBtn = document.getElementById('id_pick_sky_btn');
|
||||
if (pickSkyBtn && pickSkyBtn.style.display !== 'none') return;
|
||||
@@ -539,7 +539,7 @@ var SigSelect = (function () {
|
||||
// the overlay vanishes; overlay dismissal + waiting msg run last.
|
||||
// User sees: stage card → tray slides in → sig fades into the tray
|
||||
// cell → tray slides out → 2s pause → overlay dismisses → table hex
|
||||
// w. waiting msg (or PICK SKY btn if both polarities are done).
|
||||
// w. waiting msg (or CAST SKY btn if both polarities are done).
|
||||
function _settle() {
|
||||
_dismissSigOverlay();
|
||||
_showWaitingMsg(pendingPolarity);
|
||||
@@ -625,7 +625,7 @@ var SigSelect = (function () {
|
||||
userRole = overlay.dataset.userRole;
|
||||
userPolarity= overlay.dataset.polarity;
|
||||
|
||||
// PICK SKY btn is rendered hidden during SIG_SELECT; reveal on pick_sky_available
|
||||
// CAST SKY btn is rendered hidden during SIG_SELECT; reveal on pick_sky_available
|
||||
var pickSkyBtn = document.getElementById('id_pick_sky_btn');
|
||||
if (pickSkyBtn) {
|
||||
pickSkyBtn.addEventListener('click', function () {
|
||||
|
||||
@@ -143,26 +143,42 @@ var StageCard = (function () {
|
||||
if (qBelow) qBelow.textContent = isMajor ? qualifier : '';
|
||||
}
|
||||
|
||||
// Reversal face — four cases:
|
||||
// Polarity-split: full reversal title in qualifier slot (top-after-spin), name slot empty
|
||||
// Major: title (with comma) in qualifier slot, qualifier in name slot
|
||||
// Non-major + reversal_qual: reversal_qualifier in qualifier slot, title in name slot
|
||||
// Non-major no reversal_qual: fall back to current polarity's qualifier
|
||||
var rQual = stageCard.querySelector('.fan-card-reversal-qualifier');
|
||||
var rName = stageCard.querySelector('.fan-card-reversal-name');
|
||||
if (rQual && rName) {
|
||||
// Reversal face — class always matches semantic content:
|
||||
// .fan-card-reversal-name → title-like text
|
||||
// .fan-card-reversal-qualifier → qualifier-like text
|
||||
// DOM order controls visual layout after the 180° SPIN (DOM-second appears
|
||||
// visually on top). Sig + sea skeletons render a fixed slot pair so we
|
||||
// assign classes per-arcana here so each branch lands in the right slot:
|
||||
// Major / polarity-split — title on top → .name carried by DOM-second
|
||||
// Non-major — qualifier on top → .qualifier carried by DOM-second
|
||||
var slots = stageCard.querySelectorAll('.fan-card-face-reversal > p');
|
||||
if (slots.length === 2) {
|
||||
var bottomEl = slots[0]; // DOM-first → visually bottom after spin
|
||||
var topEl = slots[1]; // DOM-second → visually top after spin
|
||||
// Wipe any squeeze-class artifacts from a prior populate call before
|
||||
// re-stamping the base class — keeps the slot's CSS predictable.
|
||||
bottomEl.className = '';
|
||||
topEl.className = '';
|
||||
|
||||
if (reversalOverride) {
|
||||
_setTitle(rQual, reversalOverride, card);
|
||||
rName.textContent = '';
|
||||
// Polarity-split: single-line title on TOP; qualifier slot empty.
|
||||
topEl.className = 'fan-card-reversal-name';
|
||||
_setTitle(topEl, reversalOverride, card);
|
||||
bottomEl.className = 'fan-card-reversal-qualifier';
|
||||
bottomEl.textContent = '';
|
||||
} else if (isMajor) {
|
||||
_setTitle(rQual, title + ',', card);
|
||||
rName.textContent = qualifier;
|
||||
} else if (reversalQualifier) {
|
||||
rQual.textContent = reversalQualifier;
|
||||
_setTitle(rName, title, card);
|
||||
// Major: title-with-comma on TOP, qualifier on BOTTOM.
|
||||
topEl.className = 'fan-card-reversal-name';
|
||||
_setTitle(topEl, title + ',', card);
|
||||
bottomEl.className = 'fan-card-reversal-qualifier';
|
||||
bottomEl.textContent = qualifier;
|
||||
} else {
|
||||
rQual.textContent = qualifier;
|
||||
_setTitle(rName, title, card);
|
||||
// Non-major: qualifier on TOP, title on BOTTOM (inverted from
|
||||
// upright order by design — qualifier word reads first after spin).
|
||||
topEl.className = 'fan-card-reversal-qualifier';
|
||||
topEl.textContent = reversalQualifier || qualifier;
|
||||
bottomEl.className = 'fan-card-reversal-name';
|
||||
_setTitle(bottomEl, title, card);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
"""
|
||||
Countdown scheduler for the polarity-room TAKE SIG gate.
|
||||
Countdown scheduler for the polarity-room SAVE SIG gate.
|
||||
|
||||
Uses threading.Timer so no separate Celery worker is needed in development.
|
||||
Single-process only — swap for a Celery task if production uses multiple
|
||||
|
||||
@@ -879,7 +879,7 @@ class SelectRoleMultiSeatTest(TestCase):
|
||||
|
||||
|
||||
class RoomViewAllRolesFilledTest(TestCase):
|
||||
"""Room view in ROLE_SELECT with all seats assigned shows PICK SIGS button."""
|
||||
"""Room view in ROLE_SELECT with all seats assigned shows SCAN SIGS button."""
|
||||
def setUp(self):
|
||||
import lxml.html
|
||||
self.lxml = lxml.html
|
||||
@@ -1785,7 +1785,7 @@ class SigConfirmViewTest(TestCase):
|
||||
# ── SKY_SELECT rendering ──────────────────────────────────────────────────────
|
||||
|
||||
class PickSkyRenderingTest(TestCase):
|
||||
"""Room page at SKY_SELECT renders PICK SKY btn and sig card in tray cell 2."""
|
||||
"""Room page at SKY_SELECT renders CAST SKY btn and sig card in tray cell 2."""
|
||||
|
||||
def setUp(self):
|
||||
self.room, self.gamers, self.earthman, _ = _full_sig_setUp(self)
|
||||
@@ -1865,7 +1865,7 @@ class PickSkyRenderingTest(TestCase):
|
||||
self.assertEqual(founder.sky_birth_tz, "America/New_York")
|
||||
|
||||
def test_no_sky_delete_btn_in_blank_sky_select_modal(self):
|
||||
"""A fresh PICK SKY modal (no preview wheel rendered yet) must not
|
||||
"""A fresh CAST SKY modal (no preview wheel rendered yet) must not
|
||||
carry the DEL btn — it would otherwise float in the empty wheel area
|
||||
suggesting there's something to delete when the user has only seen
|
||||
the form. The JS schedulePreview success handler is the contract that
|
||||
@@ -1881,7 +1881,7 @@ class PickSkyRenderingTest(TestCase):
|
||||
# ── SEA_SELECT rendering ──────────────────────────────────────────────────────
|
||||
|
||||
class PickSeaRenderingTest(TestCase):
|
||||
"""At SKY_SELECT, a confirmed Character swaps PICK SKY → PICK SEA + sea overlay."""
|
||||
"""At SKY_SELECT, a confirmed Character swaps CAST SKY → DRAW SEA + sea overlay."""
|
||||
|
||||
def setUp(self):
|
||||
self.room, self.gamers, self.earthman, _ = _full_sig_setUp(self)
|
||||
|
||||
@@ -5,7 +5,7 @@ from apps.epic.models import Room, RoomInvite
|
||||
|
||||
# ── Game-wide constants ────────────────────────────────────────────────────
|
||||
# Reversal probability applied to any card pulled from a stack, anywhere in
|
||||
# the game (PICK SEA initially; future phases — gameplay draws etc. — will
|
||||
# the game (DRAW SEA initially; future phases — gameplay draws etc. — will
|
||||
# share this single source of truth). Stub for a future per-user profile
|
||||
# override: callers MUST go through stack_reversal_probability(user, room)
|
||||
# rather than referencing the constant directly so the user-config hookup is
|
||||
|
||||
@@ -413,7 +413,7 @@ def room_view(request, room_id):
|
||||
ctx = _role_select_context(room, request.user)
|
||||
ctx["room"] = room
|
||||
ctx["page_class"] = "page-gameboard"
|
||||
# Reversal-rate hint label under PICK SEA's SPREAD select — same helper as
|
||||
# Reversal-rate hint label under DRAW SEA's SPREAD select — same helper as
|
||||
# sea_partial so the value tracks any future per-user override automatically.
|
||||
ctx["stack_reversal_pct"] = int(round(stack_reversal_probability(request.user, room) * 100))
|
||||
return render(request, "apps/gameboard/room.html", ctx)
|
||||
@@ -1223,7 +1223,7 @@ def sky_save(request, room_id):
|
||||
@login_required
|
||||
def sky_delete(request, room_id):
|
||||
"""Purge the requesting gamer's Character on this seat — both unconfirmed
|
||||
drafts AND confirmed rows. The in-room PICK SKY DEL targets this so SAVE
|
||||
drafts AND confirmed rows. The in-room CAST SKY DEL targets this so SAVE
|
||||
SKY → DEL → refresh truly drops the saved sky for the seat. The User
|
||||
model's sky_chart_data is intentionally untouched (Dashsky / My Sky
|
||||
applet's DEL handles that separately)."""
|
||||
@@ -1239,7 +1239,7 @@ def sky_delete(request, room_id):
|
||||
|
||||
@login_required
|
||||
def sea_deck(request, room_id):
|
||||
"""Shuffled deck lists (levity + gravity halves) for PICK SEA draw.
|
||||
"""Shuffled deck lists (levity + gravity halves) for DRAW SEA draw.
|
||||
|
||||
Excludes all Significators already claimed by seated gamers.
|
||||
Returns {levity: [{id, name, arcana, suit, number, levity_qualifier,
|
||||
|
||||
Reference in New Issue
Block a user