Sea Select: refactor to scroll-snap options→cross (mirror Sky Select), drop the modal — TDD
The Gaussian spread modal couldn't hang off the burger #id_sea_btn anymore (that button now also opens the felt). Mirror Sky Select's form→wheel scroll-snap instead: the felt starts with the spread OPTIONS on the --duoUser felt; clicking OK confirms the spread → the options shunt DOWN and the spread CROSS takes page 1 (scroll down to find the options again). No modal, no corner NVM. - `_sea_overlay.html` restructured into `.sea-options-col` (the .sea-select combobox + mini preview + OK .btn-confirm + AUTO DRAW + DEL — NO deck stacks) and `.sea-cross-col` (the real .my-sea-cross + the Gravity/Levity deck stacks + the portaled stage). `#id_sea_overlay` is a `display:contents` passthrough so the two cols are the scroll-snap sections. - OK (`#id_sea_confirm_spread`) → `_chooseSpread()`: adds `sea-spread-chosen` to the felt → SCSS engages `scroll-snap-type:y mandatory`, the cross-col gets `order:-1` (page 1), options shunt to page 2; locks the combobox; eases the scroller to the cross. AUTO DRAW also confirms first. A reload of an in-progress sea renders `sea-spread-chosen` (cross revealed) server-side. - SCSS (`_sky.scss`): the sea felt is now a column scroller; `.sea-cross-col` `display:none` pre-confirm; the `sea-spread-chosen` scroll-snap block mirrors `body.sky-saved`. The options `.sea-form-col` goes transparent/content-sized (blends onto the felt, not the modal's --priUser card). - Sea sub-btn: no longer activated by openSea; it's the POST-COMPLETION reopen affordance (cascade activates it + `sea_btn_active = hand_complete` ctx flag), an active click → `window.openSeaFelt()` (review the saved spread), like the sky btn. Removed the sea_btn open-modal IIFE + the corner NVM. - IT: options-on-felt (combobox + OK + AUTO DRAW + DEL + preview) w. NO modal / NVM. 952 epic+gameboard ITs + Jasmine + PickSeaAsyncTransitionTest(3) green. my_sea.html keeps its modal (untouched) — the gameroom intentionally diverges. [[project-character-creation-spec]] Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,158 +1,148 @@
|
||||
{% load static %}
|
||||
{% comment %}
|
||||
DRAW SEA — Sea Select felt + Gaussian spread modal, unified with my_sea.html
|
||||
(2026-06-07). Replaces the legacy dark single-modal. TWO surfaces, mirroring
|
||||
my_sea:
|
||||
• Sea Select FELT (.sea-page--room) — a --duoUser fill of the hex pane; the
|
||||
real Celtic-Cross spread deals here + the deck stacks. Opened by
|
||||
#id_pick_sea_btn (html.sea-open); the room gear's NVM returns to the hex.
|
||||
• Gaussian spread MODAL (#id_sea_spread_modal) — the .sea-select combobox
|
||||
(the two Celtic Cross 6-card options ONLY, per user-spec 2026-06-07) +
|
||||
AUTO DRAW + DEL + a miniaturized shape preview + a corner #id_sea_cancel
|
||||
NVM (closes back to the felt). Opened via the burger #id_sea_btn.
|
||||
Each draw persists onto the seat's Character.celtic_cross via epic:sea_save.
|
||||
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" id="id_sea_page">
|
||||
<div class="my-sea-picker{% if hand_complete %} my-sea-picker--locked{% endif %}" id="id_sea_overlay"
|
||||
<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 }}">
|
||||
|
||||
{# ── Felt cross — the REAL deal target (.my-sea-cross). Sig pins core; the #}
|
||||
{# six CC positions render w. labels + saved-hand pre-fill. The default #}
|
||||
{# spread is the polarity-matched Celtic Cross (or the saved one). #}
|
||||
<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 fall through to the corner- #}
|
||||
{# rank + suit-icon text thumbnail (the tray sig stays text-only). #}
|
||||
{# my_tray_sig's deck_variant is the card's OWN deck (the Sig #}
|
||||
{# Select pick comes from the Role Select contributed deck via #}
|
||||
{# _room_deck_variant), so this is the right image source. #}
|
||||
<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>
|
||||
|
||||
{# ── Deck stacks — pinned bottom-right of the felt, so FLIP stays usable #}
|
||||
{# while the spread modal is closed. ALWAYS two stacks (Gravity + Levity), #}
|
||||
{# unlike my_sea / Sig Select: in the room's Sea Select phase the gamer may #}
|
||||
{# draw from EITHER populated half (sea_deck returns both), even a CARTE #}
|
||||
{# user whose monodeck is presented across the two polarity stacks #}
|
||||
{# (user-spec 2026-06-07). So the polarization-conditional shared partial #}
|
||||
{# is intentionally NOT used here. #}
|
||||
<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 %}×{% 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 %}×{% else %}FLIP{% endif %}</button>
|
||||
</div>
|
||||
<span class="sea-stack-name">Levity</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# ── Gaussian spread modal — opens on the burger #id_sea_btn. #}
|
||||
<div id="id_sea_spread_modal" class="my-sea-spread-modal" hidden>
|
||||
<div class="my-sea-spread-modal__backdrop"></div>
|
||||
<div class="my-sea-spread-modal__panel">
|
||||
{# Corner NVM (top-right) — closes the modal back to the felt. #}
|
||||
<button type="button" id="id_sea_cancel" class="btn btn-cancel btn-sm sea-modal-nvm">NVM</button>
|
||||
|
||||
{# Miniaturized spread preview — shape only, never dealt to. #}
|
||||
{% include "apps/gameboard/_partials/_sea_spread_preview.html" with preview_spread=sea_default_spread sig=my_tray_sig %}
|
||||
|
||||
<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 }}">
|
||||
<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>
|
||||
{# ── 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 }}">
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<div class="sea-form-actions">
|
||||
{# AUTO DRAW (mid-draw) — commits the remaining hand in one POST + #}
|
||||
{# animates placement onto the felt. Disabled once complete (the #}
|
||||
{# room has no GATE VIEW yet — character creation ends at the #}
|
||||
{# Voronoi map, roadmap step 21). #}
|
||||
<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 %}×{% endif %}</button>
|
||||
{# 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">
|
||||
{# OK confirms the spread → shunts the options down + reveals the #}
|
||||
{# cross (a regular .btn-confirm, NOT a big .btn-primary). #}
|
||||
<button type="button" id="id_sea_confirm_spread" class="btn btn-confirm">OK</button>
|
||||
{# 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 %}×{% 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 %}×{% 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 %}×{% else %}FLIP{% endif %}</button>
|
||||
</div>
|
||||
<span class="sea-stack-name">Levity</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# Sea stage — portaled big-card viewer (shared w. my_sea). #}
|
||||
{# Sea stage — portaled big-card viewer (position:fixed, escapes the snap). #}
|
||||
{% include "apps/gameboard/_partials/_sea_stage.html" %}
|
||||
</div>
|
||||
</div>
|
||||
@@ -166,7 +156,6 @@ Each draw persists onto the seat's Character.celtic_cross via epic:sea_save.
|
||||
if (!page || !overlay) return;
|
||||
|
||||
// ── Per-spread draw order + labels — the two Celtic Cross variants only.
|
||||
// WS / EV share the 6 positions but differ in draw order + diagonal labels.
|
||||
var DRAW_ORDER = {
|
||||
'waite-smith': ['cover', 'cross', 'crown', 'lay', 'loom', 'leave'],
|
||||
'escape-velocity': ['cover', 'cross', 'lay', 'leave', 'crown', 'loom'],
|
||||
@@ -181,6 +170,7 @@ Each draw persists onto the seat's Character.celtic_cross via epic:sea_save.
|
||||
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;
|
||||
|
||||
@@ -206,7 +196,6 @@ Each draw persists onto the seat's Character.celtic_cross via epic:sea_save.
|
||||
.catch(function () {});
|
||||
}
|
||||
|
||||
// ── FLIP affordance (CSS-driven via .sea-deck-stack--active).
|
||||
function _hideOk() {
|
||||
if (_activeStack) { _activeStack.classList.remove('sea-deck-stack--active'); _activeStack = null; }
|
||||
}
|
||||
@@ -215,16 +204,43 @@ Each draw persists onto the seat's Character.celtic_cross via epic:sea_save.
|
||||
function _lockSpread() {
|
||||
if (seaSelect) { seaSelect.classList.add('sea-select--locked'); seaSelect.setAttribute('aria-disabled', 'true'); }
|
||||
}
|
||||
function _unlockSpread() {
|
||||
if (seaSelect) { seaSelect.classList.remove('sea-select--locked'); seaSelect.removeAttribute('aria-disabled'); }
|
||||
|
||||
// ── 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() {
|
||||
if (_spreadChosen) return;
|
||||
_spreadChosen = true;
|
||||
page.classList.add('sea-spread-chosen');
|
||||
_lockSpread();
|
||||
_scrollToCross();
|
||||
}
|
||||
function _scrollToCross() {
|
||||
// Pin to the options (now page 2) so the cross slides in from above, then ease
|
||||
// up to the cross (page 1) — mirrors the sky felt's form-shunt reveal.
|
||||
var opts = overlay.querySelector('.sea-options-col');
|
||||
if (opts) page.scrollTop = opts.offsetTop;
|
||||
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', _chooseSpread);
|
||||
|
||||
function _setHasDrawn(on) {
|
||||
if (delBtn) { delBtn.classList.toggle('btn-disabled', !on); delBtn.innerHTML = on ? 'DEL' : '×'; }
|
||||
}
|
||||
function _setComplete(on, live) {
|
||||
_locked = on;
|
||||
overlay.classList.toggle('my-sea-picker--locked', on);
|
||||
overlay.querySelectorAll('.sea-deck-stack .sea-stack-ok').forEach(function (btn) {
|
||||
btn.classList.toggle('btn-disabled', on);
|
||||
btn.innerHTML = on ? '×' : 'FLIP';
|
||||
@@ -234,17 +250,13 @@ Each draw persists onto the seat's Character.celtic_cross via epic:sea_save.
|
||||
actionBtn.classList.toggle('btn-disabled', on);
|
||||
}
|
||||
_hideOk();
|
||||
// Live completion (manual FLIP of the 6th card / AUTO DRAW finishing) → run
|
||||
// the post-completion cascade. NOT on init (a reload of an already-complete
|
||||
// sea lands on the SEED MAP hex server-side, no animation).
|
||||
if (on && live) _startSeaCascade();
|
||||
}
|
||||
|
||||
// ── Post-completion cascade (mirrors CAST SKY's _startSaveCascade) ──────────
|
||||
// The 6-card spread completes → linger ~3s on the felt → the felt eases OUT
|
||||
// (revealing the table-hex) → DRAW SEA gives way to SEED MAP + the sea glow
|
||||
// fires (burger → sea_btn handoff) → +3s → SEED MAP eases IN. Same shape as
|
||||
// the CAST SKY → sky-btn glow → DRAW SEA sequence (user-spec 2026-06-07).
|
||||
// 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() {
|
||||
@@ -253,16 +265,16 @@ Each draw persists onto the seat's Character.celtic_cross via epic:sea_save.
|
||||
setTimeout(_cascadeFeltOut, _SEA_CASCADE_LINGER);
|
||||
}
|
||||
function _cascadeFeltOut() {
|
||||
page.classList.add('sea-page--cascade-out'); // CSS fades the felt out
|
||||
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();
|
||||
// DRAW SEA is stale → ease it out; keep the sea_btn active (reopen/review)
|
||||
// + start the sea glow handoff on the burger, concurrent w. the hex reveal.
|
||||
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');
|
||||
@@ -272,7 +284,7 @@ Each draw persists onto the seat's Character.celtic_cross via epic:sea_save.
|
||||
}
|
||||
function _cascadeSeedMapIn() {
|
||||
var seedBtn = document.getElementById('id_seed_map_btn');
|
||||
if (seedBtn) seedBtn.classList.remove('hex-phase-btn--out'); // eases in
|
||||
if (seedBtn) seedBtn.classList.remove('hex-phase-btn--out');
|
||||
}
|
||||
|
||||
function _collectHandFromDom() {
|
||||
@@ -292,7 +304,6 @@ Each draw persists onto the seat's Character.celtic_cross via epic:sea_save.
|
||||
return hand;
|
||||
}
|
||||
|
||||
// ── Persist — upsert the full current hand onto the seat's Character.
|
||||
function _postLock(hand) {
|
||||
if (!hand.length) return Promise.resolve(null);
|
||||
return fetch(SEA_SAVE_URL, {
|
||||
@@ -335,8 +346,9 @@ Each draw persists onto the seat's Character.celtic_cross via epic:sea_save.
|
||||
});
|
||||
overlay.addEventListener('click', _hideOk);
|
||||
|
||||
// ── AUTO DRAW — commit remaining hand in ONE POST, then animate placement.
|
||||
// ── AUTO DRAW — confirm the spread (reveal the cross) then commit + animate.
|
||||
function _autoDraw() {
|
||||
_chooseSpread(); // ensure the cross is revealed before dealing onto it
|
||||
var order = _currentOrder();
|
||||
var remaining = order.length - _filled;
|
||||
if (remaining <= 0) return;
|
||||
@@ -361,8 +373,6 @@ Each draw persists onto the seat's Character.celtic_cross via epic:sea_save.
|
||||
function placeNext() {
|
||||
if (idx >= autoEntries.length) { _setComplete(true, true); return; } // live → cascade
|
||||
var e = autoEntries[idx++];
|
||||
// Gameroom Sea Select always has BOTH polarity stacks (no single-stack
|
||||
// fallback — that's a my_sea monodeck concern, not here).
|
||||
var stack = overlay.querySelector('.sea-deck-stack--' + (e.isLevity ? 'levity' : 'gravity'));
|
||||
if (stack) _showOk(stack);
|
||||
setTimeout(function () {
|
||||
@@ -404,7 +414,7 @@ Each draw persists onto the seat's Character.celtic_cross via epic:sea_save.
|
||||
});
|
||||
}
|
||||
|
||||
// ── Spread switch → re-layout the felt cross + the preview + labels.
|
||||
// ── 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) {
|
||||
@@ -419,10 +429,8 @@ Each draw persists onto the seat's Character.celtic_cross via epic:sea_save.
|
||||
hidden.addEventListener('change', sync);
|
||||
|
||||
// ── Open / close the felt (DRAW SEA ⇄ gear NVM nav). On open: disable the
|
||||
// burger Text + Sky sub-btns (Text's swipe machine would drive into the
|
||||
// scroll-locked reelhouse; Sky would reopen the wheel mid-draw), light the
|
||||
// sea_btn, mark html.sea-entered (mutes the sky-saved glow henceforth), and
|
||||
// start the burger glow handoff. Server baseline restored on close.
|
||||
// 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 = [];
|
||||
@@ -437,14 +445,8 @@ Each draw persists onto the seat's Character.celtic_cross via epic:sea_save.
|
||||
}
|
||||
function openSea() {
|
||||
document.documentElement.classList.add('sea-open');
|
||||
// Once DRAW SEA is engaged the sky-saved glow stays muted (user-spec
|
||||
// 2026-06-07, conditions 2 & 3). The SEA glow-machine does NOT fire here —
|
||||
// mirroring Sky Select, it holds until the hand is complete (see the
|
||||
// completion-glow IIFE below), so drawing stays quiet.
|
||||
document.documentElement.classList.add('sea-entered');
|
||||
_disablePhaseBtns();
|
||||
var seaBtn = document.getElementById('id_sea_btn');
|
||||
if (seaBtn) seaBtn.classList.add('active');
|
||||
if (window.RoomViews && window.RoomViews.syncGear) window.RoomViews.syncGear();
|
||||
}
|
||||
function closeSea() {
|
||||
@@ -456,13 +458,20 @@ Each draw persists onto the seat's Character.celtic_cross via epic:sea_save.
|
||||
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. Without this, SeaDeal's
|
||||
// in-memory `_seaHand` is only populated by openStage/register during the live
|
||||
// session → after a reload the overlay click handler short-circuits on
|
||||
// `if (!_seaHand[pos]) return` and the saved slots silently no-op (user-reported
|
||||
// 2026-06-07; same fix my_sea carries). The card payloads come from the deck
|
||||
// fetch (looked up by `data-card-id`); `reversed`/polarity are DOM-sourced.
|
||||
// stay clickable to RE-OPEN the stage after a refresh.
|
||||
function _seedSavedHand() {
|
||||
if (!window.SeaDeal || !window.SeaDeal.seedHand) return;
|
||||
var byId = {};
|
||||
@@ -480,14 +489,10 @@ Each draw persists onto the seat's Character.celtic_cross via epic:sea_save.
|
||||
SeaDeal.seedHand(seed);
|
||||
}
|
||||
|
||||
// ── Init — bind SeaDeal to the (possibly injected) overlay, seed the deck,
|
||||
// then re-seed the saved hand once the deck fetch resolves.
|
||||
// ── 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;
|
||||
// Already-drawn sea hand → "in Sea Select or beyond" (user-spec 2026-06-07,
|
||||
// condition 3): mark sea-entered on load so the sky-saved glow stays muted
|
||||
// across reloads, not just after a live DRAW SEA click.
|
||||
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); }
|
||||
@@ -495,58 +500,17 @@ Each draw persists onto the seat's Character.celtic_cross via epic:sea_save.
|
||||
}());
|
||||
</script>
|
||||
|
||||
{# ── Spread modal open/close — opens on the burger #id_sea_btn (active while #}
|
||||
{# the felt is up). Closes on the corner NVM (#id_sea_cancel), the Gaussian #}
|
||||
{# backdrop, Escape, or a guard-OK (so AUTO DRAW / DEL run against a closed #}
|
||||
{# modal). Mirrors my_sea's Phase-2 IIFE. #}
|
||||
<script>
|
||||
(function () {
|
||||
var seaBtn = document.getElementById('id_sea_btn');
|
||||
var modal = document.getElementById('id_sea_spread_modal');
|
||||
if (!seaBtn || !modal) return;
|
||||
|
||||
var backdrop = modal.querySelector('.my-sea-spread-modal__backdrop');
|
||||
var cancelBtn = modal.querySelector('#id_sea_cancel');
|
||||
|
||||
function openModal() { modal.removeAttribute('hidden'); }
|
||||
function closeModal() { modal.setAttribute('hidden', ''); }
|
||||
|
||||
seaBtn.addEventListener('click', function () {
|
||||
// Inactive sub-btn click is the delegated --priRd flash (burger-btn.js);
|
||||
// only open the modal when the Sea sub-btn is active (felt is up).
|
||||
if (!seaBtn.classList.contains('active')) return;
|
||||
openModal();
|
||||
});
|
||||
if (backdrop) backdrop.addEventListener('click', closeModal);
|
||||
if (cancelBtn) cancelBtn.addEventListener('click', closeModal); // corner NVM → felt
|
||||
document.addEventListener('keydown', function (e) {
|
||||
if (e.key === 'Escape' && !modal.hasAttribute('hidden')) closeModal();
|
||||
});
|
||||
// Modal stays up through the AUTO DRAW / DEL guard portal (so it can anchor
|
||||
// against the visible btn); close on guard-OK so the draw / delete proceeds
|
||||
// against an already-closed modal (the spread animates onto the felt).
|
||||
document.addEventListener('click', function (e) {
|
||||
if (!e.target.closest('#id_guard_portal .guard-yes')) return;
|
||||
if (!modal.hasAttribute('hidden')) closeModal();
|
||||
});
|
||||
}());
|
||||
</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 review their sea. User-spec #}
|
||||
{# 2026-06-07. #}
|
||||
{# .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 modal = document.getElementById('id_sea_spread_modal');
|
||||
if (!burgerBtn || !seaBtn || !modal) return;
|
||||
|
||||
var seaSelect = modal.querySelector('.sea-select');
|
||||
if (!seaSelect) return;
|
||||
var seaSelect = document.querySelector('#id_sea_overlay .sea-select');
|
||||
if (!burgerBtn || !seaBtn || !seaSelect) return;
|
||||
|
||||
var glowDone = false;
|
||||
function endGlow() {
|
||||
@@ -556,8 +520,6 @@ Each draw persists onto the seat's Character.celtic_cross via epic:sea_save.
|
||||
seaSelect.classList.remove('glow-handoff');
|
||||
}
|
||||
|
||||
// Handoff on clicks: burger → sea_btn → .sea-select → end. (The cascade adds
|
||||
// `glow-handoff` to the burger to begin the sequence.)
|
||||
burgerBtn.addEventListener('click', function () {
|
||||
if (glowDone || !burgerBtn.classList.contains('glow-handoff')) return;
|
||||
burgerBtn.classList.remove('glow-handoff');
|
||||
|
||||
Reference in New Issue
Block a user