rev |
line source |
paulo@81
|
1 <html>
|
paulo@81
|
2 <head>
|
paulo@81
|
3 <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
paulo@81
|
4 <title>Life Calendar</title>
|
paulo@81
|
5 <style type="text/css">
|
paulo@81
|
6 body {
|
paulo@81
|
7 padding: 0;
|
paulo@81
|
8 margin: 0;
|
paulo@81
|
9 position: relative;
|
paulo@81
|
10 font-family: sans-serif;
|
paulo@81
|
11 background-size: cover;
|
paulo@81
|
12 }
|
paulo@81
|
13
|
paulo@81
|
14 a, a:visited {
|
paulo@81
|
15 color: #70BCF9;
|
paulo@81
|
16 }
|
paulo@81
|
17
|
paulo@81
|
18 table {
|
paulo@81
|
19 width: 100%;
|
paulo@81
|
20 height: 100%;
|
paulo@81
|
21 }
|
paulo@81
|
22
|
paulo@81
|
23 table, td {
|
paulo@81
|
24 border: 1px solid;
|
paulo@81
|
25 border-collapse: collapse;
|
paulo@81
|
26 padding: 0;
|
paulo@81
|
27 margin: 0;
|
paulo@81
|
28 }
|
paulo@81
|
29
|
paulo@81
|
30 div#welcome {
|
paulo@81
|
31 display: none;
|
paulo@81
|
32
|
paulo@81
|
33 width: 50vw;
|
paulo@81
|
34 margin: 8vh auto 0;
|
paulo@81
|
35 }
|
paulo@81
|
36
|
paulo@81
|
37 div#welcome h1 {
|
paulo@81
|
38 text-align: center;
|
paulo@81
|
39 text-transform: uppercase;
|
paulo@81
|
40 font-family: serif;
|
paulo@81
|
41 font-size: 4em;
|
paulo@81
|
42 }
|
paulo@81
|
43 div#welcome form {
|
paulo@81
|
44 border: 1px solid #999999;
|
paulo@81
|
45 padding: 10px 20px;
|
paulo@81
|
46 }
|
paulo@81
|
47
|
paulo@81
|
48 div#welcome .settings {
|
paulo@81
|
49 margin-left: 5px;
|
paulo@81
|
50 padding: 5px;
|
paulo@81
|
51 }
|
paulo@81
|
52
|
paulo@81
|
53 div#welcome div#footer {
|
paulo@81
|
54 color: #a6a6a6;
|
paulo@81
|
55 padding-top: 50px;
|
paulo@81
|
56 text-align: center;
|
paulo@81
|
57 }
|
paulo@81
|
58
|
paulo@81
|
59 #settingsForm span.unhappyMessage {
|
paulo@81
|
60 font-size: 0.8em;
|
paulo@81
|
61 padding-left: 10px;
|
paulo@81
|
62 }
|
paulo@81
|
63
|
paulo@81
|
64 div#tooltip {
|
paulo@81
|
65 background-color: #5070D0;
|
paulo@81
|
66 padding: 0.5em;
|
paulo@81
|
67 }
|
paulo@81
|
68
|
paulo@81
|
69 div#tooltip h1 {
|
paulo@81
|
70 color: #DFDFDF;
|
paulo@81
|
71 font-size: 1.25em;
|
paulo@81
|
72 margin: 1px;
|
paulo@81
|
73 }
|
paulo@81
|
74
|
paulo@81
|
75 div#tooltip h2 {
|
paulo@81
|
76 color: #AAAAAA;
|
paulo@81
|
77 font-size: 0.75em;
|
paulo@81
|
78 font-weight: normal;
|
paulo@81
|
79 margin: 1px;
|
paulo@81
|
80 }
|
paulo@81
|
81
|
paulo@81
|
82 td {
|
paulo@81
|
83 border-color: #FFFFFF;
|
paulo@81
|
84 }
|
paulo@81
|
85
|
paulo@81
|
86 table {
|
paulo@81
|
87 border-color: #FFFFFF;
|
paulo@81
|
88 }
|
paulo@81
|
89
|
paulo@81
|
90 tr.previous td {
|
paulo@81
|
91 background-color: #BBBBBB;
|
paulo@81
|
92 }
|
paulo@81
|
93
|
paulo@81
|
94 tr.current td.partial {
|
paulo@81
|
95 background-color: #CCDDCC;
|
paulo@81
|
96 }
|
paulo@81
|
97
|
paulo@81
|
98 tr.current td.today {
|
paulo@81
|
99 background-color: #DDDDCC;
|
paulo@81
|
100 }
|
paulo@81
|
101
|
paulo@81
|
102 tr.current td.future {
|
paulo@81
|
103 background-color: #DDCCCC;
|
paulo@81
|
104 }
|
paulo@81
|
105
|
paulo@81
|
106 tr.future {
|
paulo@81
|
107 background-color: #DDDDDD;
|
paulo@81
|
108 }
|
paulo@81
|
109
|
paulo@81
|
110 </style>
|
paulo@81
|
111 <script src="jquery-2.1.3.min.js"></script>
|
paulo@81
|
112 <script src="happy.js"></script>
|
paulo@81
|
113 <script src="tooltip.js"></script>
|
paulo@81
|
114
|
paulo@81
|
115 <script type="text/javascript">
|
paulo@81
|
116 var nWeeks = 52;
|
paulo@81
|
117 var nMsPerDay = 3600 * 24 * 1000;
|
paulo@81
|
118 var nMsPerWeek = 7 * nMsPerDay;
|
paulo@81
|
119 var QSParams;
|
paulo@81
|
120
|
paulo@81
|
121 var birthday;
|
paulo@81
|
122 var birthdayString;
|
paulo@81
|
123 var birthdayDate;
|
paulo@81
|
124
|
paulo@81
|
125 var lifespan;
|
paulo@81
|
126
|
paulo@81
|
127 var age;
|
paulo@81
|
128 var ageYears;
|
paulo@81
|
129 var remainder;
|
paulo@81
|
130
|
paulo@81
|
131 var previousYears;
|
paulo@81
|
132 var futureYears;
|
paulo@81
|
133 var remainderWeeks;
|
paulo@81
|
134 var restOfYearWeeks;
|
paulo@81
|
135
|
paulo@81
|
136 function validateBirthday(birthday) {
|
paulo@81
|
137 var birthdayPattern = /^[0-9]{4}-[0-9]{2}-[0-9]{2}$/;
|
paulo@81
|
138 return birthdayPattern.test( $("#birthday").val());
|
paulo@81
|
139 }
|
paulo@81
|
140
|
paulo@81
|
141 function validateLifespan(lifespan) {
|
paulo@81
|
142 var lifespanPattern = /^[0-9]{1,3}$/;
|
paulo@81
|
143 return lifespanPattern.test( $("#lifespan").val());
|
paulo@81
|
144 }
|
paulo@81
|
145
|
paulo@81
|
146 // Read a page's GET URL variables and return them as an associative array.
|
paulo@81
|
147 // From http://jquery-howto.blogspot.ca/2009/09/get-url-parameters-values-with-jquery.html
|
paulo@81
|
148 function getQSParams()
|
paulo@81
|
149 {
|
paulo@81
|
150 var vars = [], hash;
|
paulo@81
|
151 var hashes = window.location.href.slice(window.location.href.indexOf('?') + 1).split('&');
|
paulo@81
|
152 for(var i = 0; i < hashes.length; i++)
|
paulo@81
|
153 {
|
paulo@81
|
154 hash = hashes[i].split('=');
|
paulo@81
|
155 vars.push(hash[0]);
|
paulo@81
|
156 vars[hash[0]] = hash[1];
|
paulo@81
|
157 }
|
paulo@81
|
158 return vars;
|
paulo@81
|
159 }
|
paulo@81
|
160
|
paulo@81
|
161 function isLeapYear(year) {
|
paulo@81
|
162 var d = new Date(year, 1, 28);
|
paulo@81
|
163 d.setDate(d.getDate() + 1);
|
paulo@81
|
164 return d.getMonth() == 1;
|
paulo@81
|
165 }
|
paulo@81
|
166
|
paulo@81
|
167 function getAge(date) {
|
paulo@81
|
168 var d = new Date(date), now = new Date();
|
paulo@81
|
169 var years = now.getFullYear() - d.getFullYear();
|
paulo@81
|
170 d.setFullYear(d.getFullYear() + years);
|
paulo@81
|
171 if (d > now) {
|
paulo@81
|
172 years--;
|
paulo@81
|
173 d.setFullYear(d.getFullYear() - 1);
|
paulo@81
|
174 }
|
paulo@81
|
175 var days = (now.getTime() - d.getTime()) / nMsPerDay;
|
paulo@81
|
176 return years + days / (isLeapYear(now.getFullYear()) ? 366 : 365);
|
paulo@81
|
177 }
|
paulo@81
|
178
|
paulo@81
|
179 function getCalendar(d, week, year) {
|
paulo@81
|
180 var c = new Date(d);
|
paulo@81
|
181 c.setFullYear(d.getFullYear() + year);
|
paulo@81
|
182 c.setTime(c.getTime() + (week * nMsPerWeek));
|
paulo@81
|
183 return c;
|
paulo@81
|
184 }
|
paulo@81
|
185
|
paulo@81
|
186 function getWeekYearCal(e, week) {
|
paulo@81
|
187 var yearParent = e.parentNode;
|
paulo@81
|
188 var calParent = yearParent.parentNode;
|
paulo@81
|
189 var year;
|
paulo@81
|
190 for (year = 0; year < calParent.childNodes.length; year++) {
|
paulo@81
|
191 if (calParent.childNodes[year] === yearParent) {
|
paulo@81
|
192 break;
|
paulo@81
|
193 }
|
paulo@81
|
194 }
|
paulo@81
|
195 var cal = getCalendar(birthdayDate, week, year);
|
paulo@81
|
196 return {
|
paulo@81
|
197 week: week,
|
paulo@81
|
198 year: year,
|
paulo@81
|
199 cal: cal,
|
paulo@81
|
200 }
|
paulo@81
|
201 }
|
paulo@81
|
202
|
paulo@81
|
203 function loop(x, f) {
|
paulo@81
|
204 for (var i = 0; i < x; f(i++));
|
paulo@81
|
205 }
|
paulo@81
|
206
|
paulo@81
|
207 function mapLoop(x, f) {
|
paulo@81
|
208 var ret = [];
|
paulo@81
|
209 for (var i = 0; i < x; i++) {
|
paulo@81
|
210 ret.push(f(i));
|
paulo@81
|
211 }
|
paulo@81
|
212 return ret;
|
paulo@81
|
213 }
|
paulo@81
|
214
|
paulo@81
|
215 function cycleColors(e) {
|
paulo@81
|
216 var colors = [
|
paulo@81
|
217 undefined,
|
paulo@81
|
218 "black",
|
paulo@81
|
219 "red",
|
paulo@81
|
220 "green",
|
paulo@81
|
221 "blue",
|
paulo@81
|
222 "white",
|
paulo@81
|
223 ];
|
paulo@81
|
224
|
paulo@81
|
225 var i = 0;
|
paulo@81
|
226 for (; i < colors.length, colors[i] != e._fill_color; i++);
|
paulo@81
|
227 e._fill_color = colors[(i + 1) % colors.length];
|
paulo@81
|
228 if (e._fill_color != undefined) {
|
paulo@81
|
229 e.setAttribute("style", "background-color: " + e._fill_color);
|
paulo@81
|
230 } else {
|
paulo@81
|
231 e.setAttribute("style", undefined);
|
paulo@81
|
232 }
|
paulo@81
|
233 }
|
paulo@81
|
234
|
paulo@81
|
235 function _createElement(tagName, className) {
|
paulo@81
|
236 var e = document.createElement(tagName);
|
paulo@81
|
237 if (className) {
|
paulo@81
|
238 e.className = className;
|
paulo@81
|
239 }
|
paulo@81
|
240 return e;
|
paulo@81
|
241 }
|
paulo@81
|
242
|
paulo@81
|
243 function weekElem(className) {
|
paulo@81
|
244 return _createElement("td", className);
|
paulo@81
|
245 }
|
paulo@81
|
246
|
paulo@81
|
247 function yearElem(className) {
|
paulo@81
|
248 return _createElement("tr", className);
|
paulo@81
|
249 }
|
paulo@81
|
250
|
paulo@81
|
251 function yearWeekElems() {
|
paulo@81
|
252 return mapLoop(nWeeks, function(i) {
|
paulo@81
|
253 var e = weekElem();
|
paulo@81
|
254 addWeekMouseEvents(e, i);
|
paulo@81
|
255 return e;
|
paulo@81
|
256 });
|
paulo@81
|
257 }
|
paulo@81
|
258
|
paulo@81
|
259 function addWeekMouseEvents(e, i) {
|
paulo@81
|
260 e.addEventListener("mouseover", function(evt) {
|
paulo@81
|
261 var wyc = getWeekYearCal(e, i);
|
paulo@81
|
262 var calYear = wyc.cal.getFullYear();
|
paulo@81
|
263 var calMonth = wyc.cal.getMonth() + 1;
|
paulo@81
|
264 var calDay = wyc.cal.getDate();
|
paulo@81
|
265 createTooltip(evt, calYear + '-' + calMonth + '-' + calDay, "(Week: " + wyc.week + ", Year: " + wyc.year + ")");
|
paulo@81
|
266 });
|
paulo@81
|
267 e.addEventListener("click", function() {
|
paulo@81
|
268 cycleColors(e);
|
paulo@81
|
269 });
|
paulo@81
|
270 }
|
paulo@81
|
271
|
paulo@81
|
272 $( document ).ready(function() {
|
paulo@81
|
273 $( "#settingsForm" ).isHappy({
|
paulo@81
|
274 fields: {
|
paulo@81
|
275 '#birthday': {
|
paulo@81
|
276 required: true,
|
paulo@81
|
277 test: validateBirthday,
|
paulo@81
|
278 message: 'Please enter your birthday (like YYYY-MM-DD).'
|
paulo@81
|
279 },
|
paulo@81
|
280 '#lifespan': {
|
paulo@81
|
281 required: true,
|
paulo@81
|
282 test: validateLifespan,
|
paulo@81
|
283 message: 'Please enter your expected lifespan (like 90)'
|
paulo@81
|
284 }
|
paulo@81
|
285 }
|
paulo@81
|
286 });
|
paulo@81
|
287
|
paulo@81
|
288 QSParams = getQSParams();
|
paulo@81
|
289
|
paulo@81
|
290 if(!("birthday" in QSParams) | !("lifespan" in QSParams)) {
|
paulo@81
|
291 // Show the form
|
paulo@81
|
292 $("#welcome").css("display", "block");
|
paulo@81
|
293
|
paulo@81
|
294 // Give the first field focus
|
paulo@81
|
295 $("#birthday").focus();
|
paulo@81
|
296 return;
|
paulo@81
|
297 }
|
paulo@81
|
298
|
paulo@81
|
299 $("body").append( '<table id="calendar"></table>' );
|
paulo@81
|
300
|
paulo@81
|
301 birthday = QSParams["birthday"];
|
paulo@81
|
302 birthdayString = birthday + "T00:00:00";
|
paulo@81
|
303 birthdayDate = new Date(birthdayString);
|
paulo@81
|
304
|
paulo@81
|
305 lifespan = QSParams["lifespan"];
|
paulo@81
|
306
|
paulo@81
|
307 age = getAge(birthdayDate);
|
paulo@81
|
308 ageYears = Math.floor(age);
|
paulo@81
|
309 remainder = age - ageYears;
|
paulo@81
|
310
|
paulo@81
|
311 previousYears = (ageYears).toFixed(0);
|
paulo@81
|
312
|
paulo@81
|
313 if( lifespan > ageYears) {
|
paulo@81
|
314 futureYears = lifespan - ageYears - 1;
|
paulo@81
|
315 remainderWeeks = Math.floor(remainder * nWeeks);
|
paulo@81
|
316 restOfYearWeeks = Math.ceil(nWeeks - remainderWeeks) - 1;
|
paulo@81
|
317 } else {
|
paulo@81
|
318 futureYears = 0;
|
paulo@81
|
319 remainderWeeks = nWeeks;
|
paulo@81
|
320 restOfYearWeeks = 0;
|
paulo@81
|
321 }
|
paulo@81
|
322
|
paulo@81
|
323 // Fill in lived years
|
paulo@81
|
324 loop(previousYears, function() {
|
paulo@81
|
325 var tr = yearElem("previous");
|
paulo@81
|
326 $(tr).append(yearWeekElems());
|
paulo@81
|
327 $("#calendar").append(tr);
|
paulo@81
|
328 })
|
paulo@81
|
329
|
paulo@81
|
330 // Fill in the current birth-year (the number of weeks elapsed since the most recent birthday.)
|
paulo@81
|
331 var current_tr = yearElem("current");
|
paulo@81
|
332 loop(remainderWeeks, function(i) {
|
paulo@81
|
333 var e = weekElem("partial");
|
paulo@81
|
334 addWeekMouseEvents(e, i);
|
paulo@81
|
335 $(current_tr).append(e);
|
paulo@81
|
336 })
|
paulo@81
|
337 var e = weekElem("today");
|
paulo@81
|
338 addWeekMouseEvents(e, remainderWeeks);
|
paulo@81
|
339 $(current_tr).append(e);
|
paulo@81
|
340 loop(restOfYearWeeks, function(i) {
|
paulo@81
|
341 var e = weekElem("future");
|
paulo@81
|
342 addWeekMouseEvents(e, remainderWeeks + 1 + i);
|
paulo@81
|
343 $(current_tr).append(e);
|
paulo@81
|
344 })
|
paulo@81
|
345 $("#calendar").append(current_tr);
|
paulo@81
|
346
|
paulo@81
|
347 // Fill in future years
|
paulo@81
|
348 loop(futureYears, function() {
|
paulo@81
|
349 var tr = yearElem("future");
|
paulo@81
|
350 $(tr).append(yearWeekElems());
|
paulo@81
|
351 $("#calendar").append(tr);
|
paulo@81
|
352 })
|
paulo@81
|
353 });
|
paulo@81
|
354 </script>
|
paulo@81
|
355 </head>
|
paulo@81
|
356 <body>
|
paulo@81
|
357 <div id="welcome">
|
paulo@81
|
358 <h1>Life Calendar</h1>
|
paulo@81
|
359 <p>
|
paulo@81
|
360 A minimalist life calendar. Shows the number of weeks you've lived and the number of weeks you
|
paulo@81
|
361 have left.
|
paulo@81
|
362 </p>
|
paulo@81
|
363 <form method="get" action="" id="settingsForm">
|
paulo@81
|
364 <p>
|
paulo@81
|
365 <label for="birthday">Birthday:
|
paulo@81
|
366 <input class="settings" id="birthday" name="birthday" placeholder="1985-01-01"/>
|
paulo@81
|
367 </label>
|
paulo@81
|
368 </p>
|
paulo@81
|
369 <p>
|
paulo@81
|
370 <label for="lifespan">Life expectancy:
|
paulo@81
|
371 <input class="settings" id="lifespan" name="lifespan" placeholder="90"/>
|
paulo@81
|
372 </label>
|
paulo@81
|
373 </p>
|
paulo@81
|
374 <p>
|
paulo@81
|
375 <input type="submit" id="submit" value="Show calendar" />
|
paulo@81
|
376 </p>
|
paulo@81
|
377 </form>
|
paulo@81
|
378 <div id="footer">
|
paulo@81
|
379 <p>
|
paulo@81
|
380 Based on <a href="http://waitbutwhy.com/2014/05/life-weeks.html">Your Life in Weeks</a>
|
paulo@81
|
381 and <a href="http://count.life">count.life</a>.
|
paulo@81
|
382 </p>
|
paulo@81
|
383 </div>
|
paulo@81
|
384 </div>
|
paulo@81
|
385 </body>
|
paulo@81
|
386 </html>
|