CAST SKY cascade: felt eases out → glow → DRAW SEA eases in; burger handoff; reload-into-open — TDD

Post-save choreography + the polish asks from this session.

- Post-save cascade (_sky_overlay.html): after SAVE the gamer lingers on the
  wheel ~3s, then the felt eases OUT (fade, .sky-page--cascade-out) to reveal the
  table-hex; the burger fires its --priTk glow + #id_sky_btn goes active (both
  ride the cascade now, not the save instant); 3s later the DRAW SEA btn eases IN
  and the sea overlay is injected so it's live with NO reload.
- Hex phase-stack (room.html + _room.scss): CAST SKY + DRAW SEA share one grid
  cell (.hex-phase-stack) so they cross-fade in place (.hex-phase-btn--out); the
  server seeds --out on the inactive one, the cascade swaps them. A confirmed
  reload lands DRAW SEA visible / CAST SKY out, same as the cascade end.
- Seamless sea injection: _injectSeaOverlay fetches sea_partial (the URL already
  existed, unused) + re-creates its <script>s so the overlay's own init (openSea
  + SeaDeal.reinit) runs — DRAW SEA opens with no reload. (Bridge until Sea Select
  is itself hollowed into a felt.)
- Burger → Sky-btn handoff (burger-btn.js + _burger.scss): _pulseGlow generalized;
  once saved, the next burger-OPEN pulses #id_sky_btn --priTk ("now click me to
  reopen"). Jasmine specs added (BurgerSpec).
- #id_text_btn disabled while the felt is up (openSky/closeSky) — its swipe
  machine would otherwise half-load the scroll-locked reelhouse.
- Reload-into-open (sig-select.js + _sky_overlay.html): the SIG_SELECT→SKY_SELECT
  CAST SKY click crosses a server-render boundary (felt/phase-stack/sea-inject
  only exist in SKY_SELECT), so it still reloads — but drops a sessionStorage
  flag first so the felt OPENS on arrival. Kills the old click→reload→click-again
  double-take (the "first click reloads" report). DRAW SEA can inject in-place
  (stays within SKY_SELECT); CAST SKY can't, so this is the seamless equivalent.

Tests: BurgerSpec handoff specs + full Jasmine green; 32 render ITs + 930
epic+gameboard green. Cascade timing live-verified by the user.

[[feedback-scss-import-order-specificity]]

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Disco DeDisco
2026-06-07 18:24:32 -04:00
parent 94cd9db3a4
commit d5e4fc53f0
9 changed files with 280 additions and 27 deletions

View File

@@ -166,8 +166,26 @@
// ── Open / Close ──────────────────────────────────────────────────────────
// While the felt is up the burger Text sub-btn must be inert: its swipe
// machine (room-views.js) would otherwise drive DOWN into the reelhouse — but
// the aperture is scroll-locked, so the page half-loads a carousel view it
// can't actually reach (confusing UX). We toggle #id_text_btn OFF on open and
// restore its server-set baseline on close.
let _textBtnWasActive = false;
function _disableTextBtn() {
const tb = document.getElementById('id_text_btn');
if (!tb) return;
_textBtnWasActive = tb.classList.contains('active');
tb.classList.remove('active');
}
function _restoreTextBtn() {
const tb = document.getElementById('id_text_btn');
if (tb && _textBtnWasActive) tb.classList.add('active');
}
function openSky() {
document.documentElement.classList.add('sky-open');
_disableTextBtn();
// Re-sync the room gear to the sky NVM pane (room-views.js owns the gear
// pane-swap; sky-open toggles outside any scroll/view event it watches).
if (window.RoomViews && window.RoomViews.syncGear) window.RoomViews.syncGear();
@@ -181,6 +199,7 @@
function closeSky() {
document.documentElement.classList.remove('sky-open');
_restoreTextBtn();
hideSuggestions();
if (window.RoomViews && window.RoomViews.syncGear) window.RoomViews.syncGear();
}
@@ -424,21 +443,86 @@
SkyWheel.draw(svgEl, _lastChartData);
}
_ensureDelBtn();
// The sky now lives in the burger fan: light its Sky sub-btn so it's a live
// reopen affordance immediately (server also renders it active after any
// reload). No-op if the burger isn't on this surface.
const skyBtn = document.getElementById('id_sky_btn');
if (skyBtn) skyBtn.classList.add('active');
if (!wasSaved) {
// First reveal: pin to the form section so the wheel slides in from above
// (the form "shunts down") rather than hard-cutting into place.
// (the form "shunts down"), then begin the post-save cascade. #id_sky_btn
// activation + the burger glow ride the cascade (not the save instant) so
// they land WITH the table-hex reveal — see _cascadeFeltOut.
const formCol = overlay.querySelector('.sky-form-col');
if (formCol) overlay.scrollTop = formCol.offsetTop;
// Cue the burger thrice (--priTk) — "your sky lives here now". One-shot
// per save; the burger-btn.js load pulse covers the post-reload hex.
if (window.pulseSkyGlow) window.pulseSkyGlow();
_scrollApertureToTop();
_startSaveCascade();
} else {
// Re-assert (WS re-fire / reopen-from-saved): keep the reopen affordance
// live; no cascade (the table-hex already advanced on the first save).
const skyBtn = document.getElementById('id_sky_btn');
if (skyBtn) skyBtn.classList.add('active');
_scrollApertureToTop();
}
_scrollApertureToTop();
}
// ── Post-save cascade ───────────────────────────────────────────────────────
// After SAVE the gamer lingers on the freshly-drawn wheel ~3s; THEN the felt
// eases OUT to reveal the table-hex, the burger glow fires (your sky now lives
// in the burger fan), and 3s later the DRAW SEA btn eases IN — with the sea
// overlay injected so it's live with no reload. Each beat is its own timer.
const _CASCADE_LINGER = 3000, _FELT_FADE = 500, _SEA_DELAY = 3000;
function _startSaveCascade() {
setTimeout(_cascadeFeltOut, _CASCADE_LINGER);
}
function _cascadeFeltOut() {
overlay.classList.add('sky-page--cascade-out'); // CSS fades the felt out
setTimeout(() => {
document.documentElement.classList.remove('sky-open');
overlay.classList.remove('sky-page--cascade-out');
_restoreTextBtn();
if (window.RoomViews && window.RoomViews.syncGear) window.RoomViews.syncGear();
// CAST SKY is stale → ease it out; light the burger reopen affordance +
// fire the glow, all concurrent with the table-hex reveal.
const skyPhaseBtn = document.getElementById('id_pick_sky_btn');
if (skyPhaseBtn) skyPhaseBtn.classList.add('hex-phase-btn--out');
const skyBtn = document.getElementById('id_sky_btn');
if (skyBtn) skyBtn.classList.add('active');
if (window.pulseSkyGlow) window.pulseSkyGlow();
setTimeout(_cascadeDrawSeaIn, _SEA_DELAY);
}, _FELT_FADE);
}
function _cascadeDrawSeaIn() {
const seaPhaseBtn = document.getElementById('id_pick_sea_btn');
if (seaPhaseBtn) seaPhaseBtn.classList.remove('hex-phase-btn--out'); // eases in
_injectSeaOverlay();
}
// Fetch + inject the DRAW SEA overlay so it's live without a reload. The sea
// partial carries its own inline <script> (openSea binding on #id_pick_sea_btn
// + SeaDeal.reinit) — innerHTML won't execute it, so each <script> is re-
// created. Idempotent + best-effort (a gear NVM / refresh recovers via the
// server-rendered confirmed overlay).
function _injectSeaOverlay() {
const url = overlay.dataset.seaPartialUrl;
const container = document.getElementById('id_sea_inject');
if (!url || !container || container.dataset.injected) return;
fetch(url, { credentials: 'same-origin' })
.then((r) => { if (!r.ok) throw new Error(r.status); return r.text(); })
.then((html) => {
container.dataset.injected = '1';
const tpl = document.createElement('template');
tpl.innerHTML = html;
Array.prototype.slice.call(tpl.content.childNodes).forEach((node) => {
if (node.nodeName === 'SCRIPT') {
const s = document.createElement('script');
if (node.src) s.src = node.src;
else s.textContent = node.textContent;
container.appendChild(s);
} else {
container.appendChild(node);
}
});
})
.catch(() => { /* transient — server render recovers on next load */ });
}
// Ease the felt's scroll back to the wheel page (top) after a save —
@@ -566,6 +650,17 @@
});
}
// Reload-into-open: the SIG_SELECT → SKY_SELECT transition (sig-select.js's
// CAST SKY click) drops a sessionStorage flag then reloads here, so the felt
// OPENS on arrival — one click yields the form instead of the old click→reload
// →click-again. Read once + clear so a later manual refresh doesn't re-open.
try {
if (sessionStorage.getItem('sky-autoopen') === '1') {
sessionStorage.removeItem('sky-autoopen');
openSky();
}
} catch (_) { /* sessionStorage unavailable — non-fatal */ }
// ── STUB: lock the form once character creation completes ───────────────────
// Character creation ends after DRAW SEA → the Voronoi map (roadmap step 21),
// which isn't built yet. When it lands the server will flag the finished state