Compare commits
23 Commits
replit/v1
...
b9d3b6115a
| Author | SHA1 | Date | |
|---|---|---|---|
| b9d3b6115a | |||
| a9ccb42008 | |||
| 20f0122f59 | |||
| 548a7e29f0 | |||
| a01b584d10 | |||
| 191d66e6d4 | |||
| 462979e6f7 | |||
| e318a2c058 | |||
| c9795f816c | |||
| 01cd50abf7 | |||
| 9c9a95206d | |||
| ed46de26b3 | |||
| b69c442c90 | |||
| cddbdfaae8 | |||
| c00db744c5 | |||
| b76bcbffb2 | |||
| 67bd8d9e77 | |||
| 7696b6005b | |||
| c08c0831b1 | |||
| 6afb853874 | |||
| c0e7c08106 | |||
| 83bd893d93 | |||
| ce13860cd1 |
34
.gitignore
vendored
34
.gitignore
vendored
@@ -1,34 +1,6 @@
|
|||||||
# dependencies (bun install)
|
|
||||||
node_modules
|
node_modules
|
||||||
|
|
||||||
# output
|
|
||||||
out
|
|
||||||
dist
|
dist
|
||||||
*.tgz
|
|
||||||
|
|
||||||
# code coverage
|
|
||||||
coverage
|
|
||||||
*.lcov
|
|
||||||
|
|
||||||
# logs
|
|
||||||
logs
|
|
||||||
_.log
|
|
||||||
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
|
|
||||||
|
|
||||||
# dotenv environment variable files
|
|
||||||
.env
|
|
||||||
.env.development.local
|
|
||||||
.env.test.local
|
|
||||||
.env.production.local
|
|
||||||
.env.local
|
|
||||||
|
|
||||||
# caches
|
|
||||||
.eslintcache
|
|
||||||
.cache
|
|
||||||
*.tsbuildinfo
|
|
||||||
|
|
||||||
# IntelliJ based IDEs
|
|
||||||
.idea
|
|
||||||
|
|
||||||
# Finder (MacOS) folder config
|
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
server/public
|
||||||
|
vite.config.ts.*
|
||||||
|
*.tar.gz
|
||||||
6
bun.lock
6
bun.lock
@@ -1,15 +1,15 @@
|
|||||||
{
|
{
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"configVersion": 1,
|
"configVersion": 0,
|
||||||
"workspaces": {
|
"workspaces": {
|
||||||
"": {
|
"": {
|
||||||
"name": "buzzer",
|
"name": "rest-express",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"hono": "^4.7.7",
|
"hono": "^4.7.7",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"packages": {
|
"packages": {
|
||||||
"hono": ["hono@4.12.8", "", {}, "sha512-VJCEvtrezO1IAR+kqEYnxUOoStaQPGrCmX3j4wDTNOcD1uRPFpGlwQUIW8niPuvHXaTUxeOUl5MMDGrl+tmO9A=="],
|
"hono": ["hono@4.12.9", "", {}, "sha512-wy3T8Zm2bsEvxKZM5w21VdHDDcwVS1yUFFY6i8UobSsKfFceT7TOwhbhfKsDyx7tYQlmRM5FLpIuYvNFyjctiA=="],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,8 +7,7 @@
|
|||||||
<title>Buzzer Platform</title>
|
<title>Buzzer Platform</title>
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||||
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@300;400;500;600;700;800&display=swap"
|
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@300;400;500;600;700;800&display=swap" rel="stylesheet" />
|
||||||
rel="stylesheet" />
|
|
||||||
<link rel="stylesheet" href="/styles.css" />
|
<link rel="stylesheet" href="/styles.css" />
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
@@ -17,9 +16,9 @@
|
|||||||
|
|
||||||
<!-- HEADER -->
|
<!-- HEADER -->
|
||||||
<header>
|
<header>
|
||||||
<div>
|
<div class="logo-wrap">
|
||||||
<div class="logo">BUZZER</div>
|
<div class="logo">BUZZER</div>
|
||||||
<div class="logo-sub">// QUIZ CONTROL</div>
|
<div class="logo-sub">// QUIZ CONTROL PLATFORM</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="hdr-r">
|
<div class="hdr-r">
|
||||||
<div class="room-chip" id="hdr-room"></div>
|
<div class="room-chip" id="hdr-room"></div>
|
||||||
@@ -33,27 +32,30 @@
|
|||||||
<!-- ══════════ LANDING ══════════ -->
|
<!-- ══════════ LANDING ══════════ -->
|
||||||
<div class="scr" id="s-land">
|
<div class="scr" id="s-land">
|
||||||
<div class="hero">
|
<div class="hero">
|
||||||
|
<div class="hero-decoration"></div>
|
||||||
<h1 class="glow">BUZZ</h1>
|
<h1 class="glow">BUZZ</h1>
|
||||||
<p>REAL-TIME QUIZ BUZZER SYSTEM</p>
|
<p>REAL-TIME QUIZ BUZZER SYSTEM</p>
|
||||||
|
<div class="hero-badge">
|
||||||
|
<div class="hero-badge-dot"></div>
|
||||||
|
WEBSOCKET POWERED
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="land-cards">
|
<div class="land-cards">
|
||||||
<div class="land-card">
|
<div class="land-card">
|
||||||
|
<div class="land-card-icon">⬛</div>
|
||||||
<h2>// HOST A SESSION</h2>
|
<h2>// HOST A SESSION</h2>
|
||||||
<p>Create a room, configure teams and rules, then control the buzzer live.</p>
|
<p>Create a room, configure teams and rules, then control the buzzer live.</p>
|
||||||
<button class="btn btn-g" onclick="goSetup()">CREATE ROOM →</button>
|
<button class="btn btn-g" onclick="goSetup()">CREATE ROOM →</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="land-card">
|
<div class="land-card">
|
||||||
|
<div class="land-card-icon">⬜</div>
|
||||||
<h2>// JOIN A SESSION</h2>
|
<h2>// JOIN A SESSION</h2>
|
||||||
<p>Enter a room code and your name to join an existing session.</p>
|
<p>Enter a room code to join an existing session. You'll be assigned a numeric ID.</p>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label>ROOM CODE</label>
|
<label>ROOM CODE</label>
|
||||||
<input id="ji-code" maxlength="8" placeholder="XXXXXX" style="letter-spacing:4px;text-transform:uppercase"
|
<input id="ji-code" maxlength="8" placeholder="XXXXXX" style="letter-spacing:5px;text-transform:uppercase;font-size:20px;"
|
||||||
oninput="this.value=this.value.toUpperCase()" />
|
oninput="this.value=this.value.toUpperCase()" />
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
|
||||||
<label>YOUR NAME</label>
|
|
||||||
<input id="ji-name" maxlength="24" placeholder="Enter name…" />
|
|
||||||
</div>
|
|
||||||
<button class="btn btn-g btn-full" onclick="joinRoom()">JOIN ROOM →</button>
|
<button class="btn btn-g btn-full" onclick="joinRoom()">JOIN ROOM →</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -65,7 +67,7 @@
|
|||||||
<!-- ══════════ SETUP ══════════ -->
|
<!-- ══════════ SETUP ══════════ -->
|
||||||
<div class="scr" id="s-setup">
|
<div class="scr" id="s-setup">
|
||||||
<div class="setup-wrap">
|
<div class="setup-wrap">
|
||||||
<div>
|
<div class="setup-header">
|
||||||
<div class="setup-title">// NEW ROOM SETUP</div>
|
<div class="setup-title">// NEW ROOM SETUP</div>
|
||||||
<div class="setup-sub">Configure before creating — all settings are adjustable in-game too.</div>
|
<div class="setup-sub">Configure before creating — all settings are adjustable in-game too.</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -81,28 +83,26 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="team-opts">
|
<div id="team-opts">
|
||||||
<div class="field" style="margin-top:16px;">
|
<div class="field" style="margin-top:20px;">
|
||||||
<label>NUMBER OF TEAMS</label>
|
<label>NUMBER OF TEAMS</label>
|
||||||
<div style="display:flex;align-items:center;gap:12px;">
|
<div style="display:flex;align-items:center;gap:14px;">
|
||||||
<input type="number" id="st-numteams" value="2" min="2" max="64" style="width:90px;"
|
<input type="number" id="st-numteams" value="2" min="2" max="64" style="width:100px;"
|
||||||
oninput="renderSetupTeamNames()" />
|
oninput="renderSetupTeamNames()" />
|
||||||
<span style="font-size:12px;color:var(--dim);">2 – 64</span>
|
<span style="font-size:12px;color:var(--dim);letter-spacing:1px;">2 – 64</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="field" style="margin-top:12px;">
|
<div class="field" style="margin-top:14px;">
|
||||||
<label>TEAM NAMES <span style="color:var(--dim);font-size:9px;">(AUTO-FILLED WITH GREEK
|
<label>TEAM NAMES <span style="color:var(--dim);font-size:9px;letter-spacing:1px;">(AUTO-FILLED WITH GREEK ALPHABET)</span></label>
|
||||||
ALPHABET)</span></label>
|
|
||||||
<div id="setup-team-names"
|
<div id="setup-team-names"
|
||||||
style="display:flex;flex-direction:column;gap:8px;max-height:240px;overflow-y:auto;padding-right:4px;">
|
style="display:flex;flex-direction:column;gap:9px;max-height:240px;overflow-y:auto;padding-right:4px;">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="tog-row" style="margin-top:12px;">
|
<div class="tog-row" style="margin-top:14px;">
|
||||||
<div>
|
<div>
|
||||||
<div class="lbl">PLAYERS PICK OWN TEAM</div>
|
<div class="lbl">PLAYERS PICK OWN TEAM</div>
|
||||||
<div class="lbl-sub">If off, moderator assigns teams manually</div>
|
<div class="lbl-sub">If off, moderator assigns teams manually</div>
|
||||||
</div>
|
</div>
|
||||||
<label class="tog"><input type="checkbox" id="st-playerpick" checked /><span
|
<label class="tog"><input type="checkbox" id="st-playerpick" checked /><span class="tog-track"></span></label>
|
||||||
class="tog-track"></span></label>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -121,8 +121,7 @@
|
|||||||
<div class="lbl">SHOW FULL BUZZ ORDER TO PLAYERS</div>
|
<div class="lbl">SHOW FULL BUZZ ORDER TO PLAYERS</div>
|
||||||
<div class="lbl-sub">If off, players only see if they were first</div>
|
<div class="lbl-sub">If off, players only see if they were first</div>
|
||||||
</div>
|
</div>
|
||||||
<label class="tog"><input type="checkbox" id="st-showorder" checked /><span
|
<label class="tog"><input type="checkbox" id="st-showorder" checked /><span class="tog-track"></span></label>
|
||||||
class="tog-track"></span></label>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -133,18 +132,17 @@
|
|||||||
<div class="lbl">ENABLE COUNTDOWN TIMER</div>
|
<div class="lbl">ENABLE COUNTDOWN TIMER</div>
|
||||||
<div class="lbl-sub">Auto-closes round when it hits zero</div>
|
<div class="lbl-sub">Auto-closes round when it hits zero</div>
|
||||||
</div>
|
</div>
|
||||||
<label class="tog"><input type="checkbox" id="st-usetimer" onchange="toggleTimerField()" /><span
|
<label class="tog"><input type="checkbox" id="st-usetimer" onchange="toggleTimerField()" /><span class="tog-track"></span></label>
|
||||||
class="tog-track"></span></label>
|
|
||||||
</div>
|
</div>
|
||||||
<div id="timer-field" style="display:none;margin-top:14px;">
|
<div id="timer-field" style="display:none;margin-top:16px;">
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label>SECONDS PER ROUND</label>
|
<label>SECONDS PER ROUND</label>
|
||||||
<input type="number" id="st-timersec" value="30" min="5" max="600" style="max-width:160px;" />
|
<input type="number" id="st-timersec" value="30" min="5" max="600" style="max-width:180px;" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="display:flex;gap:10px;justify-content:flex-end;padding-bottom:32px;">
|
<div style="display:flex;gap:12px;justify-content:flex-end;padding-bottom:40px;">
|
||||||
<button class="btn btn-ghost" onclick="showScr('s-land')">← BACK</button>
|
<button class="btn btn-ghost" onclick="showScr('s-land')">← BACK</button>
|
||||||
<button class="btn btn-g" onclick="createRoom()">CREATE ROOM →</button>
|
<button class="btn btn-g" onclick="createRoom()">CREATE ROOM →</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -155,11 +153,11 @@
|
|||||||
<div class="scr" id="s-mod">
|
<div class="scr" id="s-mod">
|
||||||
<div class="mod-side">
|
<div class="mod-side">
|
||||||
<div class="side-sec">
|
<div class="side-sec">
|
||||||
<div class="side-label">ROOM</div>
|
<div class="side-label">ROOM CODE</div>
|
||||||
<div class="side-room-code glow" id="mod-code">──────</div>
|
<div class="side-room-code glow" id="mod-code">──────</div>
|
||||||
<div class="side-hint">SHARE WITH PLAYERS</div>
|
<div class="side-hint" style="margin-top:6px;">SHARE WITH PLAYERS</div>
|
||||||
<div style="display:flex;gap:6px;flex-wrap:wrap;margin-top:10px;">
|
<div style="display:flex;gap:7px;flex-wrap:wrap;margin-top:14px;">
|
||||||
<button class="btn btn-ghost btn-sm" onclick="copyCode()">COPY</button>
|
<button class="btn btn-ghost btn-sm" onclick="copyCode()">COPY CODE</button>
|
||||||
<button class="btn btn-red btn-sm" onclick="openModal('m-end')">END ROOM</button>
|
<button class="btn btn-red btn-sm" onclick="openModal('m-end')">END ROOM</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -171,7 +169,7 @@
|
|||||||
<div class="timer-digits" id="mod-timer-disp">0:30</div>
|
<div class="timer-digits" id="mod-timer-disp">0:30</div>
|
||||||
<div class="timer-controls">
|
<div class="timer-controls">
|
||||||
<button class="btn btn-g btn-sm" id="btn-timer-ss" onclick="modTimerToggle()">START</button>
|
<button class="btn btn-g btn-sm" id="btn-timer-ss" onclick="modTimerToggle()">START</button>
|
||||||
<button class="btn btn-ghost btn-sm" onclick="modTimerReset()">↺</button>
|
<button class="btn btn-ghost btn-sm" onclick="modTimerReset()">↺ RESET</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="timer-set-row">
|
<div class="timer-set-row">
|
||||||
<span class="side-hint">SET</span>
|
<span class="side-hint">SET</span>
|
||||||
@@ -184,17 +182,16 @@
|
|||||||
|
|
||||||
<!-- BUZZER CONTROLS -->
|
<!-- BUZZER CONTROLS -->
|
||||||
<div class="side-sec">
|
<div class="side-sec">
|
||||||
<div class="side-label">BUZZER</div>
|
<div class="side-label">ROUND CONTROL</div>
|
||||||
<div class="side-btn-group">
|
<div class="side-btn-group">
|
||||||
<button class="btn btn-g btn-full" onclick="ws_send({type:'open_round'})">▶ OPEN ROUND</button>
|
<button class="btn btn-g btn-full" id="mod-round-btn" onclick="toggleRound()">▶ OPEN ROUND</button>
|
||||||
<button class="btn btn-ghost btn-full" onclick="ws_send({type:'close_round'})">■ CLOSE ROUND</button>
|
<button class="btn btn-ghost btn-full" id="mod-resume-btn" style="display:none;" onclick="resumeRound()">▶ RESUME ROUND</button>
|
||||||
<button class="btn btn-yellow btn-full" onclick="ws_send({type:'reset_buzzer'})">↺ RESET BUZZER</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- ROOM CONTROLS -->
|
<!-- ROOM CONTROLS -->
|
||||||
<div class="side-sec">
|
<div class="side-sec">
|
||||||
<div class="side-label">CONTROLS</div>
|
<div class="side-label">ROOM CONTROLS</div>
|
||||||
<div class="tog-row">
|
<div class="tog-row">
|
||||||
<div class="lbl">LOCK ROOM</div>
|
<div class="lbl">LOCK ROOM</div>
|
||||||
<label class="tog"><input type="checkbox" id="lock-room-tog"
|
<label class="tog"><input type="checkbox" id="lock-room-tog"
|
||||||
@@ -205,9 +202,8 @@
|
|||||||
<label class="tog"><input type="checkbox" id="lock-teams-tog"
|
<label class="tog"><input type="checkbox" id="lock-teams-tog"
|
||||||
onchange="ws_send({type:'lock_teams',locked:this.checked})" /><span class="tog-track"></span></label>
|
onchange="ws_send({type:'lock_teams',locked:this.checked})" /><span class="tog-track"></span></label>
|
||||||
</div>
|
</div>
|
||||||
<div style="margin-top:10px;display:flex;flex-direction:column;gap:5px;">
|
<div style="margin-top:12px;display:flex;flex-direction:column;gap:6px;">
|
||||||
<button class="btn btn-ghost btn-sm btn-full" onclick="ws_send({type:'reset_teams'})">CLEAR ALL
|
<button class="btn btn-ghost btn-sm btn-full" onclick="ws_send({type:'reset_teams'})">CLEAR ALL TEAMS</button>
|
||||||
TEAMS</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -238,7 +234,7 @@
|
|||||||
<div id="tab-players" style="display:none">
|
<div id="tab-players" style="display:none">
|
||||||
<div class="panel">
|
<div class="panel">
|
||||||
<div class="panel-title">
|
<div class="panel-title">
|
||||||
<span>CONNECTED PLAYERS</span>
|
CONNECTED PLAYERS
|
||||||
<span class="tag tag-g" id="pcount-badge">0</span>
|
<span class="tag tag-g" id="pcount-badge">0</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="player-list" id="mod-plist">
|
<div class="player-list" id="mod-plist">
|
||||||
@@ -297,16 +293,16 @@
|
|||||||
<div class="sl">NUMBER OF TEAMS</div>
|
<div class="sl">NUMBER OF TEAMS</div>
|
||||||
</div>
|
</div>
|
||||||
<div style="display:flex;align-items:center;gap:8px;">
|
<div style="display:flex;align-items:center;gap:8px;">
|
||||||
<input type="number" id="ls-numteams" min="2" max="64" style="width:80px;"
|
<input type="number" id="ls-numteams" min="2" max="64" style="width:84px;"
|
||||||
onchange="pushSetting('numTeams',+this.value)" />
|
onchange="pushSetting('numTeams',+this.value)" />
|
||||||
<button class="btn btn-ghost btn-sm"
|
<button class="btn btn-ghost btn-sm"
|
||||||
onclick="pushSetting('numTeams',+document.getElementById('ls-numteams').value)">APPLY</button>
|
onclick="pushSetting('numTeams',+document.getElementById('ls-numteams').value)">APPLY</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div style="margin-top:18px;">
|
<div style="margin-top:20px;">
|
||||||
<div class="lbl" style="margin-bottom:10px;">TEAM NAMES</div>
|
<div class="lbl" style="margin-bottom:12px;">TEAM NAMES</div>
|
||||||
<div id="ls-team-names"
|
<div id="ls-team-names"
|
||||||
style="display:flex;flex-direction:column;gap:8px;max-height:260px;overflow-y:auto;padding-right:4px;">
|
style="display:flex;flex-direction:column;gap:9px;max-height:280px;overflow-y:auto;padding-right:4px;">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -351,8 +347,8 @@
|
|||||||
<div class="modal-bg" id="m-rejoin">
|
<div class="modal-bg" id="m-rejoin">
|
||||||
<div class="modal">
|
<div class="modal">
|
||||||
<h2>// REJOIN SESSION</h2>
|
<h2>// REJOIN SESSION</h2>
|
||||||
<p style="font-size:13px;color:var(--dim);">You have a saved mod session. Rejoin it?</p>
|
<p style="font-size:13px;color:var(--dim);line-height:1.7;">You have a saved mod session. Rejoin it?</p>
|
||||||
<div style="font-size:24px;letter-spacing:6px;color:var(--g);font-weight:700;" id="m-rejoin-code"></div>
|
<div style="font-size:28px;letter-spacing:8px;color:var(--g);font-weight:700;text-shadow:0 0 12px var(--g);" id="m-rejoin-code"></div>
|
||||||
<div class="modal-btns">
|
<div class="modal-btns">
|
||||||
<button class="btn btn-ghost btn-sm" onclick="clearMod();closeModal('m-rejoin')">DISCARD</button>
|
<button class="btn btn-ghost btn-sm" onclick="clearMod();closeModal('m-rejoin')">DISCARD</button>
|
||||||
<button class="btn btn-g btn-sm" onclick="doRejoin()">REJOIN →</button>
|
<button class="btn btn-g btn-sm" onclick="doRejoin()">REJOIN →</button>
|
||||||
@@ -363,7 +359,7 @@
|
|||||||
<div class="modal-bg" id="m-end">
|
<div class="modal-bg" id="m-end">
|
||||||
<div class="modal">
|
<div class="modal">
|
||||||
<h2>// END ROOM</h2>
|
<h2>// END ROOM</h2>
|
||||||
<p style="font-size:13px;color:var(--dim);">All players will be disconnected and the room deleted.</p>
|
<p style="font-size:13px;color:var(--dim);line-height:1.7;">All players will be disconnected and the room deleted.</p>
|
||||||
<div class="modal-btns">
|
<div class="modal-btns">
|
||||||
<button class="btn btn-ghost btn-sm" onclick="closeModal('m-end')">CANCEL</button>
|
<button class="btn btn-ghost btn-sm" onclick="closeModal('m-end')">CANCEL</button>
|
||||||
<button class="btn btn-red btn-sm" onclick="ws_send({type:'end_room'});closeModal('m-end')">END ROOM</button>
|
<button class="btn btn-red btn-sm" onclick="ws_send({type:'end_room'});closeModal('m-end')">END ROOM</button>
|
||||||
@@ -372,7 +368,10 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="toasts"></div>
|
<div id="toasts"></div>
|
||||||
|
|
||||||
|
<!-- GSAP -->
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.5/gsap.min.js"></script>
|
||||||
<script src="/script.js"></script>
|
<script src="/script.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
1011
src/public/script.js
1011
src/public/script.js
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,21 +1,30 @@
|
|||||||
import { readFileSync } from "fs";
|
import { readFileSync } from "fs";
|
||||||
import { handleMessage, handleClose } from "./ws-handler";
|
import { handleMessage, handleClose } from "./ws-handler";
|
||||||
|
|
||||||
const HTML = readFileSync("./src/public/index.html", "utf-8");
|
const PORT = parseInt(process.env.PORT || "3009", 10);
|
||||||
const CSS = readFileSync("./src/public/styles.css", "utf-8");
|
|
||||||
const JS = readFileSync("./src/public/script.js", "utf-8");
|
|
||||||
|
|
||||||
const server = Bun.serve({
|
const server = Bun.serve({
|
||||||
port: 3009,
|
port: PORT,
|
||||||
|
hostname: "0.0.0.0",
|
||||||
fetch(req, server) {
|
fetch(req, server) {
|
||||||
const url = new URL(req.url);
|
const url = new URL(req.url);
|
||||||
if (url.pathname === "/ws") {
|
if (url.pathname === "/ws") {
|
||||||
if (!server.upgrade(req)) return new Response("WS upgrade failed", { status: 400 });
|
if (!server.upgrade(req)) return new Response("WS upgrade failed", { status: 400 });
|
||||||
return undefined as any;
|
return undefined as any;
|
||||||
}
|
}
|
||||||
if (url.pathname === "/styles.css") return new Response(CSS, { headers: { "Content-Type": "text/css" } });
|
if (url.pathname === "/styles.css") {
|
||||||
if (url.pathname === "/script.js") return new Response(JS, { headers: { "Content-Type": "text/javascript" } });
|
return new Response(readFileSync("./src/public/styles.css", "utf-8"), {
|
||||||
return new Response(HTML, { headers: { "Content-Type": "text/html; charset=utf-8" } });
|
headers: { "Content-Type": "text/css" },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (url.pathname === "/script.js") {
|
||||||
|
return new Response(readFileSync("./src/public/script.js", "utf-8"), {
|
||||||
|
headers: { "Content-Type": "text/javascript" },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return new Response(readFileSync("./src/public/index.html", "utf-8"), {
|
||||||
|
headers: { "Content-Type": "text/html; charset=utf-8" },
|
||||||
|
});
|
||||||
},
|
},
|
||||||
websocket: {
|
websocket: {
|
||||||
perMessageDeflate: true,
|
perMessageDeflate: true,
|
||||||
@@ -26,4 +35,4 @@ const server = Bun.serve({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(`\x1b[32m[BUZZER]\x1b[0m → \x1b[36mhttp://localhost:${server.port}\x1b[0m`);
|
console.log(`\x1b[32m[BUZZER]\x1b[0m → \x1b[36mhttp://localhost:${server.port}\x1b[0m`);
|
||||||
|
|||||||
@@ -65,16 +65,19 @@ export function handleMessage(ws: WS, raw: string) {
|
|||||||
if (!room) { er(ws, "Room not found"); return; }
|
if (!room) { er(ws, "Room not found"); return; }
|
||||||
if (room.locked) { er(ws, "Room is locked"); return; }
|
if (room.locked) { er(ws, "Room is locked"); return; }
|
||||||
|
|
||||||
const name = sanitize(msg.playerName ?? "Player", 24) || "Player";
|
// Calculate numeric ID based on join order
|
||||||
const existingId = sanitize(msg.playerId ?? "", 12);
|
const joinCount = room.players.size + 1;
|
||||||
|
const playerId = joinCount.toString();
|
||||||
|
const name = sanitize(msg.playerName ?? "", 24) || ""; // Keep name for compatibility but don't use it
|
||||||
|
|
||||||
let player: Player | undefined;
|
let player: Player | undefined;
|
||||||
|
|
||||||
if (existingId && room.players.has(existingId)) {
|
if (playerId && room.players.has(playerId)) {
|
||||||
player = room.players.get(existingId)!;
|
player = room.players.get(playerId)!;
|
||||||
player.ws = ws; player.isConnected = true; player.name = name;
|
player.ws = ws; player.isConnected = true; player.name = name;
|
||||||
} else {
|
} else {
|
||||||
player = { id: genId(), name, teamIndex: null, ws, isConnected: true };
|
player = { id: playerId, name, teamIndex: null, ws, isConnected: true };
|
||||||
room.players.set(player.id, player);
|
room.players.set(playerId, player);
|
||||||
}
|
}
|
||||||
|
|
||||||
wsToPlayer.set(ws, { roomId: room.id, playerId: player.id });
|
wsToPlayer.set(ws, { roomId: room.id, playerId: player.id });
|
||||||
@@ -132,6 +135,15 @@ export function handleMessage(ws: WS, raw: string) {
|
|||||||
room.buzzerState = freshBuzzer();
|
room.buzzerState = freshBuzzer();
|
||||||
room.buzzerState.roundOpen = true;
|
room.buzzerState.roundOpen = true;
|
||||||
broadcast(room, { type: "round_open", room: publicRoom(room) });
|
broadcast(room, { type: "round_open", room: publicRoom(room) });
|
||||||
|
// Send new_round notification to all players (with toast)
|
||||||
|
const d = JSON.stringify({ type: "new_round" });
|
||||||
|
for (const p of room.players.values()) if (p.ws && p.isConnected) try { p.ws.send(d); } catch {}
|
||||||
|
if (room.modWs) try { room.modWs.send(d); } catch {}
|
||||||
|
break;
|
||||||
|
case "resume_round":
|
||||||
|
// Open round WITHOUT clearing existing buzzes (for accidental close recovery)
|
||||||
|
room.buzzerState.roundOpen = true;
|
||||||
|
broadcast(room, { type: "round_open", room: publicRoom(room) });
|
||||||
break;
|
break;
|
||||||
case "close_round":
|
case "close_round":
|
||||||
room.buzzerState.roundOpen = false;
|
room.buzzerState.roundOpen = false;
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"target": "ESNext",
|
|
||||||
"module": "ESNext",
|
|
||||||
"moduleResolution": "bundler",
|
|
||||||
"strict": true,
|
|
||||||
"types": ["bun-types"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user