PICK SEA Sprint B: deck stacks, OK btn, card draw, LOCK HAND/DEL — TDD

- _sea_overlay.html: DEAL btn replaced by two .sea-deck-stack--levity/gravity
  piles; .sea-pos-cover + .sea-pos-cross overlaid on center sig slot; LOCK HAND
  (disabled) + DEL (.btn-danger) in .sea-form-actions; data-sea-deck-url attr
- sea overlay inline JS: _fetchDeck() loads shuffled piles from sea_deck endpoint;
  stack click → _showOk(); click elsewhere → _hideOk(); OK click → _fillPos()
  in next spread-order position; DEL → _reset(); LOCK HAND enables at 6 fills
- SPREAD_ORDER constants for waite-smith + escape-velocity spread types
- sea_deck view: shuffles full equipped deck minus all seated Significators,
  splits into levity (first half) + gravity (second half) JSON arrays
- epic:sea_deck URL registered
- sea_partial view: ctx['room'] = room added (fixes NoReverseMatch for sea_deck URL)
- _card-deck.scss: .sea-card-slot--filled; .sea-pos-cover/cross absolute overlay;
  .sea-deck-stack + .sea-stack-face; .sea-form-actions layout; removed old DEAL rule
- 9 Sprint B FTs green; 3 Sprint A FTs green; 730 ITs 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:
Disco DeDisco
2026-04-28 23:02:49 -04:00
parent ff3e4d295c
commit 132e60864e
4 changed files with 240 additions and 11 deletions

View File

