Skip to content

Instantly share code, notes, and snippets.

@weirdyang
Created February 25, 2024 12:24
Show Gist options
  • Select an option

  • Save weirdyang/682be5041c7a0ad8c2546edcf81ef101 to your computer and use it in GitHub Desktop.

Select an option

Save weirdyang/682be5041c7a0ad8c2546edcf81ef101 to your computer and use it in GitHub Desktop.
Do πŸ’― things βœ…

Do πŸ’― things βœ…

Do 100 things, and track it with this ugly ass tracker.

A Pen by yo on CodePen.

License.

<div class="modal-overlay close" id="modal-overlay"></div>
<main class="dark">
<section class="controls">
<section class="controls-right">
<button class="reset-button button button-reset">
reset
</button>
<button class="export-button button button-export">
export</button>
</section>
<button class="theme-button button button-theme"></button>
</section>
<section class="cells">
</section>
</main>
<article class="card modal dark" id="modal">
<section class="card-header">
<h1 class="title">The Thing</h1>
<p class="subtitle muted"></p>
</section>
<section class="card-content">
<div class="card-content-item">
<div class="item-text-description">
<textarea class="item-text-content" cols="120" rows="8" style="max-width:100%;"></textarea>
</div>
</div>
</section>
<section class="card-footer">
<button class="done-button button button-primary">Edit</button>
<button class="save-button button button-link">Save</button>
<button class="close-button button button-danger-borderless">Close</button>
</section>
</article>
const cells = 100;
const app = { data: [], dark: false, counter: 0 };
const modalOverlay = document.querySelector("#modal-overlay");
const main = document.querySelector("main");
const storageKey = "hundo";
const themeKey = "theme";
const button = document.querySelector(".done-button");
const save = document.querySelector(".save-button");
const modal = document.getElementById("modal");
const info = document.querySelector(".subtitle");
const cellContainer = document.querySelector(".cells");
const theme = document.querySelector(".theme-button");
const exportButton = document.querySelector(".export-button");
const reset = document.querySelector(".reset-button");
const progress = "--progress-width";
const barWidth = 200;
const truncate = (str, n = 55) =>
str.length > n ? str.substr(0, n - 1).trim() + "..." : str;
const defaultText = (i) => `week ${i}`;
const hideModal = () => {
modal.style.display = "none";
modalOverlay.classList.toggle("close");
clearInfo();
};
const showModal = () => {
modal.style.display = "block";
modalOverlay.classList.toggle("close");
};
const setUpModal = (modal) => {
const close = document.querySelector(".close-button");
// When the user clicks on <span> (x), close the modal
close.addEventListener("click", () => {
hideModal();
});
// When the user clicks anywhere outside of the modal, close it
modalOverlay.addEventListener("click", () => {
hideModal();
});
};
const checkAndParse = (key, prop) => {
const data = localStorage.getItem(key);
if (!data) {
return app[prop];
}
return JSON.parse(data);
};
const toggleDark = () => {
main.classList.toggle("dark");
modal.classList.toggle("dark");
};
const downloadFile = (jsonStr, fileName) => {
let element = document.createElement("a");
element.setAttribute(
"href",
"data:text/json;charset=utf-8," + encodeURIComponent(jsonStr)
);
element.setAttribute("download", fileName);
element.style.display = "none";
document.body.appendChild(element);
element.click();
document.body.removeChild(element);
};
const setUpListeners = () => {
reset.addEventListener("click", () => {
resetData();
});
exportButton.addEventListener("click", () => {
const data = JSON.stringify(app);
downloadFile(data, `${new Date().toISOString().slice(0, 10)}.json`);
});
save.addEventListener("click", () => {
const id = modal.dataset.cell;
const data = modal.querySelector(".item-text-content").value;
app.data[id].text = data;
const text = data ?? "The thing";
const cellText = document.querySelector(`.cell-text-${id}`);
cellText.innerText = truncate(text);
saveToLocal(storageKey, app.data);
updateInfo();
});
theme.addEventListener("click", () => {
toggleDark();
app.dark = !app.dark;
saveToLocal(themeKey, app.dark);
});
button.addEventListener("click", () => {
const data = modal.dataset.cell;
app.data[data].filled = !app.data[data].filled;
toggleDone(app.data[data].filled, button, data);
saveToLocal(storageKey, app.data);
updateInfo();
});
};
const updateInfo = () => {
info.innerText = `Updated: ${new Date().toLocaleTimeString()}`;
};
const clearInfo = () => {
info.innerText = ``;
};
const toggleDone = (done, button, id) => {
button.innerText = done ? "undo" : "done";
app.counter = done ? app.counter + 1 : app.counter - 1;
updateWidth(app.counter, cells, barWidth);
document.getElementById(id).classList.toggle("done");
};
const saveToLocal = (storageKey, data) => {
localStorage.setItem(storageKey, JSON.stringify(data));
};
const updateWidth = (num, cells, width) => {
const accumulated = Math.ceil((num /cells ) * width);
document.documentElement.style
.setProperty(progress, `${accumulated}px`);
}
const createCells = (cells) => {
console.clear();
for (let i = 0; i < cells; i++) {
const cell = document.createElement("article");
const cellNumber = i + 1;
cell.classList.add("cell");
cell.id = i;
const cellNum = document.createElement("section");
cellNum.classList.add("cell-num");
cellNum.innerText = cellNumber;
cell.appendChild(cellNum);
const cellText = document.createElement("section");
cellText.classList.add("cell-text", `cell-text-${i}`);
cell.appendChild(cellText);
const info = app.data[i];
if (info) {
if (info.filled) {
cell.classList.toggle("done");
}
const text = info.text
} else {
app.data.push({
id: i,
text: "",
filled: false
});
}
cellText.innerText = truncate(app.data[i].text);
cell.addEventListener("click", () => {
modal.setAttribute("data-cell", i);
modalOverlay.classList.toggle("close");
modal.style.display = "block";
modal.querySelector(".item-text-content").value = app.data[i].text;
button.innerText = app.data[i].filled ? "undo" : "done";
});
cellContainer.appendChild(cell);
}
};
const resetData = () => {
localStorage.removeItem(storageKey);
localStorage.removeItem(themeKey);
resetUpApp();
};
const resetUpApp = () => {
app.data = [];
app.dark = true;
app.counter = 0;
modal.classList.add("dark");
main.classList.add("dark");
cellContainer.innerHTML = "";
createCells(cells);
localStorage.setItem(storageKey, JSON.stringify(app.data));
};
document.addEventListener("DOMContentLoaded", () => {
setUpModal(modal);
app.data = checkAndParse(storageKey, "data");
app.dark = checkAndParse(themeKey, "dark");
if (app.dark) {
toggleDark();
}
createCells(cells);
setUpListeners();
localStorage.setItem(storageKey, JSON.stringify(app.data));
});
@import "https://fonts.googleapis.com/css?family=Prompt:400,700";
:root {
--primary: hsl(var(--base-hue), 100%, 41%);
--primary-invert: #fff;
--base-hue: 171;
--primary-light: hsl(var(--base-hue), 100%, 96%);
--primary-med: hsl(var(--base-hue), 100%, 75%);
--primary-dark: hsl(var(--base-hue), 100%, 20%);
--peach: hsl(25, 88%, 76%);
--link: hsl(25, 88%, 76%);
--danger: #dc1c13;
--danger-dark: hsla(3, 84%, 66%, 0.5);
--shadow-base: 0 0 0;
--shadow-1: rgb(var(--shadow-base) / 26%);
--shadow-2: rgb(var(--shadow-base) / 28%);
--shadow-3: rgb(var(--shadow-base) / 22%);
--progress-width:0px;
background: var(--primary-light);
font-family: "Prompt", sans-serif;
text-overflow: ellipsis;
}
$color-bg-darkest: #13141b;
$color-bg-darker: #1b1e27;
$color-bg-dark: #232837;
$color-bg-med: #2f3646;
$color-bg-light: #455066;
$color-bg-lighter: #5b6882;
$color-text-dark: #72809b;
$color-text-med-dark: #919db5;
$color-text-med: #a0aabe;
$color-text-med-light: #d9dce1;
$color-text-light: #f0f1f6;
$color-text-lighter: #fff;
$breakpoint-med: 600px;
// located in _mixin.scss partial file
@mixin med-screen {
@media screen and (max-width: $breakpoint-med) {
@content;
}
}
.dark {
--primary-light: #{$color-bg-dark};
--primary-dark: var(--primary);
--link: #{$color-bg-lighter};
.button {
&-export {
background: var(--primary-dark);
}
&-reset {
background: var(--danger-dark);
}
}
.cell {
color: #{$color-text-light};
background: var(--link);
&.done {
color: #{$color-text-lighter};
}
}
&.modal {
h1 {
color: #{$color-text-light};
}
.subtitle {
color: #{$color-text-light};
}
--link: hsl(25, 88%, 76%);
.button-danger-borderless {
background: var(--danger);
color: #{$color-text-light};
&:hover {
filter: brightness(1.4);
}
}
}
.controls {
background: var(--primary-light);
.theme-button {
&:after {
content: "πŸŒ™";
}
}
}
}
.wrapper {
display: grid;
grid-template-rows: auto 1fr;
}
.controls {
display: grid;
place-items: center;
padding: 1rem;
grid-template-columns: auto 1fr auto;
padding-left: 3rem;
padding-right: 3rem;
&-right {
margin-right: auto;
display: flex;
flex: 1 1 auto;
justify-content: flex-start;
grid-gap: 1rem;
}
}
.theme-button {
margin-left: auto!important;
&:after {
content: "🌻";
}
}
@mixin circle {
width: 50px;
height: 50px;
border-radius: 50%;
}
.progress-indicator {
@include med-screen{
width: 50px;
height: 50px;
border-radius: 50%;
.number {
display: none;
}
}
width: 200px;
height: 25px;
display: grid;
grid-template-columns: auto 1fr auto;
grid-gap: 0;
align-items: center;
.number {
&-start {
margin-right: 0.25rem;
&:after {
content: "🚨";
}
}
&-end {
margin-left: 0.25rem;
&:after {
content: "✨";
}
}
}
.progress-wrapper {
border: 1px solid var(--primary);
border-radius: 5px;
height: 100%;
width: 200px;
@include med-screen {
display: none;
}
.progress-bar {
@include med-screen {
@include circle;
height: calc(var(--progress-width) / 200 * 50);
width: 50px;
}
width: var(--progress-width);
height: 100%;
background: var(--primary-dark);
display: flex;
align-items: center;
justify-content: space-between;
}
}
}
@mixin styled-scrollbar {
overflow-y: auto;
overflow-x: hidden;
&::-webkit-scrollbar {
width: 3em;
}
&::-webkit-scrollbar-track {
background-color: var(--shadow-3);
opacity: 0.2;
}
&::-webkit-scrollbar-thumb {
background-color: var(--shadow-1);
outline: 0.25rem var(--shadow-1);
}
}
@mixin hide-scrollbar {
overflow-x: hidden;
overflow-y: auto;
scrollbar-width: none; /* Firefox */
-ms-overflow-style: none; /* Internet Explorer 10+ */
&::-webkit-scrollbar {
/* WebKit */
width: 0;
height: 0;
}
}
@mixin light-shadow {
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.15), 0 2px 2px rgba(0, 0, 0, 0.15),
0 4px 4px rgba(0, 0, 0, 0.15), 0 8px 8px rgba(0, 0, 0, 0.15);
}
html,
body {
box-sizing: border-box;
height: 100%;
width: 100%;
}
main {
background: var(--primary-light);
padding: 1rem;
grid-gap: 1rem;
display: grid;
.cells {
display: grid;
place-items: center;
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
grid-template-rows: minmax(150px, 15%);
}
.cell {
border-radius: 10px;
display: flex;
flex-direction: column;
flex: 1 1 150px;
margin: 1rem;
padding: 1rem;
height: 150px;
width: 150px;
opacity: 0.9;
background: var(--primary-med);
@include light-shadow;
&:hover {
transform: scale(1.04);
}
.cell-text {
margin-top: 0.25rem;
}
&.done {
background: var(--primary-dark);
color: var(--primary-light);
}
}
}
$border-width: 1.25px;
.button:disabled {
cursor: not-allowed;
pointer-events: all !important;
background: inherit !important;
filter: brightness(0.5) !important;
color: rgb(0 0 0 / 20%);
&:hover {
background: inherit !important;
filter: brightness(0.5) !important;
}
}
@mixin color-button($color, $surface) {
border: $border-width solid $surface;
background: $color;
color: $surface;
&:hover {
border: $border-width solid $color;
color: $color;
background: transparent;
}
}
.button {
text-transform: Capitalize;
font-size: 1em;
background-color: #fff;
border-color: #dbdbdb;
border-width: 1px;
color: #363636;
cursor: pointer;
justify-content: center;
padding-bottom: calc(0.5em - 1px);
padding-left: 1em;
padding-right: 1em;
padding-top: calc(0.5em - 1px);
text-align: center;
white-space: nowrap;
border-radius: 0.25rem;
margin: 0;
font-family: Consolas, monaco, monospace;
&-reset {
@include color-button(var(--danger), white);
}
&-export {
@include color-button(var(--primary-med), black);
}
&-theme {
@include color-button(var(--primary-light), black);
border:none;
&:hover {
filter: brightness(1.4);
border: none;
}
}
&-primary {
@include color-button(var(--primary), black);
}
&-link {
@include color-button(var(--link), black);
}
&-danger-borderless {
border: 1px solid var(--danger);
color: var(--danger);
&:hover {
background: var(--danger);
color: #fff;
}
}
&-link-flat {
border: $border-width solid var(--link);
color: var(--link);
&:hover {
background: var(--link);
color: #fff;
}
}
}
.modal.card {
display: none;
background: var(--primary-light);
}
.modal {
/* This way it could be display flex or grid or whatever also. */
display: block;
/* Probably need media queries here */
width: 600px;
max-width: 100%;
max-height: 100%;
position: fixed;
z-index: 100;
left: 50%;
top: 35%;
/* Use this for centering if unknown width/height */
transform: translate(-50%, -35%);
/* If known, negative margins are probably better (less chance of blurry text). */
/* margin: -200px 0 0 -200px; */
background: var(--primary-light);
@include light-shadow;
ul {
margin: 10px 0 10px 30px;
}
li,
p {
margin: 0 0 10px 0;
}
h1 {
font-weight: 700;
font-size: 2em;
}
}
.close {
display: none;
}
.modal-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 50;
background: rgba(0, 0, 0, 0.6);
}
$card-width: 315px;
$breakpoint-narrow: 350px;
.card {
display: grid;
padding: 1rem 2rem;
grid-template-row: auto 1fr auto;
max-width: 75vw;
min-width: 315px;
gap: 1rem;
border: 1px solid black;
border-radius: 2rem;
background: #fff;
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.15), 0 2px 2px rgba(0, 0, 0, 0.15),
0 4px 4px rgba(0, 0, 0, 0.15), 0 8px 8px rgba(0, 0, 0, 0.15);
@media screen and (max-width: 375) {
padding: 1rem 1.25rem;
width: 70vw;
}
}
.title {
margin-top: 0;
color: #4e4b4b;
}
.title + .subtitle {
margin-bottom: 0;
}
.subtitle {
font-size: 0.75rem;
margin-top: 1rem;
margin-left: 0.25rem!important;
}
.muted {
opacity: 0.75;
}
.brand-tag {
border-radius: 0.375rem;
border: 1px solid black;
padding: 0.25rem 0.5rem;
margin: 0.25rem 0;
background: var(--primary-light);
font-weight: 300;
&.category {
text-transform: capitalize;
background: var(--link);
}
}
.card-header {
display: flex;
margin-top: 0.25rem;
min-height: 75px;
align-items: flex-start;
flex-direction: column;
}
.card-image {
display: flex;
flex-direction: row;
justify-content: center;
img {
max-width: $card-width - 16px - 16px;
margin: auto;
border-radius: 0.25rem;
padding: 0;
border: 1px solid rgb(0 0 0 / 20%);
}
}
.card-image + .card-content {
}
.card-content {
padding: 0.25rem;
&:hover {
.description {
box-shadow: 0 0 0 1px var(--shadow-3);
border-radius: 0.25rem;
}
}
&:last-child {
margin-bottom: 0.75rem;
}
p {
font-weight: 200;
}
.card-content-item {
&:not(last-child) {
margin-bottom: 1rem;
}
@media screen and (min-width: 400px) {
display: grid;
grid-template-columns: auto 1fr;
}
.item-icon {
margin-right: 0;
padding-right: 0;
@media screen and (max-width: $breakpoint-narrow) {
display: none;
}
}
.item-text {
@media screen and (max-width: $breakpoint-narrow) {
display: flex;
flex-direction: column;
justify-content: space-between;
}
word-break: break-word;
text-overflow: ellipsis;
&-header {
text-transform: capitalize;
font-weight: 600;
}
&.description {
@include hide-scrollbar;
display: flex;
width: 100%;
max-width: 580px;
}
}
}
}
.card-footer {
display: flex;
justify-content: space-between;
margin-bottom: 1rem;
margin-top: auto;
}
textarea {
max-width: 100%;
width: 100%;
font-size: 1em;
flex: 1;
display: block;
padding: 0.25rem;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment