Compare commits

..

16 Commits

Author SHA1 Message Date
keshavmathguy
1a1c494ade Saved progress at the end of the loop
Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 22590254-16a6-4e67-8db6-70bf23ec5efa
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 480bdc5f-3460-4340-bad8-55c04b01e09d
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/3f3f1d57-54c5-43b3-bdea-ac659ef0a32c/22590254-16a6-4e67-8db6-70bf23ec5efa/PuWDZtN
Replit-Helium-Checkpoint-Created: true
2026-04-10 18:38:21 +00:00
d9b2c5ee22 Change timer toggle label to 'LINK TIMER', remove 'open a round' text from empty buzzer state 2026-04-08 20:34:50 -05:00
57c5f4e054 Open Round: start timer if toggle ON, regardless of current value 2026-04-08 20:31:08 -05:00
3c0ca7843f Open Round: properly reset and start timer when toggle ON 2026-04-08 20:30:56 -05:00
a135746e37 Resume Round: preserve current timer value instead of restarting from set value 2026-04-08 20:29:42 -05:00
56ecfaad54 Timer toggle: manual ON/OFF control, buttons auto-link when ON, NEVER auto-uncheck 2026-04-08 20:28:17 -05:00
1ad1d96cb2 Timer toggle: manual ON/OFF control, buttons auto-link when ON, never auto-uncheck 2026-04-08 20:24:44 -05:00
1a2dc295e1 Timer toggle: manual ON/OFF control, links to Open/Pause/Resume buttons, auto-syncs state 2026-04-08 19:28:07 -05:00
9d34d648d8 Round closed: uncheck timer toggle when round ends 2026-04-08 19:25:26 -05:00
7e52e1768b Fix timer toggle: manual control, starts from set value, syncs with modTimerRunning 2026-04-08 19:25:22 -05:00
3dc9bdcc21 Link timer toggle to Open/Pause/Resume buttons — timer auto-starts/pauses/resumes with round 2026-04-08 19:21:32 -05:00
22c4d28b42 Fix renderMod() duplicate code that broke script execution 2026-04-08 19:18:31 -05:00
b5031c4f71 Add timer toggle switch beneath timer block — links to START/STOP buttons 2026-04-08 19:17:44 -05:00
b9d3b6115a Fix resumeRound: send resume_round type instead of open_round to preserve buzzes 2026-04-08 19:11:02 -05:00
a9ccb42008 Add resume_round message and NEW ROUND toast notification for fresh round starts 2026-04-08 19:09:28 -05:00
20f0122f59 Fix toggleRound logic: Open Round opens, Pause Round closes 2026-04-08 19:07:34 -05:00
4 changed files with 160 additions and 21 deletions

31
.replit Normal file
View 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

View File

@@ -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>

View File

@@ -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){

View File

@@ -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;