Created
March 8, 2023 18:26
-
-
Save RaycatWhoDat/fa57ee365647b4b85262897da679ca8f to your computer and use it in GitHub Desktop.
Subathon Timer
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!doctype html> | |
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"/> | |
<title>Timer</title> | |
<link rel="preconnect" href="https://fonts.googleapis.com"> | |
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> | |
<link href="https://fonts.googleapis.com/css2?family=Oswald:wght@500&display=swap" rel="stylesheet"> | |
<link rel="stylesheet" href="https://unpkg.com/[email protected]/css/tachyons.min.css"/> | |
<style> | |
* { | |
font-family: 'Oswald', sans-serif; | |
} | |
#timer-display, #subs-display { | |
text-shadow: 4px 4px #000; | |
} | |
.event:last-child { | |
border: none !important; | |
} | |
.active-break-button { | |
background-color: #ff4136 !important; | |
color: #fff !important; | |
} | |
.timer-finished { | |
visibility: hidden; | |
} | |
</style> | |
</head> | |
<body class="bg-green flex flex-column w-100 h-100"> | |
<div class="row flex w-100"> | |
<div class="timer flex flex-column tc items-center w-50 ma2 ph2 pv3 "> | |
<span id="timer-display" class="f-headline white text-top">00:00:00</span> | |
<span id="subs-display" class="f-headline white text-top"> | |
<span id="sub-count-display">0</span>/<span id="sub-goal-display">0</span> | |
</span> | |
<div class="event-feed flex flex-column w-50 ba bw2 b--black br3 bg-near-white mt4"> | |
<p class="f3 b bb b--black bw1 pa2 mv0 white bg-near-black">EVENT FEED</p> | |
</div> | |
</div> | |
<div class="buttons ma2 ph2 pv3 tc items-center justify-center w-50 br3 bg-near-white ba bw2 b--black"> | |
<div class="flex justify-center"> | |
<button id="undo-button" class="ph3 pv2 ma1 ba bw2 bg-navy white br4 b--black w-50 outline-0 usn pointer"> | |
<span class="f3">UNDO</span> | |
</button> | |
</div> | |
<div class="big-controls flex"> | |
<button id="pause-button" class="ph3 pv2 ma1 ba bw2 bg-washed-blue br4 b--black w-25 outline-0 usn pointer"> | |
<span class="f3">PAUSE</span> | |
</button> | |
<button id="break-10-button" class="ph3 pv2 ma1 ba bw2 bg-lightest-blue br4 b--black w-25 outline-0 usn pointer"> | |
<span class="button-text f3">10 MIN BREAK</span> | |
<span class="break-display f3 dn"></span> | |
</button> | |
<button id="break-15-button" class="ph3 pv2 ma1 ba bw2 bg-light-blue br4 b--black w-25 outline-0 usn pointer"> | |
<span class="button-text f3">15 MIN BREAK</span> | |
<span class="break-display f3 dn"></span> | |
</button> | |
<button id="break-20-button" class="ph3 pv2 ma1 ba bw2 bg-blue br4 b--black w-25 outline-0 usn pointer"> | |
<span class="button-text f3">20 MIN BREAK</span> | |
<span class="break-display f3 dn"></span> | |
</button> | |
</div> | |
<div class="time-control"> | |
<p class="f3 b bb b--black bw1 pa2">TIMER CONTROL</p> | |
<div class="time-form flex justify-around mt2"> | |
<input id="hours-time" type="text" placeholder="H" class="w-20 outline-0 mr1" /> | |
<input id="minutes-time" type="text" placeholder="M" class="w-20 outline-0 mr1" /> | |
<input id="seconds-time" type="text" placeholder="S" class="w-20 outline-0 mr1" /> | |
<button id="set-timer" class="w-20">Set Timer</button> | |
</div> | |
<div class="buttons mv3"> | |
<button id="add-hour">Add 1 Hour</button> | |
<button id="add-five-minutes">Add 5 Minutes</button> | |
<button id="add-one-minute">Add 1 Minute</button> | |
</div> | |
<div class="seconds-form flex justify-around mt2"> | |
<input id="seconds-input" type="text" placeholder="Enter # of seconds" class="w-60 outline-0" /> | |
<button id="add-seconds" class="w-30">Add Seconds</button> | |
</div> | |
</div> | |
<div class="flex w-100"> | |
<div class="bits-donations w-50"> | |
<p class="f3 b bb b--black bw1 pa2">BITS/DONATIONS</p> | |
<div class="bits-form"> | |
<input id="bits-input" type="text" placeholder="Enter # of bits" value="100"/> | |
<button id="add-bits">Remove Time (Bits)</button> | |
</div> | |
<div class="dollars-form mt2"> | |
<input id="dollars-input" type="text" placeholder="Enter # of dollars" value="1"/> | |
<button id="add-dollars">Remove Time (Dollars)</button> | |
</div> | |
</div> | |
<div class="sub-count w-50"> | |
<p class="f3 b bb b--black bw1 pa2">SUB COUNT CONTROL</p> | |
<div class="sub-count-form"> | |
<input id="sub-count-input" type="text" placeholder="Enter sub count" /> | |
<button id="set-sub-count">Set Sub Count</button> | |
</div> | |
<div class="sub-goal-form mt2"> | |
<input id="sub-goal-input" type="text" placeholder="Enter goal" /> | |
<button id="set-goal">Set Sub Goal</button> | |
</div> | |
</div> | |
</div> | |
<div class="subs"> | |
<p class="f3 b bb b--black bw1 pa2">SUBS</p> | |
<div class="add-multiple-subs-form mt2"> | |
<input id="multiple-subs-input" type="text" placeholder="Enter # of subs" class="w-40 outline-0 mr1" /> | |
<button id="add-multiple-tier-1-subs">Add as Tier 1 Subs</button> | |
<button id="add-multiple-tier-2-subs">Add as Tier 2 Subs</button> | |
<button id="add-multiple-tier-3-subs">Add as Tier 3 Subs</button> | |
</div> | |
</div> | |
<div class="resubs"> | |
<p class="f3 b bb b--black bw1 pa2">RESUBS</p> | |
<div class="add-multiple-resubs-form mt2"> | |
<input id="multiple-resubs-input" type="text" placeholder="Enter # of resubs" class="w-40 outline-0 mr1" /> | |
<button id="add-multiple-tier-1-resubs">Add as Tier 1 Resubs</button> | |
<button id="add-multiple-tier-2-resubs">Add as Tier 2 Resubs</button> | |
<button id="add-multiple-tier-3-resubs">Add as Tier 3 Resubs</button> | |
</div> | |
</div> | |
<div class="flex flex-column tc items-center justify-center w-100 mt4"> | |
<p class="f3">QUICK CALCULATIONS (in seconds)</p> | |
<div class="tiers flex b bt b--black bw1 pa2"> | |
<div class="fives flex flex-column tl pr2"> | |
<span class="f4 pa2">5 T1 Subs: <span id="five-tier-1-subs"></span></span> | |
<span class="f4 pa2">5 T2 Subs: <span id="five-tier-2-subs"></span></span> | |
<span class="f4 pa2">5 T3 Subs: <span id="five-tier-3-subs"></span></span> | |
</div> | |
<div class="fives flex flex-column tl pr2"> | |
<span class="f4 pa2">5 T1 Resubs: <span id="five-tier-1-resubs"></span></span> | |
<span class="f4 pa2">5 T2 Resubs: <span id="five-tier-2-resubs"></span></span> | |
<span class="f4 pa2">5 T3 Resubs: <span id="five-tier-3-resubs"></span></span> | |
</div> | |
<div class="twenty-fives flex flex-column tl pl2"> | |
<span class="f4 pa2">25 T1 Subs: <span id="twenty-five-tier-1-subs"></span></span> | |
<span class="f4 pa2">25 T2 Subs: <span id="twenty-five-tier-2-subs"></span></span> | |
<span class="f4 pa2">25 T3 Subs: <span id="twenty-five-tier-3-subs"></span></span> | |
</div> | |
<div class="twenty-fives flex flex-column tl pl2"> | |
<span class="f4 pa2">25 T1 Resubs: <span id="twenty-five-tier-1-resubs"></span></span> | |
<span class="f4 pa2">25 T2 Resubs: <span id="twenty-five-tier-2-resubs"></span></span> | |
<span class="f4 pa2">25 T3 Resubs: <span id="twenty-five-tier-3-resubs"></span></span> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
<script src="https://cdn.jsdelivr.net/npm/[email protected]/plugin/duration.js"></script> | |
<script src="https://cdn.jsdelivr.net/npm/[email protected]/plugin/relativeTime.js"></script> | |
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dayjs.min.js"></script> | |
<script> | |
(() => { | |
const SECONDS = { | |
BITS: 60, | |
DOLLARS: 60, | |
TIER_1_SUB: 120, | |
TIER_1_RESUB: 120, | |
TIER_2_SUB: 180, | |
TIER_2_RESUB: 180, | |
TIER_3_SUB: 240, | |
TIER_3_RESUB: 240 | |
}; | |
const MAX_NUMBER_OF_CHANGES = 25; | |
let timerInterval = null; | |
let blinkInterval = null; | |
let isPaused = false; | |
let breakTime = null; | |
let breakTimeDisplay = null; | |
let breakTimeInterval = null; | |
dayjs.extend(dayjs_plugin_duration); | |
dayjs.extend(dayjs_plugin_relativeTime); | |
const changes = []; | |
const pushChange = (change, numberOfSubs) => { | |
changes.push({ time: change, subs: numberOfSubs }); | |
const newChange = document.createElement("span"); | |
const verb = Math.sign(change.asMinutes()) === 1 ? 'Added' : 'Removed'; | |
const minutesToDisplay = change.asMinutes() * (1 + (-2 * (Math.sign(change.asMinutes()) === -1))) | |
newChange.textContent = `${verb} ${minutesToDisplay} minute${minutesToDisplay !== 1 ? 's' : ''}`; | |
newChange.classList.add("event", "pa2", "bb", "bw1", "b--gray"); | |
const eventFeed = document.querySelector(".event-feed"); | |
if (eventFeed) eventFeed.appendChild(newChange); | |
if (changes.length >= MAX_NUMBER_OF_CHANGES) changes.unshift(); | |
} | |
const undoChange = () => { | |
if (changes.length < 1) return; | |
let { time: timeChange, subs } = changes.pop(); | |
remainingTime = remainingTime.subtract(timeChange); | |
setSubCount(currentSubCount - subs); | |
document.querySelector(".event:last-child").remove(); | |
setTimeInSeconds(0); | |
} | |
const previousRemainingTime = localStorage.getItem('remainingTime') || 'PT7H50M00.000S'; | |
let currentSubCount = parseInt(localStorage.getItem('currentSubCount')) || 1032; | |
let subGoal = parseInt(localStorage.getItem('subGoal')) || 1200; | |
const setSubCount = newSubCount => { | |
currentSubCount = newSubCount; | |
localStorage.setItem('currentSubCount', currentSubCount); | |
document.getElementById('sub-count-display').textContent = currentSubCount; | |
}; | |
const setSubGoal = newSubGoal => { | |
subGoal = newSubGoal; | |
localStorage.setItem('subGoal', subGoal); | |
document.getElementById('sub-goal-display').textContent = subGoal; | |
}; | |
setSubCount(currentSubCount); | |
setSubGoal(subGoal); | |
const defaultTime = dayjs().add(99, 'hour').add(59, 'minute').add(59, 'second'); | |
let remainingTime = dayjs.duration(previousRemainingTime || defaultTime.diff(dayjs())); | |
let remainingTimeDisplay = `${Math.floor(remainingTime.asHours())}` + remainingTime.format(':mm:ss'); | |
document.getElementById('timer-display').textContent = remainingTimeDisplay; | |
const startBreak = numberOfMinutes => { | |
if (breakTimeInterval) return; | |
breakTime = dayjs.duration(dayjs().add(numberOfMinutes, 'minutes').diff(dayjs())); | |
breakTimeDisplay = breakTime.format('mm:ss'); | |
const element = document.getElementById(`break-${numberOfMinutes}-button`); | |
element.classList.add("active-break-button"); | |
element.querySelector('.button-text').classList.add("dn"); | |
element.querySelector('.break-display').classList.remove("dn"); | |
element.querySelector('.break-display').textContent = breakTimeDisplay; | |
breakTimeInterval = setInterval(() => { | |
breakTime = breakTime.subtract(dayjs.duration(1, 's')); | |
breakTimeDisplay = breakTime.format('mm:ss'); | |
element.querySelector('.break-display').textContent = breakTimeDisplay; | |
if (breakTime.asSeconds() <= 0) toggleTimer(false); | |
}, 1000); | |
}; | |
const startBreak10Mins = () => { | |
toggleTimer(true); | |
startBreak(10); | |
}; | |
const startBreak15Mins = () => { | |
toggleTimer(true); | |
startBreak(15); | |
}; | |
const startBreak20Mins = () => { | |
toggleTimer(true); | |
startBreak(20); | |
}; | |
const endBreak = () => { | |
if (!breakTimeInterval) return; | |
const element = document.querySelector(".active-break-button"); | |
breakTime = null; | |
breakTimeDisplay = ""; | |
element.querySelector('.button-text').classList.remove("dn"); | |
element.querySelector('.break-display').classList.add("dn"); | |
element.querySelector('.break-display').textContent = breakTimeDisplay; | |
breakTimeInterval = clearInterval(breakTimeInterval) || null; | |
element.classList.remove("active-break-button"); | |
}; | |
const setTime = unit => (numberOfMinutes, numberOfSubs) => { | |
if (numberOfMinutes) { | |
let additionalTime = dayjs.duration(numberOfMinutes, unit); | |
if (remainingTime.add(additionalTime).asSeconds() >= 0) { | |
remainingTime = remainingTime.add(additionalTime); | |
pushChange(additionalTime, numberOfSubs || 0); | |
} | |
} | |
remainingTimeDisplay = `${Math.floor(remainingTime.asHours())}` + remainingTime.format(':mm:ss'); | |
document.getElementById('timer-display').textContent = remainingTimeDisplay; | |
if (!timerInterval && remainingTime.asSeconds() > 1) { | |
timerInterval = startTimer(); | |
clearInterval(blinkInterval); | |
blinkInterval = null; | |
} | |
}; | |
const setTimeInMinutes = setTime('m'); | |
const setTimeInSeconds = setTime('s'); | |
const startTimer = () => setInterval(() => { | |
if (remainingTime.asSeconds() <= 0) { | |
clearInterval(timerInterval); | |
timerInterval = null; | |
blinkInterval = setInterval(() => { | |
document.getElementById("timer-display").classList.toggle('timer-finished'); | |
}, 500) | |
return; | |
} | |
remainingTime = remainingTime.subtract(dayjs.duration(1, 's')); | |
localStorage.setItem('remainingTime', remainingTime.toISOString()); | |
setTimeInSeconds(0); | |
}, 1000); | |
const toggleTimer = isPausedOverride => { | |
const timerElem = document.getElementById("timer-display"); | |
const pauseButton = document.querySelector("#pause-button > .f3"); | |
const timerColors = ["white", "red"]; | |
timerElem.classList.remove(timerColors[+isPaused]); | |
if (isPausedOverride != null) { | |
isPaused = isPausedOverride; | |
} else { | |
isPaused = !isPaused; | |
} | |
pauseButton.textContent = `${isPaused ? 'UN' : ''}PAUSE`; | |
timerElem.classList.add(timerColors[+isPaused]); | |
if (isPaused) { | |
clearInterval(timerInterval); | |
} else { | |
timerInterval = startTimer(); | |
endBreak(); | |
} | |
}; | |
timerInterval = startTimer(); | |
const addBits = numberOfBits => setTimeInSeconds(SECONDS.BITS * Math.floor(numberOfBits / 100)); | |
const addDollars = numberOfDollars => setTimeInSeconds(SECONDS.DOLLARS * Math.floor(numberOfDollars)); | |
const removeBits = numberOfBits => setTimeInSeconds(-1 * SECONDS.BITS * Math.floor(numberOfBits / 100)); | |
const removeDollars = numberOfDollars => setTimeInSeconds(-1 * SECONDS.DOLLARS * Math.floor(numberOfDollars)); | |
document.getElementById('undo-button').addEventListener('click', () => undoChange()); | |
document.getElementById('pause-button').addEventListener('click', () => toggleTimer()); | |
document.getElementById('add-hour').addEventListener('click', () => setTimeInMinutes(60)); | |
document.getElementById('add-five-minutes').addEventListener('click', () => setTimeInMinutes(5)); | |
document.getElementById('add-one-minute').addEventListener('click', () => setTimeInMinutes(1)); | |
document.getElementById('add-seconds').addEventListener('click', () => { | |
const numberOfSeconds = document.getElementById('seconds-input').value || '0'; | |
setTimeInSeconds(parseInt(numberOfSeconds)); | |
}); | |
document.getElementById('add-bits').addEventListener('click', () => { | |
const numberOfBits = document.getElementById('bits-input').value || '0'; | |
removeBits(parseInt(numberOfBits)); | |
}); | |
document.getElementById('add-dollars').addEventListener('click', () => { | |
const numberOfDollars = document.getElementById('dollars-input').value || '0'; | |
removeDollars(parseInt(numberOfDollars)); | |
}); | |
document.getElementById('set-sub-count').addEventListener('click', () => { | |
const numberOfSubs = document.getElementById('sub-count-input').value || '0'; | |
setSubCount(parseInt(numberOfSubs)); | |
}); | |
document.getElementById('set-goal').addEventListener('click', () => { | |
const numberOfSubs = document.getElementById('sub-goal-input').value || '0'; | |
setSubGoal(parseInt(numberOfSubs)); | |
}); | |
const setTimer = () => { | |
const hours = document.getElementById('hours-time').value || '0'; | |
const minutes = document.getElementById('minutes-time').value || '0'; | |
const seconds = document.getElementById('seconds-time').value || '0'; | |
remainingTime = dayjs.duration(`PT${hours}H${minutes}M${seconds}.000S`); | |
setTimeInSeconds(0); | |
}; | |
document.getElementById('set-timer').addEventListener('click', setTimer); | |
const addMultipleSubs = (interval, numberOfSubs) => { | |
setSubCount(currentSubCount + numberOfSubs); | |
setTimeInSeconds(interval * numberOfSubs, numberOfSubs); | |
}; | |
document.getElementById('add-multiple-tier-1-subs').addEventListener('click', () => { | |
const numberOfSubs = document.getElementById('multiple-subs-input').value || '0'; | |
addMultipleSubs(SECONDS.TIER_1_SUB, parseInt(numberOfSubs)); | |
}); | |
document.getElementById('add-multiple-tier-2-subs').addEventListener('click', () => { | |
const numberOfSubs = document.getElementById('multiple-subs-input').value || '0'; | |
addMultipleSubs(SECONDS.TIER_2_SUB, parseInt(numberOfSubs)); | |
}); | |
document.getElementById('add-multiple-tier-3-subs').addEventListener('click', () => { | |
const numberOfSubs = document.getElementById('multiple-subs-input').value || '0'; | |
addMultipleSubs(SECONDS.TIER_3_SUB, parseInt(numberOfSubs)); | |
}); | |
document.getElementById('add-multiple-tier-1-resubs').addEventListener('click', () => { | |
const numberOfSubs = document.getElementById('multiple-resubs-input').value || '0'; | |
addMultipleSubs(SECONDS.TIER_1_RESUB, parseInt(numberOfSubs)); | |
}); | |
document.getElementById('add-multiple-tier-2-resubs').addEventListener('click', () => { | |
const numberOfSubs = document.getElementById('multiple-resubs-input').value || '0'; | |
addMultipleSubs(SECONDS.TIER_2_RESUB, parseInt(numberOfSubs)); | |
}); | |
document.getElementById('add-multiple-tier-3-resubs').addEventListener('click', () => { | |
const numberOfSubs = document.getElementById('multiple-resubs-input').value || '0'; | |
addMultipleSubs(SECONDS.TIER_3_RESUB, parseInt(numberOfSubs)); | |
}); | |
document.getElementById('break-10-button').addEventListener('click', () => startBreak10Mins()); | |
document.getElementById('break-15-button').addEventListener('click', () => startBreak15Mins()); | |
document.getElementById('break-20-button').addEventListener('click', () => startBreak20Mins()); | |
for (let tier = 1; tier < 4; tier++) { | |
document.getElementById(`five-tier-${tier}-subs`).textContent = SECONDS[`TIER_${tier}_SUB`] * 5; | |
document.getElementById(`five-tier-${tier}-resubs`).textContent = SECONDS[`TIER_${tier}_RESUB`] * 5; | |
document.getElementById(`twenty-five-tier-${tier}-subs`).textContent = SECONDS[`TIER_${tier}_SUB`] * 25; | |
document.getElementById(`twenty-five-tier-${tier}-resubs`).textContent = SECONDS[`TIER_${tier}_RESUB`] * 25; | |
} | |
})(document); | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment