Compare commits
4 Commits
faaa4ecfb0
...
2c2ec16f08
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2c2ec16f08 | ||
|
|
44bf4e626c | ||
|
|
a6ce20761b | ||
|
|
f3f509a59a |
@@ -117,17 +117,39 @@
|
|||||||
// multi-seat owner never receives the other polarity's countdown.
|
// multi-seat owner never receives the other polarity's countdown.
|
||||||
const seatParam = new URLSearchParams(window.location.search).get('seat');
|
const seatParam = new URLSearchParams(window.location.search).get('seat');
|
||||||
const wsSeat = seatParam ? `?seat=${encodeURIComponent(seatParam)}` : '';
|
const wsSeat = seatParam ? `?seat=${encodeURIComponent(seatParam)}` : '';
|
||||||
const ws = new WebSocket(`${wsScheme}://${window.location.host}/ws/room/${roomId}/${wsSeat}`);
|
const wsUrl = `${wsScheme}://${window.location.host}/ws/room/${roomId}/${wsSeat}`;
|
||||||
window._roomSocket = ws; // exposed for sig-select.js hover broadcast
|
|
||||||
|
|
||||||
ws.onmessage = function (event) {
|
// Auto-reconnect with capped exponential backoff. A dropped socket (proxy
|
||||||
const data = JSON.parse(event.data);
|
// idle-timeout, brief network blip, server restart) otherwise stayed dead
|
||||||
window.dispatchEvent(new CustomEvent('room:' + data.type, { detail: data }));
|
// until a manual refresh, silently losing live events — most painfully the
|
||||||
};
|
// 12s sig countdown's polarity_room_done / pick_sky_available, so the
|
||||||
|
// tray→hex animation never played. Backoff resets on a clean open; we stop
|
||||||
|
// retrying once the page is unloading so we don't reconnect during nav.
|
||||||
|
let backoff = 1000;
|
||||||
|
const BACKOFF_MAX = 30000;
|
||||||
|
let unloading = false;
|
||||||
|
window.addEventListener('beforeunload', function () { unloading = true; });
|
||||||
|
|
||||||
ws.onclose = function (event) {
|
function connect() {
|
||||||
if (!event.wasClean) {
|
const ws = new WebSocket(wsUrl);
|
||||||
console.warn('Room WebSocket closed unexpectedly');
|
window._roomSocket = ws; // exposed for sig-select.js hover broadcast
|
||||||
}
|
|
||||||
};
|
ws.onopen = function () { backoff = 1000; };
|
||||||
|
|
||||||
|
ws.onmessage = function (event) {
|
||||||
|
const data = JSON.parse(event.data);
|
||||||
|
window.dispatchEvent(new CustomEvent('room:' + data.type, { detail: data }));
|
||||||
|
};
|
||||||
|
|
||||||
|
ws.onclose = function (event) {
|
||||||
|
if (unloading) return;
|
||||||
|
if (!event.wasClean) {
|
||||||
|
console.warn('Room WebSocket closed unexpectedly — reconnecting in ' + backoff + 'ms');
|
||||||
|
}
|
||||||
|
setTimeout(connect, backoff);
|
||||||
|
backoff = Math.min(backoff * 2, BACKOFF_MAX);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
connect();
|
||||||
}());
|
}());
|
||||||
|
|||||||
@@ -370,7 +370,7 @@ var SigSelect = (function () {
|
|||||||
if (_countdownTimer !== null) {
|
if (_countdownTimer !== null) {
|
||||||
clearInterval(_countdownTimer);
|
clearInterval(_countdownTimer);
|
||||||
_countdownTimer = null;
|
_countdownTimer = null;
|
||||||
if (_takeSigBtn) _takeSigBtn.style.fontSize = '';
|
if (_takeSigBtn) _takeSigBtn.classList.remove('sig-take-sig-btn--counting');
|
||||||
}
|
}
|
||||||
if (_takeSigBtn) _takeSigBtn.textContent = 'SAVE SIG';
|
if (_takeSigBtn) _takeSigBtn.textContent = 'SAVE SIG';
|
||||||
_stopWaitNoGlow();
|
_stopWaitNoGlow();
|
||||||
@@ -477,7 +477,12 @@ var SigSelect = (function () {
|
|||||||
_stopWaitNoGlow();
|
_stopWaitNoGlow();
|
||||||
if (_takeSigBtn) {
|
if (_takeSigBtn) {
|
||||||
_takeSigBtn.textContent = _countdownSecondsLeft;
|
_takeSigBtn.textContent = _countdownSecondsLeft;
|
||||||
_takeSigBtn.style.fontSize = '2em';
|
// Enlarge via a class, NOT inline font-size: the .btn-primary
|
||||||
|
// down-size media query (font-size !important on small landscape/
|
||||||
|
// portrait) out-!importants an inline 2em, so the numeral stayed
|
||||||
|
// button-sized on phones. The .sig-take-sig-btn--counting rule
|
||||||
|
// re-asserts the doubling with matching !important + specificity.
|
||||||
|
_takeSigBtn.classList.add('sig-take-sig-btn--counting');
|
||||||
}
|
}
|
||||||
_startCountdownGlow();
|
_startCountdownGlow();
|
||||||
if (_countdownTimer !== null) clearInterval(_countdownTimer);
|
if (_countdownTimer !== null) clearInterval(_countdownTimer);
|
||||||
@@ -499,7 +504,7 @@ var SigSelect = (function () {
|
|||||||
}
|
}
|
||||||
_stopCountdownGlow();
|
_stopCountdownGlow();
|
||||||
if (_takeSigBtn) {
|
if (_takeSigBtn) {
|
||||||
_takeSigBtn.style.fontSize = '';
|
_takeSigBtn.classList.remove('sig-take-sig-btn--counting');
|
||||||
if (_isReady) {
|
if (_isReady) {
|
||||||
// Countdown cancelled by another gamer — restore WAIT NVM state
|
// Countdown cancelled by another gamer — restore WAIT NVM state
|
||||||
_takeSigBtn.textContent = 'WAIT NVM';
|
_takeSigBtn.textContent = 'WAIT NVM';
|
||||||
|
|||||||
@@ -888,6 +888,25 @@ describe("SigSelect", () => {
|
|||||||
expect(takeSigBtn.textContent).toBe("8");
|
expect(takeSigBtn.textContent).toBe("8");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("enlarges the numeral via the --counting class (not a fragile inline 2em)", () => {
|
||||||
|
// The .btn-primary media-query font-size !important beats an inline
|
||||||
|
// 2em on small queries; the class lets SCSS re-assert the doubling.
|
||||||
|
makeFixture({ reservations: '{"42":"PC"}', ready: true, countdownRemaining: 8 });
|
||||||
|
takeSigBtn = document.getElementById("id_take_sig_btn");
|
||||||
|
expect(takeSigBtn.classList.contains("sig-take-sig-btn--counting")).toBe(true);
|
||||||
|
expect(takeSigBtn.style.fontSize).toBe(""); // no inline override
|
||||||
|
});
|
||||||
|
|
||||||
|
it("clears the --counting class when the countdown is cancelled", () => {
|
||||||
|
makeFixture({ reservations: '{"42":"PC"}', ready: true, countdownRemaining: 8 });
|
||||||
|
takeSigBtn = document.getElementById("id_take_sig_btn");
|
||||||
|
expect(takeSigBtn.classList.contains("sig-take-sig-btn--counting")).toBe(true);
|
||||||
|
window.dispatchEvent(new CustomEvent("room:countdown_cancel", {
|
||||||
|
detail: { polarity: "levity", seconds_remaining: 5 },
|
||||||
|
}));
|
||||||
|
expect(takeSigBtn.classList.contains("sig-take-sig-btn--counting")).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
it("the restored numeral counts down each second", () => {
|
it("the restored numeral counts down each second", () => {
|
||||||
makeFixture({ reservations: '{"42":"PC"}', ready: true, countdownRemaining: 8 });
|
makeFixture({ reservations: '{"42":"PC"}', ready: true, countdownRemaining: 8 });
|
||||||
takeSigBtn = document.getElementById("id_take_sig_btn");
|
takeSigBtn = document.getElementById("id_take_sig_btn");
|
||||||
@@ -900,6 +919,25 @@ describe("SigSelect", () => {
|
|||||||
takeSigBtn = document.getElementById("id_take_sig_btn");
|
takeSigBtn = document.getElementById("id_take_sig_btn");
|
||||||
expect(takeSigBtn.textContent).toBe("WAIT NVM");
|
expect(takeSigBtn.textContent).toBe("WAIT NVM");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("re-renders the numeral when countdown_start arrives again after a cancel", () => {
|
||||||
|
// Diagnostic for the re-ready bug: cancel → re-ready must restart the
|
||||||
|
// visual countdown. This exercises the CLIENT handler chain only; if
|
||||||
|
// it passes, the live failure is WS delivery (socket / broadcast), not
|
||||||
|
// client logic.
|
||||||
|
makeFixture({ reservations: '{"42":"PC"}', ready: true, countdownRemaining: 9 });
|
||||||
|
takeSigBtn = document.getElementById("id_take_sig_btn");
|
||||||
|
expect(takeSigBtn.textContent).toBe("9"); // counting
|
||||||
|
window.dispatchEvent(new CustomEvent("room:countdown_cancel", {
|
||||||
|
detail: { polarity: "levity", seconds_remaining: 7 },
|
||||||
|
}));
|
||||||
|
expect(takeSigBtn.textContent).toBe("WAIT NVM"); // cancelled
|
||||||
|
window.dispatchEvent(new CustomEvent("room:countdown_start", {
|
||||||
|
detail: { polarity: "levity", seconds: 7 },
|
||||||
|
}));
|
||||||
|
expect(takeSigBtn.textContent).toBe("7"); // restarted
|
||||||
|
expect(takeSigBtn.classList.contains("sig-take-sig-btn--counting")).toBe(true);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// ── polarity_room_done → tray sequence ─────────────────────────────────── //
|
// ── polarity_room_done → tray sequence ─────────────────────────────────── //
|
||||||
|
|||||||
@@ -665,6 +665,15 @@
|
|||||||
// 80% stage height × 5/8) via --sig-card-w CSS variable — libsass can't handle
|
// 80% stage height × 5/8) via --sig-card-w CSS variable — libsass can't handle
|
||||||
// container query units inside min().
|
// container query units inside min().
|
||||||
|
|
||||||
|
// The polarity-countdown numeral must read large at EVERY breakpoint. The
|
||||||
|
// .btn-primary down-size media query (`font-size: 0.625rem !important` on small
|
||||||
|
// landscape / short portrait) otherwise out-!importants the countdown's enlarge
|
||||||
|
// and the numeral stayed button-sized on phones / short viewports. Re-assert the
|
||||||
|
// 2em doubling with matching !important at (0,3,0) so it wins at all queries.
|
||||||
|
.sig-stage .sig-take-sig-btn.sig-take-sig-btn--counting {
|
||||||
|
font-size: 2em !important;
|
||||||
|
}
|
||||||
|
|
||||||
.sig-stage {
|
.sig-stage {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
|
|||||||
@@ -888,6 +888,25 @@ describe("SigSelect", () => {
|
|||||||
expect(takeSigBtn.textContent).toBe("8");
|
expect(takeSigBtn.textContent).toBe("8");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("enlarges the numeral via the --counting class (not a fragile inline 2em)", () => {
|
||||||
|
// The .btn-primary media-query font-size !important beats an inline
|
||||||
|
// 2em on small queries; the class lets SCSS re-assert the doubling.
|
||||||
|
makeFixture({ reservations: '{"42":"PC"}', ready: true, countdownRemaining: 8 });
|
||||||
|
takeSigBtn = document.getElementById("id_take_sig_btn");
|
||||||
|
expect(takeSigBtn.classList.contains("sig-take-sig-btn--counting")).toBe(true);
|
||||||
|
expect(takeSigBtn.style.fontSize).toBe(""); // no inline override
|
||||||
|
});
|
||||||
|
|
||||||
|
it("clears the --counting class when the countdown is cancelled", () => {
|
||||||
|
makeFixture({ reservations: '{"42":"PC"}', ready: true, countdownRemaining: 8 });
|
||||||
|
takeSigBtn = document.getElementById("id_take_sig_btn");
|
||||||
|
expect(takeSigBtn.classList.contains("sig-take-sig-btn--counting")).toBe(true);
|
||||||
|
window.dispatchEvent(new CustomEvent("room:countdown_cancel", {
|
||||||
|
detail: { polarity: "levity", seconds_remaining: 5 },
|
||||||
|
}));
|
||||||
|
expect(takeSigBtn.classList.contains("sig-take-sig-btn--counting")).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
it("the restored numeral counts down each second", () => {
|
it("the restored numeral counts down each second", () => {
|
||||||
makeFixture({ reservations: '{"42":"PC"}', ready: true, countdownRemaining: 8 });
|
makeFixture({ reservations: '{"42":"PC"}', ready: true, countdownRemaining: 8 });
|
||||||
takeSigBtn = document.getElementById("id_take_sig_btn");
|
takeSigBtn = document.getElementById("id_take_sig_btn");
|
||||||
@@ -900,6 +919,25 @@ describe("SigSelect", () => {
|
|||||||
takeSigBtn = document.getElementById("id_take_sig_btn");
|
takeSigBtn = document.getElementById("id_take_sig_btn");
|
||||||
expect(takeSigBtn.textContent).toBe("WAIT NVM");
|
expect(takeSigBtn.textContent).toBe("WAIT NVM");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("re-renders the numeral when countdown_start arrives again after a cancel", () => {
|
||||||
|
// Diagnostic for the re-ready bug: cancel → re-ready must restart the
|
||||||
|
// visual countdown. This exercises the CLIENT handler chain only; if
|
||||||
|
// it passes, the live failure is WS delivery (socket / broadcast), not
|
||||||
|
// client logic.
|
||||||
|
makeFixture({ reservations: '{"42":"PC"}', ready: true, countdownRemaining: 9 });
|
||||||
|
takeSigBtn = document.getElementById("id_take_sig_btn");
|
||||||
|
expect(takeSigBtn.textContent).toBe("9"); // counting
|
||||||
|
window.dispatchEvent(new CustomEvent("room:countdown_cancel", {
|
||||||
|
detail: { polarity: "levity", seconds_remaining: 7 },
|
||||||
|
}));
|
||||||
|
expect(takeSigBtn.textContent).toBe("WAIT NVM"); // cancelled
|
||||||
|
window.dispatchEvent(new CustomEvent("room:countdown_start", {
|
||||||
|
detail: { polarity: "levity", seconds: 7 },
|
||||||
|
}));
|
||||||
|
expect(takeSigBtn.textContent).toBe("7"); // restarted
|
||||||
|
expect(takeSigBtn.classList.contains("sig-take-sig-btn--counting")).toBe(true);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// ── polarity_room_done → tray sequence ─────────────────────────────────── //
|
// ── polarity_room_done → tray sequence ─────────────────────────────────── //
|
||||||
|
|||||||
Reference in New Issue
Block a user