Do 100 things, and track it with this ugly ass tracker.
Created
February 25, 2024 12:24
-
-
Save weirdyang/682be5041c7a0ad8c2546edcf81ef101 to your computer and use it in GitHub Desktop.
Do π― things β
This file contains hidden or 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
| <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> |
This file contains hidden or 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
| 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)); | |
| }); |
This file contains hidden or 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
| @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