Courtside MVP: engine, web app, Docker + Cloudflare Tunnel deploy, docs

Tournament engine (pools, single/double elimination, standings with proper
tiebreaks, withdrawals, two-court queue with ETAs and alerts), a single-process
Node server with SQLite via node:sqlite and a WebSocket live board, organizer
desk, QR landing page that follows the tournament phase, Dockerfile and
compose with cloudflared, and documentation for deploying and running a day.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01MB7nCCAscYsb3zzkT6LHZi
This commit is contained in:
2026-09-03 20:31:46 +00:00
co-authored by Claude Fable 5.1
commit 5ade592384
39 changed files with 3103 additions and 0 deletions
+32
View File
@@ -0,0 +1,32 @@
import { esc } from '../http.js';
export function layout({ title, body, state = null, script = null, bodyClass = '', admin = null, nav = '' }) {
return `<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<meta name="theme-color" content="#0F1418">
<title>${esc(title)} · Courtside</title>
<link rel="icon" href="/static/favicon.svg" type="image/svg+xml">
<link rel="manifest" href="/static/manifest.webmanifest">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Barlow+Condensed:wght@500;600;700&family=Source+Sans+3:wght@400;600&family=JetBrains+Mono:wght@500&display=swap">
<link rel="stylesheet" href="/static/app.css">
</head>
<body class="${esc(bodyClass)}">
<header class="top">
<a class="brand" href="/">Courtside</a>
<nav>${nav}${admin ? `<a href="/admin">Desk</a><a href="/admin/logout">Sign out</a>` : ''}</nav>
</header>
<main>
${body}
</main>
<footer class="foot">Courtside · self-hosted tournament desk · <a href="https://gitea.cloudfreeiot.com/bkvargyas/courtside">source</a></footer>
${state ? `<script id="state" type="application/json">${JSON.stringify(state).replace(/</g, '\\u003c')}</script>` : ''}
${script ? `<script src="${esc(script)}" defer></script>` : ''}
</body>
</html>`;
}
export const flash = (msg, kind = 'ok') => msg ? `<div class="flash ${kind}">${esc(msg)}</div>` : '';