@@ -4,7 +4,8 @@
{# Layout is the reverse of PICK SKY: cards left (transparent), form right #}
<div class="sea-backdrop"></div>
<div class="sea-overlay" id="id_sea_overlay">
<div class="sea-overlay" id="id_sea_overlay"
data-sea-deck-url="{% url 'epic:sea_deck' room.id %}">
<div class="sea-modal-wrap">
<div class="sea-modal">
@@ -19,15 +20,15 @@
{# ── Cards column (transparent) ───────────────────────────── #}
<div class="sea-cards-col">
<div class="sea-cross">
{# Crown — position 3 #}
{# Crown — CC pos 3 / EV pos 5 #}
<div class="sea-cross-cell sea-pos-crown">
<div class="sea-card-slot sea-card-slot--empty"></div>
</div>
{# Past — position 4 #}
{# Beneath (past)CC pos 4 / EV pos 3 #}
<div class="sea-cross-cell sea-pos-past">
<div class="sea-card-slot sea-card-slot--empty"></div>
</div>
{# Center — Significator (already placed) #}
{# Center — Significator (always placed) + Cover + Cross overlaid #}
<div class="sea-cross-cell sea-pos-center">
<div class="sig-stage-card" style="--sig-card-w: 4rem">
{% if my_tray_sig %}
@@ -42,16 +43,23 @@
</div>
{% endif %}
</div>
{# Cover — CC/EV pos 1, stacked face-up on Sig #}
<div class="sea-pos-cover">
<div class="sea-card-slot sea-card-slot--empty"></div>
</div>
{# Cross — CC/EV pos 2, rotated 90° on Cover #}
<div class="sea-pos-cross">
<div class="sea-card-slot sea-card-slot--empty sea-card-slot--crossing"></div>
</div>
</div>
{# Future — position 5 #}
{# Before (future)CC pos 5 / EV pos 6 #}
<div class="sea-cross-cell sea-pos-future">
<div class="sea-card-slot sea-card-slot--empty"></div>
</div>
{# Root — position 1 #}
{# Behind (root)CC pos 6 / EV pos 4 #}
<div class="sea-cross-cell sea-pos-root">
<div class="sea-card-slot sea-card-slot--empty"></div>
</div>
{# Crossing — position 2 (rotated) deferred; re-add once layout is finalized #}
</div>
</div>
@@ -71,11 +79,28 @@
{% endif %}
</select>
</div>
{# Two face-down deck piles — tap to proffer OK #}
<div class="sea-stacks">
<div class="sea-deck-stack sea-deck-stack--levity">
<div class="sea-stack-face"></div>
<button class="btn btn-confirm sea-stack-ok" type="button">OK</button>
</div>
<div class="sea-deck-stack sea-deck-stack--gravity">
<div class="sea-stack-face"></div>
<button class="btn btn-confirm sea-stack-ok" type="button">OK</button>
</div>
</div>
</div>
<button type="button" id="id_sea_deal" class="btn btn-primary" disabled>
Deal
</button>
<div class="sea-form-actions">
<button type="button" id="id_sea_lock_hand" class="btn btn-primary" disabled>
LOCK HAND
</button>
<button type="button" id="id_sea_del" class="btn btn-danger">
DEL
</button>
</div>
</div>
@@ -107,5 +132,104 @@
if (pickSeaBtn) pickSeaBtn.addEventListener('click', openSea);
cancelBtn.addEventListener('click', closeSea);
overlay.addEventListener('click', (e) => { if (e.target === overlay) closeSea(); });
// ── Deck draw ──────────────────────────────────────────────────────────────
const SEA_DECK_URL = overlay.dataset.seaDeckUrl;
const SPREAD_ORDER = {
'waite-smith': ['.sea-pos-cover', '.sea-pos-cross', '.sea-pos-crown', '.sea-pos-root', '.sea-pos-future', '.sea-pos-past'],
'escape-velocity': ['.sea-pos-cover', '.sea-pos-cross', '.sea-pos-root', '.sea-pos-past', '.sea-pos-crown', '.sea-pos-future'],
};
let levityPile = [], gravityPile = [];
let _filled = 0;
let _activeStack = null;
const spreadSel = overlay.querySelector('#id_sea_spread');
const lockBtn = overlay.querySelector('#id_sea_lock_hand');
const delBtn = overlay.querySelector('#id_sea_del');
function _spreadKey() {
return spreadSel ? spreadSel.value : 'waite-smith';
}
function _nextPosSelector() {
const order = SPREAD_ORDER[_spreadKey()] || SPREAD_ORDER['waite-smith'];
return order[_filled] || null;
}
function _hideOk() {
if (_activeStack) {
const ok = _activeStack.querySelector('.sea-stack-ok');
if (ok) ok.style.display = 'none';
_activeStack = null;
}
}
function _showOk(stack) {
_hideOk();
_activeStack = stack;
const ok = stack.querySelector('.sea-stack-ok');
if (ok) ok.style.display = '';
}
function _fillPos(sel, card) {
const cell = overlay.querySelector(sel);
if (!cell) return;
const slot = cell.querySelector('.sea-card-slot');
if (!slot) return;
slot.classList.remove('sea-card-slot--empty');
slot.classList.add('sea-card-slot--filled');
slot.dataset.cardId = String(card.id);
slot.textContent = card.name;
_filled++;
if (lockBtn) lockBtn.disabled = (_filled < 6);
}
function _reset() {
_filled = 0;
_hideOk();
overlay.querySelectorAll('.sea-card-slot').forEach(s => {
s.classList.remove('sea-card-slot--filled');
s.classList.add('sea-card-slot--empty');
s.textContent = '';
delete s.dataset.cardId;
});
if (lockBtn) lockBtn.disabled = true;
_fetchDeck();
}
function _fetchDeck() {
fetch(SEA_DECK_URL, { credentials: 'same-origin' })
.then(r => r.json())
.then(data => { levityPile = data.levity || []; gravityPile = data.gravity || []; })
.catch(() => {});
}
overlay.querySelectorAll('.sea-deck-stack').forEach(stack => {
stack.addEventListener('click', e => {
e.stopPropagation();
_activeStack === stack ? _hideOk() : _showOk(stack);
});
const ok = stack.querySelector('.sea-stack-ok');
if (ok) {
ok.style.display = 'none';
ok.addEventListener('click', e => {
e.stopPropagation();
const isLevity = stack.classList.contains('sea-deck-stack--levity');
const pile = isLevity ? levityPile : gravityPile;
const card = pile.length ? pile.shift() : null;
const pos = _nextPosSelector();
if (card && pos) _fillPos(pos, card);
_hideOk();
});
}
});
overlay.addEventListener('click', _hideOk);
if (delBtn) delBtn.addEventListener('click', _reset);
_fetchDeck();
})();
</script>