feat(finale): server-stamped phase timer helper
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
36
src/lib/live-timer.ts
Normal file
36
src/lib/live-timer.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* Server-stamped phase timer math for the grand-finale ceremony.
|
||||
*
|
||||
* The cursor stores `phaseStartedAt` + `phaseDurationSeconds` plus a pause
|
||||
* accumulator; every client derives the countdown locally from those stamps,
|
||||
* so all screens agree and overtime is just a negative remainder.
|
||||
*/
|
||||
|
||||
export type PhaseTimerState = {
|
||||
phaseStartedAt: Date | string | null
|
||||
phaseDurationSeconds: number | null
|
||||
phasePausedAt: Date | string | null
|
||||
phasePausedAccumMs: number
|
||||
}
|
||||
|
||||
export function elapsedMs(t: PhaseTimerState, now: Date = new Date()): number {
|
||||
if (!t.phaseStartedAt) return 0
|
||||
const start = new Date(t.phaseStartedAt).getTime()
|
||||
const end = t.phasePausedAt ? new Date(t.phasePausedAt).getTime() : now.getTime()
|
||||
return Math.max(0, end - start - t.phasePausedAccumMs)
|
||||
}
|
||||
|
||||
/** Seconds left on the phase timer; negative = overtime; null = no timer running. */
|
||||
export function remainingSeconds(t: PhaseTimerState, now: Date = new Date()): number | null {
|
||||
if (!t.phaseStartedAt || t.phaseDurationSeconds == null) return null
|
||||
return t.phaseDurationSeconds - Math.floor(elapsedMs(t, now) / 1000)
|
||||
}
|
||||
|
||||
/** `5:05` for positive seconds, `+1:23` for overtime. */
|
||||
export function formatClock(seconds: number): string {
|
||||
const over = seconds < 0
|
||||
const abs = Math.abs(seconds)
|
||||
const m = Math.floor(abs / 60)
|
||||
const s = abs % 60
|
||||
return `${over ? '+' : ''}${m}:${s.toString().padStart(2, '0')}`
|
||||
}
|
||||
Reference in New Issue
Block a user