diff --git a/src/apps/epic/tests/integrated/test_views.py b/src/apps/epic/tests/integrated/test_views.py index 70f7987..ae481de 100644 --- a/src/apps/epic/tests/integrated/test_views.py +++ b/src/apps/epic/tests/integrated/test_views.py @@ -4270,6 +4270,25 @@ class PickSeaUnifiedFeltTest(TestCase): [sea] = parsed.cssselect("#id_pick_sea_btn") self.assertIn("hex-phase-btn--out", sea.get("class", "")) + def test_sea_felt_leaves_sky_btn_lit_and_swaps_cleanly(self): + """Symmetric with Sky Select (which leaves the sea btn lit while the sky + felt is up): opening the Sea Select felt must NOT grey the burger Sky + sub-btn — it stays .active so a revisited Sea Select keeps the completed + sky one click away (user-spec 2026-06-08). openSea therefore disables + ONLY id_text_btn (not id_sky_btn). And because both felts are z-5 siblings + that otherwise STACK (sea paints over sky; neither open path drops the + other's open-class), clicking the lit sky btn must SWAP felts: openSea + closes the sky felt + openSky closes the sea felt (both expose their close + on window). Regression guard for the symmetry + clean-swap fix.""" + content = self.client.get(self.url).content.decode() + # openSea disables only the Text btn now — the old 2-btn list is gone. + self.assertIn("['id_text_btn']", content) + self.assertNotIn("'id_text_btn', 'id_sky_btn'", content) + # Clean felt-swap wiring both ways (sea exposes + sky calls it; sky + # exposes + sea calls it). + self.assertIn("window.closeSeaFelt", content) + self.assertIn("window.closeSkyFelt", content) + def test_sea_btn_and_glow_bindings_defer_past_parse_time(self): """room.html includes _sea_overlay.html (~line 77) BEFORE _burger.html (~line 167, which holds #id_burger_btn + #id_sea_btn). So the overlay's diff --git a/src/static_src/scss/_burger.scss b/src/static_src/scss/_burger.scss index e879fcc..6bb52a3 100644 --- a/src/static_src/scss/_burger.scss +++ b/src/static_src/scss/_burger.scss @@ -191,13 +191,14 @@ #id_sea_btn.glow-handoff { color: rgba(var(--priId), 1); border-color: rgba(var(--priId), 1); - animation: glow-handoff-pulse 1.8s infinite; + animation: glow-handoff-pulse 3.2s infinite; } -// Quick ease-IN to the bright peak over the first ~22% of the cycle, then a slow -// ease-OUT fade back to the dim trough across the remaining ~78%. The per-segment -// timing fns set the in/out asymmetry; the lopsided keystop split makes the swell -// feel snappy and the decay linger. +// Quick ease-IN to the bright peak over the first ~12.5% of the cycle (~0.4s), +// then a slow ease-OUT fade back to the dim trough across the remaining ~87.5% +// (~2.8s — roughly twice the earlier decay; user asked for a longer linger). The +// per-segment timing fns set the in/out asymmetry; the lopsided keystop split +// keeps the swell snappy while the fade lingers. @keyframes glow-handoff-pulse { 0% { box-shadow: @@ -205,7 +206,7 @@ 0 0 0.7rem 0.1rem rgba(var(--ninUser), 0.12); animation-timing-function: ease-in; } - 22% { + 12% { box-shadow: 0 0 0.6rem 0.15rem rgba(var(--ninUser), 0.95), 0 0 1.4rem 0.4rem rgba(var(--ninUser), 0.55); diff --git a/src/templates/apps/gameboard/_partials/_sea_overlay.html b/src/templates/apps/gameboard/_partials/_sea_overlay.html index cb173c9..05c4249 100644 --- a/src/templates/apps/gameboard/_partials/_sea_overlay.html +++ b/src/templates/apps/gameboard/_partials/_sea_overlay.html @@ -449,13 +449,18 @@ via epic:sea_save. `?seat` threads the CARTE-selected seat onto the action URLs. } hidden.addEventListener('change', sync); - // ── Open / close the felt (DRAW SEA ⇄ gear NVM nav). On open: disable the - // burger Text + Sky sub-btns. The sea btn is NOT activated here — it's the - // post-completion reopen affordance (see the cascade + the reopen binding). + // ── Open / close the felt (DRAW SEA ⇄ gear NVM nav). On open: disable ONLY the + // burger Text sub-btn (its swipe machine would drive into a scroll-locked + // reelhouse). The SKY sub-btn is left .active (lit) — symmetric with Sky Select, + // which leaves the sea btn lit while the sky felt is up (user-spec 2026-06-08): + // both completed phases stay one click apart. The sea btn itself is NOT + // activated here — it's the post-completion reopen affordance (see the cascade + // + the reopen binding). Clicking the lit sky btn from here SWAPS felts cleanly + // (openSea/openSky close each other — see openSea below). var _disabledBtns = []; function _disablePhaseBtns() { _disabledBtns = []; - ['id_text_btn', 'id_sky_btn'].forEach(function (id) { + ['id_text_btn'].forEach(function (id) { var b = document.getElementById(id); if (b && b.classList.contains('active')) { b.classList.remove('active'); _disabledBtns.push(b); } }); @@ -465,6 +470,13 @@ via epic:sea_save. `?seat` threads the CARTE-selected seat onto the action URLs. _disabledBtns = []; } function openSea() { + // Clean SWAP, not a double-open: if the sky felt is up (its lit sea btn was + // just clicked) close it first — both felts are z-5 siblings that otherwise + // stack (sea paints over sky), and neither open path drops the other's + // open-class on its own. Mirrored by openSky closing the sea felt. + if (document.documentElement.classList.contains('sky-open') && window.closeSkyFelt) { + window.closeSkyFelt(); + } document.documentElement.classList.add('sea-open'); document.documentElement.classList.add('sea-entered'); _disablePhaseBtns(); @@ -476,6 +488,7 @@ via epic:sea_save. `?seat` threads the CARTE-selected seat onto the action URLs. if (window.RoomViews && window.RoomViews.syncGear) window.RoomViews.syncGear(); } window.openSeaFelt = openSea; + window.closeSeaFelt = closeSea; var pickSeaBtn = document.getElementById('id_pick_sea_btn'); if (pickSeaBtn) pickSeaBtn.addEventListener('click', openSea); diff --git a/src/templates/apps/gameboard/_partials/_sky_overlay.html b/src/templates/apps/gameboard/_partials/_sky_overlay.html index 5b9d822..ad63dd8 100644 --- a/src/templates/apps/gameboard/_partials/_sky_overlay.html +++ b/src/templates/apps/gameboard/_partials/_sky_overlay.html @@ -184,6 +184,13 @@ } function openSky() { + // Clean SWAP, not a double-open: if the sea felt is up (its lit sky btn was + // just clicked) close it first. Both felts are z-5 siblings that otherwise + // stack; neither open path drops the other's open-class on its own. Mirrored + // by openSea closing the sky felt (user-spec 2026-06-08). + if (document.documentElement.classList.contains('sea-open') && window.closeSeaFelt) { + window.closeSeaFelt(); + } document.documentElement.classList.add('sky-open'); _disableTextBtn(); // Re-sync the room gear to the sky NVM pane (room-views.js owns the gear @@ -624,6 +631,9 @@ // Exposed so the burger fan's #id_sky_btn (active once the sky is saved) can // reopen the saved wheel. burger-btn.js calls window.openSkyFelt(). window.openSkyFelt = openSky; + // Exposed so openSea can close the sky felt for a clean felt-swap when a user + // clicks the lit sky btn from inside the sea felt (user-spec 2026-06-08). + window.closeSkyFelt = closeSky; // ── Restore persisted form data + prime a saved (confirmed) wheel ─────────── // Pre-save the wheel draw is deferred to openSky (form shows alone). Once the