Compare commits
16 Commits
548a7e29f0
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1a1c494ade | ||
| d9b2c5ee22 | |||
| 57c5f4e054 | |||
| 3c0ca7843f | |||
| a135746e37 | |||
| 56ecfaad54 | |||
| 1ad1d96cb2 | |||
| 1a2dc295e1 | |||
| 9d34d648d8 | |||
| 7e52e1768b | |||
| 3dc9bdcc21 | |||
| 22c4d28b42 | |||
| b5031c4f71 | |||
| b9d3b6115a | |||
| a9ccb42008 | |||
| 20f0122f59 |
31
.replit
Normal file
31
.replit
Normal file
@@ -0,0 +1,31 @@
|
||||
modules = ["bun-1.3"]
|
||||
[agent]
|
||||
expertMode = true
|
||||
|
||||
[workflows]
|
||||
runButton = "Project"
|
||||
|
||||
[[workflows.workflow]]
|
||||
name = "Project"
|
||||
mode = "parallel"
|
||||
author = "agent"
|
||||
|
||||
[[workflows.workflow.tasks]]
|
||||
task = "workflow.run"
|
||||
args = "Start application"
|
||||
|
||||
[[workflows.workflow]]
|
||||
name = "Start application"
|
||||
author = "agent"
|
||||
|
||||
[[workflows.workflow.tasks]]
|
||||
task = "shell.exec"
|
||||
args = "PORT=5000 bun --hot run src/server.ts"
|
||||
waitForPort = 5000
|
||||
|
||||
[workflows.workflow.metadata]
|
||||
outputType = "webview"
|
||||
|
||||
[[ports]]
|
||||
localPort = 5000
|
||||
externalPort = 80
|
||||
@@ -177,6 +177,12 @@
|
||||
onclick="this.select()" />
|
||||
<span class="side-hint">SEC</span>
|
||||
</div>
|
||||
<div class="tog-row">
|
||||
<div class="lbl">LINK TIMER</div>
|
||||
<label class="tog">
|
||||
<input type="checkbox" id="timer-tog" onchange="toggleTimerFromSwitch()" /><span class="tog-track"></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -226,7 +232,7 @@
|
||||
<div id="tab-buzzer">
|
||||
<div class="panel">
|
||||
<div class="panel-title">BUZZ ORDER</div>
|
||||
<div class="empty" id="mod-bz-empty">No buzzes — open a round to begin.</div>
|
||||
<div class="empty" id="mod-bz-empty">No buzzes</div>
|
||||
<div class="buzz-list" id="mod-bz-list"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -79,10 +79,18 @@ function handle(msg){
|
||||
room=msg.room;
|
||||
if(role==='mod'){renderMod();renderRoundButtons();renderModSettings();}else renderPlayer();
|
||||
break;
|
||||
case 'new_round':
|
||||
toast('NEW ROUND STARTED','ok');
|
||||
break;
|
||||
case 'round_open':
|
||||
room=msg.room;
|
||||
if(role==='mod'){renderMod();renderRoundButtons();}
|
||||
else{renderPlayerBuzzer();startPlayerTimer();toast('ROUND OPEN','ok');}
|
||||
else{renderPlayerBuzzer();startPlayerTimer();}
|
||||
// If timer is enabled and this is a fresh open, load the timer
|
||||
if(room.settings.timerSeconds>0 && modTimerRemaining===0 && !modTimerRunning){
|
||||
modTimerRemaining=room.settings.timerSeconds;
|
||||
renderModTimerDisplay();
|
||||
}
|
||||
break;
|
||||
case 'round_closed':
|
||||
room=msg.room;
|
||||
@@ -93,15 +101,6 @@ function handle(msg){
|
||||
room=msg.room;
|
||||
if(role==='mod'){renderMod();renderRoundButtons();}else renderPlayerBuzzer();
|
||||
break;
|
||||
case 'round_closed':
|
||||
room=msg.room;
|
||||
if(role==='mod')renderMod();
|
||||
else{renderPlayerBuzzer();stopPlayerTimer();toast('ROUND CLOSED','warn');}
|
||||
break;
|
||||
case 'buzzer_reset':
|
||||
room=msg.room;
|
||||
if(role==='mod')renderMod();else renderPlayerBuzzer();
|
||||
break;
|
||||
case 'buzz_event':
|
||||
room=msg.room;
|
||||
if(role==='mod'){renderMod();renderRoundButtons();}else{renderPlayerBuzzer();addFeed(msg);}
|
||||
@@ -323,6 +322,41 @@ function modTimerToggle(){
|
||||
}
|
||||
}
|
||||
|
||||
function toggleTimer(){
|
||||
modTimerToggle();
|
||||
}
|
||||
|
||||
function modTimerToggle(){
|
||||
if(modTimerRunning){
|
||||
clearInterval(modTimerInterval);modTimerRunning=false;
|
||||
document.getElementById('btn-timer-ss').textContent='START';
|
||||
broadcastTimerToPlayers(modTimerRemaining,false);
|
||||
} else {
|
||||
if(modTimerRemaining<=0)modTimerLoad();
|
||||
modTimerRunning=true;
|
||||
document.getElementById('btn-timer-ss').textContent='PAUSE';
|
||||
broadcastTimerToPlayers(modTimerRemaining,true);
|
||||
modTimerInterval=setInterval(()=>{
|
||||
modTimerRemaining--;
|
||||
renderModTimerDisplay();
|
||||
broadcastTimerToPlayers(modTimerRemaining,true);
|
||||
if(modTimerRemaining<=0){
|
||||
clearInterval(modTimerInterval);modTimerRunning=false;
|
||||
document.getElementById('btn-timer-ss').textContent='START';
|
||||
ws_send({type:'close_round'});
|
||||
toast('TIME UP — round closed','warn');
|
||||
if(typeof gsap!=='undefined'){
|
||||
gsap.to('#mod-timer-disp',{scale:1.08,duration:0.1,yoyo:true,repeat:3,ease:'power2.inOut'});
|
||||
}
|
||||
}
|
||||
},1000);
|
||||
}
|
||||
}
|
||||
|
||||
function toggleTimerFromSwitch(){
|
||||
modTimerToggle();
|
||||
}
|
||||
|
||||
function renderModTimerDisplay(){
|
||||
const el=document.getElementById('mod-timer-disp');
|
||||
const s=modTimerRemaining;
|
||||
@@ -330,6 +364,23 @@ function renderModTimerDisplay(){
|
||||
el.className='timer-digits'+(s<=5?' danger':s<=10?' warn':'');
|
||||
}
|
||||
|
||||
function modTimerLoad(){
|
||||
const sec=parseInt(document.getElementById('mod-timer-set').value)||30;
|
||||
modTimerRemaining=sec;
|
||||
modTimerRunning=false;
|
||||
clearInterval(modTimerInterval);
|
||||
document.getElementById('btn-timer-ss').textContent='START';
|
||||
renderModTimerDisplay();
|
||||
}
|
||||
|
||||
function modTimerReset(){
|
||||
clearInterval(modTimerInterval);
|
||||
modTimerRunning=false;
|
||||
modTimerRemaining=Math.max(5,parseInt(document.getElementById('mod-timer-set').value)||30);
|
||||
renderModTimerDisplay();
|
||||
broadcastTimerToPlayers(modTimerRemaining,false);
|
||||
}
|
||||
|
||||
function broadcastTimerToPlayers(sec,running){
|
||||
try{
|
||||
const bc=new BroadcastChannel('buzzer_timer');
|
||||
@@ -342,17 +393,58 @@ function broadcastTimerToPlayers(sec,running){
|
||||
// MOD ROUND CONTROL
|
||||
// ══════════════════════════════════════════════════════
|
||||
function toggleRound(){
|
||||
// Close round (pause)
|
||||
if(room.buzzerState.roundOpen){
|
||||
ws_send({type:'close_round'});
|
||||
// Change button to open state
|
||||
const btn=document.getElementById('mod-round-btn');
|
||||
btn.innerHTML='▶ OPEN ROUND';
|
||||
btn.className='btn btn-g btn-full';
|
||||
// If linked (toggle ON) and timer running, pause it
|
||||
if(document.getElementById('timer-tog').checked && modTimerRunning){
|
||||
modTimerToggle();
|
||||
}
|
||||
} else {
|
||||
ws_send({type:'open_round'});
|
||||
// If linked (toggle ON) and timer stopped, reset and start it
|
||||
if(document.getElementById('timer-tog').checked && !modTimerRunning){
|
||||
modTimerLoad();
|
||||
modTimerRunning=true;
|
||||
document.getElementById('btn-timer-ss').textContent='PAUSE';
|
||||
modTimerInterval=setInterval(()=>{
|
||||
modTimerRemaining--;
|
||||
renderModTimerDisplay();
|
||||
broadcastTimerToPlayers(modTimerRemaining,true);
|
||||
if(modTimerRemaining<=0){
|
||||
clearInterval(modTimerInterval);modTimerRunning=false;
|
||||
document.getElementById('btn-timer-ss').textContent='START';
|
||||
ws_send({type:'close_round'});
|
||||
toast('TIME UP — round closed','warn');
|
||||
}
|
||||
},1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function resumeRound(){
|
||||
ws_send({type:'open_round'});
|
||||
ws_send({type:'resume_round'});
|
||||
// If linked (toggle ON) and timer stopped, resume it (preserve current value)
|
||||
if(document.getElementById('timer-tog').checked && !modTimerRunning){
|
||||
modTimerRunning=true;
|
||||
document.getElementById('btn-timer-ss').textContent='PAUSE';
|
||||
broadcastTimerToPlayers(modTimerRemaining,true);
|
||||
modTimerInterval=setInterval(()=>{
|
||||
modTimerRemaining--;
|
||||
renderModTimerDisplay();
|
||||
broadcastTimerToPlayers(modTimerRemaining,true);
|
||||
if(modTimerRemaining<=0){
|
||||
clearInterval(modTimerInterval);modTimerRunning=false;
|
||||
document.getElementById('btn-timer-ss').textContent='START';
|
||||
ws_send({type:'close_round'});
|
||||
toast('TIME UP — round closed','warn');
|
||||
}
|
||||
},1000);
|
||||
}
|
||||
}
|
||||
|
||||
// ══════════════════════════════════════════════════════
|
||||
// TIMER
|
||||
// ══════════════════════════════════════════════════════
|
||||
|
||||
function renderRoundButtons(){
|
||||
const btn=document.getElementById('mod-round-btn');
|
||||
@@ -494,6 +586,7 @@ function renderModSettings(){
|
||||
segActivate('ls-seg-mode',s.mode);
|
||||
renderLiveTeamNames();
|
||||
renderRoundButtons();
|
||||
renderModTimerDisplay();
|
||||
}
|
||||
|
||||
function segActivate(groupId,val){
|
||||
|
||||
@@ -135,6 +135,15 @@ export function handleMessage(ws: WS, raw: string) {
|
||||
room.buzzerState = freshBuzzer();
|
||||
room.buzzerState.roundOpen = true;
|
||||
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;
|
||||
case "close_round":
|
||||
room.buzzerState.roundOpen = false;
|
||||
|
||||
Reference in New Issue
Block a user