PICK SEA styling: deck backs, card rank+icon display, fa-hand-dots Major Arcana — TDD
- migration 0010: icon='fa-hand-dots' for all Earthman Major Arcana number >= 2 (Nomad/Schizo kept empty for distinct icons later) - sea_deck view: switch from .values() to model instances; serializes corner_rank + suit_icon computed properties alongside DB fields - sea overlay JS: _fillPos() renders <span class=fan-corner-rank> + <i fa-solid> HTML; tracks levity/gravity source via sea-card-slot--levity/gravity class; _reset() strips polarity classes; _showOk/_hideOk toggle sea-deck-stack--active - template: gravity deck before levity; OK btn inside .sea-stack-face (absolute center); DECKS label (vertical-rl CCW) on stacks left; Gravity/Levity names under each pile - _card-deck.scss: .sea-stacks-label (vertical-rl); .sea-stack-ok (absolute center on face); .sea-stack-name w. --quaUser/--terUser; glow on hover+:active+--active class — --ninUser for levity, --quaUser for gravity; sea-sig-card compact rank+icon display - sea_partial view: ctx['room'] fix carried in from Sprint B 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:
49
src/apps/epic/migrations/0010_major_arcana_hand_dots_icon.py
Normal file
49
src/apps/epic/migrations/0010_major_arcana_hand_dots_icon.py
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
"""Assign fa-hand-dots icon to all Earthman Major Arcana cards with number >= 2.
|
||||||
|
|
||||||
|
Cards 0 (The Nomad) and 1 (The Schizo) keep their existing icon value so they
|
||||||
|
can receive distinct icons later. All other Major Arcana groups (Popes, Implicit
|
||||||
|
Virtues, Elements, Realms, Explicit Virtues, Zodiac, Lunars, Planets, Inner Rings,
|
||||||
|
polarity-split finals) default to fa-hand-dots until per-group icons are assigned.
|
||||||
|
"""
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
def assign_hand_dots(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="MAJOR",
|
||||||
|
number__gte=2,
|
||||||
|
icon="",
|
||||||
|
).update(icon="fa-hand-dots")
|
||||||
|
|
||||||
|
|
||||||
|
def clear_hand_dots(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="MAJOR",
|
||||||
|
number__gte=2,
|
||||||
|
icon="fa-hand-dots",
|
||||||
|
).update(icon="")
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("epic", "0009_schizo_card_ref_spans"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RunPython(assign_hand_dots, reverse_code=clear_hand_dots),
|
||||||
|
]
|
||||||
@@ -1131,15 +1131,28 @@ def sea_deck(request, room_id):
|
|||||||
.values_list('significator_id', flat=True)
|
.values_list('significator_id', flat=True)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _card_dict(c):
|
||||||
|
return {
|
||||||
|
'id': c.id,
|
||||||
|
'name': c.name,
|
||||||
|
'arcana': c.arcana,
|
||||||
|
'suit': c.suit,
|
||||||
|
'number': c.number,
|
||||||
|
'corner_rank': c.corner_rank,
|
||||||
|
'suit_icon': c.suit_icon,
|
||||||
|
'levity_qualifier': c.levity_qualifier,
|
||||||
|
'gravity_qualifier': c.gravity_qualifier,
|
||||||
|
}
|
||||||
|
|
||||||
available = list(
|
available = list(
|
||||||
TarotCard.objects.filter(deck_variant=deck)
|
TarotCard.objects.filter(deck_variant=deck).exclude(id__in=sig_ids)
|
||||||
.exclude(id__in=sig_ids)
|
|
||||||
.values('id', 'name', 'arcana', 'suit', 'number',
|
|
||||||
'levity_qualifier', 'gravity_qualifier')
|
|
||||||
)
|
)
|
||||||
_random.shuffle(available)
|
_random.shuffle(available)
|
||||||
mid = len(available) // 2
|
mid = len(available) // 2
|
||||||
return JsonResponse({'levity': available[:mid], 'gravity': available[mid:]})
|
return JsonResponse({
|
||||||
|
'levity': [_card_dict(c) for c in available[:mid]],
|
||||||
|
'gravity': [_card_dict(c) for c in available[mid:]],
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
|
|||||||
@@ -801,14 +801,29 @@ $sea-card-h: 6.5rem;
|
|||||||
|
|
||||||
.sea-card-slot--filled {
|
.sea-card-slot--filled {
|
||||||
border-style: solid;
|
border-style: solid;
|
||||||
border-color: rgba(var(--secUser), 0.6);
|
flex-direction: column;
|
||||||
|
gap: 0.2rem;
|
||||||
|
|
||||||
|
.fan-corner-rank {
|
||||||
|
font-size: 1.15rem;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
i { font-size: 0.9rem; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Levity drawn card — standard polarity (priUser bg, secUser text)
|
||||||
|
.sea-card-slot--filled.sea-card-slot--levity {
|
||||||
background: rgba(var(--priUser), 1);
|
background: rgba(var(--priUser), 1);
|
||||||
color: rgba(var(--terUser), 1);
|
border-color: rgba(var(--secUser), 0.6);
|
||||||
font-size: 0.55rem;
|
color: rgba(var(--secUser), 0.85);
|
||||||
font-weight: 600;
|
}
|
||||||
text-align: center;
|
|
||||||
padding: 0.2rem;
|
// Gravity drawn card — inverted polarity (secUser bg, priUser text)
|
||||||
line-height: 1.2;
|
.sea-card-slot--filled.sea-card-slot--gravity {
|
||||||
|
background: rgba(var(--secUser), 0.9);
|
||||||
|
border-color: rgba(var(--priUser), 0.4);
|
||||||
|
color: rgba(var(--priUser), 0.9);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cover + Cross — absolutely overlaid on the Sig card in .sea-pos-center
|
// Cover + Cross — absolutely overlaid on the Sig card in .sea-pos-center
|
||||||
@@ -828,6 +843,23 @@ $sea-card-h: 6.5rem;
|
|||||||
|
|
||||||
.sea-pos-cross .sea-card-slot { transform: rotate(90deg); }
|
.sea-pos-cross .sea-card-slot { transform: rotate(90deg); }
|
||||||
|
|
||||||
|
// Sig card in center slot — compact rank + icon display
|
||||||
|
.sea-sig-card {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 0.2rem;
|
||||||
|
|
||||||
|
.fan-corner-rank {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 1;
|
||||||
|
color: rgba(var(--secUser), 0.85);
|
||||||
|
}
|
||||||
|
i { font-size: 1rem; color: rgba(var(--secUser), 0.75); }
|
||||||
|
}
|
||||||
|
|
||||||
// .sig-stage-card is normally scoped inside .sig-stage — re-apply the card shell
|
// .sig-stage-card is normally scoped inside .sig-stage — re-apply the card shell
|
||||||
// here so it renders correctly outside that context.
|
// here so it renders correctly outside that context.
|
||||||
.sea-cross .sig-stage-card {
|
.sea-cross .sig-stage-card {
|
||||||
@@ -896,40 +928,86 @@ $sea-card-h: 6.5rem;
|
|||||||
option { background: rgba(var(--priUser), 1); }
|
option { background: rgba(var(--priUser), 1); }
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deck stacks — two face-down piles side by side
|
// Deck stacks — DECKS label + gravity + levity piles
|
||||||
.sea-stacks {
|
.sea-stacks {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 1rem;
|
align-items: center;
|
||||||
justify-content: center;
|
gap: 0.75rem;
|
||||||
margin: 1rem 0;
|
margin: 1rem 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sea-stacks-label {
|
||||||
|
writing-mode: vertical-rl;
|
||||||
|
transform: rotate(180deg);
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-size: 0.6rem;
|
||||||
|
letter-spacing: 0.15em;
|
||||||
|
opacity: 0.45;
|
||||||
|
white-space: nowrap;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.sea-deck-stack {
|
.sea-deck-stack {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.4rem;
|
gap: 0.35rem;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sea-stack-face {
|
.sea-stack-face {
|
||||||
|
position: relative;
|
||||||
width: $sea-card-w;
|
width: $sea-card-w;
|
||||||
height: $sea-card-h;
|
height: $sea-card-h;
|
||||||
border-radius: 0.3rem;
|
border-radius: 0.3rem;
|
||||||
background: rgba(var(--duoUser), 0.8);
|
border: 0.15rem solid;
|
||||||
border: 0.12rem solid rgba(var(--secUser), 0.5);
|
display: flex;
|
||||||
box-shadow: 0 2px 0 rgba(0,0,0,0.2), 0 4px 0 rgba(var(--duoUser),0.7), 0 5px 0 rgba(0,0,0,0.15);
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
transition: box-shadow 0.15s;
|
transition: box-shadow 0.15s;
|
||||||
|
|
||||||
.sea-deck-stack:hover & {
|
|
||||||
box-shadow: 0 2px 0 rgba(0,0,0,0.2), 0 4px 0 rgba(var(--duoUser),0.7), 0 5px 0 rgba(0,0,0,0.15),
|
|
||||||
0 0 0.5rem rgba(var(--terUser), 0.4);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.sea-deck-stack--levity .sea-stack-face { border-color: rgba(var(--terUser), 0.5); }
|
.sea-stack-ok {
|
||||||
.sea-deck-stack--gravity .sea-stack-face { border-color: rgba(var(--quaUser), 0.5); }
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
z-index: 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sea-stack-name {
|
||||||
|
font-size: 0.6rem;
|
||||||
|
letter-spacing: 0.06em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
.sea-deck-stack--gravity .sea-stack-name { color: rgba(var(--quaUser), 1); }
|
||||||
|
.sea-deck-stack--levity .sea-stack-name { color: rgba(var(--terUser), 1); }
|
||||||
|
|
||||||
|
// Deck backs — face-down pile colour identifies polarity
|
||||||
|
$_sea-shadow: 1px 2px 0 rgba(0,0,0,0.7), 0 4px 0 rgba(0,0,0,0.18), 2px 5px 5px rgba(0,0,0,0.5);
|
||||||
|
$_glow-levity: 0 0 0.8rem 0.15rem rgba(var(--ninUser), 0.6);
|
||||||
|
$_glow-gravity: 0 0 0.8rem 0.15rem rgba(var(--quaUser), 0.6);
|
||||||
|
|
||||||
|
.sea-deck-stack--levity .sea-stack-face {
|
||||||
|
background: rgba(var(--terUser), 0.88);
|
||||||
|
border-color: rgba(var(--ninUser), 0.65);
|
||||||
|
box-shadow: $_sea-shadow;
|
||||||
|
}
|
||||||
|
.sea-deck-stack--gravity .sea-stack-face {
|
||||||
|
background: rgba(var(--quiUser), 0.88);
|
||||||
|
border-color: rgba(var(--quaUser), 0.65);
|
||||||
|
box-shadow: $_sea-shadow;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Glow on hover, :active, and while OK is showing (--active class set by JS)
|
||||||
|
.sea-deck-stack--levity:hover .sea-stack-face,
|
||||||
|
.sea-deck-stack--levity:active .sea-stack-face,
|
||||||
|
.sea-deck-stack--levity.sea-deck-stack--active .sea-stack-face { box-shadow: $_sea-shadow, $_glow-levity; }
|
||||||
|
.sea-deck-stack--gravity:hover .sea-stack-face,
|
||||||
|
.sea-deck-stack--gravity:active .sea-stack-face,
|
||||||
|
.sea-deck-stack--gravity.sea-deck-stack--active .sea-stack-face { box-shadow: $_sea-shadow, $_glow-gravity; }
|
||||||
|
|
||||||
// Form action row — LOCK HAND + DEL side by side at the bottom
|
// Form action row — LOCK HAND + DEL side by side at the bottom
|
||||||
.sea-form-actions {
|
.sea-form-actions {
|
||||||
|
|||||||
@@ -30,17 +30,10 @@
|
|||||||
</div>
|
</div>
|
||||||
{# Center — Significator (always placed) + Cover + Cross overlaid #}
|
{# Center — Significator (always placed) + Cover + Cross overlaid #}
|
||||||
<div class="sea-cross-cell sea-pos-center">
|
<div class="sea-cross-cell sea-pos-center">
|
||||||
<div class="sig-stage-card" style="--sig-card-w: 4rem">
|
<div class="sig-stage-card sea-sig-card" style="--sig-card-w: 4rem">
|
||||||
{% if my_tray_sig %}
|
{% if my_tray_sig %}
|
||||||
<div class="fan-card-face">
|
<span class="fan-corner-rank">{{ my_tray_sig.corner_rank }}</span>
|
||||||
{% if my_tray_sig.arcana == "MAJOR" %}
|
{% if my_tray_sig.suit_icon %}<i class="fa-solid {{ my_tray_sig.suit_icon }}"></i>{% endif %}
|
||||||
<p class="fan-card-name">{{ my_tray_sig.name_title }}</p>
|
|
||||||
<p class="sig-qualifier-below">{% if user_polarity == "levity" %}{{ my_tray_sig.levity_qualifier }}{% else %}{{ my_tray_sig.gravity_qualifier }}{% endif %}</p>
|
|
||||||
{% else %}
|
|
||||||
<p class="sig-qualifier-above">{% if user_polarity == "levity" %}{{ my_tray_sig.levity_qualifier }}{% else %}{{ my_tray_sig.gravity_qualifier }}{% endif %}</p>
|
|
||||||
<p class="fan-card-name">{{ my_tray_sig.name_title }}</p>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{# Cover — CC/EV pos 1, stacked face-up on Sig #}
|
{# Cover — CC/EV pos 1, stacked face-up on Sig #}
|
||||||
@@ -82,14 +75,19 @@
|
|||||||
|
|
||||||
{# Two face-down deck piles — tap to proffer OK #}
|
{# Two face-down deck piles — tap to proffer OK #}
|
||||||
<div class="sea-stacks">
|
<div class="sea-stacks">
|
||||||
<div class="sea-deck-stack sea-deck-stack--levity">
|
<span class="sea-stacks-label">DECKS</span>
|
||||||
<div class="sea-stack-face"></div>
|
<div class="sea-deck-stack sea-deck-stack--gravity">
|
||||||
|
<div class="sea-stack-face">
|
||||||
<button class="btn btn-confirm sea-stack-ok" type="button">OK</button>
|
<button class="btn btn-confirm sea-stack-ok" type="button">OK</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="sea-deck-stack sea-deck-stack--gravity">
|
<span class="sea-stack-name">Gravity</span>
|
||||||
<div class="sea-stack-face"></div>
|
</div>
|
||||||
|
<div class="sea-deck-stack sea-deck-stack--levity">
|
||||||
|
<div class="sea-stack-face">
|
||||||
<button class="btn btn-confirm sea-stack-ok" type="button">OK</button>
|
<button class="btn btn-confirm sea-stack-ok" type="button">OK</button>
|
||||||
</div>
|
</div>
|
||||||
|
<span class="sea-stack-name">Levity</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -163,6 +161,7 @@
|
|||||||
if (_activeStack) {
|
if (_activeStack) {
|
||||||
const ok = _activeStack.querySelector('.sea-stack-ok');
|
const ok = _activeStack.querySelector('.sea-stack-ok');
|
||||||
if (ok) ok.style.display = 'none';
|
if (ok) ok.style.display = 'none';
|
||||||
|
_activeStack.classList.remove('sea-deck-stack--active');
|
||||||
_activeStack = null;
|
_activeStack = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -170,19 +169,23 @@
|
|||||||
function _showOk(stack) {
|
function _showOk(stack) {
|
||||||
_hideOk();
|
_hideOk();
|
||||||
_activeStack = stack;
|
_activeStack = stack;
|
||||||
|
stack.classList.add('sea-deck-stack--active');
|
||||||
const ok = stack.querySelector('.sea-stack-ok');
|
const ok = stack.querySelector('.sea-stack-ok');
|
||||||
if (ok) ok.style.display = '';
|
if (ok) ok.style.display = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
function _fillPos(sel, card) {
|
function _fillPos(sel, card, isLevity) {
|
||||||
const cell = overlay.querySelector(sel);
|
const cell = overlay.querySelector(sel);
|
||||||
if (!cell) return;
|
if (!cell) return;
|
||||||
const slot = cell.querySelector('.sea-card-slot');
|
const slot = cell.querySelector('.sea-card-slot');
|
||||||
if (!slot) return;
|
if (!slot) return;
|
||||||
slot.classList.remove('sea-card-slot--empty');
|
slot.classList.remove('sea-card-slot--empty');
|
||||||
slot.classList.add('sea-card-slot--filled');
|
slot.classList.add('sea-card-slot--filled');
|
||||||
|
slot.classList.add(isLevity ? 'sea-card-slot--levity' : 'sea-card-slot--gravity');
|
||||||
slot.dataset.cardId = String(card.id);
|
slot.dataset.cardId = String(card.id);
|
||||||
slot.textContent = card.name;
|
slot.innerHTML =
|
||||||
|
`<span class="fan-corner-rank">${card.corner_rank}</span>` +
|
||||||
|
(card.suit_icon ? `<i class="fa-solid ${card.suit_icon}"></i>` : '');
|
||||||
_filled++;
|
_filled++;
|
||||||
if (lockBtn) lockBtn.disabled = (_filled < 6);
|
if (lockBtn) lockBtn.disabled = (_filled < 6);
|
||||||
}
|
}
|
||||||
@@ -191,9 +194,9 @@
|
|||||||
_filled = 0;
|
_filled = 0;
|
||||||
_hideOk();
|
_hideOk();
|
||||||
overlay.querySelectorAll('.sea-card-slot').forEach(s => {
|
overlay.querySelectorAll('.sea-card-slot').forEach(s => {
|
||||||
s.classList.remove('sea-card-slot--filled');
|
s.classList.remove('sea-card-slot--filled', 'sea-card-slot--levity', 'sea-card-slot--gravity');
|
||||||
s.classList.add('sea-card-slot--empty');
|
s.classList.add('sea-card-slot--empty');
|
||||||
s.textContent = '';
|
s.innerHTML = '';
|
||||||
delete s.dataset.cardId;
|
delete s.dataset.cardId;
|
||||||
});
|
});
|
||||||
if (lockBtn) lockBtn.disabled = true;
|
if (lockBtn) lockBtn.disabled = true;
|
||||||
@@ -221,7 +224,7 @@
|
|||||||
const pile = isLevity ? levityPile : gravityPile;
|
const pile = isLevity ? levityPile : gravityPile;
|
||||||
const card = pile.length ? pile.shift() : null;
|
const card = pile.length ? pile.shift() : null;
|
||||||
const pos = _nextPosSelector();
|
const pos = _nextPosSelector();
|
||||||
if (card && pos) _fillPos(pos, card);
|
if (card && pos) _fillPos(pos, card, isLevity);
|
||||||
_hideOk();
|
_hideOk();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user