├── .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 | 
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 | Memento mori death calendar is open source on GitHub
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 |
--------------------------------------------------------------------------------