Initial commit: private Texas Hold'em ring game site (lobby + table stub)
- 6-digit access key gate with long-lived HttpOnly cookie, brute-force limits - Lobby with saved player name and live table list - Table creation (name, starting stack, blinds, blind timer) - Table play screen stub (seats, button/SB/BB, phase, blinds, timer) - Security hardening: strict CSP, origin checks, host pinning, input sanitizing Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
commit
b2c91062f1
22 files changed
+3450
No files matched your search
@@ -0,0 +1,72 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
const { Store, esc, chips } = App;
|
||||
const nameInput = document.getElementById('name');
|
||||
const nameError = document.getElementById('name-error');
|
||||
const listEl = document.getElementById('list');
|
||||
const countEl = document.getElementById('count');
|
||||
|
||||
nameInput.value = Store.name;
|
||||
nameInput.addEventListener('input', () => {
|
||||
Store.name = nameInput.value.trim();
|
||||
nameInput.classList.remove('invalid');
|
||||
nameError.textContent = '';
|
||||
});
|
||||
|
||||
function requireName() {
|
||||
if (Store.name) return true;
|
||||
nameInput.classList.add('invalid');
|
||||
nameError.textContent = '先に名前を入力してください。';
|
||||
nameInput.focus();
|
||||
return false;
|
||||
}
|
||||
|
||||
document.getElementById('create').addEventListener('click', () => {
|
||||
if (requireName()) location.href = '/create';
|
||||
});
|
||||
|
||||
listEl.addEventListener('click', (e) => {
|
||||
const btn = e.target.closest('[data-join]');
|
||||
if (btn && requireName()) location.href = '/table/' + encodeURIComponent(btn.dataset.join);
|
||||
});
|
||||
|
||||
function phaseBadge(t) {
|
||||
if (t.paused) return '<span class="badge paused">PAUSED</span>';
|
||||
if (t.phase === 'WAITING') return '<span class="badge">WAITING</span>';
|
||||
return '<span class="badge live">PLAYING</span>';
|
||||
}
|
||||
|
||||
function render(tables) {
|
||||
countEl.textContent = tables.length ? `${tables.length} テーブル` : '';
|
||||
if (!tables.length) {
|
||||
listEl.innerHTML = '<div class="empty">開催中のテーブルはありません。<br>テーブルを作成して友達を招待しよう。</div>';
|
||||
return;
|
||||
}
|
||||
listEl.innerHTML = tables
|
||||
.map((t) => {
|
||||
const full = t.playerCount >= t.maxSeats;
|
||||
const timer = t.timerEnabled ? `⏱ ${t.intervalMin}分` : '⏱ なし';
|
||||
return `
|
||||
<div class="table-item">
|
||||
<div class="info">
|
||||
<div class="name">${esc(t.name)}${phaseBadge(t)}</div>
|
||||
<div class="meta">
|
||||
<span>👤 <b>${t.playerCount}/${t.maxSeats}</b></span>
|
||||
<span>開始 <b>${chips(t.startingStack)}</b></span>
|
||||
<span class="gold">Lv.${t.level} <b class="gold">${chips(t.smallBlind)}/${chips(t.bigBlind)}</b></span>
|
||||
<span>${timer}</span>
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn btn-blue btn-sm" data-join="${esc(t.id)}" ${full ? 'title="満席(観戦のみ)"' : ''}>
|
||||
${full ? '観戦' : '参加'}
|
||||
</button>
|
||||
</div>`;
|
||||
})
|
||||
.join('');
|
||||
}
|
||||
|
||||
const socket = App.connectSocket();
|
||||
socket.on('connect', () => socket.emit('lobby:subscribe'));
|
||||
socket.on('lobby:tables', render);
|
||||
})();
|
||||
Reference in new issue
Block a user