Files
2026-09-10 09:09:38 +02:00

146 lines
8.8 KiB
JavaScript

document.addEventListener('DOMContentLoaded', async () => {
const byId = id => document.getElementById(id);
const client = new RealtimeClient();
let username; let lobbyId;
const easterTitle = byId('easter-title');
let easterStage = 0;
let easterSequenceTimer = null;
let easterOpen = false;
let easterPending = false;
const secretCue = document.createElement('div');
secretCue.id = 'secret-cue';
secretCue.className = 'secret-cue';
secretCue.setAttribute('aria-hidden', 'true');
document.body.appendChild(secretCue);
const secretFlash = document.createElement('div');
secretFlash.id = 'secret-flash';
secretFlash.className = 'secret-flash';
secretFlash.setAttribute('aria-hidden', 'true');
document.body.appendChild(secretFlash);
const error = message => {byId('join-error').textContent = message; byId('join-error').classList.toggle('hidden', !message);};
const matrixColor = () => `hsl(${Math.floor(Math.random() * 360)} 100% 62%)`;
const unlockEasterTitle = () => {
easterOpen = true;
easterTitle.contentEditable = 'true';
easterTitle.classList.add('easter-open');
easterTitle.focus();
document.getSelection()?.selectAllChildren(easterTitle);
};
const cueSecret = (letter, strength = 'soft') => {
document.body.classList.remove('secret-shake', 'secret-shake-strong');
void document.body.offsetWidth;
document.body.classList.add(strength === 'soft' ? 'secret-shake' : 'secret-shake-strong');
if (!letter) return;
secretCue.textContent = letter;
secretCue.className = 'secret-cue';
void secretCue.offsetWidth;
secretCue.className = `secret-cue secret-cue-${strength}`;
};
const flashSecret = () => {
cueSecret('K', 'strong');
document.body.classList.remove('secret-shake-strong');
void document.body.offsetWidth;
document.body.classList.add('secret-shake-strong');
secretFlash.classList.remove('secret-flash-active');
void secretFlash.offsetWidth;
secretFlash.classList.add('secret-flash-active');
};
const armEasterSequence = (resetStage = true) => {
if (resetStage) easterStage = 0;
clearTimeout(easterSequenceTimer);
easterSequenceTimer = setTimeout(() => {easterStage = 0;}, 10000);
};
const redeemEasterTitle = async () => {
if (easterPending) return;
const code = easterTitle.textContent.trim().toUpperCase();
easterTitle.textContent = 'READY TO DEAL';
easterTitle.contentEditable = 'false';
easterTitle.classList.remove('easter-open');
easterOpen = false;
if (!['ILIKEMONEY', 'FRETUX', 'KRISHD', 'LOSERNOOB'].includes(code)) return;
easterPending = true;
try {
const result = await apiRequest('/easter-egg', {method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({code})});
if (result.effect === 'matrix') {
document.documentElement.style.setProperty('--matrix-rain', matrixColor());
error('Matrix stream randomized.');
} else {
byId('balance').textContent = result.balance;
error('Terminal command accepted.');
}
} catch (problem) {error(problem.message);}
finally {easterPending = false;}
};
document.addEventListener('keydown', event => {
if (easterOpen || event.repeat) return;
if (event.altKey && !event.ctrlKey && !event.metaKey && !event.shiftKey && event.code === 'KeyP') {event.preventDefault(); armEasterSequence(); cueSecret('', 'soft'); return;}
if (easterStage === 0 && event.code === 'KeyJ') {easterStage = 1; armEasterSequence(false); cueSecret('J', 'medium'); return;}
if (easterStage === 1 && event.code === 'KeyF') {easterStage = 2; armEasterSequence(false); cueSecret('F', 'strong'); return;}
if (easterStage === 2 && event.code === 'KeyK') {event.preventDefault(); clearTimeout(easterSequenceTimer); flashSecret(); unlockEasterTitle(); easterStage = 0; return;}
if (easterStage) armEasterSequence();
});
easterTitle.addEventListener('keydown', event => { if (event.key === 'Enter') {event.preventDefault(); redeemEasterTitle();} });
const send = async message => {try {error(''); await client.send(message);} catch (problem) {error(problem.message);}};
const hideLobby = () => {
lobbyId = null; localStorage.removeItem('activeLobbyId'); byId('lobby-room').classList.add('hidden');
byId('host').disabled = false; byId('join').disabled = false;
};
async function refreshBalance() {
try {
const [balance, ranks] = await Promise.all([apiRequest('/balance'), apiRequest('/leaderboard')]);
byId('balance').textContent = balance.balance;
byId('leaderboard-list').replaceChildren(...ranks.leaderboard.map((player, index) => {
const item = document.createElement('li'); item.textContent = `${index + 1}. ${player.username}: ${player.money}`;
item.classList.toggle('you', player.username === username); return item;
}));
byId('your-rank').textContent = `Your rank: #${ranks.self.position}`;
} catch (problem) {error(problem.message);}
}
client.addEventListener('status', event => {error(event.detail); byId('start-game').disabled = !client.ready || byId('start-game').dataset.allowed !== 'true';});
client.addEventListener('message', event => {
const message = event.detail;
if (message.type === 'auth:success') username = message.username;
if (message.type === 'lobby:left') {hideLobby(); refreshBalance();}
if (message.type !== 'lobby:state') return;
lobbyId = message.lobbyId;
if (message.gameStarted) {
localStorage.setItem('activeLobbyId', lobbyId);
location.assign(`/poker.html?lobbyId=${encodeURIComponent(lobbyId)}`); return;
}
byId('lobby-id').textContent = lobbyId; byId('lobby-room').classList.remove('hidden');
byId('host-form').classList.add('hidden'); byId('join-form').classList.add('hidden');
byId('host').disabled = true; byId('join').disabled = true;
byId('player-list').replaceChildren(...message.players.map(player => {const item = document.createElement('p'); item.textContent = player; return item;}));
const allowed = message.host === username && message.canStart;
byId('start-game').dataset.allowed = String(allowed);
byId('start-game').disabled = !allowed; byId('start-game').classList.toggle('hidden', message.host !== username);
});
byId('host').addEventListener('click', () => {byId('host-form').classList.toggle('hidden'); byId('join-form').classList.add('hidden');});
byId('create-lobby').addEventListener('click', () => send({type: 'lobby:create', private: document.querySelector('[name="lobby-visibility"]:checked').value === 'private'}));
async function loadLobbies() {
try {
const {lobbies} = await apiRequest('/lobby/public');
byId('public-lobbies').replaceChildren();
if (!lobbies.length) {byId('public-lobbies').textContent = 'No public lobbies found'; return;}
for (const lobby of lobbies) {
const row = document.createElement('div'); row.className = 'public-lobby';
const info = document.createElement('span'); info.textContent = `${lobby.host}: ${lobby.players}/10 players`;
const button = document.createElement('button'); button.textContent = 'Join'; button.type = 'button';
button.addEventListener('click', () => send({type: 'lobby:join', lobbyId: lobby.lobbyId}));
row.append(info, button); byId('public-lobbies').append(row);
}
} catch (problem) {error(problem.message);}
}
byId('join').addEventListener('click', () => {byId('join-form').classList.toggle('hidden'); byId('host-form').classList.add('hidden'); byId('join-code').focus(); loadLobbies();});
byId('refresh-lobbies').addEventListener('click', loadLobbies);
byId('submit-join').addEventListener('click', () => send({type: 'lobby:join', lobbyId: byId('join-code').value.trim().toLowerCase()}));
byId('leave-button').addEventListener('click', () => send({type: 'lobby:leave', lobbyId}));
byId('start-game').addEventListener('click', () => send({type: 'poker:start', lobbyId}));
byId('logout').addEventListener('click', async () => {
try {await apiRequest('/logout', {method: 'DELETE'}); client.close(); localStorage.removeItem('activeLobbyId'); location.assign('/login');}
catch (problem) {error(`Logout failed: ${problem.message}`);}
});
try {username = (await apiRequest('/verify', {method: 'POST'})).user.username; await client.connect(); await refreshBalance();}
catch (problem) {error(problem.message);}
});