my_sea_visit nav: phase-aware NVM (hex→bud page, draw→hex) + navbar GATE VIEW → visit gate + guard reposition on resize — TDD

Spectator guard-portal + NVM/GATE VIEW routing (user-spec 2026-05-30):
- The leave-the-sea guard now confirms with OK (was NVM); `_my_sea_gear.html`
  gains an `nvm_handler` param so a caller can swap the default leave-nav.
- my_sea_visit NVM is phase-aware (`mySeaVisitNvm`): on the DRAW/spread phase it
  flips back to the table hex (client toggle, stays in-ecosphere → no voice
  guard, mirroring the owner's picker→landing); on the table hex it LEAVES to the
  bud's page (`billboard:bud_page`) behind the shared voice-disconnect guard.
- The navbar GATE VIEW opens THIS owner's visitor gatekeeper on my_sea_visit (+
  its gate page, whose page_class also carries `page-my-sea-visit`), not the
  viewer's own sea gate; owner pages are unchanged.
- showGuard now RE-POSITIONS the open guard on resize/orientationchange
  (rAF-throttled) so it follows its anchor instead of stranding at its show-time
  coords — a cross-cutting fix (every gear-menu guard) for the portal landing
  off-screen after an orientation flip relocated the gear menu.

Coverage: MySeaVisitNavTest (navbar→visit gate, gear NVM→mySeaVisitNvm, bud URL,
OK label) + MySeaOwnerNavbarGateUnaffectedTest (owner gate untouched). Verified
live in Firefox: picker NVM returns to the hex; the guard followed its anchor
from 882px→54px on a simulated orientation flip, staying in-viewport.

Code architected by Disco DeDisco <discodedisco@outlook.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Disco DeDisco
2026-05-30 02:15:23 -04:00
parent 571d5a84ae
commit 7e39740f9c
5 changed files with 144 additions and 22 deletions

View File

@@ -31,7 +31,7 @@ window.mySeaGuardedNav = window.mySeaGuardedNav || function (e, url) {
window.location.href = url;
},
null, // dismiss = stay
{ yesLabel: 'NVM' }
{ yesLabel: 'OK' } // confirm = OK (user-spec 2026-05-30)
);
return;
}
@@ -43,7 +43,10 @@ window.mySeaGuardedNav = window.mySeaGuardedNav || function (e, url) {
{# column in portrait / row in landscape (the menu itself can't be flexed #}
{# since applets.js force-sets display:block on open). #}
<div class="menu-btns">
<button type="button" class="btn btn-cancel" onclick="mySeaGuardedNav(event, '{{ nvm_url }}')">NVM</button>
{# `nvm_handler` lets a caller swap the default leave-the-sea nav for a #}
{# phase-aware handler (the visitor's my_sea_visit routes NVM to the bud #}
{# page from the table hex, but back to the hex from the draw phase). #}
<button type="button" class="btn btn-cancel" onclick="{% if nvm_handler %}{{ nvm_handler }}{% else %}mySeaGuardedNav(event, '{{ nvm_url }}'){% endif %}">NVM</button>
{# Spectator-only BYE (Phase B) — drops presence (status=LEFT, frees seat #}
{# 2C, kills voice). Rendered below NVM only when the caller passes a #}
{# `leave_url`; the owner's pages never do, so their menu is unchanged. #}

View File

@@ -73,9 +73,11 @@
</div>
{% endif %}
{# Gear menu — NVM (back to /gameboard/) + BYE (drop presence, free 2C). #}
{# Gear menu — NVM is phase-aware (mySeaVisitNvm): table hex → the bud's #}
{# page (voice-disconnect guard); draw phase → back to the hex (client #}
{# toggle, stays in-ecosphere, no guard). BYE drops presence + frees 2C. #}
{% url 'my_sea_visit_leave' owner.id as leave_url %}
{% include "apps/gameboard/_partials/_my_sea_gear.html" with leave_url=leave_url %}
{% include "apps/gameboard/_partials/_my_sea_gear.html" with leave_url=leave_url nvm_handler="mySeaVisitNvm(event)" %}
{# Burger fan — carries the voice sub-btn (active while voice_active). #}
{% include "apps/gameboard/_partials/_burger.html" %}
</div>
@@ -323,14 +325,42 @@
var viewBtn = document.getElementById('id_my_sea_view_draw_btn');
var draw = document.getElementById('id_my_sea_visit_draw');
var landing = document.querySelector('.my-sea-visit-landing');
function _setDraw(show) {
if (!draw) return;
draw.style.display = show ? '' : 'none';
if (landing) landing.style.display = show ? 'none' : '';
if (page) page.setAttribute('data-phase', show ? 'picker' : 'landing');
}
if (viewBtn && draw) {
viewBtn.addEventListener('click', function () {
var showing = draw.style.display !== 'none';
draw.style.display = showing ? 'none' : '';
if (landing) landing.style.display = showing ? '' : 'none';
if (page) page.setAttribute('data-phase', showing ? 'landing' : 'picker');
_setDraw(draw.style.display === 'none');
});
}
// Phase-aware NVM (the gear menu's NVM calls this). On the DRAW/spread
// phase NVM just flips back to the table hex — it stays inside the
// my_sea_visit ecosphere (voice untouched), so NO guard, like the owner's
// picker→landing NVM. On the table hex NVM LEAVES to the bud's page, with
// the shared voice-disconnect guard (mySeaGuardedNav). (user-spec
// 2026-05-30.)
var BUD_URL = '{% url 'billboard:bud_page' owner.id %}';
function _closeGearMenu() {
var menu = document.getElementById('id_my_sea_menu');
if (menu) menu.style.display = 'none';
var gear = document.querySelector('.gear-btn[data-menu-target="id_my_sea_menu"]');
if (gear) gear.classList.remove('active');
}
window.mySeaVisitNvm = function (e) {
if (page && page.getAttribute('data-phase') === 'picker') {
if (e && e.preventDefault) e.preventDefault();
_setDraw(false); // back to the table hex
_closeGearMenu();
return;
}
// Table hex → leave to the bud's page (guarded if voice is live).
if (window.mySeaGuardedNav) window.mySeaGuardedNav(e, BUD_URL);
else window.location.href = BUD_URL;
};
// GATE VIEW → visitor gate.
var gateBtn = document.getElementById('id_my_sea_gate_view_btn');
if (gateBtn) {