diff --git a/.prettierrc.js b/.prettierrc.js new file mode 100644 index 0000000..aeb1e60 --- /dev/null +++ b/.prettierrc.js @@ -0,0 +1,12 @@ +module.exports = { + semi: true, + tabWidth: 4, + useTabs: false, + singleQuote: true, + trailingComma: 'es5', + bracketSpacing: true, + arrowParens: 'avoid', + printWidth: 120, + endOfLine: 'lf', + quoteProps: 'as-needed', +}; diff --git a/images/cancel.svg b/asset/images/cancel.svg similarity index 100% rename from images/cancel.svg rename to asset/images/cancel.svg diff --git a/images/edit.svg b/asset/images/edit.svg similarity index 100% rename from images/edit.svg rename to asset/images/edit.svg diff --git a/asset/images/exit-full-screen.svg b/asset/images/exit-full-screen.svg new file mode 100644 index 0000000..8c1fb8f --- /dev/null +++ b/asset/images/exit-full-screen.svg @@ -0,0 +1,3 @@ + + + diff --git a/asset/images/favicon.ico b/asset/images/favicon.ico new file mode 100644 index 0000000..37eaa29 Binary files /dev/null and b/asset/images/favicon.ico differ diff --git a/images/full-screen.svg b/asset/images/full-screen.svg similarity index 100% rename from images/full-screen.svg rename to asset/images/full-screen.svg diff --git a/images/ok.svg b/asset/images/ok.svg similarity index 100% rename from images/ok.svg rename to asset/images/ok.svg diff --git a/images/pause.svg b/asset/images/pause.svg similarity index 100% rename from images/pause.svg rename to asset/images/pause.svg diff --git a/images/play.svg b/asset/images/play.svg similarity index 100% rename from images/play.svg rename to asset/images/play.svg diff --git a/images/reset.svg b/asset/images/reset.svg similarity index 100% rename from images/reset.svg rename to asset/images/reset.svg diff --git a/asset/javascript/TimeController.js b/asset/javascript/TimeController.js new file mode 100644 index 0000000..e3213bc --- /dev/null +++ b/asset/javascript/TimeController.js @@ -0,0 +1,337 @@ +import './helper.js'; +import { TimerStatus } from './TimerStatus.js'; +import { + formatTimeUnit, + hourToSeconds, + minuteToSeconds, + remainingSeconds, + secondsToHour, + secondsToMinute, +} from './TimeUtils.js'; + +function TimerController(reference) { + const hourInput = reference.querySelector('.js-hour-input'); + const minuteInput = reference.querySelector('.js-minute-input'); + const secondInput = reference.querySelector('.js-seconds-input'); + + const actionButtonsContainer = reference.querySelector('.js-stopwatch-action-buttons'); + const enterFullscreenButton = actionButtonsContainer.querySelector('.js-enter-fullscreen-button'); + const exitFullscreenButton = actionButtonsContainer.querySelector('.js-exit-fullscreen-button'); + const startButton = actionButtonsContainer.querySelector('.js-start-button'); + const stopButton = actionButtonsContainer.querySelector('.js-stop-button'); + const pauseButton = actionButtonsContainer.querySelector('.js-pause-button'); + const editButton = actionButtonsContainer.querySelector('.js-edit-button'); + + const editActionButtonsContainer = reference.querySelector('.js-edit-container-stopwatch'); + const cancelEditButton = editActionButtonsContainer.querySelector('.js-cancel-edit-button'); + const finishEditButton = editActionButtonsContainer.querySelector('.js-finish-edit-button'); + + const countdownContainerReference = reference.querySelector('.js-countdown-container'); + const countdownNumber = countdownContainerReference.querySelector('.js-countdown-number'); + const closeCountdownButton = countdownContainerReference.querySelector('.js-close-countdown-button'); + + const DEFAULT_INTERVAL = 1000; + const DEFAULT_SECONDS = 30; + + const tickTackSound = new Audio('./asset/sound/tick-tack.wav'); + const stopSound = new Audio('./asset/sound/stop.mp3'); + + let lastTimerStatus = TimerStatus.STOPPED; + let previousTimerValue = DEFAULT_SECONDS; + let timerIntervalId = null; + let preventOpenCountdown = false; + + function init() { + bindInputs(); + bindButtons(); + setInputValues(DEFAULT_SECONDS); + } + + var bindInputs = function () { + hourInput.addEventListener('input', function () { + let maxHours = 99; + validateInput(hourInput, maxHours); + }); + + hourInput.addEventListener('keydown', function (event) { + if (event.key === 'Enter') { + event.preventDefault(); + event.stopPropagation(); + minuteInput.focus(); + } + }); + + minuteInput.addEventListener('input', function () { + let maxMinutes = 59; + validateInput(minuteInput, maxMinutes); + }); + + minuteInput.addEventListener('keydown', function (event) { + if (event.key === 'Enter') { + event.preventDefault(); + event.stopPropagation(); + secondInput.focus(); + } + }); + + secondInput.addEventListener('input', function () { + let maxSeconds = 59; + validateInput(secondInput, maxSeconds); + }); + + secondInput.addEventListener('keydown', function (event) { + if (event.key === 'Enter') { + event.preventDefault(); + event.stopPropagation(); + finishEditInput(); + startButton.focus(); + } + }); + }; + + function validateInput(input, maxValue) { + let value = parseInt(input.value) || 0; + + if (value < 0) value = 0; + if (value > maxValue) value = maxValue; + + input.value = formatTimeUnit(value); + } + + function bindButtons() { + editButton.addEventListener('click', openEditInput); + cancelEditButton.addEventListener('click', cancelEditInput); + finishEditButton.addEventListener('click', finishEditInput); + + startButton.addEventListener('click', start); + stopButton.addEventListener('click', stop); + pauseButton.addEventListener('click', pause); + + enterFullscreenButton.addEventListener('click', handleFullscreen); + exitFullscreenButton.addEventListener('click', handleFullscreen); + closeCountdownButton.addEventListener('click', closeCountdownContainer); + } + + function openEditInput() { + lastTimerStatus = TimerStatus.EDITING; + previousTimerValue = getInputsValueAsSeconds(); + + toggleDisableInputs(false); + toggleButtonsContainer(true); + + setTimeout(() => { + hourInput.focus(); + }, 10); + } + + function cancelEditInput() { + setInputValues(previousTimerValue); + finishEditInput(); + } + + function finishEditInput() { + lastTimerStatus = TimerStatus.PAUSED; + toggleDisableInputs(true); + toggleButtonsContainer(false); + window.getSelection().removeAllRanges(); + + let seconds = getInputsValueAsSeconds(); + + if (seconds <= 0) { + setInputValues(DEFAULT_SECONDS); + } + } + + function start() { + const canStart = TimerStatus.isStopped(lastTimerStatus) || TimerStatus.isPaused(lastTimerStatus); + + if (!canStart) return; + + startButton.hideElement(); + pauseButton.showElement(); + stopButton.showElement(); + editButton.hideElement(); + + lastTimerStatus = TimerStatus.RUNNING; + initTimer(); + } + + function initTimer() { + timerIntervalId = setInterval(() => { + var canStart = + TimerStatus.isRunning(lastTimerStatus) || + TimerStatus.isCountdown(lastTimerStatus) || + TimerStatus.isPaused(lastTimerStatus); + + if (canStart) { + let seconds = getInputsValueAsSeconds(); + seconds--; + + if (seconds <= 10) { + lastTimerStatus = TimerStatus.COUNTDOWN; + playCountdownSound(); + + if (!preventOpenCountdown) executeCountdown(seconds); + } + + setInputValues(seconds); + + if (seconds == 0) { + preventOpenCountdown = false; + lastTimerStatus = TimerStatus.STOPPED; + + showDefaultButtons(); + playStopSound(); + clearInterval(timerIntervalId); + } + } else { + clearInterval(timerIntervalId); + } + }, DEFAULT_INTERVAL); + } + + function playCountdownSound() { + tickTackSound.volume = 0.5; + tickTackSound.loop = false; + tickTackSound.currentTime = 0; + tickTackSound.play(); + } + + function playStopSound() { + stopSound.volume = 0.5; + stopSound.loop = false; + stopSound.currentTime = 0; + stopSound.play(); + + setTimeout(() => { + const fade = setInterval(() => { + if (stopSound.volume > 0.05) { + stopSound.volume -= 0.05; + } else { + stopSound.volume = 0; + stopSound.pause(); + stopSound.currentTime = 0; + clearInterval(fade); + setInputValues(DEFAULT_SECONDS); + preventOpenCountdown = false; + } + }, 200); + }, 3000); + } + + function executeCountdown(seconds) { + countdownContainerReference.showElement(); + countdownNumber.textContent = seconds; + + if (seconds % 2 === 0) { + countdownContainerReference.classList.add('even'); + countdownContainerReference.classList.remove('odd'); + return; + } + + countdownContainerReference.classList.add('odd'); + countdownContainerReference.classList.remove('even'); + } + + function stop() { + if (TimerStatus.isStopped(lastTimerStatus)) return; + + preventOpenCountdown = false; + lastTimerStatus = TimerStatus.STOPPED; + showDefaultButtons(); + setInputValues(DEFAULT_SECONDS); + clearInterval(timerIntervalId); + } + + function pause() { + if (lastTimerStatus === TimerStatus.RUNNING || lastTimerStatus === TimerStatus.COUNTDOWN) { + lastTimerStatus = TimerStatus.PAUSED; + startButton.showElement(); + pauseButton.hideElement(); + clearInterval(timerIntervalId); + } + } + + function handleFullscreen() { + if (!document.fullscreenElement) { + exitFullscreenButton.showElement(); + enterFullscreenButton.hideElement(); + + document.documentElement.requestFullscreen(); + return; + } + + enterFullscreenButton.showElement(); + exitFullscreenButton.hideElement(); + + document.exitFullscreen(); + } + + function closeCountdownContainer() { + preventOpenCountdown = true; + countdownContainerReference.hideElement(); + countdownContainerReference.classList.remove('even', 'odd'); + countdownNumber.textContent = ''; + stopSound.pause(); + stopSound.currentTime = 0; + stopSound.volume = 0; + } + + function toggleButtonsContainer(isEditing) { + if (isEditing) { + actionButtonsContainer.hideElement(); + editActionButtonsContainer.showElement(); + } else { + actionButtonsContainer.showElement(); + editActionButtonsContainer.hideElement(); + } + } + + function toggleDisableInputs(disable) { + hourInput.disabled = disable; + minuteInput.disabled = disable; + secondInput.disabled = disable; + } + + function showDefaultButtons() { + startButton.showElement(); + pauseButton.hideElement(); + stopButton.hideElement(); + editButton.showElement(); + } + + function getInputsValueAsSeconds() { + const { seconds, minutes, hours } = getInputValues(); + + const minutesAsSeconds = minuteToSeconds(minutes); + const hourAsSeconds = hourToSeconds(hours); + + return seconds + minutesAsSeconds + hourAsSeconds; + } + + function getInputValues() { + const hours = parseInt(hourInput.value) || 0; + const minutes = parseInt(minuteInput.value) || 0; + const seconds = parseInt(secondInput.value) || 0; + + return { seconds, minutes, hours }; + } + + function setInputValues(totalSeconds = 0) { + const hours = secondsToHour(totalSeconds); + const minutes = secondsToMinute(totalSeconds); + const seconds = remainingSeconds(totalSeconds); + + hourInput.value = formatTimeUnit(hours); + minuteInput.value = formatTimeUnit(minutes); + secondInput.value = formatTimeUnit(seconds); + } + + init(); +} + +document.addEventListener('DOMContentLoaded', function () { + let reference = document.querySelector('.js-body'); + const timerController = new TimerController(reference); + window.timerController = timerController; +}); diff --git a/asset/javascript/TimeUtils.js b/asset/javascript/TimeUtils.js new file mode 100644 index 0000000..ceb7492 --- /dev/null +++ b/asset/javascript/TimeUtils.js @@ -0,0 +1,32 @@ +const hourToSeconds = hour => { + return hour * 3600; +}; + +const minuteToSeconds = minute => { + return minute * 60; +}; + +const secondsToHour = seconds => { + return Math.floor(seconds / 3600); +}; + +const secondsToMinute = seconds => { + return Math.floor((seconds % 3600) / 60); +}; + +const remainingSeconds = seconds => { + return seconds % 60; +}; + +const formatTimeUnit = value => { + return value.toString().padStart(2, '0').slice(-2); +}; + +export { + hourToSeconds, + minuteToSeconds, + secondsToHour, + secondsToMinute, + remainingSeconds, + formatTimeUnit, +}; diff --git a/asset/javascript/TimerStatus.js b/asset/javascript/TimerStatus.js new file mode 100644 index 0000000..6f92340 --- /dev/null +++ b/asset/javascript/TimerStatus.js @@ -0,0 +1,11 @@ +export const TimerStatus = { + STOPPED: 'STOPPED', + PAUSED: 'PAUSED', + COUNTDOWN: 'COUNTDOWN', + RUNNING: 'RUNNING', + EDITING: 'EDITING', + isRunning: status => status === TimerStatus.RUNNING, + isCountdown: status => status === TimerStatus.COUNTDOWN, + isPaused: status => status === TimerStatus.PAUSED, + isStopped: status => status === TimerStatus.STOPPED, +}; diff --git a/asset/javascript/helper.js b/asset/javascript/helper.js new file mode 100644 index 0000000..e5ab8b2 --- /dev/null +++ b/asset/javascript/helper.js @@ -0,0 +1,11 @@ +HTMLElement.prototype.showElement = function showElement() { + if (this.classList.contains('hide')) { + this.classList.remove('hide'); + } +}; + +HTMLElement.prototype.hideElement = function hideElement() { + if (!this.classList.contains('hide')) { + this.classList.add('hide'); + } +}; diff --git a/asset/sound/stop.mp3 b/asset/sound/stop.mp3 new file mode 100644 index 0000000..3fcd229 Binary files /dev/null and b/asset/sound/stop.mp3 differ diff --git a/asset/sound/tick-tack.wav b/asset/sound/tick-tack.wav new file mode 100644 index 0000000..41817d8 Binary files /dev/null and b/asset/sound/tick-tack.wav differ diff --git a/asset/style/timer.css b/asset/style/timer.css new file mode 100644 index 0000000..17a8128 --- /dev/null +++ b/asset/style/timer.css @@ -0,0 +1,137 @@ +@import url("https://fonts.googleapis.com/css2?family=Orbitron:wght@400..900&display=swap"); +:root { + --primary-bg-color: #242424; + --primary-text-color: #46ffbe; + --secondary-bg-color: #46ffbe; + --secondary-text-color: #444444; +} + +* { + font-family: "Orbitron", sans-serif; + box-sizing: border-box; +} + +body, +html { + background-color: var(--primary-bg-color); + color: var(--primary-text-color); + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + gap: 48px; + margin: 0; + height: 100dvh; + width: 100dvw; +} +body::-moz-selection, html::-moz-selection { + background: var(--secondary-bg-color); + color: var(--secondary-text-color); +} +body::selection, +html::selection { + background: var(--secondary-bg-color); + color: var(--secondary-text-color); +} +body ::-moz-selection, +html ::-moz-selection { + background: var(--secondary-bg-color); + color: var(--secondary-text-color); +} + +.countdown-container { + width: 100%; + height: 100%; + position: absolute; + background: var(--primary-bg-color); + display: flex; + align-items: center; + justify-content: center; + flex-direction: column; + transition: all 0.5s ease; +} +.countdown-container .countdown-number { + font-size: 202px; + transition: all 0.5s ease; +} +.countdown-container.odd { + background: var(--secondary-bg-color); + color: var(--secondary-text-color); +} +.countdown-container.even { + background: var(--primary-bg-color); + color: var(--primary-text-color); +} + +.input-stopwatch-container { + width: 100%; + display: flex; + justify-content: center; + transition: all 0.5s ease; + font-size: 112px; +} +@media screen and (max-width: 768px) { + .input-stopwatch-container { + font-size: 56px; + } +} +.input-stopwatch-container input { + width: auto; + min-width: 1px; + max-width: 2ch; + margin: 0; + padding: 0; + color: var(--primary-text-color); + border: none; + background: none; + outline: none; + text-align: center; + font-size: inherit; + border-radius: 5px; +} +.input-stopwatch-container input::-webkit-outer-spin-button, .input-stopwatch-container input::-webkit-inner-spin-button { + -webkit-appearance: none; + margin: 0; +} +.input-stopwatch-container input[type=number] { + appearance: none; + -webkit-appearance: none; + -moz-appearance: textfield; +} + +.action-button-container { + display: flex; + flex-direction: row; + gap: 32px; +} +.action-button-container button { + display: flex; + align-items: center; + justify-content: center; + padding: 0; + height: 32px; + width: 32px; + background-color: transparent; + border: 1px solid white; + border-radius: 99px; + color: inherit; + cursor: pointer; + transition: all 0.5s ease; +} +.action-button-container button img { + max-width: 100%; + max-height: 100%; + display: block; +} +.action-button-container button:hover { + background-color: rgba(255, 255, 255, 0.15); +} +.action-button-container button:active, .action-button-container button:focus, .action-button-container button:focus-visible, .action-button-container button:focus-within { + box-shadow: 0 0 10px var(--secondary-bg-color); + background-color: rgba(255, 255, 255, 0.15); + outline: none; +} + +.hide { + display: none !important; +}/*# sourceMappingURL=timer.css.map */ \ No newline at end of file diff --git a/asset/style/timer.css.map b/asset/style/timer.css.map new file mode 100644 index 0000000..0c5bc96 --- /dev/null +++ b/asset/style/timer.css.map @@ -0,0 +1 @@ +{"version":3,"sources":["timer.scss","timer.css"],"names":[],"mappings":"AAAQ,2FAAA;AAER;EACI,2BAAA;EACA,6BAAA;EACA,6BAAA;EACA,+BAAA;ACAJ;;ADGA;EACI,mCAAA;EACA,sBAAA;ACAJ;;ADGA;;EAEI,yCAAA;EACA,gCAAA;EACA,aAAA;EACA,sBAAA;EACA,uBAAA;EACA,mBAAA;EACA,SAAA;EACA,SAAA;EACA,cAAA;EACA,aAAA;ACAJ;ADEI;EACI,qCAAA;EACA,kCAAA;ACCR;ADHI;;EACI,qCAAA;EACA,kCAAA;ACCR;ADEI;;EACI,qCAAA;EACA,kCAAA;ACCR;;ADGA;EACI,WAAA;EACA,YAAA;EACA,kBAAA;EACA,mCAAA;EACA,aAAA;EACA,mBAAA;EACA,uBAAA;EACA,sBAAA;EACA,yBAAA;ACAJ;ADEI;EACI,gBAAA;EACA,yBAAA;ACAR;ADGI;EACI,qCAAA;EACA,kCAAA;ACDR;ADII;EACI,mCAAA;EACA,gCAAA;ACFR;;ADMA;EACI,WAAA;EACA,aAAA;EACA,uBAAA;EACA,yBAAA;EACA,gBAAA;ACHJ;ADKI;EAPJ;IAQQ,eAAA;ECFN;AACF;ADII;EACI,WAAA;EACA,cAAA;EACA,cAAA;EACA,SAAA;EACA,UAAA;EACA,gCAAA;EACA,YAAA;EACA,gBAAA;EACA,aAAA;EACA,kBAAA;EACA,kBAAA;EACA,kBAAA;ACFR;ADIQ;EAEI,wBAAA;EACA,SAAA;ACHZ;ADMQ;EACI,gBAAA;EACA,wBAAA;EACA,0BAAA;ACJZ;;ADSA;EACI,aAAA;EACA,mBAAA;EACA,SAAA;ACNJ;ADQI;EACI,aAAA;EACA,mBAAA;EACA,uBAAA;EACA,UAAA;EACA,YAAA;EACA,WAAA;EACA,6BAAA;EACA,uBAAA;EACA,mBAAA;EACA,cAAA;EACA,eAAA;EACA,yBAAA;ACNR;ADQQ;EACI,eAAA;EACA,gBAAA;EACA,cAAA;ACNZ;ADSQ;EACI,2CAAA;ACPZ;ADUQ;EAII,8CAAA;EACA,2CAAA;EACA,aAAA;ACXZ;;ADgBA;EACI,wBAAA;ACbJ","file":"timer.css"} \ No newline at end of file diff --git a/asset/style/timer.scss b/asset/style/timer.scss new file mode 100644 index 0000000..f773877 --- /dev/null +++ b/asset/style/timer.scss @@ -0,0 +1,147 @@ +@import url("https://fonts.googleapis.com/css2?family=Orbitron:wght@400..900&display=swap"); + +:root { + --primary-bg-color: #242424; + --primary-text-color: #46ffbe; + --secondary-bg-color: #46ffbe; + --secondary-text-color: #444444; +} + +* { + font-family: "Orbitron", sans-serif; + box-sizing: border-box; +} + +body, +html { + background-color: var(--primary-bg-color); + color: var(--primary-text-color); + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + gap: 48px; + margin: 0; + height: 100dvh; + width: 100dvw; + + &::selection { + background: var(--secondary-bg-color); + color: var(--secondary-text-color); + } + + ::-moz-selection { + background: var(--secondary-bg-color); + color: var(--secondary-text-color); + } +} + +.countdown-container { + width: 100%; + height: 100%; + position: absolute; + background: var(--primary-bg-color); + display: flex; + align-items: center; + justify-content: center; + flex-direction: column; + transition: all 0.5s ease; + + .countdown-number { + font-size: 202px; + transition: all 0.5s ease; + } + + &.odd { + background: var(--secondary-bg-color); + color: var(--secondary-text-color); + } + + &.even { + background: var(--primary-bg-color); + color: var(--primary-text-color); + } +} + +.input-stopwatch-container { + width: 100%; + display: flex; + justify-content: center; + transition: all 0.5s ease; + font-size: 112px; + + @media screen and (max-width: 768px) { + font-size: 56px; + } + + input { + width: auto; + min-width: 1px; + max-width: 2ch; + margin: 0; + padding: 0; + color: var(--primary-text-color); + border: none; + background: none; + outline: none; + text-align: center; + font-size: inherit; + border-radius: 5px; + + &::-webkit-outer-spin-button, + &::-webkit-inner-spin-button { + -webkit-appearance: none; + margin: 0; + } + + &[type="number"] { + appearance: none; + -webkit-appearance: none; + -moz-appearance: textfield; + } + } +} + +.action-button-container { + display: flex; + flex-direction: row; + gap: 32px; + + button { + display: flex; + align-items: center; + justify-content: center; + padding: 0; + height: 32px; + width: 32px; + background-color: transparent; + border: 1px solid white; + border-radius: 99px; + color: inherit; + cursor: pointer; + transition: all 0.5s ease; + + img { + max-width: 100%; + max-height: 100%; + display: block; + } + + &:hover { + background-color: rgba(255, 255, 255, 0.15); + } + + &:active, + &:focus, + &:focus-visible, + &:focus-within { + box-shadow: 0 0 10px var(--secondary-bg-color); + background-color: rgba(255, 255, 255, 0.15); + outline: none; + } + } +} + +.hide { + display: none !important; +} diff --git a/index.html b/index.html index 4f20ad2..38e315b 100644 --- a/index.html +++ b/index.html @@ -1,50 +1,62 @@ - + + - - Cronômetro - - -
- - : - - : - + + + Timer <Codecon> + + + +
+ + : + + : +
-
- - - - - + +
+ + + + + +
-
- - + +
+ +
-
- - + +
+ +
+ +
- - - + + + + \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 86b820a..008b4aa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "license": "ISC", "devDependencies": { "parcel": "^2.13.3", + "prettier": "^3.5.3", "svgo": "^3.3.2" } }, @@ -3108,6 +3109,22 @@ "node": ">=12" } }, + "node_modules/prettier": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz", + "integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/react-error-overlay": { "version": "6.0.9", "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.9.tgz", diff --git a/package.json b/package.json index a8b21f0..0de36d3 100644 --- a/package.json +++ b/package.json @@ -4,13 +4,15 @@ "description": "", "scripts": { "dev": "parcel index.html", - "build": "parcel build index.html --public-url ./" + "build": "parcel build index.html --public-url ./", + "format": "prettier --write \"**/*.js\"" }, "keywords": [], "author": "", "license": "ISC", "devDependencies": { "parcel": "^2.13.3", + "prettier": "^3.5.3", "svgo": "^3.3.2" } } diff --git a/script.js b/script.js deleted file mode 100644 index e7cc8ce..0000000 --- a/script.js +++ /dev/null @@ -1,227 +0,0 @@ -const TimerStatus = { - STOPPED: "STOPPED", - PAUSED: "PAUSED", - COUNTDOWN: "COUNTDOWN", - RUNNING: "RUNNING", - EDITING: "EDITING", - isRunning: (status) => status === TimerStatus.RUNNING, - isCountdown: (status) => status === TimerStatus.COUNTDOWN, -}; - -const DEFAULT_INTERVAL = 1000; -const DEFAULT_SECONDS = 30; -let TIMER_STATUS = TimerStatus.STOPPED; - -setInputValues(DEFAULT_SECONDS); - -document.getElementById("startBtn").addEventListener("click", start); -document.getElementById("pauseBtn").addEventListener("click", pause); -document.getElementById("toZeroBtn").addEventListener("click", stop); -document.getElementById("restartBtn").addEventListener("click", stop); -document - .querySelector(".js-edit-button") - .addEventListener("click", openEditInput); -document - .querySelector(".js-finish-edit-button") - .addEventListener("click", closeEditInput); -document - .querySelector(".js-cancel-button") - .addEventListener("click", cancelEditInput); -document - .querySelector(".js-active-fullscreen") - .addEventListener("click", handleFullscreen); - -document.getElementById("hour").addEventListener("input", function () { - validateInput(this, 99); -}); -document.getElementById("min").addEventListener("input", function () { - validateInput(this, 59); -}); -document.getElementById("sec").addEventListener("input", function () { - validateInput(this, 59); -}); - -function timer() { - let seconds = getInputSeconds(); - - setTimeout(function () { - if ( - TimerStatus.isRunning(TIMER_STATUS) || - TimerStatus.isCountdown(TIMER_STATUS) - ) { - seconds--; - - if (seconds <= 10) { - TIMER_STATUS = TimerStatus.COUNTDOWN; - styleBlinkCountdown(seconds); - } - - setInputValues(seconds); - if (seconds == 0) { - document.body.classList.add("zero"); - return; - } else { - timer(); - } - } - }, DEFAULT_INTERVAL); -} - -function validateInput(input, maxValue) { - let value = parseInt(input.value) || 0; - - if (value < 0) value = 0; - if (value > maxValue) value = maxValue; - - input.value = formatInput(value); -} - -function formatInput(value = 0) { - return value.toString().padStart(2, "0").slice(-2); -} - -function getInputSeconds() { - const { seconds, minutes, hours } = getInputValues(); - - const sumMinutes = hours * 60 + minutes; - const sumSeconds = sumMinutes * 60 + seconds; - - return sumSeconds; -} - -function getInputValues() { - const hours = parseInt(document.getElementById("hour").value) || 0; - const minutes = parseInt(document.getElementById("min").value) || 0; - const seconds = parseInt(document.getElementById("sec").value) || 0; - - return { seconds, minutes, hours }; -} - -function setInputValues(fromSeconds = 0) { - const hours = Math.floor(fromSeconds / 3600); - const minutes = Math.floor((fromSeconds % 3600) / 60); - const seconds = fromSeconds % 60; - - document.getElementById(`sec`).value = formatInput(seconds); - document.getElementById(`min`).value = formatInput(minutes); - document.getElementById(`hour`).value = formatInput(hours); -} - -function start() { - TIMER_STATUS = TimerStatus.RUNNING; - applyStyles(); - timer(); -} - -function pause() { - TIMER_STATUS = TimerStatus.PAUSED; - applyStyles(); -} - -function stop() { - TIMER_STATUS = TimerStatus.STOPPED; - applyStyles(); - setInputValues(DEFAULT_SECONDS); -} - -function handleFullscreen() { - if (!document.fullscreenElement) { - document.documentElement.requestFullscreen(); - document.querySelector(".js-active-fullscreen").style = - "background-color: rgba(255, 255, 255, 0.25)"; - } else { - document.exitFullscreen(); - document.querySelector(".js-active-fullscreen").style = ""; - } -} - -let PRE_EDIT_SECONDS = DEFAULT_SECONDS; - -function cancelEditInput() { - setInputValues(PRE_EDIT_SECONDS); - closeEditInput(); -} - -function openEditInput() { - TIMER_STATUS = TimerStatus.EDITING; - PRE_EDIT_SECONDS = getInputSeconds(); - document.getElementById("hour").disabled = false; - document.getElementById("min").disabled = false; - document.getElementById("sec").disabled = false; - - document.querySelector(".js-stopwatch-button").classList.add("hide"); - document - .querySelector(".js-edit-container-stopwatch") - .classList.remove("hide"); -} - -function closeEditInput() { - TIMER_STATUS = TimerStatus.PAUSED; - document.getElementById("hour").disabled = true; - document.getElementById("min").disabled = true; - document.getElementById("sec").disabled = true; - - document.querySelector(".js-stopwatch-button").classList.remove("hide"); - document.querySelector(".js-edit-container-stopwatch").classList.add("hide"); - - applyStyles(); -} - -function resetStyles() { - document.querySelector(".js-play-button").classList.remove("press-start"); - document.querySelector(".js-stop-button").classList.remove("press-stop"); - document.querySelector(".js-pause-button").classList.remove("press-pause"); - document.querySelector(".js-edit-button").classList.remove("press-edit"); - document.querySelector(".input-stopwatch").classList.remove("hide"); - document.querySelector(".js-stopwatch-button").classList.remove("hide"); - document.getElementById("countdown").classList.add("hide"); - document.body.classList.remove("zero"); -} - -const styles = { - RUNNING: styleRunning, - PAUSED: stylePause, - STOPED: styleStop, - EDITING: styleEditing, -}; - -function applyStyles() { - resetStyles(); - styles[TIMER_STATUS]?.(); -}; - -function styleBlinkCountdown(seconds = 10) { - document.querySelector(".input-stopwatch").classList.add("hide"); - document.querySelector(".js-stopwatch-button").classList.add("hide"); - document.getElementById("countdown").classList.remove("hide"); - document.getElementById("countdown-number").textContent = seconds; - - // Alterna a cor a cada segundo - if (seconds % 2 === 0) { - document.getElementById("countdown").style.backgroundColor = "#46ffbe"; - document.getElementById("countdown-number").style.color = "#444444"; - } else { - document.getElementById("countdown").style.backgroundColor = "#242424"; - document.getElementById("countdown-number").style.color = "#46ffbe"; - } -}; - -// function styleCountdown(seconds = 0) { - -// } - -function styleRunning() { - document.querySelector(".js-play-button").classList.add("press-start"); -} - -function styleStop() { - document.querySelector(".js-stop-button").classList.add("press-stop"); -} - -function stylePause() { - document.querySelector(".js-pause-button").classList.add("press-pause"); -} - -function styleEditing() { - document.querySelector(".js-edit-button").classList.add("press-edit"); -} diff --git a/style.css b/style.css deleted file mode 100644 index 5ecd07c..0000000 --- a/style.css +++ /dev/null @@ -1,132 +0,0 @@ -@import url("https://fonts.googleapis.com/css2?family=Orbitron:wght@400..900&display=swap"); - -:root { - --primary-bg-color: #242424; - --primary-text-color: #46ffbe; - --secondary-bg-color: #46ffbe; - --secondary-text-color: #444444; -} - -*:not(#countdown-number) { - font-family: "Orbitron", sans-serif; - font-size: 112px; -} - -body { - background-color: var(--primary-bg-color); - color: var(--primary-text-color); - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - margin: 0px; - height: 100dvh; - width: 100dvw; - transition: background-color 0.5s ease; -} - -body.zero { - background-color: var(--secondary-bg-color); - color: var(--secondary-text-color); -} - -.input-stopwatch { - width: 100%; - display: flex; - justify-content: center; -} - -input { - min-width: 1px; - width: auto; - max-width: 2ch; - color: var(--primary-text-color); - border: none; - background: none; - outline: none; - text-align: center; -} - -button { - background-color: transparent; - border: 1px solid white; - width: 32px; - height: 32px; - border-radius: 99px; - display: flex; - align-items: center; - justify-content: center; - cursor: pointer; - color: inherit; -} - -.js-restart-button { - border: 1px solid inherit; -} - -button:hover { - background-color: rgba(255, 255, 255, 0.15); -} - -.button-wrapper { - display: flex; - flex-direction: row; - gap: 32px; -} - -.hide { - display: none !important; -} - -input::-webkit-outer-spin-button, -input::-webkit-inner-spin-button { - -webkit-appearance: none; - margin: 0; -} - -input[type=number] { - /* ta aparecendo um warning aqui pra mim, mas não sei arrumar, talvez eu seja burro */ - /* - Cara tu não é burro -moz-appearance é específica para Firefox - Para melhorar compatibilidade adicionei duas duas propriedades - */ - -moz-appearance: textfield; /* Para Firefox */ - -webkit-appearance: none; /* Para Chrome, Safari, Edge */ - appearance: none; /* Padrão - navegadores mais modernos */ -} - -#countdown { - display: flex; - justify-content: center; - align-items: center; - position: fixed; - flex-direction: column; - top: 0; - left: 0; - width: 100%; - height: 100%; - background-color: inherit; - color: inherit; - z-index: 1000; - font-size: 202px; -} - -#countdown.hide { - display: none; -} - -.press-start { - background-color: rgba(70, 255, 190, 0.25); -} - -.press-stop { - background-color: rgba(255, 70, 70, 0.25); -} - -.press-pause { - background-color: rgba(255, 221, 70, 0.25); -} - -.press-edit { - background-color: rgba(255, 255, 255, 0.25); -}