diff --git a/src/apps/epic/tests/integrated/test_views.py b/src/apps/epic/tests/integrated/test_views.py index 8c80f35..d3db315 100644 --- a/src/apps/epic/tests/integrated/test_views.py +++ b/src/apps/epic/tests/integrated/test_views.py @@ -4211,6 +4211,33 @@ class PickSeaUnifiedFeltTest(TestCase): self.assertNotIn("sea-deck-stack--single", content) self.assertNotIn("sea-stacks--single", content) + def test_seed_map_btn_out_until_hand_complete(self): + """SEED MAP joins the hex phase stack (the post-completion cascade eases + it IN, mirroring CAST SKY → DRAW SEA). Pre-completion it renders --out + while DRAW SEA is in (user-spec 2026-06-07).""" + import lxml.html + parsed = lxml.html.fromstring(self.client.get(self.url).content) + [seed] = parsed.cssselect("#id_seed_map_btn") + self.assertIn("hex-phase-btn--out", seed.get("class", "")) + [sea] = parsed.cssselect("#id_pick_sea_btn") + self.assertNotIn("hex-phase-btn--out", sea.get("class", "")) + + def test_seed_map_in_and_draw_sea_out_when_hand_complete(self): + """A reload of an already-complete sea lands on SEED MAP server-side: + SEED MAP in, DRAW SEA --out (the cascade's end-state, no animation).""" + card_id = TarotCard.objects.filter(deck_variant=self.earthman).first().id + char = Character.objects.get(seat=self.pc_seat, confirmed_at__isnull=False) + char.celtic_cross = {"spread": "waite-smith", "hand": [ + {"position": p, "card_id": card_id, "reversed": False, "polarity": "gravity"} + for p in ["cover", "cross", "crown", "lay", "loom", "leave"]]} + char.save(update_fields=["celtic_cross"]) + import lxml.html + parsed = lxml.html.fromstring(self.client.get(self.url).content) + [seed] = parsed.cssselect("#id_seed_map_btn") + self.assertNotIn("hex-phase-btn--out", seed.get("class", "")) + [sea] = parsed.cssselect("#id_pick_sea_btn") + self.assertIn("hex-phase-btn--out", sea.get("class", "")) + class CarteSeatSwitchSkySeaTest(TestCase): """A CARTE owner (one gamer owns all 6 seats) must drive EACH seat through diff --git a/src/static_src/scss/_sky.scss b/src/static_src/scss/_sky.scss index 238cf5b..833883d 100644 --- a/src/static_src/scss/_sky.scss +++ b/src/static_src/scss/_sky.scss @@ -103,6 +103,15 @@ html.sea-open .position-strip { visibility: hidden; } +// Post-completion cascade ease-out — once the 6-card spread is complete the felt +// fades to transparent over ~0.5s (the JS _SEA_FELT_FADE timer) BEFORE sea-open +// is removed, revealing the table-hex with a soft dissolve (mirrors the CAST SKY +// post-save cascade's .sky-page--cascade-out). +.sea-page.sea-page--cascade-out { + opacity: 0; + transition: opacity 0.5s ease; +} + // ── Backdrop ────────────────────────────────────────────────────────────────── .sky-backdrop { diff --git a/src/templates/apps/gameboard/_partials/_room_hex_center.html b/src/templates/apps/gameboard/_partials/_room_hex_center.html index 86a1f7f..e437823 100644 --- a/src/templates/apps/gameboard/_partials/_room_hex_center.html +++ b/src/templates/apps/gameboard/_partials/_room_hex_center.html @@ -29,14 +29,17 @@ sky_confirmed, polarity_done, user_polarity, current_slot, …). onclick="window.location.href='{% url 'epic:room_gate' room.id %}{% if current_slot %}?seat={{ current_slot }}{% endif %}'">GATE
VIEW {% else %} {% if room.table_status == "SKY_SELECT" %} - {# Both phase btns render so the post-save cascade can cross-fade CAST #} - {# SKY → DRAW SEA in place (no reload). The server sets --out on the #} - {# inactive one; sky-select.js's cascade swaps them. On a confirmed #} - {# reload the server lands DRAW SEA visible + CAST SKY out, same as the #} - {# cascade end. #} + {# All three phase btns render so the cascades can cross-fade in place #} + {# (no reload): CAST SKY → DRAW SEA on sky-save, then DRAW SEA → SEED MAP #} + {# on the 6-card spread completing. The server sets --out on the inactive #} + {# ones; the sky/sea cascades swap them. On a reload the server lands the #} + {# right one visible (DRAW SEA once sky_confirmed; SEED MAP once the sea #} + {# hand is complete), same as each cascade's end. SEED MAP → the Voronoi #} + {# map (roadmap step 21) is a stub for now — it only needs to APPEAR here. #}
- + +
{% elif room.table_status == "SIG_SELECT" %} diff --git a/src/templates/apps/gameboard/_partials/_sea_overlay.html b/src/templates/apps/gameboard/_partials/_sea_overlay.html index e06b80b..be08783 100644 --- a/src/templates/apps/gameboard/_partials/_sea_overlay.html +++ b/src/templates/apps/gameboard/_partials/_sea_overlay.html @@ -222,7 +222,7 @@ Each draw persists onto the seat's Character.celtic_cross via epic:sea_save. function _setHasDrawn(on) { if (delBtn) { delBtn.classList.toggle('btn-disabled', !on); delBtn.innerHTML = on ? 'DEL' : '×'; } } - function _setComplete(on) { + 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) { @@ -234,6 +234,45 @@ 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). + var _SEA_CASCADE_LINGER = 3000, _SEA_FELT_FADE = 500, _SEED_DELAY = 3000; + var _cascadeRun = false; + function _startSeaCascade() { + if (_cascadeRun) return; + _cascadeRun = true; + setTimeout(_cascadeFeltOut, _SEA_CASCADE_LINGER); + } + function _cascadeFeltOut() { + page.classList.add('sea-page--cascade-out'); // CSS fades the felt 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'); + var seaBtn = document.getElementById('id_sea_btn'); + if (seaBtn) seaBtn.classList.add('active'); + var burgerBtn = document.getElementById('id_burger_btn'); + if (burgerBtn) burgerBtn.classList.add('glow-handoff'); + setTimeout(_cascadeSeedMapIn, _SEED_DELAY); + }, _SEA_FELT_FADE); + } + function _cascadeSeedMapIn() { + var seedBtn = document.getElementById('id_seed_map_btn'); + if (seedBtn) seedBtn.classList.remove('hex-phase-btn--out'); // eases in } function _collectHandFromDom() { @@ -288,7 +327,7 @@ Each draw persists onto the seat's Character.celtic_cross via epic:sea_save. _filled++; if (_filled === 1) { _lockSpread(); _setHasDrawn(true); } _postLock(_collectHandFromDom()); - if (_filled >= order.length) _setComplete(true); + if (_filled >= order.length) _setComplete(true, true); // live → cascade } _hideOk(); }); @@ -320,7 +359,7 @@ Each draw persists onto the seat's Character.celtic_cross via epic:sea_save. _setHasDrawn(true); var idx = 0; function placeNext() { - if (idx >= autoEntries.length) { _setComplete(true); return; } + 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). @@ -493,12 +532,12 @@ Each draw persists onto the seat's Character.celtic_cross via epic:sea_save. }()); -{# ── Completion glow cue (mirrors Sky Select) — the SEA glow-machine does NOT #} -{# fire while drawing. It starts only once ALL slots are drawn (the action btn #} -{# flips to data-state="complete", DRAW SEA gives way to SEED MAP), then hands #} -{# off burger → sea_btn → .sea-select so the user can review their sea. The #} -{# cue starts on the live completion TRANSITION (manual FLIP or AUTO DRAW), not #} -{# on a reload of an already-complete sea. User-spec 2026-06-07. #} +{# ── 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. #}