├── .gitignore ├── .editorconfig ├── package.json ├── README.md ├── favicon.svg ├── index.html ├── .stylelintrc ├── style.css └── script.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | package-lock.json 3 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | indent_style = space 3 | indent_size = 2 4 | end_of_line = lf 5 | charset = utf-8 6 | trim_trailing_whitespace = true 7 | insert_final_newline = true 8 | 9 | [*.js] 10 | quote_type = single 11 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "memento-mori", 3 | "version": "1.0.0", 4 | "description": "Memento mori (Latin for 'remember that you [have to] die'). Self-filling calendar.", 5 | "scripts": { 6 | "test": "echo \"Error: no test specified\" && exit 1" 7 | }, 8 | "repository": { 9 | "type": "git", 10 | "url": "git+https://github.com/ronilaukkarinen/memento-mori.git" 11 | }, 12 | "author": "Roni Laukkarinen", 13 | "license": "ISC", 14 | "bugs": { 15 | "url": "https://github.com/ronilaukkarinen/memento-mori/issues" 16 | }, 17 | "homepage": "https://github.com/ronilaukkarinen/memento-mori#readme", 18 | "devDependencies": { 19 | "stylelint": "^14.9.1", 20 | "stylelint-config-recommended": "^8.0.0", 21 | "stylelint-config-standard": "^26.0.0", 22 | "stylelint-declaration-strict-value": "^1.8.0", 23 | "stylelint-order": "^5.0.0" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Memento mori 2 | 3 | > Latin for 'Remember your death', Memento Mori is a powerful concept that's been used for centuries to help people focus on what truly matters. 4 | > The calendar was designed as an interactive tool to help you harness the concept by visualizing your life week by week. 5 | 6 | [Stoic Reflections](https://stoicreflections.com) 7 | 8 | A self filling calendar based on the design used [on stoicreflections.com](https://stoicreflections.com/collections/memento-mori-life-calendar-chart-poster-frame). 9 | The code is hybrid of [sunday](https://github.com/ronilaukkarinen/sunday) and [mementoMori](https://github.com/afonsocrg/mementoMori) (huge thanks to JS belongs to [afonsocrg](https://github.com/afonsocrg)) + bunch of my own additions. 10 | 11 | ## Features 12 | 13 | * Dark/light mode support 14 | * Add your own events in JSON format 15 | * Add emojis/colors on events 16 | * Change styles, cell sizes 17 | * Shows weeks left 18 | 19 | ![_C__Users_Rolle_Projects_memento-mori_index html](https://user-images.githubusercontent.com/1534150/187994535-24eb152e-e935-450a-9a62-5c8ac14bd954.png) 20 | -------------------------------------------------------------------------------- /favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Memento mori 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 |
18 | 19 |
20 | 21 | "It is not that we have a short time to live, but that we waste a lot of 22 | it. Life is long enough, and a sufficiently generous amount has been 23 | given to us for the highest achievements if it were all well invested. 24 | But when it is wasted in heedless luxury and spent on no good activity, 25 | we are forced at last by death's final constraint to realize that it has 26 | passed away before we knew it was passing. So it is: we are not given a 27 | short life but we make it short, and we are not ill-supplied but 28 | wasteful of it… Life is long if you know how to use it." ~ Seneca 29 | 30 |
31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /.stylelintrc: -------------------------------------------------------------------------------- 1 | { 2 | "defaultSeverity": "warning", 3 | "plugins": [ 4 | "stylelint-order" 5 | ], 6 | "rules": { 7 | "order/order": [ 8 | { 9 | "type": "at-rule", 10 | "name": "import" 11 | }, 12 | { 13 | "type": "at-rule", 14 | "name": "include" 15 | }, 16 | { 17 | "type": "at-rule", 18 | "name": "extend" 19 | }, 20 | "custom-properties", 21 | "dollar-variables", 22 | "declarations", 23 | "rules", 24 | { 25 | "type": "at-rule", 26 | "name": "media" 27 | } 28 | ], 29 | "order/properties-alphabetical-order": true, 30 | "property-case": "lower", 31 | "color-no-invalid-hex": true, 32 | "color-hex-case": "lower", 33 | "color-hex-length": "short", 34 | "selector-pseudo-element-case": "lower", 35 | "selector-type-case": "lower", 36 | "selector-pseudo-class-parentheses-space-inside": "never", 37 | "function-parentheses-space-inside": "never", 38 | "function-comma-newline-after": "never-multi-line", 39 | "function-name-case": "lower", 40 | "function-max-empty-lines": 0, 41 | "max-empty-lines": 1, 42 | "max-line-length": 250, 43 | "unit-case": "lower", 44 | "selector-pseudo-class-case": "lower", 45 | "selector-attribute-brackets-space-inside": "never", 46 | "selector-attribute-quotes": "always", 47 | "selector-combinator-space-after": "always", 48 | "selector-combinator-space-before": "always", 49 | "selector-descendant-combinator-no-non-space": true, 50 | "comment-whitespace-inside": "always", 51 | "indentation": 2, 52 | "linebreaks": "unix", 53 | "selector-max-specificity": "1,4,4", 54 | "block-no-empty": true, 55 | "declaration-colon-newline-after": null, 56 | "declaration-empty-line-before": null, 57 | "number-leading-zero": "never", 58 | "number-no-trailing-zeros": true, 59 | "value-list-comma-newline-after": "never-multi-line", 60 | "selector-list-comma-newline-after": "always", 61 | "declaration-colon-space-before": "never", 62 | "declaration-block-trailing-semicolon": "always", 63 | "no-eol-whitespace": true, 64 | "no-empty-first-line": true, 65 | "no-missing-end-of-source-newline": true, 66 | "block-opening-brace-newline-after": "always", 67 | "block-closing-brace-newline-after": "always-multi-line", 68 | "string-quotes": null, 69 | "media-feature-colon-space-after": "always", 70 | "media-feature-colon-space-before": "never", 71 | "media-feature-parentheses-space-inside": "never", 72 | "media-feature-range-operator-space-after": "always", 73 | "media-feature-range-operator-space-before": "always", 74 | "media-query-list-comma-newline-after": "never-multi-line", 75 | "media-feature-name-case": "lower", 76 | "font-family-no-missing-generic-family-keyword": true, 77 | "font-family-name-quotes": "always-where-required", 78 | "at-rule-no-unknown": null, 79 | "no-invalid-position-at-import-rule": null, 80 | "declaration-no-important": true, 81 | "comment-empty-line-before": null, 82 | "at-rule-name-newline-after": null, 83 | "at-rule-semicolon-newline-after": null, 84 | "at-rule-semicolon-space-before": "never", 85 | "at-rule-name-space-after": "always", 86 | "at-rule-name-case": "lower", 87 | "function-url-quotes": "always", 88 | "unit-no-unknown": true, 89 | "property-no-unknown": true, 90 | "no-duplicate-selectors": true, 91 | "no-extra-semicolons": true, 92 | "length-zero-no-unit": true, 93 | "font-weight-notation": "numeric", 94 | "number-max-precision": 3, 95 | "selector-class-pattern": null, 96 | "selector-max-class": 4, 97 | "selector-max-combinators": 3, 98 | "selector-max-compound-selectors": 4, 99 | "selector-max-pseudo-class": 2, 100 | "selector-max-universal": 1, 101 | "property-no-vendor-prefix": true, 102 | "selector-no-vendor-prefix": true, 103 | "selector-no-qualifying-type": null, 104 | "declaration-block-no-duplicate-properties": true, 105 | "no-unknown-animations": true, 106 | "selector-pseudo-element-colon-notation": "double", 107 | "shorthand-property-no-redundant-values": true, 108 | "declaration-block-single-line-max-declarations": 1, 109 | "value-keyword-case": [ 110 | "lower", 111 | { 112 | "camelCaseSvgKeywords": true 113 | } 114 | ], 115 | "declaration-property-unit-allowed-list": [ 116 | { 117 | "font-size": [ 118 | "rem", 119 | "em", 120 | "px" 121 | ], 122 | "/^animation/": [ 123 | "ms", 124 | "s" 125 | ], 126 | "line-height": [ 127 | "px", 128 | "%", 129 | "" 130 | ] 131 | } 132 | ], 133 | "property-disallowed-list": [ 134 | "font", 135 | "background" 136 | ] 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /style.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --columns-per-row: 16; 3 | --color-background: #fff; 4 | --color-dark-gray: #333; 5 | --color-dark-gray-filled: #888; 6 | --color-black: #000; 7 | --color-white: #fff; 8 | --rect-gap: 6px; 9 | --week-gap: 2px; 10 | --week-size: 4px; 11 | --month-gap: 10px; 12 | 13 | /* Do not change these */ 14 | --month-cell-width: calc(calc(calc(var(--week-size) * 4) + calc(var(--week-gap) * 4) * 4)); 15 | --calendar-width: calc(calc(var(--month-cell-width) * var(--columns-per-row)) - calc(var(--month-cell-width) * 3)); 16 | } 17 | 18 | .calendar { 19 | display: grid; 20 | grid-gap: var(--month-gap); 21 | grid-template-columns: repeat(var(--columns-per-row), auto); 22 | padding-top: 60px; 23 | width: 100%; 24 | } 25 | 26 | body { 27 | background-color: var(--color-background); 28 | font-family: Inter, sans-serif; 29 | margin: 0; 30 | padding: 0; 31 | } 32 | 33 | @page { 34 | size: a4 portrait; 35 | } 36 | 37 | @media print { 38 | .week-cell { 39 | -webkit-print-color-adjust: exact; 40 | } 41 | } 42 | 43 | .title { 44 | color: var(--color-dark-gray); 45 | font-size: 50px; 46 | font-weight: 700; 47 | margin: 60px 0; 48 | text-align: center; 49 | } 50 | 51 | .wrapper, 52 | .quote { 53 | font-size: 12px; 54 | margin: 60px auto; 55 | max-width: var(--calendar-width); 56 | padding: 0 2rem; 57 | width: calc(100% - 4rem); 58 | } 59 | 60 | .stats.wrapper { 61 | margin-bottom: 0; 62 | margin-top: 0; 63 | opacity: .7; 64 | padding: 0; 65 | text-align: right; 66 | transform: translateX(5px); 67 | } 68 | 69 | @media (max-width: 660px) { 70 | .stats.wrapper { 71 | max-width: 80%; 72 | text-align: center; 73 | transform: none; 74 | } 75 | } 76 | 77 | @media screen { 78 | .calendar { 79 | justify-content: center; 80 | margin: auto; 81 | } 82 | } 83 | 84 | .week-cell { 85 | align-items: center; 86 | border: 1px solid var(--color-dark-gray); 87 | display: inline-flex; 88 | height: var(--week-size); 89 | justify-content: center; 90 | width: var(--week-size); 91 | } 92 | 93 | .week-cell.filled { 94 | /* stylelint-disable-next-line */ 95 | background-color: var(--color-dark-gray-filled); 96 | /* stylelint-disable-next-line */ 97 | border-color: var(--color-dark-gray-filled); 98 | } 99 | 100 | @keyframes pulse { 101 | 0% { 102 | border-radius: 0; 103 | box-shadow: 0 0 0 0 currentColor; 104 | font-size: 7px; 105 | } 106 | 107 | 50% { 108 | border-radius: 25%; 109 | box-shadow: 0 0 0 3px currentColor; 110 | font-size: 15px; 111 | } 112 | 113 | 100% { 114 | border-radius: 0; 115 | box-shadow: 0 0 0 0 currentColor; 116 | font-size: 7px; 117 | } 118 | } 119 | 120 | .week-cell.has-tooltip { 121 | /* animation: pulse 2s infinite; */ 122 | border-color: currentColor; 123 | border-radius: 0; 124 | font-size: 7px; 125 | position: relative; 126 | } 127 | 128 | .week-cell:not(.filled) { 129 | opacity: .8; 130 | } 131 | 132 | .week-cell.invisible, 133 | .invisible { 134 | /* stylelint-disable-next-line */ 135 | background-color: var(--color-background) !important; 136 | /* stylelint-disable-next-line */ 137 | border: 1px solid var(--color-background) !important; 138 | } 139 | 140 | .year-cell { 141 | display: grid; 142 | gap: var(--week-gap); 143 | } 144 | 145 | .year-wrapper { 146 | position: relative; 147 | z-index: 0; 148 | } 149 | 150 | .year-wrapper:hover { 151 | z-index: 99999999; 152 | } 153 | 154 | .month-cell { 155 | display: grid; 156 | gap: var(--week-gap); 157 | grid-template-columns: repeat(4, calc(var(--week-size) + var(--week-gap))); 158 | max-width: var(--month-cell-width); 159 | transition: all 150ms; 160 | } 161 | 162 | .year-label { 163 | font-size: 12px; 164 | font-weight: 400; 165 | margin-bottom: 4px; 166 | margin-top: 0; 167 | text-align: center; 168 | } 169 | 170 | /* Tooltips */ 171 | [data-tooltip] { 172 | cursor: default; 173 | position: relative; 174 | } 175 | 176 | [data-tooltip]:hover { 177 | cursor: help; 178 | } 179 | 180 | [data-tooltip]:hover::before { 181 | animation: fade-in 150ms ease; 182 | background-color: var(--color-black); 183 | border-radius: 4px; 184 | bottom: calc(100% + 10px); 185 | color: var(--color-white); 186 | content: attr(data-tooltip); 187 | display: block; 188 | font-size: 12px; 189 | left: 50%; 190 | max-width: 170px; 191 | min-width: 60px; 192 | padding: 6px; 193 | position: absolute; 194 | text-align: center; 195 | transform: translate(-50%); 196 | width: max-content; 197 | z-index: 20000; 198 | } 199 | 200 | [data-tooltip]:hover::after { 201 | animation: fade-in 150ms ease; 202 | border: 1px solid var(--color-black); 203 | border-color: var(--color-black) transparent transparent; 204 | border-width: 4px 6px 0; 205 | bottom: calc(100% + 6px); 206 | content: ""; 207 | display: block; 208 | height: 0; 209 | left: 50%; 210 | margin-left: -6px; 211 | position: absolute; 212 | width: 0; 213 | z-index: 1; 214 | } 215 | 216 | @keyframes fade-in { 217 | from { 218 | opacity: 0; 219 | } 220 | 221 | to { 222 | opacity: 1; 223 | } 224 | } 225 | 226 | /* Mobile responsiveness */ 227 | @media (max-width: 740px) { 228 | .calendar { 229 | grid-template-columns: repeat(16, auto); 230 | } 231 | } 232 | 233 | @media (max-width: 660px) { 234 | .calendar { 235 | grid-template-columns: repeat(12, auto); 236 | } 237 | } 238 | 239 | @media (max-width: 500px) { 240 | .calendar { 241 | grid-template-columns: repeat(10, auto); 242 | } 243 | } 244 | 245 | @media (max-width: 430px) { 246 | .calendar { 247 | grid-template-columns: repeat(8, auto); 248 | } 249 | } 250 | 251 | @media (prefers-color-scheme: dark) { 252 | :root { 253 | --color-dark-gray: #c9d1d9; 254 | --color-dark-gray-filled: #c9d1d9; 255 | --color-background: #0d1117; 256 | } 257 | 258 | body { 259 | background-color: var(--color-background); 260 | color: #c9d1d9; 261 | } 262 | } 263 | 264 | .screen-reader-text { 265 | border: 0; 266 | clip: rect(1px, 1px, 1px, 1px); 267 | clip-path: inset(50%); 268 | height: 1px; 269 | margin: -1px; 270 | overflow: hidden; 271 | padding: 0; 272 | position: absolute; 273 | width: 1px; 274 | /* stylelint-disable-next-line */ 275 | word-wrap: normal !important; 276 | } 277 | 278 | .github-link-wrapper { 279 | margin: 0 auto 60px; 280 | max-width: var(--calendar-width); 281 | padding: 0 2rem; 282 | width: calc(100% - 4rem); 283 | } 284 | 285 | .github-link-wrapper a { 286 | align-items: center; 287 | color: var(--color-white); 288 | display: inline-flex; 289 | font-size: 12px; 290 | gap: .6rem; 291 | text-decoration: none; 292 | } 293 | -------------------------------------------------------------------------------- /script.js: -------------------------------------------------------------------------------- 1 | // Set up vars 2 | const myBirthDay = '1988-11-01'; 3 | const myLifeExpectancy = 80; 4 | const totalWeeksInLife = myLifeExpectancy * 52.1429; // As per the modern Gregorian calendar, one year is equal to 365 days whic is 52.1429 weeks in total 5 | 6 | /* 7 | Calculate your life expectency 8 | 9 | Specific enough: https://www.blueprintincome.com/tools/life-expectancy-calculator-how-long-will-i-live/ 10 | More specific (needs an account): https://livingto100.com/calculator/age 11 | */ 12 | 13 | // Weeks left in life 14 | function returnWeeks(birthday) { 15 | bday = new Date(birthday); 16 | var ageDifMs = Date.now() - bday.getTime(); 17 | yearOfBirth = bday.getFullYear(); 18 | birthdayDate = bday; 19 | 20 | return Math.ceil(totalWeeksInLife - ageDifMs / (1000 * 60 * 60 * 24 * 7)); 21 | } 22 | 23 | // Get how many weeks are left 24 | weeksLeft = returnWeeks(myBirthDay); 25 | progress = Math.ceil(totalWeeksInLife) - Math.ceil(weeksLeft) + ' weeks lived. ' + weeksLeft + ' weeks left of all total ' + Math.ceil(totalWeeksInLife) + ' weeks available.'; 26 | 27 | // Fill calendar with year cells 28 | function populate_calendar(birthday, numYears) { 29 | const root = document.getElementById('calendar'); 30 | 31 | // Remove every existing child first, just in case 32 | while(root.children.length > 0) { 33 | root.children[0].remove(); 34 | } 35 | 36 | let baseYear = birthday.getFullYear(); 37 | // Spawn years 38 | for (let i = 0; i < numYears; i++) { 39 | root.appendChild(spawn_year(baseYear + i, birthday)); 40 | } 41 | } 42 | 43 | // Stats 44 | var weeksleft_label = document.createElement('p'); 45 | weeksleft_label.classList.add('weeksleft-label'); 46 | weeksleft_label.innerHTML = progress; 47 | document.getElementById('stats').appendChild(weeksleft_label); 48 | 49 | function spawn_year(_year, birthday) { 50 | let year_div = document.createElement('div'); 51 | year_div.classList.add('year-wrapper'); 52 | 53 | let title = document.createElement('h2'); 54 | title.classList.add('year-label'); 55 | title.innerHTML = _year; 56 | year_div.appendChild(title); 57 | 58 | let year_cell = document.createElement('div'); 59 | year_cell.classList.add('year-cell'); 60 | year_div.appendChild(year_cell); 61 | 62 | for(let i = 0; i < 12; i++) { 63 | let month_div = document.createElement('div'); 64 | 65 | month_div.classList.add('month-cell'); 66 | let num_days_per_square = days_in_month(i+1, _year) / 4 67 | for(let j = 0; j < 4; j++) { 68 | week_div = document.createElement('div'); 69 | week_div.id = `${_year}-${i+1}-${j+1}`; 70 | week_div.classList.add('week-cell'); 71 | // week_date_epoch = new Date(`${_year}-${i+1}-${(j==0 ? 1 : Math.floor(j*num_days_per_square))}`).getTime(); 72 | // today_epoch = new Date().getTime(); 73 | 74 | // Ditch epochs because of mobile Safari and use normal dates instead 75 | week_date = new Date(_year, i, Math.floor((j+1)*num_days_per_square)); 76 | today = new Date(); 77 | 78 | // Use <= to fill the first week cell on the first day of a month 79 | if (week_date <= today) { 80 | week_div.classList.add('filled'); 81 | week_div.style.backgroundColor = 'var(--color-dark-gray-filled)'; 82 | week_div.style.borderColor = 'var(--color-dark-gray-filled)'; 83 | } 84 | 85 | let _ = new Date(_year, i, Math.floor((j+1)*num_days_per_square)); 86 | 87 | if (new Date(_year, i, Math.floor((j+1)*num_days_per_square)) < birthday) { 88 | week_div.classList.add('invisible'); 89 | } 90 | 91 | month_div.appendChild(week_div); 92 | } 93 | year_cell.appendChild(month_div); 94 | } 95 | 96 | return year_div; 97 | } 98 | 99 | // Month in JavaScript is 0-indexed (January is 0, February is 1, etc), 100 | // but by using 0 as the day it will give us the last day of the prior 101 | // month. So passing in 1 as the month number will return the last day 102 | // of January, not February 103 | function days_in_month (month, year) { 104 | return new Date(year, month, 0).getDate(); 105 | } 106 | 107 | function get_week_id_from_date(date) { 108 | let n_days = days_in_month(date.getMonth() + 1, date.getFullYear()) 109 | let week_number = Math.floor((date.getDate()-1) / (n_days / 4)); 110 | return `${date.getFullYear()}-${date.getMonth()+1}-${week_number + 1}`; 111 | } 112 | 113 | function write_life_event(life_event) { 114 | let id = get_week_id_from_date(life_event['date']); 115 | week_div = document.getElementById(id); 116 | 117 | if (week_div == null || week_div.classList.contains('invisible')) { 118 | let y = life_event['date'].getFullYear(); 119 | let m = life_event['date'].getMonth() + 1; 120 | let d = life_event['date'].getDate(); 121 | 122 | console.error(`Event '${life_event['description']}' has an invalid date (${y}-${m}-${d})`); 123 | return; 124 | } 125 | 126 | week_div.style.color = life_event['color']; 127 | week_div.style.borderColor = life_event['color']; 128 | week_div.style.backgroundColor = life_event['color']; 129 | 130 | if (week_div.style.backgroundColor) { 131 | week_div.classList.add('has-tooltip'); 132 | 133 | week_div.dataset.tooltip = life_event['description']; 134 | } 135 | 136 | if ('icon' in life_event) { 137 | week_div.classList.add('has-tooltip'); 138 | 139 | week_div.dataset.tooltip = life_event['description']; 140 | week_div.insertAdjacentHTML('beforeend', life_event['icon']); 141 | } 142 | } 143 | 144 | events = [ 145 | { 146 | 'date': new Date('1995-08-14'), 147 | 'description': 'Tammirinteen ala-aste 1. luokka', 148 | 'color': '#18aedb', 149 | }, 150 | { 151 | 'date': new Date('1997-08-11'), 152 | 'description': 'Koulun vaihto kristilliseen kouluun', 153 | 'color': '#18aedb', 154 | }, 155 | { 156 | 'date': new Date('1999-05-07'), 157 | 'description': 'Ensimmäiset kotisivut nettiin', 158 | 'color': '#e806f8', 159 | }, 160 | { 161 | 'date': new Date('2004-07-30'), 162 | 'description': '1. ulkomaanmatka', 163 | 'color': '#e806f8', 164 | }, 165 | { 166 | 'date': new Date('2007-07-02'), 167 | 'description': 'Lakkiaiset', 168 | 'color': '#18aedb', 169 | }, 170 | { 171 | 'date': new Date('2007-06-01'), 172 | 'description': 'Ensitapaaminen IRCissä Veeran kanssa', 173 | 'color': '#f8312f', 174 | 'icon': '❤️', 175 | }, 176 | { 177 | 'date': new Date('2007-06-11'), 178 | 'description': 'Siviilipalvelus alkaa', 179 | 'color': '#18aedb', 180 | }, 181 | { 182 | 'date': new Date('2007-12-25'), 183 | 'description': 'Muutto omilleen', 184 | 'color': '#f8f806', 185 | }, 186 | { 187 | 'date': new Date('2008-06-01'), 188 | 'description': 'Muutto Kauppakadulle', 189 | 'color': '#f8f806', 190 | }, 191 | { 192 | 'date': new Date('2009-03-30'), 193 | 'description': 'Lotan syntymä', 194 | 'color': '#3ef806', 195 | }, 196 | { 197 | 'date': new Date('2009-09-07'), 198 | 'description': 'Harjoitteluun Data Groupille', 199 | 'color': '#18aedb', 200 | }, 201 | { 202 | 'date': new Date('2010-03-16'), 203 | 'description': 'Vanhempien ero', 204 | 'color': '#222222', 205 | }, 206 | { 207 | 'date': new Date('2010-03-30'), 208 | 'description': 'Muutto Kangaslammelle', 209 | 'color': '#f8f806', 210 | }, 211 | { 212 | 'date': new Date('2010-12-07'), 213 | 'description': 'Ensimmäinen työpaikka', 214 | 'color': '#18aedb', 215 | }, 216 | { 217 | 'date': new Date('2013-05-02'), 218 | 'description': 'Muutto Rasinrinteelle', 219 | 'color': '#f8f806', 220 | }, 221 | { 222 | 'date': new Date('2013-05-02'), 223 | 'description': 'Muutto Rasinrinteelle', 224 | 'color': '#f8f806', 225 | }, 226 | { 227 | 'date': new Date('2013-08-26'), 228 | 'description': 'Manun syntymä', 229 | 'color': '#3ef806', 230 | }, 231 | { 232 | 'date': new Date('2013-05-22'), 233 | 'description': 'Oman yrityksen perustaminen', 234 | 'color': '#18aedb', 235 | }, 236 | { 237 | 'date': new Date('2015-08-01'), 238 | 'description': 'Muutto Vapaudenkadulle', 239 | 'color': '#f8f806', 240 | }, 241 | { 242 | 'date': new Date('2008-03-28'), 243 | 'description': 'Kihloihin Veeran kanssa', 244 | 'color': '#f8312f', 245 | 'icon': '❤️', 246 | }, 247 | { 248 | 'date': new Date('2008-07-02'), 249 | 'description': 'Naimisiin Veeran kanssa', 250 | 'color': '#f8312f', 251 | 'icon': '❤️', 252 | }, 253 | { 254 | 'date': new Date('2018-02-07'), 255 | 'description': '10-vuotishääpäivä', 256 | 'color': '#f8312f', 257 | 'icon': '❤️', 258 | }, 259 | { 260 | 'date': new Date('2019-01-15'), 261 | 'description': 'Isän kuolema', 262 | 'color': '#222222', 263 | 'icon': '💀', 264 | }, 265 | { 266 | 'date': new Date('2020-08-09'), 267 | 'description': 'Alkoholinkäytön lopettaminen', 268 | 'color': '#f88b06', 269 | }, 270 | { 271 | 'date': new Date('2020-09-10'), 272 | 'description': 'Veloista 100% eroon pääseminen', 273 | 'color': '#f88b06', 274 | }, 275 | { 276 | 'date': new Date('2022-07-21'), 277 | 'description': '-20kg, paino 73kg', 278 | 'color': '#f88b06', 279 | }, 280 | { 281 | 'date': new Date('2022-08-01'), 282 | 'description': 'Muutto Vaihdekujalle', 283 | 'color': '#f8f806', 284 | }, 285 | { 286 | 'date': new Date('2023-05-22'), 287 | 'description': '10 vuotta yrittäjänä', 288 | 'color': '#18aedb', 289 | }, 290 | ] 291 | 292 | let calendar = document.getElementById('calendar'); 293 | populate_calendar(new Date(myBirthDay), myLifeExpectancy); 294 | 295 | events.forEach(e => { 296 | write_life_event(e); 297 | }); 298 | --------------------------------------------------------------------------------