Files
python-tdd/src/templates/apps/gameboard/_partials/_sea_overlay.html
Disco DeDisco 2bf439eab5 Sea Select options: disabled-btn contrast + AUTO DRAW scrolls back to the cross
- A disabled OK/DEL × inside a --priUser option chunk blended into it (the global
  `.btn-disabled` bg is also --priUser → no visible circle). Drop the disabled
  btns in `.sea-options-col` to the felt --duoUser so they read as a distinct
  disabled circle, like the deck-stack FLIP ×.
- AUTO DRAW now eases the felt back UP to the cross even when the user already
  OK'd the spread + scrolled DOWN to the options page — so he watches the cards
  land one-by-one. `_chooseSpread(slideIn)`: the OK reveal pins to the options
  (slide-in from above); AUTO DRAW (already chosen) skips the pin + just eases up
  to the cross. `_scrollToCross` now eases from the current scroll position.
- 12 PickSeaUnifiedFeltTest render ITs green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 00:14:25 -04:00

560 lines
28 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{% load static %}
{% comment %}
DRAW SEA — Sea Select felt (2026-06-07), mirroring Sky Select's form→wheel
scroll-snap. The felt starts with the spread OPTIONS (.sea-options-col, page 1):
the .sea-select combobox (the two Celtic Cross spreads only) + a mini shape
preview + OK + AUTO DRAW + DEL. Clicking OK confirms the spread → the options
shunt DOWN and the spread CROSS (.sea-cross-col, w. the Gravity/Levity deck
stacks) takes page 1 via scroll-snap (scroll down to find the options again).
NO modal, NO corner NVM. The burger #id_sea_btn is the post-completion REOPEN
affordance (active only once the spread is complete, like the sky btn) — NOT
active during drawing. Each draw persists onto the seat's Character.celtic_cross
via epic:sea_save. `?seat` threads the CARTE-selected seat onto the action URLs.
{% endcomment %}
<div class="sea-page sea-page--room{% if saved_by_position %} sea-spread-chosen{% endif %}" id="id_sea_page">
<div id="id_sea_overlay" class="sea-overlay-content"
data-sea-deck-url="{% url 'epic:sea_deck' room.id %}{% if current_slot %}?seat={{ current_slot }}{% endif %}"
data-sea-save-url="{% url 'epic:sea_save' room.id %}{% if current_slot %}?seat={{ current_slot }}{% endif %}"
data-sea-delete-url="{% url 'epic:sea_delete' room.id %}{% if current_slot %}?seat={{ current_slot }}{% endif %}"
data-sea-user-polarity="{{ user_polarity }}">
{# ── OPTIONS section — page 1 initially; shunts to page 2 on OK. The select #}
{# form + preview + the OK / AUTO DRAW / DEL actions. NO deck stacks here. #}
<div class="sea-options-col">
<div class="sea-form-col">
<div class="sea-form-main">
<div class="sea-field">
<label for="id_sea_spread" id="id_sea_spread_label">Spread</label>
<p class="sea-reversal-hint">{{ stack_reversal_pct|default:25 }}% reversals</p>
{# Two Celtic Cross 6-card spreads only (user-spec 2026-06-07). #}
<input type="hidden" id="id_sea_spread" name="spread" autocomplete="off"
value="{{ sea_default_spread }}">
{# Combobox + OK on one row — OK confirms the spread → shunts. #}
{# OK gains .btn-disabled + × the moment the first card is drawn #}
{# (the spread is then locked, the select disables, DEL enables). #}
<div class="sea-select-row">
<div class="sea-select"
data-combobox
data-combobox-target="id_sea_spread"
role="combobox"
aria-expanded="false"
aria-haspopup="listbox"
aria-labelledby="id_sea_spread_label"
tabindex="0">
<span class="sea-select-current">{% if sea_default_spread == "escape-velocity" %}Celtic Cross, Escape Velocity{% else %}Celtic Cross, Waite-Smith{% endif %}</span>
<span class="sea-select-arrow" aria-hidden="true"></span>
<ul class="sea-select-list" role="listbox">
{% if user_polarity == "levity" %}
<li role="option" data-value="waite-smith" aria-selected="{% if sea_default_spread == 'escape-velocity' %}false{% else %}true{% endif %}">Celtic Cross, Waite-Smith</li>
<li role="option" data-value="escape-velocity" aria-selected="{% if sea_default_spread == 'escape-velocity' %}true{% else %}false{% endif %}">Celtic Cross, Escape Velocity</li>
{% else %}
<li role="option" data-value="escape-velocity" aria-selected="{% if sea_default_spread == 'waite-smith' %}false{% else %}true{% endif %}">Celtic Cross, Escape Velocity</li>
<li role="option" data-value="waite-smith" aria-selected="{% if sea_default_spread == 'waite-smith' %}true{% else %}false{% endif %}">Celtic Cross, Waite-Smith</li>
{% endif %}
</ul>
</div>
<button type="button" id="id_sea_confirm_spread" class="btn btn-confirm{% if saved_by_position %} btn-disabled{% endif %}">{% if saved_by_position %}&times;{% else %}OK{% endif %}</button>
</div>
</div>
{# Miniaturized spread preview — shape only, never dealt to. Shows #}
{# the chosen spread's shape before OK reveals the real cross. #}
{% include "apps/gameboard/_partials/_sea_spread_preview.html" with preview_spread=sea_default_spread sig=my_tray_sig %}
</div>
<div class="sea-form-actions">
{# AUTO DRAW commits the remaining hand in one POST + animates onto #}
{# the cross (confirms the spread first if not yet). DEL clears. #}
<button type="button"
id="id_sea_action_btn"
class="btn btn-primary{% if hand_complete %} btn-disabled{% endif %}"
data-state="{% if hand_complete %}complete{% else %}auto-draw{% endif %}">AUTO<br>DRAW</button>
<button type="button"
id="id_sea_del"
class="btn btn-danger{% if not saved_by_position %} btn-disabled{% endif %}">{% if saved_by_position %}DEL{% else %}&times;{% endif %}</button>
</div>
</div>
</div>
{# ── CROSS section — hidden until OK; then order:-1 takes page 1. The REAL #}
{# deal target (.my-sea-cross) + the Gravity/Levity deck stacks (FLIP). #}
<div class="sea-cross-col">
<div class="sea-cards-col">
<div class="sea-cross my-sea-cross" data-spread="{{ sea_default_spread }}">
<div class="sea-crucifix-cell sea-pos-crown">
<span class="sea-pos-label" data-position="crown"></span>
{% include "apps/gameboard/_partials/_my_sea_slot.html" with position="crown" saved=saved_by_position.crown crossing=False %}
</div>
<div class="sea-crucifix-cell sea-pos-leave">
<span class="sea-pos-label" data-position="leave"></span>
{% include "apps/gameboard/_partials/_my_sea_slot.html" with position="leave" saved=saved_by_position.leave crossing=False %}
</div>
<div class="sea-crucifix-cell sea-pos-core">
{# Center significator — supply the card-FACE image when the sig's #}
{# deck has one (mirrors my_sea); else the corner-rank + suit-icon #}
{# text thumbnail. my_tray_sig.deck_variant is the card's OWN deck #}
{# (Sig Select picks from the Role Select contributed deck). #}
<div class="sig-stage-card sea-sig-card{% if my_tray_sig.deck_variant.has_card_images %} sig-stage-card--image{% endif %}" style="--sig-card-w: 4rem"{% if my_tray_sig %} data-card-id="{{ my_tray_sig.id }}" data-arcana-key="{{ my_tray_sig.arcana }}"{% endif %}>
{% if my_tray_sig %}
{% if my_tray_sig.deck_variant.has_card_images %}
<img class="sig-stage-card-img" src="{{ my_tray_sig.image_url }}" alt="{{ my_tray_sig.name }}">
{% else %}
<span class="fan-corner-rank">{{ my_tray_sig.corner_rank }}</span>
{% if my_tray_sig.suit_icon %}<i class="fa-solid {{ my_tray_sig.suit_icon }}"></i>{% endif %}
{% endif %}
{% endif %}
</div>
<div class="sea-pos-cover">
<span class="sea-pos-label" data-position="cover"></span>
{% include "apps/gameboard/_partials/_my_sea_slot.html" with position="cover" saved=saved_by_position.cover crossing=False %}
</div>
<div class="sea-pos-cross">
<span class="sea-pos-label" data-position="cross"></span>
{% include "apps/gameboard/_partials/_my_sea_slot.html" with position="cross" saved=saved_by_position.cross crossing=True %}
</div>
</div>
<div class="sea-crucifix-cell sea-pos-loom">
<span class="sea-pos-label" data-position="loom"></span>
{% include "apps/gameboard/_partials/_my_sea_slot.html" with position="loom" saved=saved_by_position.loom crossing=False %}
</div>
<div class="sea-crucifix-cell sea-pos-lay">
<span class="sea-pos-label" data-position="lay"></span>
{% include "apps/gameboard/_partials/_my_sea_slot.html" with position="lay" saved=saved_by_position.lay crossing=False %}
</div>
</div>
</div>
{# ALWAYS two stacks (Gravity + Levity) — the gamer draws from EITHER #}
{# populated half (sea_deck returns both), even a CARTE monodeck. #}
<div class="my-sea-stacks-wrap">
<div class="sea-stacks">
<span class="sea-stacks-label">DECKS</span>
<div class="sea-deck-stack sea-deck-stack--gravity">
<div class="sea-stack-face">
<button class="btn btn-reveal sea-stack-ok{% if hand_complete %} btn-disabled{% endif %}" type="button">{% if hand_complete %}&times;{% else %}FLIP{% endif %}</button>
</div>
<span class="sea-stack-name">Gravity</span>
</div>
<div class="sea-deck-stack sea-deck-stack--levity">
<div class="sea-stack-face">
<button class="btn btn-reveal sea-stack-ok{% if hand_complete %} btn-disabled{% endif %}" type="button">{% if hand_complete %}&times;{% else %}FLIP{% endif %}</button>
</div>
<span class="sea-stack-name">Levity</span>
</div>
</div>
</div>
</div>
{# Sea stage — portaled big-card viewer (position:fixed, escapes the snap). #}
{% include "apps/gameboard/_partials/_sea_stage.html" %}
</div>
</div>
<script>
(function () {
'use strict';
var page = document.getElementById('id_sea_page');
var overlay = document.getElementById('id_sea_overlay');
if (!page || !overlay) return;
// ── Per-spread draw order + labels — the two Celtic Cross variants only.
var DRAW_ORDER = {
'waite-smith': ['cover', 'cross', 'crown', 'lay', 'loom', 'leave'],
'escape-velocity': ['cover', 'cross', 'lay', 'leave', 'crown', 'loom'],
};
var POSITION_LABELS = {
'waite-smith': { crown: 'Crown', leave: 'Behind', cover: 'Cover', cross: 'Cross', loom: 'Before', lay: 'Beneath' },
'escape-velocity': { crown: 'Crown', leave: 'Leave', cover: 'Cover', cross: 'Cross', loom: 'Loom', lay: 'Lay' },
};
var hidden = overlay.querySelector('#id_sea_spread');
var cross = overlay.querySelector('.my-sea-cross');
var preview = overlay.querySelector('.sea-cross--preview');
var actionBtn = overlay.querySelector('#id_sea_action_btn');
var delBtn = overlay.querySelector('#id_sea_del');
var okBtn = overlay.querySelector('#id_sea_confirm_spread');
var seaSelect = overlay.querySelector('[data-combobox][data-combobox-target="id_sea_spread"]');
if (!hidden || !cross) return;
var SEA_DECK_URL = overlay.dataset.seaDeckUrl;
var SEA_SAVE_URL = overlay.dataset.seaSaveUrl;
var SEA_DELETE_URL = overlay.dataset.seaDeleteUrl;
var _levityPile = [], _gravityPile = [];
var _filled = 0;
var _activeStack = null;
var _locked = false;
function _csrf() {
var m = document.cookie.match(/(?:^|; )csrftoken=([^;]+)/);
return m ? decodeURIComponent(m[1]) : '';
}
function _currentOrder() { return DRAW_ORDER[hidden.value] || DRAW_ORDER['waite-smith']; }
function _fetchDeck() {
return fetch(SEA_DECK_URL, { credentials: 'same-origin' })
.then(function (r) { return r.json(); })
.then(function (data) { _levityPile = data.levity || []; _gravityPile = data.gravity || []; })
.catch(function () {});
}
function _hideOk() {
if (_activeStack) { _activeStack.classList.remove('sea-deck-stack--active'); _activeStack = null; }
}
function _showOk(stack) { _hideOk(); _activeStack = stack; stack.classList.add('sea-deck-stack--active'); }
function _lockSpread() {
if (seaSelect) { seaSelect.classList.add('sea-select--locked'); seaSelect.setAttribute('aria-disabled', 'true'); }
}
// ── Confirm the chosen spread → shunt the options DOWN + reveal the cross.
// Mirrors Sky Select's form→wheel: the felt page gets `sea-spread-chosen` → the
// SCSS engages the scroll-snap (the cross takes page 1 via order:-1, the options
// shunt to page 2); lock the combobox; ease the scroller up to the cross.
// Idempotent. Triggered by OK and (defensively) by AUTO DRAW.
var _spreadChosen = page.classList.contains('sea-spread-chosen');
function _chooseSpread(slideIn) {
if (!_spreadChosen) {
_spreadChosen = true;
page.classList.add('sea-spread-chosen');
slideIn = true; // the first reveal always slides in from below
}
// NB: the combobox is NOT locked here — the spread stays changeable (scroll
// up) until the FIRST card is drawn, when _lockSpread() + _setHasDrawn(true)
// disable the select + OK together (user-spec 2026-06-07).
// `slideIn` (OK reveal) pins to the options (page 2) first so the cross
// slides in from above. AUTO DRAW fired from a scrolled-down options page
// skips the pin + just eases UP to the cross (so the user watches the draw).
if (slideIn) {
var opts = overlay.querySelector('.sea-options-col');
if (opts) page.scrollTop = opts.offsetTop;
}
_scrollToCross();
}
function _scrollToCross() {
// Ease the scroller UP to the cross (page 1, scrollTop 0) from wherever it is.
if (page.scrollTop === 0) return;
var start = page.scrollTop, t0 = null, DUR = 320;
function step(now) {
if (t0 === null) t0 = now;
var t = Math.min(1, (now - t0) / DUR);
var u = 1 - t, e = 1 - u * u * u;
page.scrollTop = Math.max(0, start * (1 - e));
if (t < 1) requestAnimationFrame(step);
}
requestAnimationFrame(step);
}
if (okBtn) okBtn.addEventListener('click', function () {
if (okBtn.classList.contains('btn-disabled')) return;
_chooseSpread(true);
});
// First card drawn → DEL un-disables ("DEL") + OK disables ("×"), simultaneous
// with the combobox locking (`_lockSpread`). The two btns are inverse states.
function _setHasDrawn(on) {
if (delBtn) { delBtn.classList.toggle('btn-disabled', !on); delBtn.innerHTML = on ? 'DEL' : '×'; }
if (okBtn) { okBtn.classList.toggle('btn-disabled', on); okBtn.innerHTML = on ? '×' : 'OK'; }
}
function _setComplete(on, live) {
_locked = on;
overlay.querySelectorAll('.sea-deck-stack .sea-stack-ok').forEach(function (btn) {
btn.classList.toggle('btn-disabled', on);
btn.innerHTML = on ? '×' : 'FLIP';
});
if (actionBtn) {
actionBtn.dataset.state = on ? 'complete' : 'auto-draw';
actionBtn.classList.toggle('btn-disabled', on);
}
_hideOk();
if (on && live) _startSeaCascade();
}
// ── Post-completion cascade (mirrors CAST SKY's _startSaveCascade) ──────────
// 6 cards down → linger ~3s → the felt eases OUT (revealing the table-hex) →
// DRAW SEA gives way to SEED MAP + the sea glow fires (burger → sea_btn) → +3s
// → SEED MAP eases IN. Same shape as CAST SKY → sky-btn glow → DRAW SEA.
var _SEA_CASCADE_LINGER = 3000, _SEA_FELT_FADE = 500, _SEED_DELAY = 3000;
var _cascadeRun = false;
function _startSeaCascade() {
if (_cascadeRun) return;
_cascadeRun = true;
setTimeout(_cascadeFeltOut, _SEA_CASCADE_LINGER);
}
function _cascadeFeltOut() {
page.classList.add('sea-page--cascade-out');
setTimeout(function () {
document.documentElement.classList.remove('sea-open');
page.classList.remove('sea-page--cascade-out');
_restorePhaseBtns();
if (window.RoomViews && window.RoomViews.syncGear) window.RoomViews.syncGear();
var seaPhaseBtn = document.getElementById('id_pick_sea_btn');
if (seaPhaseBtn) seaPhaseBtn.classList.add('hex-phase-btn--out');
// The sea btn becomes the reopen/review affordance (active only NOW — the
// post-completion cue, like the sky btn) + the sea glow fires on the burger.
var seaBtn = document.getElementById('id_sea_btn');
if (seaBtn) seaBtn.classList.add('active');
var burgerBtn = document.getElementById('id_burger_btn');
if (burgerBtn) burgerBtn.classList.add('glow-handoff');
setTimeout(_cascadeSeedMapIn, _SEED_DELAY);
}, _SEA_FELT_FADE);
}
function _cascadeSeedMapIn() {
var seedBtn = document.getElementById('id_seed_map_btn');
if (seedBtn) seedBtn.classList.remove('hex-phase-btn--out');
}
function _collectHandFromDom() {
var byPos = {};
cross.querySelectorAll('.sea-card-slot.sea-card-slot--filled').forEach(function (slot) {
var pos = slot.dataset.posKey || '';
if (!pos) return;
byPos[pos] = {
position: pos,
card_id: parseInt(slot.dataset.cardId, 10),
reversed: /sea-card-slot--reversed\b/.test(slot.className),
polarity: /sea-card-slot--levity\b/.test(slot.className) ? 'levity' : 'gravity',
};
});
var hand = [];
_currentOrder().forEach(function (pos) { if (byPos[pos]) hand.push(byPos[pos]); });
return hand;
}
function _postLock(hand) {
if (!hand.length) return Promise.resolve(null);
return fetch(SEA_SAVE_URL, {
method: 'POST',
credentials: 'same-origin',
headers: { 'Content-Type': 'application/json', 'X-CSRFToken': _csrf() },
body: JSON.stringify({ spread: hidden.value, hand: hand }),
}).then(function (r) { return r.ok ? r.json() : null; }).catch(function () { return null; });
}
// ── Deck-stack FLIP → deposit one card onto the felt cross.
overlay.querySelectorAll('.sea-deck-stack').forEach(function (stack) {
stack.addEventListener('click', function (e) {
e.stopPropagation();
if (_activeStack === stack) _hideOk(); else _showOk(stack);
});
var ok = stack.querySelector('.sea-stack-ok');
if (ok) {
ok.addEventListener('click', function (e) {
e.stopPropagation();
if (_locked) { _hideOk(); return; }
var isLevity = stack.classList.contains('sea-deck-stack--levity');
var pile = isLevity ? _levityPile : _gravityPile;
if (!pile.length) { pile = isLevity ? _gravityPile : _levityPile; isLevity = !isLevity; }
var card = pile.length ? pile.shift() : null;
var order = _currentOrder();
var posName = order[_filled];
if (card && posName) {
if (window.SeaDeal && window.SeaDeal.openStage) {
SeaDeal.openStage(card, '.sea-pos-' + posName, isLevity);
}
_filled++;
if (_filled === 1) { _lockSpread(); _setHasDrawn(true); }
_postLock(_collectHandFromDom());
if (_filled >= order.length) _setComplete(true, true); // live → cascade
}
_hideOk();
});
}
});
overlay.addEventListener('click', _hideOk);
// ── AUTO DRAW — confirm the spread (reveal the cross) then commit + animate.
function _autoDraw() {
// Reveal the cross if not yet, AND scroll back UP to it even when the user
// already OK'd + scrolled down to the options page — so he watches the cards
// land one-by-one (user-spec 2026-06-08). `_chooseSpread(false)` skips the
// slide-in pin when already chosen, just easing up to the cross.
_chooseSpread(false);
var order = _currentOrder();
var remaining = order.length - _filled;
if (remaining <= 0) return;
var autoEntries = [];
for (var i = 0; i < remaining; i++) {
var isLevity = false;
var pile = _gravityPile;
if (_levityPile.length && (!_gravityPile.length || Math.round(Math.random()))) { isLevity = true; pile = _levityPile; }
if (!pile.length) { isLevity = !isLevity; pile = isLevity ? _levityPile : _gravityPile; }
if (!pile.length) break;
autoEntries.push({ card: pile.shift(), posName: order[_filled + i], isLevity: isLevity });
}
var fullHand = _collectHandFromDom();
autoEntries.forEach(function (e) {
fullHand.push({ position: e.posName, card_id: e.card.id, reversed: !!e.card.reversed, polarity: e.isLevity ? 'levity' : 'gravity' });
});
_postLock(fullHand).then(function (body) {
if (!body || !body.ok) return;
if (_filled === 0) _lockSpread();
_setHasDrawn(true);
var idx = 0;
function placeNext() {
if (idx >= autoEntries.length) { _setComplete(true, true); return; } // live → cascade
var e = autoEntries[idx++];
var stack = overlay.querySelector('.sea-deck-stack--' + (e.isLevity ? 'levity' : 'gravity'));
if (stack) _showOk(stack);
setTimeout(function () {
if (window.SeaDeal && window.SeaDeal.register) {
SeaDeal.register(e.card, '.sea-pos-' + e.posName, e.isLevity);
}
var sl = cross.querySelector('.sea-pos-' + e.posName + ' .sea-card-slot');
if (sl) sl.classList.add('sea-card-slot--visible');
_filled++;
_hideOk();
setTimeout(placeNext, 250);
}, 350);
}
placeNext();
});
}
// ── DEL — guard → clear the seat's celtic_cross → reload.
if (delBtn) {
delBtn.addEventListener('click', function (e) {
e.stopPropagation();
if (delBtn.classList.contains('btn-disabled')) return;
if (!window.showGuard) return;
window.showGuard(delBtn, 'Are you sure?', function () {
fetch(SEA_DELETE_URL, {
method: 'POST', credentials: 'same-origin', headers: { 'X-CSRFToken': _csrf() },
}).then(function (r) { if (r.ok) window.location.reload(); });
});
});
}
// ── AUTO DRAW btn → guard portal.
if (actionBtn) {
actionBtn.addEventListener('click', function (e) {
e.preventDefault();
if (actionBtn.dataset.state !== 'auto-draw') return;
if (!window.showGuard) return;
window.showGuard(actionBtn, 'Auto Draw cards?', _autoDraw);
});
}
// ── Spread switch (pre-OK) → re-layout the felt cross + the preview + labels.
function syncLabels(spread) {
var labels = POSITION_LABELS[spread] || {};
cross.querySelectorAll('.sea-pos-label').forEach(function (el) {
el.textContent = labels[el.dataset.position] || '';
});
}
function sync() {
cross.setAttribute('data-spread', hidden.value);
if (preview) preview.setAttribute('data-spread', hidden.value);
syncLabels(hidden.value);
}
hidden.addEventListener('change', sync);
// ── Open / close the felt (DRAW SEA ⇄ gear NVM nav). On open: disable the
// burger Text + Sky sub-btns. The sea btn is NOT activated here — it's the
// post-completion reopen affordance (see the cascade + the reopen binding).
var _disabledBtns = [];
function _disablePhaseBtns() {
_disabledBtns = [];
['id_text_btn', 'id_sky_btn'].forEach(function (id) {
var b = document.getElementById(id);
if (b && b.classList.contains('active')) { b.classList.remove('active'); _disabledBtns.push(b); }
});
}
function _restorePhaseBtns() {
_disabledBtns.forEach(function (b) { b.classList.add('active'); });
_disabledBtns = [];
}
function openSea() {
document.documentElement.classList.add('sea-open');
document.documentElement.classList.add('sea-entered');
_disablePhaseBtns();
if (window.RoomViews && window.RoomViews.syncGear) window.RoomViews.syncGear();
}
function closeSea() {
document.documentElement.classList.remove('sea-open');
_restorePhaseBtns();
if (window.RoomViews && window.RoomViews.syncGear) window.RoomViews.syncGear();
}
window.openSeaFelt = openSea;
var pickSeaBtn = document.getElementById('id_pick_sea_btn');
if (pickSeaBtn) pickSeaBtn.addEventListener('click', openSea);
// ── Sea sub-btn = post-completion REOPEN (active only once the spread is
// complete, like the sky btn). An ACTIVE click re-shows the felt to review the
// saved spread (window.openSeaFelt); inactive clicks fall to burger-btn.js's
// --priRd flash.
var seaSubBtn = document.getElementById('id_sea_btn');
if (seaSubBtn) {
seaSubBtn.addEventListener('click', function () {
if (!seaSubBtn.classList.contains('active')) return;
openSea();
});
}
// Re-seed SeaDeal's `_seaHand` from the server-rendered saved slots so they
// stay clickable to RE-OPEN the stage after a refresh.
function _seedSavedHand() {
if (!window.SeaDeal || !window.SeaDeal.seedHand) return;
var byId = {};
_levityPile.concat(_gravityPile).forEach(function (c) { byId[c.id] = c; });
var seed = {};
cross.querySelectorAll('.sea-card-slot.sea-card-slot--filled').forEach(function (slot) {
var posName = slot.dataset.posKey;
var card = byId[parseInt(slot.dataset.cardId, 10)];
if (!posName || !card) return;
var slotCard = {};
for (var k in card) if (Object.prototype.hasOwnProperty.call(card, k)) slotCard[k] = card[k];
slotCard.reversed = slot.classList.contains('sea-card-slot--reversed');
seed[posName] = { card: slotCard, isLevity: slot.classList.contains('sea-card-slot--levity') };
});
SeaDeal.seedHand(seed);
}
// ── Init — bind SeaDeal, seed the deck, then re-seed the saved hand.
if (window.SeaDeal && window.SeaDeal.reinit) SeaDeal.reinit();
_fetchDeck().then(_seedSavedHand);
_filled = cross.querySelectorAll('.sea-card-slot.sea-card-slot--filled').length;
if (_filled > 0) document.documentElement.classList.add('sea-entered');
if (_filled >= _currentOrder().length) { _setComplete(true); _lockSpread(); _setHasDrawn(true); }
else if (_filled > 0) { _lockSpread(); _setHasDrawn(true); }
syncLabels(hidden.value);
}());
</script>
{# ── Sea glow handoff — the SEA glow-machine does NOT fire while drawing. The #}
{# post-completion CASCADE (in the picker IIFE above) STARTS it on the burger #}
{# as the felt eases out (DRAW SEA → SEED MAP), mirroring CAST SKY's burger → #}
{# sky-btn cue. This IIFE only carries the handoff: burger → sea_btn → #}
{# .sea-select → end, so the user is led to reopen + review their sea. #}
<script>
(function () {
var burgerBtn = document.getElementById('id_burger_btn');
var seaBtn = document.getElementById('id_sea_btn');
var seaSelect = document.querySelector('#id_sea_overlay .sea-select');
if (!burgerBtn || !seaBtn || !seaSelect) return;
var glowDone = false;
function endGlow() {
glowDone = true;
burgerBtn.classList.remove('glow-handoff');
seaBtn.classList.remove('glow-handoff');
seaSelect.classList.remove('glow-handoff');
}
burgerBtn.addEventListener('click', function () {
if (glowDone || !burgerBtn.classList.contains('glow-handoff')) return;
burgerBtn.classList.remove('glow-handoff');
seaBtn.classList.add('glow-handoff');
});
seaBtn.addEventListener('click', function () {
if (glowDone || !seaBtn.classList.contains('glow-handoff')) return;
seaBtn.classList.remove('glow-handoff');
seaSelect.classList.add('glow-handoff');
});
seaSelect.addEventListener('click', function () {
if (glowDone) return;
endGlow();
});
}());
</script>