{% extends "core/base.html" %}
{% load static %}

{% block title_text %}Game Sea{% endblock title_text %}
{% block header_text %}<span>Game</span><span>Sea</span>{% endblock header_text %}

{% block content %}
    {# note.js exposes window.Brief — needed by BOTH the no-sig branch's   #}
    {# sign-gate Brief partial AND the with-sig branch's Default-deck +    #}
    {# Free-draw-locked Briefs. Hoisted out of the branches so it loads   #}
    {# exactly once regardless of which path the user is on (note.js      #}
    {# declares `const Brief = ...` at global scope — a second load would #}
    {# SyntaxError on re-declaration).                                     #}
    <script src="{% static 'apps/dashboard/note.js' %}"></script>
    <div class="my-sea-page"
         data-phase="{% if show_picker %}picker{% else %}landing{% endif %}"
         data-polarity="{% if significator_reversed %}levity{% else %}gravity{% endif %}">
        {% if not user_has_sig %}
            {# Sprint 4b sign-gate — DRYly Brief-banner'd (was inline div    #}
            {# .my-sea-sign-gate, refactored 2026-05-22 to use the project- #}
            {# wide `.note-banner` portal via `Brief.showBanner`). NVM lives #}
            {# inside the Brief shell; FYI links to /billboard/my-sign/.    #}
            {# Page body stays empty — the Brief is the entire no-sig UX.   #}
            {% include "apps/gameboard/_partials/_my_sea_sign_gate_brief.html" %}
        {% else %}
            {% if not show_picker %}
            {# Sprint 5 iter 1 — DRAW SEA landing UX. DRY table hex from    #}
            {# the room shell (.room-shell > .room-table > … > .table-hex) #}
            {# w. 6 chair seats labeled 1C-6C as placeholders for the      #}
            {# friend-invite feature per the My Sea roadmap architectural  #}
            {# anchor "Six chairs retained even in solo".                  #}
            {#                                                              #}
            {# Iter 6b — landing primary-btn state machine, 3-way:           #}
            {#   • FREE DRAW (`#id_draw_sea_btn`) when no active quota row    #}
            {#     (fresh user OR >24h since last draw);                     #}
            {#   • PAID DRAW (`#id_my_sea_paid_draw_btn`) when the user has   #}
            {#     deposited a token via the gatekeeper but hasn't yet       #}
            {#     committed it; one-click POST commits + redirects to       #}
            {#     fresh-quota /gameboard/my-sea/.                           #}
            {#   • GATE VIEW (`#id_my_sea_gate_view_btn`) when the user's    #}
            {#     quota row exists, the hand is empty, AND no deposit yet   #}
            {#     (post-DEL state). Routes to the Sprint-6 gatekeeper.      #}
            {# Seat 1 (`data-slot="1"`) carries `.seated` + `.fa-circle-     #}
            {# check` when the user's hand is non-empty (server-rendered     #}
            {# so reloads preserve the state). Otherwise `.fa-ban`. Other    #}
            {# 5 seats are placeholders for the future friend-invite         #}
            {# feature — always banned in solo my-sea.                       #}
            <div class="my-sea-landing">
                <div class="room-shell">
                    <div id="id_game_table" class="room-table">
                        <div class="room-table-scene">
                            <div class="table-hex-border">
                                <div class="table-hex">
                                    <div class="table-center">
                                        {% if show_cont_draw %}
                                            {# CONT DRAW — user came to the landing via the gear-     #}
                                            {# menu NVM mid-draw (?phase=landing). The picker hand    #}
                                            {# is non-empty + incomplete; CONT DRAW re-enters the     #}
                                            {# picker (a fresh GET w.o. ?phase=landing falls through  #}
                                            {# to show_picker=True via the hand_non_empty branch).    #}
                                            <a id="id_my_sea_cont_draw_btn"
                                               href="{% url 'my_sea' %}"
                                               class="btn btn-primary">CONT<br>DRAW</a>
                                        {% elif show_paid_draw %}
                                            {# PAID DRAW — two underlying states collapse into one    #}
                                            {# button (user-spec 2026-05-23):                          #}
                                            {#   • `deposit_reserved` → POST commits the deposited     #}
                                            {#     token via `my_sea_paid_draw` + redirects to picker. #}
                                            {#   • `paid_through` (token already spent this cycle,    #}
                                            {#     hand still empty) → POST is a no-op commit branch  #}
                                            {#     in the view; the view redirects to picker anyway.  #}
                                            <form method="POST" action="{% url 'my_sea_paid_draw' %}" style="display:contents">
                                                {% csrf_token %}
                                                <button type="submit"
                                                        id="id_my_sea_paid_draw_btn"
                                                        class="btn btn-primary">PAID<br>DRAW</button>
                                            </form>
                                        {% elif show_gate_view %}
                                            <button id="id_my_sea_gate_view_btn"
                                                    type="button"
                                                    class="btn btn-primary"
                                                    data-gate-url="{% url 'my_sea_gate' %}">GATE<br>VIEW</button>
                                        {% else %}
                                            <button id="id_draw_sea_btn" type="button" class="btn btn-primary">FREE<br>DRAW</button>
                                        {% endif %}
                                    </div>
                                </div>
                            </div>
                            {# Owner 1C + present visitors 2C-6C — the shared    #}
                            {# `_my_sea_seats` ring, so the owner sees who's      #}
                            {# seated at her table, not just herself. The FREE    #}
                            {# DRAW client-side transition still targets          #}
                            {# `.table-seat[data-slot="1"]`. `.position-status-   #}
                            {# icon` / `.fa-ban` are role-agnostic in _room.scss. #}
                            {% for seat in seats %}
                                {% if seat.present %}
                                    <div class="table-seat seated" data-slot="{{ seat.n }}" data-seat-token="{{ seat.token }}">
                                        <i class="fa-solid fa-chair"></i>
                                        <span class="seat-position-label">{{ seat.label }}</span>
                                        <i class="position-status-icon fa-solid fa-circle-check"></i>
                                    </div>
                                {% else %}
                                    <div class="table-seat" data-slot="{{ seat.n }}">
                                        <i class="fa-solid fa-chair"></i>
                                        <span class="seat-position-label">{{ seat.label }}</span>
                                        <i class="position-status-icon fa-solid fa-ban"></i>
                                    </div>
                                {% endif %}
                            {% endfor %}
                        </div>
                    </div>
                </div>
            </div>
            {% endif %}

            {# Picker phase — per-spread flexible layout. Sig pins .sea-  #}
            {# pos-core; the 6 surrounding positions all render in DOM   #}
            {# so the SPREAD dropdown can swap `.my-sea-cross[data-      #}
            {# spread]` between the 4 three-card variants (each w. its   #}
            {# own 3-position subset + draw order) + the 2 six-card      #}
            {# Celtic Cross variants (all 6 surrounding positions).      #}
            {# Each empty slot carries a `.sea-pos-label` caption (re-   #}
            {# appropriated from the GRAVITY/LEVITY .sea-stack-name look) #}
            {# that JS updates per spread.                                #}
            {#                                                            #}
            {# `id="id_sea_overlay"` aliases the picker to what SeaDeal   #}
            {# binds to (the gameroom uses the same ID on a different     #}
            {# page — no DOM collision since my-sea + gameroom never co-  #}
            {# exist in one DOM). FLIP click delegates to SeaDeal.        #}
            {# openStage(), which fills the slot AND opens the portaled   #}
            {# stage modal w. SPIN / FYI controls.                        #}
            <div class="my-sea-picker{% if hand_complete %} my-sea-picker--locked{% endif %}" id="id_sea_overlay"{% if not show_picker %} style="display:none"{% endif %}>
                <div class="sea-cards-col">
                    <div class="sea-cross my-sea-cross" data-spread="{{ default_spread }}">
                        {# `.sea-pos-label` lives OUTSIDE the slot so SeaDeal._fillSlot's #}
                        {# `slot.innerHTML = …` (which writes the drawn card's corner-   #}
                        {# rank + suit-icon) doesn't clobber it. Labels persist as       #}
                        {# adjacent siblings + are positioned via absolute SCSS to       #}
                        {# touch the slot's nearest edge.                                #}
                        <div class="sea-crucifix-cell sea-pos-crown">
                            <span class="sea-pos-label" data-position="crown">Outcome</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">
                            {# Sprint A.5 — central sig card mirrors my_sign.html's image-mode #}
                            {# render: when the user's deck has card images (Minchiate today, #}
                            {# future Earthman), show the transparent-PNG <img> w. contour    #}
                            {# stroke + depth shadow per A.3's `.sig-stage-card--image` rule. #}
                            {# Otherwise fall through to the existing corner-rank + suit-icon #}
                            {# text render (Earthman, RWS).                                    #}
                            <div class="sig-stage-card sea-sig-card{% if significator.deck_variant.has_card_images %} sig-stage-card--image{% endif %}"
                                 data-card-id="{{ significator.id }}"
                                 data-arcana-key="{{ significator.arcana }}">
                                {% if significator.deck_variant.has_card_images %}
                                    <img class="sig-stage-card-img" src="{{ significator.image_url }}" alt="{{ significator.name }}">
                                {% else %}
                                    <span class="fan-corner-rank">{{ significator.corner_rank }}</span>
                                    {% if significator.suit_icon %}<i class="fa-solid {{ significator.suit_icon }}"></i>{% endif %}
                                {% endif %}
                            </div>
                            <div class="sea-pos-cover">
                                <span class="sea-pos-label" data-position="cover">Action</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">Situation</span>
                            {% include "apps/gameboard/_partials/_my_sea_slot.html" with position="lay" saved=saved_by_position.lay crossing=False %}
                        </div>
                    </div>
                </div>

                {# Deck-stack column — STAYS on the picker page (out of the   #}
                {# modal) so the FLIP affordance + count remain visible while  #}
                {# the spread modal is closed. Wrapped in `.my-sea-stacks-wrap`#}
                {# so SCSS can pin it bottom-right of the aperture.            #}
                {#                                                              #}
                {# Sprint A.7-polish — deck-stack rendering depends on the     #}
                {# equipped deck's polarization. Polarized (Earthman) keeps    #}
                {# dual gravity/levity stacks; non-polarized (Minchiate, RWS)  #}
                {# collapses to a single unnamed stack (DECKS → DECK).         #}
                {# FLIP btn picks up `.btn-disabled` once hand_complete.       #}
                <div class="my-sea-stacks-wrap">
                    {% include "apps/gameboard/_partials/_my_sea_deck_stack.html" with deck=request.user.equipped_deck flip_disabled=hand_complete %}
                </div>

                {# Spread modal — opens on #id_sea_btn click (burger fan).     #}
                {# Contains the SPREAD combobox + AUTO DRAW + DEL btns. Closed #}
                {# by default (hidden attr); JS toggles. AUTO DRAW closes the  #}
                {# modal then runs the existing draw flow. Click backdrop or   #}
                {# press Escape to dismiss. Deck stacks live OUTSIDE so the    #}
                {# FLIP btn stays usable when the modal is closed.             #}
                <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">
                        <div class="sea-form-col my-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">{{ reversals_pct|default:25 }}% reversals</p>
                                    {# autocomplete="off" opts the hidden input out of   #}
                                    {# Firefox's form-history autofill, which otherwise #}
                                    {# restores the LAST value on soft reload (F5).     #}
                                    {# Without this, combobox.js's `select(i)` short-   #}
                                    {# circuits its change-event dispatch when the      #}
                                    {# user re-picks the value Firefox already restored #}
                                    {# → my-sea's sync() never fires → data-spread on   #}
                                    {# .my-sea-cross stays stuck on SAO default.        #}
                                    <input type="hidden" id="id_sea_spread" name="spread"
                                           value="{{ default_spread }}" autocomplete="off">
                                    <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 default_spread == 'past-present-future' %}Past, Present, Future{% elif default_spread == 'mind-body-spirit' %}Mind, Body, Spirit{% elif default_spread == 'desire-obstacle-solution' %}Desire, Obstacle, Solution{% elif default_spread == 'waite-smith' %}Celtic Cross, Waite-Smith{% elif default_spread == 'escape-velocity' %}Celtic Cross, Escape Velocity{% else %}Situation, Action, Outcome{% endif %}</span>
                                        <span class="sea-select-arrow" aria-hidden="true">▾</span>
                                        <ul class="sea-select-list" role="listbox">
                                            <li role="presentation" class="sea-select-divider">3-card spreads</li>
                                            <li role="option" data-value="past-present-future"      aria-selected="{% if default_spread == 'past-present-future' %}true{% else %}false{% endif %}">Past, Present, Future</li>
                                            <li role="option" data-value="situation-action-outcome" aria-selected="{% if default_spread == 'situation-action-outcome' %}true{% else %}false{% endif %}">Situation, Action, Outcome</li>
                                            <li role="option" data-value="mind-body-spirit"         aria-selected="{% if default_spread == 'mind-body-spirit' %}true{% else %}false{% endif %}">Mind, Body, Spirit</li>
                                            <li role="option" data-value="desire-obstacle-solution" aria-selected="{% if default_spread == 'desire-obstacle-solution' %}true{% else %}false{% endif %}">Desire, Obstacle, Solution</li>
                                            <li role="presentation" class="sea-select-divider">6-card spreads</li>
                                            <li role="option" data-value="waite-smith"     aria-selected="{% if default_spread == 'waite-smith' %}true{% else %}false{% endif %}">Celtic Cross, Waite-Smith</li>
                                            <li role="option" data-value="escape-velocity" aria-selected="{% if default_spread == 'escape-velocity' %}true{% else %}false{% endif %}">Celtic Cross, Escape Velocity</li>
                                        </ul>
                                    </div>
                                </div>
                            </div>

                            <div class="sea-form-actions">
                                {# Iter 4c — LOCK HAND replaced by AUTO DRAW (mid-draw) #}
                                {# / GATE VIEW (hand complete). Same button slot; JS    #}
                                {# transitions label + behavior + `data-state` when     #}
                                {# the final card lands. AUTO DRAW opens a guard portal #}
                                {# ("Auto deal cards?") + POSTs the remaining hand in   #}
                                {# one shot so a navigate-away mid-animation still      #}
                                {# persists. GATE VIEW navigates to the Sprint-6 gate-  #}
                                {# keeper.                                              #}
                                <button type="button"
                                        id="id_sea_action_btn"
                                        class="btn btn-primary"
                                        data-state="{% if hand_complete %}gate-view{% else %}auto-draw{% endif %}"
                                        data-gate-url="{% url 'my_sea_gate' %}">{% if hand_complete %}GATE<br>VIEW{% else %}AUTO<br>DRAW{% endif %}</button>
                                {# DEL gates on whether ANY card has been drawn (user #}
                                {# spec 2026-05-26): pre-first-draw it's disabled; as #}
                                {# soon as `saved_by_position` has at least one entry #}
                                {# the user can DEL the in-progress hand. `&times;`   #}
                                {# is the disabled-state label (parity w. game-kit's  #}
                                {# DON/DOFF disabled convention). JS `_setHasDrawn`   #}
                                {# swaps text + class in lockstep w. live deposits.  #}
                                <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>
                </div>
                {# Sea stage — portaled modal that opens on FLIP click via #}
                {# SeaDeal.openStage. `position:fixed; inset:0` covers the #}
                {# viewport; click backdrop to dismiss + reveal the slot   #}
                {# thumbnail.                                              #}
                {% include "apps/gameboard/_partials/_sea_stage.html" %}
            </div>

            {# Iter 4b — DEL guard reuses the shared `#id_guard_portal`   #}
            {# from base.html (the same one the room's gear-menu DEL btn  #}
            {# uses). Gaussian-glass tooltip positioned above the DEL btn,#}
            {# no backdrop. The picker IIFE below invokes it via          #}
            {# `window.showGuard(delBtn, "Are you sure?", confirmFn, null,#}
            {# {yesLabel: "DEL"})` when DEL is clicked post-lock.         #}
            {# Sprint 5 iter 4a — shuffled deck (levity + gravity halves,  #}
            {# sig excluded) embedded as JSON; JS reads on init and       #}
            {# pops from the relevant pile on each deposit.                #}
            {{ sea_deck_data|json_script:"id_my_sea_deck" }}
            {# StageCard + SeaDeal — both bind to `#id_sea_overlay` (the   #}
            {# my-sea-picker) + `#id_sea_stage` (the stage partial) on    #}
            {# DOMContentLoaded; openStage() runs on FLIP click below.   #}
            <script src="{% static 'apps/epic/stage-card.js' %}"></script>
            <script src="{% static 'apps/epic/sea.js' %}"></script>
            <script src="{% static 'apps/epic/combobox.js' %}"></script>
            <script>
            (function () {
                // Per-spread draw order + position labels — locked in spec
                // (user 2026-05-19). Each three-card spread uses a DIFFERENT
                // 3-position subset of the 6 surrounding positions, in a
                // specific order. The Celtic Cross variants share position
                // labels (Crown/Beneath/Cover/Cross/Before/Behind — gameroom
                // vocabulary) but differ in draw order.
                var DRAW_ORDER = {
                    'past-present-future':      ['leave', 'cover', 'loom'],
                    'situation-action-outcome': ['lay',   'cover', 'crown'],
                    'mind-body-spirit':         ['crown', 'lay',   'loom'],
                    'desire-obstacle-solution': ['loom',  'cross', 'crown'],
                    'waite-smith':              ['cover', 'cross', 'crown', 'lay',   'loom',  'leave'],
                    'escape-velocity':          ['cover', 'cross', 'lay',   'leave', 'crown', 'loom'],
                };
                var POSITION_LABELS = {
                    'past-present-future':      { leave: 'Past',      cover: 'Present', loom: 'Future' },
                    'situation-action-outcome': { lay:   'Situation', cover: 'Action',  crown: 'Outcome' },
                    'mind-body-spirit':         { crown: 'Mind',      lay:   'Body',    loom: 'Spirit' },
                    'desire-obstacle-solution': { loom:  'Desire',    cross: 'Obstacle',crown: 'Solution' },
                    // CSS grid (`_card-deck.scss:1276-1279`) places sea-pos-
                    // leave at LEFT + sea-pos-lay at BOTTOM. Traditional
                    // Celtic Cross has "Behind" (past) at LEFT + "Beneath"
                    // (root) at BOTTOM, so labels here pair lay→"Beneath"
                    // + leave→"Behind". Fix landed 2026-05-22: prior assign-
                    // ment had Beneath at LEFT + Behind at BOTTOM (inverted
                    // vs tradition + vs the grid's own commented intent in
                    // `_sea_overlay.html`).
                    'waite-smith':     { crown: 'Crown', leave: 'Behind',  cover: 'Cover', cross: 'Cross', loom: 'Before', lay: 'Beneath' },
                    // Escape Velocity remaps the diagonal positions per the
                    // user-locked spec (2026-05-19): Beneath→Lay, Before→
                    // Loom, Behind→Leave. Crown/Cover/Cross keep the WS
                    // names. Carries the same LEFT/BOTTOM correction as WS.
                    'escape-velocity': { crown: 'Crown', leave: 'Leave',   cover: 'Cover', cross: 'Cross', loom: 'Loom',   lay: 'Lay'     },
                };
                var hidden = document.getElementById('id_sea_spread');
                var cross  = document.querySelector('.my-sea-cross');
                var picker = document.querySelector('.my-sea-picker');
                var actionBtn = document.getElementById('id_sea_action_btn');
                var delBtn = document.getElementById('id_sea_del');
                var deckEl = document.getElementById('id_my_sea_deck');
                var seaSelect = document.querySelector('[data-combobox][data-combobox-target="id_sea_spread"]');
                if (!hidden || !cross || !picker) return;

                // ── Deck state ──────────────────────────────────────────
                // `_deckData` is the immutable initial payload from the
                // server; `_levityPile` + `_gravityPile` are working
                // copies that pop one card per deposit. DEL re-clones
                // from `_deckData` rather than re-fetching.
                var _deckData = { levity: [], gravity: [] };
                try { _deckData = JSON.parse((deckEl && deckEl.textContent) || '{}'); } catch (e) {}
                var _levityPile  = (_deckData.levity  || []).slice();
                var _gravityPile = (_deckData.gravity || []).slice();
                var _filled = 0;
                var _activeStack = null;
                var _locked = false;

                function _currentOrder() {
                    return DRAW_ORDER[hidden.value] || [];
                }

                // FLIP visibility is now CSS-driven (opacity on hover/focus +
                // the `.sea-deck-stack--active` persist class — unified w. the
                // spectator page, user-spec 2026-05-30). `_showOk`/`_hideOk` just
                // toggle `--active`; no more inline `display` juggling.
                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 _fillSlot(positionName, card, isLevity) {
                    // Lifted from gameroom sea.js's `_fillSlot`: strip
                    // .--empty + the position label, layer .--filled +
                    // polarity classes, set corner-rank + suit-icon.
                    var cell = cross.querySelector('.sea-pos-' + positionName);
                    if (!cell) return;
                    var slot = cell.querySelector('.sea-card-slot');
                    if (!slot) return;
                    slot.classList.remove('sea-card-slot--empty');
                    slot.classList.add('sea-card-slot--filled');
                    slot.classList.add(isLevity ? 'sea-card-slot--levity' : 'sea-card-slot--gravity');
                    if (card.reversed) slot.classList.add('sea-card-slot--reversed');
                    slot.dataset.cardId = String(card.id);
                    slot.dataset.posKey = positionName;
                    slot.innerHTML =
                        '<span class="fan-corner-rank">' + (card.corner_rank || '') + '</span>' +
                        (card.suit_icon ? '<i class="fa-solid ' + card.suit_icon + '"></i>' : '');
                }

                function _emptySlot(cell) {
                    // DEL restores each filled slot to its initial empty
                    // state. `.sea-pos-label` is now a SIBLING of the slot
                    // (outside it) so SeaDeal's innerHTML clobber on draw
                    // doesn't touch it — we don't need to re-render the
                    // label here, just wipe slot contents + classes.
                    var slot = cell.querySelector('.sea-card-slot');
                    if (!slot) return;
                    slot.className = slot.className
                        .split(' ')
                        .filter(function (c) {
                            return !/^sea-card-slot--(filled|visible|focused|levity|gravity|reversed|rank-long)$/.test(c);
                        })
                        .join(' ');
                    slot.classList.add('sea-card-slot--empty');
                    delete slot.dataset.cardId;
                    delete slot.dataset.posKey;
                    slot.innerHTML = '';
                }

                function _lockSpread() {
                    // Lock the SPREAD combobox after the first deposit —
                    // switching spread mid-draw would scramble the in-
                    // progress hand. Unlocks on DEL.
                    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');
                    }
                }

                function _reshuffleDeck() {
                    // Fisher-Yates re-shuffle on DEL — re-distributes cards
                    // across both polarity halves + re-rolls the 25% reversal
                    // axis per card. Page-load shuffle already came from the
                    // server (`_my_sea_deck_data`); subsequent shuffles run
                    // client-side so DEL → fresh-hand doesn't require a
                    // network round-trip.
                    var all = (_deckData.levity || []).concat(_deckData.gravity || []);
                    for (var i = all.length - 1; i > 0; i--) {
                        var j = Math.floor(Math.random() * (i + 1));
                        var tmp = all[i]; all[i] = all[j]; all[j] = tmp;
                    }
                    // Clone each card (don't mutate the immutable server payload
                    // — DEL can fire many times; we don't want successive shuffles
                    // to fold previous reversal flips into the next round).
                    all = all.map(function (c) {
                        var clone = {};
                        for (var k in c) if (Object.prototype.hasOwnProperty.call(c, k)) clone[k] = c[k];
                        clone.reversed = Math.random() < 0.25;
                        return clone;
                    });
                    var mid = Math.floor(all.length / 2);
                    _levityPile  = all.slice(0, mid);
                    _gravityPile = all.slice(mid);
                }

                function _resetHand() {
                    // Spread-switch reset only (mid-draw guard inside sync()).
                    // Post-DEL routes through the guard portal → server-side
                    // delete + page reload, so this path doesn't fire there.
                    // Re-disable DEL + re-enable FLIP for the fresh-empty state.
                    _filled = 0;
                    _hideOk();
                    _unlockSpread();
                    _setHasDrawn(false);
                    _setComplete(false);
                    cross.querySelectorAll(
                        '.sea-crucifix-cell.sea-pos-crown, ' +
                        '.sea-crucifix-cell.sea-pos-leave, ' +
                        '.sea-crucifix-cell.sea-pos-loom, ' +
                        '.sea-crucifix-cell.sea-pos-lay, ' +
                        '.sea-pos-cover, .sea-pos-cross'
                    ).forEach(_emptySlot);
                    _reshuffleDeck();
                    if (window.SeaDeal && window.SeaDeal.resetHand) {
                        SeaDeal.resetHand();
                    }
                }

                function _setComplete(on) {
                    // State transition for "hand complete": deck-stack FLIP
                    // buttons render as disabled (visual signal that no more
                    // draws remain), action btn becomes GATE VIEW. The deck
                    // STACKS themselves stay click-responsive so the user
                    // sees the disabled-FLIP feedback — `_locked` gates the
                    // actual draw at the click-handler level. DEL state was
                    // here previously but is now driven by `_setHasDrawn`
                    // instead (user spec 2026-05-26: DEL unlocks at first
                    // draw, NOT at completion).
                    _locked = on;
                    picker.classList.toggle('my-sea-picker--locked', on);
                    picker.querySelectorAll('.sea-deck-stack .sea-stack-ok').forEach(function (btn) {
                        btn.classList.toggle('btn-disabled', on);
                        // Mirrors DEL btn convention: disabled label is × (the
                        // template's initial render does the same), active is
                        // 'FLIP'. Keeps visual + class in lockstep.
                        btn.innerHTML = on ? '×' : 'FLIP';
                    });
                    if (actionBtn) {
                        actionBtn.dataset.state = on ? 'gate-view' : 'auto-draw';
                        actionBtn.innerHTML = on ? 'GATE<br>VIEW' : 'AUTO<br>DRAW';
                    }
                    _hideOk();
                }

                function _setHasDrawn(on) {
                    // DEL btn state — un-disables as soon as ANY card has
                    // been drawn (user spec 2026-05-26). Pre-first-draw the
                    // user can't DEL an empty hand; once a card is down,
                    // DEL routes to the guard portal → server-side delete.
                    // Disabled-state label `×` mirrors the game-kit DON/DOFF
                    // convention.
                    if (delBtn) {
                        delBtn.classList.toggle('btn-disabled', !on);
                        delBtn.innerHTML = on ? 'DEL' : '×';
                    }
                }

                // ── Deck-stack click → show FLIP → click FLIP → deposit ─
                // Iter 4c — stacks remain click-responsive even after hand
                // is complete (so the user sees the disabled-FLIP feedback,
                // signalling "no more draws allowed"). Each card placement
                // POSTs the current hand to /lock for server upsert.
                picker.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();
                            // Hand complete → FLIP is disabled. No draw.
                            if (_locked) { _hideOk(); return; }
                            var isLevity = stack.classList.contains('sea-deck-stack--levity');
                            var pile     = isLevity ? _levityPile : _gravityPile;
                            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);
                                } else {
                                    _fillSlot(posName, card, isLevity);
                                }
                                _filled++;
                                if (_filled === 1) {
                                    _lockSpread();
                                    _setHasDrawn(true);  // first card → DEL enables
                                }
                                // Per-placement upsert — server stays current
                                // so a navigate-away mid-draw still persists
                                // the partial hand. User-spec 2026-05-20.
                                _postLock(_collectHandFromDom());
                                if (_filled >= order.length) {
                                    _setComplete(true);
                                }
                            }
                            _hideOk();
                        });
                    }
                });

                function _collectHandFromDom() {
                    // Walks every `.sea-card-slot--filled` and reconstructs
                    // the hand array in DRAW_ORDER. data-card-id, polarity
                    // class, and `.sea-card-slot--reversed` are set at
                    // FLIP-click time so this is always current.
                    var byPos = {};
                    cross.querySelectorAll(
                        '.sea-card-slot.sea-card-slot--filled'
                    ).forEach(function (slot) {
                        var cls = slot.className;
                        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(cls),
                            polarity: /sea-card-slot--levity\b/.test(cls) ? 'levity' : 'gravity',
                        };
                    });
                    // Emit in DRAW_ORDER so the server stores cards in
                    // chronological draw order (Sprint 7 applet reads this
                    // sequence left-to-right).
                    var hand = [];
                    _currentOrder().forEach(function (pos) {
                        if (byPos[pos]) hand.push(byPos[pos]);
                    });
                    return hand;
                }

                function _autoDraw() {
                    // Compute all remaining cards client-side + POST in one
                    // shot (per user spec 2026-05-20: "commit all six draws
                    // in the same POST, just display them in order via js"
                    // — survives navigate-away mid-animation). After server
                    // commit, animate placement sequentially.
                    var order = _currentOrder();
                    var remaining = order.length - _filled;
                    if (remaining <= 0) return;
                    var autoEntries = [];
                    for (var i = 0; i < remaining; i++) {
                        var isLevity = Math.random() < 0.5;
                        var pile = isLevity ? _levityPile : _gravityPile;
                        if (!pile.length) {
                            isLevity = !isLevity;
                            pile = isLevity ? _levityPile : _gravityPile;
                        }
                        if (!pile.length) break;
                        var card = pile.shift();
                        autoEntries.push({
                            card: card,
                            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;
                        // POST succeeded → animate placement client-side.
                        // Even if the user navigates away now, the server
                        // already has the full hand — reload restores the
                        // complete picker state.
                        if (autoEntries.length === 0) {
                            _setComplete(true);
                            return;
                        }
                        // First-draw transitions — mirror the manual-flip flow.
                        // Lock spread + un-disable DEL as soon as the POST
                        // commits (we KNOW ≥1 card is now server-saved). Was a
                        // bug: AUTO DRAW only synced DEL state at completion
                        // via `_setComplete(true)`, so a mid-animation refresh
                        // (or backing out during the staggered placeNext loop)
                        // would leave DEL stuck disabled even though cards were
                        // already saved (user-reported 2026-05-26).
                        if (_filled === 0) _lockSpread();
                        _setHasDrawn(true);
                        var idx = 0;
                        function placeNext() {
                            if (idx >= autoEntries.length) {
                                _setComplete(true);
                                return;
                            }
                            var e = autoEntries[idx++];
                            // Polarized decks (Earthman): polarity-keyed stack
                            // exists + flashes its FLIP btn per-card. Monodecks
                            // (Minchiate, RWS): the template renders only `.sea-
                            // deck-stack--single`, so the polarity-keyed query
                            // returns null — fall back to the single stack so
                            // the per-card FLIP-flash UX is the same regardless
                            // of polarization. User-spec 2026-05-26 PM.
                            var stack = picker.querySelector(
                                '.sea-deck-stack--' + (e.isLevity ? 'levity' : 'gravity')
                            ) || picker.querySelector('.sea-deck-stack--single');
                            if (stack) _showOk(stack);
                            setTimeout(function () {
                                // Route thru SeaDeal.register (not the inline
                                // `_fillSlot`) so SeaDeal's `_seaHand` dict
                                // gets the entry — the overlay click handler
                                // reads from there, so without registration
                                // the auto-drawn slots are silently un-
                                // clickable (user-reported bug, 2026-05-21).
                                // Fallback to the inline shim only if sea.js
                                // hasn't loaded yet (defensive — script load
                                // order makes this unlikely).
                                if (window.SeaDeal && window.SeaDeal.register) {
                                    SeaDeal.register(e.card, '.sea-pos-' + e.posName, e.isLevity);
                                } else {
                                    _fillSlot(e.posName, e.card, e.isLevity);
                                }
                                cross.querySelector('.sea-pos-' + e.posName + ' .sea-card-slot')
                                     .classList.add('sea-card-slot--visible');
                                _filled++;
                                _hideOk();
                                setTimeout(placeNext, 250);
                            }, 350);
                        }
                        placeNext();
                    });
                }

                // Click elsewhere inside the picker dismisses the FLIP btn.
                picker.addEventListener('click', _hideOk);

                // ── DEL: post-completion only (server-rendered `.btn-
                // disabled` pre-completion gates pre-hand DEL attempts).
                // Opens the shared `#id_guard_portal` from base.html;
                // CONFIRM POSTs to /gameboard/my-sea/delete which clears
                // the hand but preserves the row (quota stays committed
                // for the 24h window). Server returns 204; we reload to
                // land on the GATE VIEW landing (FREE DRAW is gone until
                // the row expires).
                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('{% url "my_sea_delete" %}', {
                                    method: 'POST',
                                    credentials: 'same-origin',
                                    headers: {'X-CSRFToken': _csrf()},
                                }).then(function (r) {
                                    if (r.ok) window.location.reload();
                                });
                            }
                        );
                    });
                }

                // ── Action btn: AUTO DRAW (pre-complete) ↔ GATE VIEW
                // (post-complete). Same DOM node, label + behavior keyed
                // on `data-state`. AUTO DRAW opens a guard portal "Auto
                // deal cards?"; on OK, fills remaining slots client-side
                // + commits to server in one POST. GATE VIEW navigates
                // to the Sprint-6 gatekeeper (currently 404 stub).
                if (actionBtn) {
                    actionBtn.addEventListener('click', function (e) {
                        e.preventDefault();
                        var state = actionBtn.dataset.state;
                        if (state === 'gate-view') {
                            window.location.href = actionBtn.dataset.gateUrl || '#';
                            return;
                        }
                        // AUTO DRAW
                        if (!window.showGuard) return;
                        window.showGuard(
                            actionBtn,
                            'Auto deal cards?',
                            _autoDraw
                        );
                    });
                }

                function _csrf() {
                    var m = document.cookie.match(/(?:^|; )csrftoken=([^;]+)/);
                    return m ? decodeURIComponent(m[1]) : '';
                }
                function _formatTimestamp(iso) {
                    // Mirror the server-side `D, M j @ g:i A` format
                    // (e.g., "Thu, May 21 @ 2:41 AM").
                    var d = new Date(iso);
                    if (isNaN(d)) return '';
                    var DAYS = ['Sun','Mon','Tue','Wed','Thu','Fri','Sat'];
                    var MONTHS = ['Jan','Feb','Mar','Apr','May','Jun',
                                  'Jul','Aug','Sep','Oct','Nov','Dec'];
                    var h = d.getHours();
                    var ampm = h >= 12 ? 'PM' : 'AM';
                    h = h % 12; if (h === 0) h = 12;
                    var m = d.getMinutes();
                    var mm = (m < 10 ? '0' : '') + m;
                    return DAYS[d.getDay()] + ', ' + MONTHS[d.getMonth()]
                        + ' ' + d.getDate() + ' @ ' + h + ':' + mm + ' ' + ampm;
                }
                // Render an arbitrary TAX_LEDGER Brief payload as a slide-down
                // banner. Server-driven via `free_draw_brief_payload` /
                // `paid_draw_brief_payload` from the my_sea view's context;
                // the payload is exactly `Brief.to_banner_dict()` + a populated
                // `dismiss_url`. `note.js`'s NVM handler POSTs to dismiss_url
                // on click → server stamps the user's anchor → suppressed on
                // future loads until the cycle resets. User-spec 2026-05-26.
                window._showTaxBrief = function (payload, bannerClass) {
                    if (!payload || !window.Brief || !Brief.showBanner) return;
                    Brief.showBanner(payload);
                    var banner = document.querySelector('.note-banner');
                    if (banner) {
                        banner.classList.add(bannerClass);
                        // Full date+time stamp (D, M j @ g:i A) — note.js
                        // renders a short `toLocaleDateString` by default;
                        // these Briefs want the precise log moment visible.
                        var ts = banner.querySelector('.note-banner__timestamp');
                        if (ts && payload.created_at) {
                            ts.textContent = _formatTimestamp(payload.created_at);
                        }
                    }
                };
                // Backwards-compat shim used by `_postLock` below (which
                // surfaces the freshly-emitted Brief in-place without a page
                // reload). The view's `my_sea_lock` response carries the new
                // Brief payload — `_postLock` retrieves the latest one via
                // a follow-up GET (cheaper than refetching the full page).
                window._showFreeDrawLockedBrief = function (payload) {
                    window._showTaxBrief(payload, 'my-sea-locked-banner');
                };
                function _postLock(hand) {
                    // Returns the parsed JSON body promise so callers (e.g.
                    // _autoDraw) can chain animation onto server commit.
                    // Surfaces the Brief banner only on the *first* commit
                    // of this session (quota commit moment) — dedupe via
                    // `.my-sea-locked-banner` presence in DOM, so per-
                    // placement POSTs don't spam multiple banners.
                    return fetch('{% url "my_sea_lock" %}', {
                        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;
                    }).then(function (body) {
                        // First-card-of-cycle response carries the freshly-
                        // emitted Brief payload — surface it in-place w.o. a
                        // page reload. Subsequent mid-draw POSTs omit the
                        // payload (no new debit fires) so this no-ops.
                        if (body && body.free_draw_brief_payload
                            && !document.querySelector('.my-sea-locked-banner')) {
                            window._showFreeDrawLockedBrief(body.free_draw_brief_payload);
                        }
                        return body;
                    });
                }

                function syncLabels(spread) {
                    var labels = POSITION_LABELS[spread] || {};
                    cross.querySelectorAll('.sea-pos-label').forEach(function (el) {
                        var pos = el.dataset.position;
                        el.textContent = labels[pos] || '';
                    });
                }
                function sync() {
                    cross.setAttribute('data-spread', hidden.value);
                    syncLabels(hidden.value);
                    // Spread switch invalidates any in-progress hand —
                    // position-subset + draw-order both change. Reset.
                    _resetHand();
                }
                hidden.addEventListener('change', sync);

                // Initial state — iter-4c restoration of partial / complete
                // saved-hand sessions. Server pre-renders the slots from
                // saved_by_position; count the filled slots to seed _filled.
                // If hand is complete (server-rendered `.my-sea-picker--
                // locked`), call `_setComplete(true)` to align JS state
                // (decks click-responsive but FLIP arrives disabled; DEL
                // enabled; action btn = GATE VIEW). Mid-draw saved hands
                // just need the count + spread lock (the user resumes
                // drawing from where they left off).
                _filled = cross.querySelectorAll(
                    '.sea-card-slot.sea-card-slot--filled'
                ).length;
                if (_filled >= _currentOrder().length) {
                    _setComplete(true);
                    _lockSpread();
                    _setHasDrawn(true);  // also enables DEL post-completion
                } else if (_filled > 0) {
                    _lockSpread();
                    _setHasDrawn(true);  // any partial draw → DEL enabled
                }

                // Seed SeaDeal's `_seaHand` from the server-rendered
                // saved slots so they remain clickable for re-opening
                // the stage modal after a refresh. Walks the filled
                // slots + maps each `data-card-id` against the embedded
                // deck JSON. Without this, the overlay click handler
                // short-circuits on `if (!_seaHand[pos]) return;` —
                // sea.js's in-memory `_seaHand` only gets populated by
                // openStage/register during the live session.
                // (User-reported 2026-05-21.)
                if (window.SeaDeal && window.SeaDeal.seedHand) {
                    var _allCards = (_deckData.levity || [])
                        .concat(_deckData.gravity || []);
                    var _byId = {};
                    _allCards.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 cardId  = parseInt(slot.dataset.cardId, 10);
                        var card    = _byId[cardId];
                        if (!posName || !card) return;
                        // Reconstruct the per-instance `reversed` flag
                        // from the slot's DOM class (the deck JSON's
                        // `reversed` field is the original-shuffle axis
                        // — server's saved hand may have flipped it
                        // independently). Polarity also DOM-sourced.
                        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'
                        );
                        var isLevity = slot.classList.contains(
                            'sea-card-slot--levity'
                        );
                        _seed[posName] = { card: slotCard, isLevity: isLevity };
                    });
                    SeaDeal.seedHand(_seed);
                }

                // Belt-and-braces autofill defense (paired w. autocomplete=
                // off on the hidden input above). Firefox occasionally
                // restores form-history values on soft reload even on
                // hidden inputs; if it does, hidden.value diverges from
                // the server-rendered aria-selected option + combobox.js
                // short-circuits its change-event dispatch on subsequent
                // option picks of the autofilled value. Force-sync from
                // the server-rendered aria-selected source-of-truth.
                var _initialOpt = document.querySelector(
                    '.sea-select-list [role="option"][aria-selected="true"]'
                );
                if (_initialOpt && hidden.value !== _initialOpt.dataset.value) {
                    hidden.value = _initialOpt.dataset.value;
                }
                // Mirror the hidden value onto the cross's `data-spread` +
                // re-run syncLabels. Idempotent when server-rendered state
                // is internally consistent; corrective if a prior page
                // state (Firefox bfcache restoring a Celtic-Cross DOM,
                // session left mid-draw etc.) left a stale `data-spread`
                // that SCSS-hides the wrong subset of cells. Without this,
                // a post-DEL reload could land on the picker w. data-
                // spread="waite-smith" but SAO labels + SAO hidden value
                // → all 6 cells visible, sometimes unlabeled (the user-
                // observed bug, 2026-05-20).
                cross.setAttribute('data-spread', hidden.value);
                syncLabels(hidden.value);

                // Exposed for iter 4b / future surfaces.
                window._mySeaDrawOrder = DRAW_ORDER;
            }());
            </script>

            <script src="{% static 'apps/epic/room.js' %}"></script>
            {# Shared one-shot "seated" glow module — also drives seat 2C on   #}
            {# the spectator page. Exposes window.playSeatGlow + auto-plays on  #}
            {# load for any server-rendered .seated[data-seat-token] seat.      #}
            <script src="{% static 'apps/gameboard/my-sea-seats.js' %}"></script>
            {# Live seat ring — subscribe the OWNER's page to her own spectate WS #}
            {# (`mysea_<id>`) so visitors arriving (deposit → 2C-6C) / leaving     #}
            {# (BYE) appear without a refresh, the same `sea_seats` broadcast the  #}
            {# spectators receive. Empty self-token: the owner's 1C isn't `--self` #}
            {# server-side, so the live re-render shouldn't add it either.         #}
            <script>
            (function () {
                if (window.mySeaConnectSeatRing) {
                    window.mySeaConnectSeatRing('{{ request.user.id }}', '');
                }
            }());
            </script>
            <script>
            (function () {
                var page = document.querySelector('.my-sea-page');
                if (!page) return;
                var landing = page.querySelector('.my-sea-landing');
                var picker  = page.querySelector('.my-sea-picker');
                var drawBtn = document.getElementById('id_draw_sea_btn');
                // FREE DRAW click flow:
                //   1) seat 1C transitions to .seated (chair --terUser +
                //      drop-shadow glow + .fa-ban → .fa-circle-check —
                //      _room.scss line 596 makes the colour change a
                //      0.6s ease transition);
                //   2) after a brief delay (so the user sees the seat
                //      animation), data-phase swaps to 'picker' + the
                //      landing hides. Picker content lands in iter 2.
                // The seat-take logic is solo-coded for now: 1C is the
                // lowest-numeral chair, and my-sea is 1-user-per-page
                // until the friend-invite feature (per [[project-my-
                // sea-roadmap]]) — so 1C is always the user's seat.
                var SEAT_ANIM_MS = 800;
                if (drawBtn) {
                    drawBtn.addEventListener('click', function () {
                        var seat1 = page.querySelector('.table-seat[data-slot="1"]');
                        if (seat1) {
                            seat1.classList.add('seated');
                            var statusIcon = seat1.querySelector('.position-status-icon');
                            if (statusIcon) {
                                statusIcon.classList.remove('fa-ban');
                                statusIcon.classList.add('fa-circle-check');
                            }
                            // First-draw moment → play the one-shot seated flare
                            // (same module the spectator page + load-time handler
                            // use). On a later reload the server-rendered token
                            // gates a repeat via localStorage.
                            if (window.playSeatGlow) window.playSeatGlow(seat1);
                        }
                        setTimeout(function () {
                            page.setAttribute('data-phase', 'picker');
                            if (landing) landing.style.display = 'none';
                            if (picker)  picker.style.display  = '';
                            // Phase 2 of the Sea sub-btn rollout — server
                            // rendered the burger fan w. sea_btn inactive
                            // (show_picker was False on landing). After the
                            // client-side phase swap, flip .active so the
                            // modal-open click handler treats sea_btn as
                            // active. Sea_btn stays active for the rest of
                            // the picker phase (incl. hand_complete) so DEL
                            // + GATE VIEW stay reachable inside the modal.
                            var seaBtn = document.getElementById('id_sea_btn');
                            if (seaBtn) seaBtn.classList.add('active');
                            // Phase 3 — the FREE DRAW path enters the picker
                            // w. an empty hand → first-draw pending. Start
                            // the glow handoff on the burger so the user
                            // knows where to click next.
                            var burgerBtn = document.getElementById('id_burger_btn');
                            if (burgerBtn) burgerBtn.classList.add('glow-handoff');
                        }, SEAT_ANIM_MS);
                    });
                }
                // Iter 4c — landing GATE VIEW (replaces FREE DRAW when the
                // user's quota row exists w. empty hand, i.e. post-DEL).
                // Navigates to the Sprint-6 gatekeeper (currently 404 stub).
                var gateLandingBtn = document.getElementById('id_my_sea_gate_view_btn');
                if (gateLandingBtn) {
                    gateLandingBtn.addEventListener('click', function () {
                        window.location.href = gateLandingBtn.dataset.gateUrl || '#';
                    });
                }
                // Mirror my-sign's scaleTable() init timing fix — the
                // .my-sea-page hasn't flushed its flex sizing on
                // DOMContentLoaded, so the hex stays unscaled until we
                // dispatch a resize once layout settles.
                window.requestAnimationFrame(function () {
                    window.dispatchEvent(new Event('resize'));
                });
            }());
            </script>

            {# Brief 'Default deck warning' banner — lifted verbatim from  #}
            {# /billboard/my-sign/'s no-equipped-deck path. Same copy,    #}
            {# same FYI (→ /gameboard/) + NVM (dismiss + proceed) actions.#}
            {# Tagged w. .my-sea-intro-banner so FTs disambiguate from    #}
            {# any other Briefs on the page. note.js itself is hoisted    #}
            {# to the top of {% block content %} (single load per page).  #}
            {# @taxman Briefs — server-driven render. Payloads are populated by  #}
            {# the my_sea view's `_tax_brief_payload` helper, which returns      #}
            {# None when the user's NVM-dismissal anchor is more recent than    #}
            {# the latest TAX_LEDGER Brief of that slug (FREE/PAID DRAW). User- #}
            {# spec 2026-05-26: Briefs persist-dismiss until the next spend     #}
            {# resets the cycle. `dismiss_url` is in the payload — note.js     #}
            {# POSTs it on NVM click + the my_sea_lock/paid_draw views clear   #}
            {# the matching User.*_brief_dismissed_at on the next spend.        #}
            {% if free_draw_brief_payload %}
            {{ free_draw_brief_payload|json_script:"id_free_draw_brief_payload" }}
            <script>
                document.addEventListener('DOMContentLoaded', function () {
                    var el = document.getElementById('id_free_draw_brief_payload');
                    if (!el || !window._showTaxBrief) return;
                    window._showTaxBrief(JSON.parse(el.textContent), 'my-sea-locked-banner');
                });
            </script>
            {% endif %}
            {% if paid_draw_brief_payload %}
            {{ paid_draw_brief_payload|json_script:"id_paid_draw_brief_payload" }}
            <script>
                document.addEventListener('DOMContentLoaded', function () {
                    var el = document.getElementById('id_paid_draw_brief_payload');
                    if (!el || !window._showTaxBrief) return;
                    window._showTaxBrief(JSON.parse(el.textContent), 'my-sea-paid-locked-banner');
                });
            </script>
            {% endif %}
            {% if show_backup_intro_banner %}
            <script>
                document.addEventListener('DOMContentLoaded', function () {
                    if (!window.Brief || !Brief.showBanner) return;
                    Brief.showBanner({
                        title: 'Default deck warning',
                        line_text: 'Look!—no deck is equipped. Navigate to the Game Kit to equip one (FYI) or (NVM) proceed with the Earthman [Shabby Cardstock] deck.',
                        post_url: '{% url "gameboard" %}',
                        created_at: '',
                        kind: 'NUDGE',
                    });
                    var banner = document.querySelector('.note-banner');
                    if (banner) banner.classList.add('my-sea-intro-banner');
                });
            </script>
            {% endif %}
        {% endif %}
        {# Sprint 6 iter 6c — gear-btn lives on every my-sea page state #}
        {# (sign-gate / landing / picker). NVM-only menu. Picker NVM     #}
        {# nav-backs to the my-sea TABLE HEX via `?phase=landing` —      #}
        {# without the query param, the server would re-render the       #}
        {# picker on the next GET (hand_non_empty branch wins). The      #}
        {# landing then renders a CONT DRAW btn (since active_draw       #}
        {# exists + incomplete) so the user can re-enter the picker.    #}
        {# Sign-gate + landing NVM nav-back to /gameboard/ (default).    #}
        {% if show_picker %}{% url 'my_sea' as nvm_base %}{% with nvm_url=nvm_base|add:'?phase=landing' %}{% include "apps/gameboard/_partials/_my_sea_gear.html" %}{% endwith %}{% else %}{% include "apps/gameboard/_partials/_my_sea_gear.html" %}{% endif %}
        {# Bud + burger persist across every stage too — same affordance #}
        {# as on the my-sea gatekeeper, so the user can invite + reach   #}
        {# the cross-cutting menu from sign-gate / landing / picker alike.#}
        {% include "apps/gameboard/_partials/_my_sea_bud_panel.html" %}
        {% include "apps/gameboard/_partials/_burger.html" %}
        <script src="{% static 'apps/epic/burger-btn.js' %}"></script>
        {# Voice-affordance glow/pulse machine (keys on voice_active + the live #}
        {# mesh state). Coexists w. the sea-btn glow-handoff machine below —    #}
        {# sea takes burger precedence; voice is the second-place reveal.       #}
        <script src="{% static 'apps/voice/voice-glow.js' %}"></script>

        {# Phase 2 of the Sea sub-btn rollout — wires #id_sea_btn click to    #}
        {# open #id_sea_spread_modal. AUTO DRAW + Escape + backdrop click     #}
        {# all close the modal. AUTO DRAW closes BEFORE its own handler fires #}
        {# (capture-phase listener) so the existing guard-portal + draw flow  #}
        {# runs against a closed modal, returning the spread crucifix to view #}
        {# as the cards animate in.                                           #}
        <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 actionBtn = document.getElementById('id_sea_action_btn');

            function openModal() {
                modal.removeAttribute('hidden');
            }
            // On close, push the owner's CURRENT spread to watching spectators
            // so their cross re-lays-out immediately — not only once the first
            // card lands (user-spec 2026-05-30). Fires on backdrop / Escape /
            // guard-OK close. Guarded by `_lastSpread` so re-opening + closing
            // without a change doesn't re-broadcast. The select's change handler
            // already mirrors the choice onto `.my-sea-cross[data-spread]`, so
            // that's the live source of truth at close time.
            var crossEl = document.querySelector('.my-sea-cross');
            var _lastSpread = crossEl ? crossEl.getAttribute('data-spread') : null;
            function _broadcastSpread() {
                if (!crossEl) return;
                var spread = crossEl.getAttribute('data-spread');
                if (!spread || spread === _lastSpread) return;
                _lastSpread = spread;
                var m = document.cookie.match(/(?:^|; )csrftoken=([^;]+)/);
                fetch('{% url "my_sea_spread" %}', {
                    method: 'POST',
                    credentials: 'same-origin',
                    headers: {
                        'Content-Type': 'application/json',
                        'X-CSRFToken': m ? decodeURIComponent(m[1]) : '',
                    },
                    body: JSON.stringify({ spread: spread }),
                }).catch(function () { /* best-effort — spectate is an enhancement */ });
            }
            function closeModal() {
                modal.setAttribute('hidden', '');
                _broadcastSpread();
            }

            seaBtn.addEventListener('click', function () {
                // Inactive sub-btn click is handled by burger-btn.js's
                // delegated handler (flash --priRd, no modal). Only fire
                // the modal-open path when this sub-btn is currently active.
                if (!seaBtn.classList.contains('active')) return;
                openModal();
            });

            if (backdrop) {
                backdrop.addEventListener('click', closeModal);
            }

            document.addEventListener('keydown', function (e) {
                if (e.key === 'Escape' && !modal.hasAttribute('hidden')) {
                    closeModal();
                }
            });

            // Modal stays open through the AUTO DRAW + DEL guard-portal flow
            // so the portal can position itself against the visible action /
            // del btn. Close the modal only on guard OK (.guard-yes) so the
            // draw / delete proceeds against an already-closed modal. NVM
            // (.guard-no) leaves the modal up so the user can try again.
            document.addEventListener('click', function (e) {
                if (!e.target.closest('#id_guard_portal .guard-yes')) return;
                if (!modal.hasAttribute('hidden')) closeModal();
            });
        }());
        </script>

        {# Phase 3 — first-draw glow handoff state machine. Owner of the     #}
        {# .glow-handoff transitions across burger → sea_btn → sea-select →  #}
        {# end. Modal close w/o AUTO DRAW restarts at burger. AUTO DRAW      #}
        {# guard-OK ends the cycle permanently. The initial glow on burger   #}
        {# is set either server-side (see sea_first_draw_pending below) OR   #}
        {# client-side in the FREE DRAW handler that transitions data-phase  #}
        {# to 'picker'.                                                       #}
        <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');
            var actionBtn = document.getElementById('id_sea_action_btn');
            if (!seaSelect || !actionBtn) return;

            var glowDone = false;
            var autoDrawConfirmed = false;
            // Set by ANY guard-YES (AUTO DRAW or DEL). Distinguishes "user
            // confirmed a destructive action" from "user dismissed the
            // modal w/o action". Latter restarts the glow on burger; former
            // doesn't — a page redirect is en route (DEL) or AUTO DRAW
            // anim is taking over (endPermanently). Without this flag, DEL
            // briefly flashed burger w. glow before the post-DEL redirect.
            var guardYesClosing = false;

            function startOnBurger() {
                if (glowDone) return;
                burgerBtn.classList.add('glow-handoff');
                seaBtn.classList.remove('glow-handoff');
                seaSelect.classList.remove('glow-handoff');
            }

            function endPermanently() {
                glowDone = true;
                burgerBtn.classList.remove('glow-handoff');
                seaBtn.classList.remove('glow-handoff');
                seaSelect.classList.remove('glow-handoff');
            }

            // burger click → glow hands off to sea_btn (only if burger had it)
            burgerBtn.addEventListener('click', function () {
                if (glowDone) return;
                if (!burgerBtn.classList.contains('glow-handoff')) return;
                burgerBtn.classList.remove('glow-handoff');
                seaBtn.classList.add('glow-handoff');
            });

            // sea_btn click → glow hands off to .sea-select (modal opens too,
            // owned by the Phase 2 IIFE above — both run in target phase, no
            // ordering coupling).
            seaBtn.addEventListener('click', function () {
                if (glowDone) return;
                if (!seaBtn.classList.contains('glow-handoff')) return;
                seaBtn.classList.remove('glow-handoff');
                seaSelect.classList.add('glow-handoff');
            });

            // .sea-select click → cycle ends (this round). Glow doesn't move
            // anywhere; it just clears. Cycle restarts if modal closes w/o
            // AUTO DRAW (see MutationObserver below).
            seaSelect.addEventListener('click', function () {
                if (glowDone) return;
                seaSelect.classList.remove('glow-handoff');
            });

            // AUTO DRAW guard-OK → mark the next modal-close as the permanent
            // end (the draw is about to happen — user found the path).
            document.addEventListener('click', function (e) {
                if (!e.target.closest('#id_guard_portal .guard-yes')) return;
                if (actionBtn.dataset.state === 'auto-draw') {
                    autoDrawConfirmed = true;
                }
            });

            // Modal close handler — observes the `hidden` attribute. If the
            // close was triggered by AUTO DRAW guard-OK, end the cycle for
            // good. Otherwise restart on burger so the user gets another
            // nudge cycle the next time they open the modal.
            var observer = new MutationObserver(function (mutations) {
                for (var i = 0; i < mutations.length; i++) {
                    var m = mutations[i];
                    if (m.attributeName !== 'hidden') continue;
                    if (!modal.hasAttribute('hidden')) continue;
                    if (autoDrawConfirmed) {
                        endPermanently();
                        autoDrawConfirmed = false;
                    } else if (!glowDone) {
                        startOnBurger();
                    }
                }
            });
            observer.observe(modal, {attributes: true, attributeFilter: ['hidden']});

            // Hand-complete observer — end the glow permanently when ALL
            // slots are filled, regardless of how (AUTO DRAW or manual
            // FLIP). #id_sea_action_btn's data-state flips to 'gate-view'
            // at that exact moment (see the picker IIFE _setComplete).
            // Async + decoupled from guard-YES tracking — works for both
            // the AUTO DRAW path AND the all-manual-FLIP path.
            var actionObserver = new MutationObserver(function (mutations) {
                for (var i = 0; i < mutations.length; i++) {
                    var m = mutations[i];
                    if (m.attributeName !== 'data-state') continue;
                    if (actionBtn.dataset.state === 'gate-view') {
                        endPermanently();
                    }
                }
            });
            actionObserver.observe(actionBtn, {attributes: true, attributeFilter: ['data-state']});
        }());
        </script>
    </div>
{% endblock content %}
