Skip to content

Instantly share code, notes, and snippets.

@mhv75
Created January 10, 2021 00:11
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mhv75/c772f3c850a89bdf73beed0428e5edc2 to your computer and use it in GitHub Desktop.
Save mhv75/c772f3c850a89bdf73beed0428e5edc2 to your computer and use it in GitHub Desktop.
freeCodeCamp: Pomodoro Clock

freeCodeCamp: Pomodoro Clock

Objective: Build a CodePen.io app that is functionally similar to this: https://codepen.io/FreeCodeCamp/full/aNyxXR/.

Fulfill the below user stories. Use whichever libraries or APIs you need. Give it your own personal style.

  • User Story: I can start a 25 minute pomodoro, and the timer will go off once 25 minutes has elapsed.
  • User Story: I can reset the clock for my next pomodoro.
  • User Story: I can customize the length of each pomodoro.

A Pen by JoeCodesStuff on CodePen.

License.

<!-- --------------------------------------------- -->
<!-- Image inspired by Tide, a pomodoro app I use. -->
<!-- Check it out at: https://tide.moreless.io/en/ -->
<!-- --------------------------------------------- -->
<div id="pomodoro">
<div id="clock">
<div id="timer">
<div id="title">Ready?</div>
<div id="countdown">
<span id="minutes">30</span>
<span id="seconds">00</span>
</div>
<div id="controls" class="reset">
<div id="start"><i class="fas fa-play"></i> Start</div>
<div id="pause"><i class="fas fa-pause"></i> Pause</div>
<div id="reset"><i class="fas fa-sync-alt"></i> Reset</div>
</div>
</div>
</div>
<div id="options">
<div id="session">
<i id="incrSession" class="fas fa-angle-double-up"></i>
<span class="option-title">Session</span>
<input id="sessionInput" type="number" value="30" max="60" min="5">
<i id="decrSession" class="fas fa-angle-double-down"></i>
</div>
<div id="break">
<i id="incrBreak" class="fas fa-angle-double-up"></i>
<span class="option-title">Break</span>
<input id="breakInput" type="number" value="5" max="10" min="1">
<i id="decrBreak" class="fas fa-angle-double-down"></i>
</div>
</div>
</div>
<div id="audio-selector">
<div id="forest" class="theme"><!--🌲 -->Forest</div>
<div id="ocean" class="theme"><!--🌊 -->Ocean</div>
<div id="rainy" class="selected theme"><!--🌧 -->Rainy</div>
<div id="peace" class="theme"><!--🧘 -->Peace</div>
<div id="cafe" class="theme"><!--☕ -->Caf&eacute;</div>
</div>
<audio loop autoplay="false" src="https://joeweaver.me/codepenassets/freecodecamp/challenges/build-a-pomodoro-clock/rain.mp3">
$(() => {
let $audio = $("audio"), // from https://tide.moreless.io/en/
$theme = $(".theme"),
$title = $("#title"),
$controls = $("#controls"),
$options = $("#options"),
$minutes = $("#minutes"),
$seconds = $("#seconds"),
$start = $("#start"),
$pause = $("#pause"),
$reset = $("#reset"),
$incrSession = $("#incrSession"),
$sessionInput = $("#sessionInput"),
$decrSession = $("#decrSession"),
$incrBreak = $("#incrBreak"),
$breakInput = $("#breakInput"),
$decrBreak = $("#decrBreak"),
breakLength = 5 * 60,
breakMax = 10,
breakMin = 1,
sessionLength = 30 * 60,
sessionMax = 60,
sessionMin = 5,
sessionNum = 0,
countdown,
countType,
remainingTime = sessionLength;
init();
function init(){
$audio.prop("volume", 0);
$incrSession.click(() => incrSession());
$decrSession.click(() => decrSession());
$incrBreak.click(() => incrBreak());
$decrBreak.click(() => decrBreak());
$sessionInput.on("change", e => updateSession(e.target.value));
$breakInput.on("change", e => updateBreak(e.target.value));
$start.click(() => { if (countType === "break"){ startBreak(); } else { startSession(); } });
$pause.click(() => pause());
$reset.click(() => reset());
$theme.click(e => audioSelect(e));
}
function startSession(){
sessionNum++;
countType = "session";
$options.slideUp(143);
$controls.removeClass().addClass("started");
$title.fadeOut(43, function(){
$(this).html("Session " + sessionNum).fadeIn();
});
$audio.animate({volume: 1}, 1000);
start(remainingTime || sessionLength);
}
function startBreak(){
countType = "break";
$title.fadeOut(43, function(){
$(this).html("Break " + sessionNum).fadeIn();
});
$audio.animate({volume: 0}, 5000);
start(remainingTime || breakLength);
}
function start(timeLeft){
clearInterval(countdown);
countdown = setInterval(() => {
timeLeft--;
remainingTime = timeLeft;
let minLeft = Math.floor(timeLeft / 60),
secLeft = timeLeft - minLeft * 60;
updateMinutes(minLeft);
updateSeconds(secLeft < 10 ? "0" + secLeft : secLeft);
if (timeLeft < 1){
if (countType === "session"){
startBreak(breakLength);
} else {
startSession();
}
}
}, 1000);
}
function pause(){
sessionNum--;
$audio.animate({volume: 0}, 1000);
clearInterval(countdown);
$options.slideDown(143);
$controls.removeClass().addClass("paused");
$title.fadeOut(43, function(){
$(this).html("Paused").fadeIn();
});
}
function reset(){
clearInterval(countdown);
updateMinutes(sessionLength / 60);
updateSeconds("00");
countType = undefined;
$controls.removeClass().addClass("reset");
$title.html("Ready?");
remainingTime = sessionLength;
}
function incrSession(){
let num = Number($sessionInput.val());
num = num + (num === sessionMax ? 0 : 1);
sessionLength = num * 60;
updateSession(num);
updateMinutes(num);
updateSeconds("00");
reset();
}
function decrSession(){
let num = Number($sessionInput.val());
num = num - (num === sessionMin ? 0 : 1);
sessionLength = num * 60;
updateSession(num);
updateMinutes(num);
updateSeconds("00");
reset();
}
function incrBreak(){
let num = Number($breakInput.val());
num = num + (num === breakMax ? 0 : 1);
breakLength = num * 60;
updateBreak(num);
reset();
}
function decrBreak(){
let num = Number($breakInput.val());
num = num - (num === breakMin ? 0 : 1);
breakLength = num * 60;
updateBreak(num);
reset();
}
function updateMinutes(num){
$minutes.text(num);
}
function updateSeconds(num){
$seconds.text(num);
}
function updateSession(num){
num = num < sessionMin ? sessionMin : num > sessionMax ? sessionMax : num;
$sessionInput.val(num).blur();
updateMinutes(num);
updateSeconds("00");
sessionLength = num * 60;
reset();
}
function updateBreak(num){
$breakInput.val(num < breakMin ? breakMin : num > breakMax ? breakMax : num).blur();
breakLength = num * 60;
reset();
}
function audioSelect(e){
$theme.removeClass("selected");
$(e.target).addClass("selected");
switch(e.target.id){
case "forest": $audio.attr("src", "https://joeweaver.me/codepenassets/freecodecamp/challenges/build-a-pomodoro-clock/forest.mp3"); break;
case "ocean": $audio.attr("src", "https://joeweaver.me/codepenassets/freecodecamp/challenges/build-a-pomodoro-clock/ocean.mp3"); break;
case "rainy": $audio.attr("src", "https://joeweaver.me/codepenassets/freecodecamp/challenges/build-a-pomodoro-clock/rain.mp3"); break;
case "peace": $audio.attr("src", "https://joeweaver.me/codepenassets/freecodecamp/challenges/build-a-pomodoro-clock/peace.mp3"); break;
case "cafe": $audio.attr("src", "https://joeweaver.me/codepenassets/freecodecamp/challenges/build-a-pomodoro-clock/cafe.mp3"); break;
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script src="https://use.fontawesome.com/releases/v5.0.6/js/all.js"></script>
* {
box-sizing: border-box;
outline: none;
user-select: none;
}
body {
align-items: center;
background: radial-gradient(ellipse at center, rgba(255,179,206,0) 0%, rgba(255,180,199,0) 43%, rgba(255,181,189,0.43) 100%);
background-attachment: fixed;
background-repeat: no-repeat;
display: flex;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00ffb3ce', endColorstr='#6effb5bd', GradientType=1);
flex-direction: column;
font-family: "Open Sans", sans-serif;
height: 100vh;
justify-content: center;
min-width: 184px;
overflow-x: hidden;
transition: background-color .2s;
}
#pomodoro {
margin-bottom: 43px;
max-width: 512px;
width: 100%;
#clock {
background: url(http://joeweaver.me/codepenassets/freecodecamp/challenges/build-a-pomodoro-clock/bg.png) no-repeat center center;
background-size: contain;
display: flex;
justify-content: center;
margin-bottom: -6.25vw;
padding: 40vw 0;
@media all and (min-width: 512px){
margin-bottom: -32px;
padding: 204px 0;
}
@media all and (max-width: 184px){
margin-bottom: -20px;
margin-bottom: -11.5px;
padding: 73px 0;
}
#timer {
$hover: 5;
align-items: center;
cursor: pointer;
display: flex;
flex-direction: column;
#title {
color: #FF8B8A;
font-size: 28px;
line-height: 28px;
margin: 3px 0 1px;
@media all and (max-width: 512px){
font-size: 5.5vw;
line-height: 5.5vw;
}
@media all and (max-width: 184px){
font-size: 10px;
line-height: 10px;
}
}
#countdown {
color: #ff778d;
display: flex;
font-size: 43px;
font-weight: 600;
line-height: 43px;
@media all and (max-width: 512px){
font-size: 8.4vw;
line-height: 8.4vw;
}
@media all and (max-width: 184px){
font-size: 15.4px;
line-height: 15.4px;
}
#minutes::after {
content: ":";
}
}
#controls {
align-items: center;
display: flex;
color: #FF6292;
flex-direction: column;
font-size: 15px;
font-weight: 700;
line-height: 15px;
margin-top: 5px;
text-align: center;
text-transform: uppercase;
.fas, .svg-inline--fa {
position: relative;
}
#start, #pause, #reset {
white-space: nowrap;
}
#start {
.fa-play {
font-size: 77%;
top: -1px;
}
&:hover {
color: darken(#FF6292, $hover);
}
}
#pause {
.fa-pause {
font-size: 90%;
top: -1px;
transform: scaleX(.84);
}
&:hover {
color: darken(#FF6292, $hover);
}
}
#reset {
position: absolute;
margin-top: 17.56px;
transition: margin-top .43s, opacity .43s;
.fa-sync-alt {
font-size: 84%;
}
&:hover {
color: darken(#FF6292, $hover);
}
}
&.reset {
#pause {
display: none;
}
#reset {
margin-top: 0;
opacity: 0;
z-index: -1;
}
}
&.started {
#start {
display: none;
}
#reset {
opacity: 0;
}
}
&.paused {
#pause {
display: none;
}
}
@media all and (max-width: 512px){
font-size: 5.5vw;
line-height: 5.5vw;
#start, #pause, #reset {
font-size: 3vw;
line-height: 3vw;
}
#reset {
margin-top: 3.43vw;
}
}
@media all and (max-width: 184px){
font-size: 10px;
line-height: 10px;
#start, #pause, #reset {
font-size: 5.5px;
line-height: 5.5px;
}
#reset {
margin-top: 6.31px;
}
}
}
}
}
#options {
color: #FF6292;
display: flex;
font-size: 28px;
font-weight: normal;
justify-content: center;
margin-bottom: 28px;
text-align: center;
.option-title {
color: #FF6F8A;
font-size: 14px;
font-weight: bold;
margin-top: 8px;
margin-bottom: -6px;
text-transform: uppercase;
}
.fa-angle-double-up {
color: #FF848A;
cursor: pointer;
}
.fa-angle-double-down {
color: #FF5792;
cursor: pointer;
}
#session, #break {
align-items: center;
display: flex;
flex-direction: column;
}
input[type=number]{
background: linear-gradient(lighten(#FF8B8A, 20), lighten(#FF6292, 22));
border: 0;
border-bottom: calc(3px + .28vw) solid #FF6292;
border-radius: 10px;
color: #FF6292;
font: bold calc(21px + 1.43vw) "Open Sans", sans-serif;
margin: calc(5px + .43vw) calc(3px + .43vw);
padding: calc(3px + .43vw) calc(5px + .43vw);
text-align: center;
&::-webkit-inner-spin-button,
&::-webkit-outer-spin-button {
-webkit-appearance: none;
}
@media all and (min-width: 512px){
border-bottom: 4.43px solid #FF6292;
font: bold 28.43px "Open Sans", sans-serif;
margin: 7.2px 5.2px;
padding: 5.2px 7.2px;
}
@media all and (max-width: 184px){
border-bottom: 3.5px solid #FF6292;
font: bold 23.6px "Open Sans", sans-serif;
margin: 5.7px 3.7px;
padding: 3.7px 5.7px;
}
}
}
}
audio {
display: none;
}
#audio-selector {
display: flex;
left: 0;
position: fixed;
top: 0;
width: 100vw;
.theme {
background: linear-gradient(to bottom, rgba(255,99,127,0.28) 0%, rgba(255,99,127,0.28) 1%, rgba(255,98,143,0) 84%, rgba(255,98,146,0) 100%);
color: darken(desaturate(#FF7B8A, 30), 10);
cursor: pointer;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#47ff637f', endColorstr='#00ff6292', GradientType=0);
flex: 1 1 25%;
font: 600 calc(9px + 1vw) "Open Sans", sans-serif;
margin: 0 2px 0 0;
padding: 2vw 1vw 5vw;
text-align: center;
transition: all .43s;
white-space: nowrap;
&:last-child {
margin-right: 0;
}
&:hover, &.selected {
background: linear-gradient(to bottom, rgba(255,99,127,0.43) 0%, rgba(255,99,127,0.43) 1%, rgba(255,98,143,0) 84%, rgba(255,98,146,0) 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#6eff637f', endColorstr='#00ff6292', GradientType=0);
}
@media all and (min-width: 512px){
font-size: 15.33px;
padding: 9px 5px 25px;
}
}
@media all and (max-width: 300px){
flex-wrap: wrap;
.theme {
flex: 1 0 100%;
font-size: 14px;
padding: 7px;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment