Mercurial > hg > index.fcgi > www > www-1
view life_calendar/index.html @ 106:712cc41e0be2
merge heads
author | paulo |
---|---|
date | Sat, 28 Dec 2019 00:58:28 -0800 |
parents | 256b8df1c686 |
children |
line source
1 <html>
2 <head>
3 <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
4 <title>Life Calendar</title>
5 <style type="text/css">
6 body {
7 padding: 0;
8 margin: 0;
9 position: relative;
10 font-family: sans-serif;
11 background-size: cover;
12 }
14 a, a:visited {
15 color: #70BCF9;
16 }
18 table {
19 width: 100%;
20 height: 100%;
21 }
23 table, td {
24 border: 1px solid;
25 border-collapse: collapse;
26 padding: 0;
27 margin: 0;
28 }
30 div#welcome {
31 display: none;
33 width: 50vw;
34 margin: 8vh auto 0;
35 }
37 div#welcome h1 {
38 text-align: center;
39 text-transform: uppercase;
40 font-family: serif;
41 font-size: 4em;
42 }
43 div#welcome form {
44 border: 1px solid #999999;
45 padding: 10px 20px;
46 }
48 div#welcome .settings {
49 margin-left: 5px;
50 padding: 5px;
51 }
53 div#welcome div#footer {
54 color: #a6a6a6;
55 padding-top: 50px;
56 text-align: center;
57 }
59 #settingsForm span.unhappyMessage {
60 font-size: 0.8em;
61 padding-left: 10px;
62 }
64 div#tooltip {
65 background-color: #5070D0;
66 padding: 0.5em;
67 }
69 div#tooltip h1 {
70 color: #DFDFDF;
71 font-size: 1.25em;
72 margin: 1px;
73 }
75 div#tooltip h2 {
76 color: #AAAAAA;
77 font-size: 0.75em;
78 font-weight: normal;
79 margin: 1px;
80 }
82 div#tooltip h3 {
83 color: #333333;
84 font-size: 0.8em;
85 margin: 2px;
86 }
88 td {
89 border-color: #FFFFFF;
90 }
92 table {
93 border-color: #FFFFFF;
94 }
96 tr.previous td {
97 background-color: #BBBBBB;
98 }
100 tr.current td.partial {
101 background-color: #CCDDCC;
102 }
104 tr.current td.today {
105 background-color: #DDDDCC;
106 }
108 tr.current td.future {
109 background-color: #DDCCCC;
110 }
112 tr.future {
113 background-color: #DDDDDD;
114 }
116 </style>
117 <script src="jquery-2.1.3.min.js"></script>
118 <script src="happy.js"></script>
119 <script src="tooltip.js"></script>
121 <script type="text/javascript">
122 var nWeeks = 52;
123 var nMsPerDay = 3600 * 24 * 1000;
124 var nMsPerWeek = 7 * nMsPerDay;
125 var QSParams;
127 var birthday;
128 var birthdayString;
129 var birthdayDate;
131 var lifespan;
133 var age;
134 var ageYears;
135 var remainder;
137 var previousYears;
138 var futureYears;
139 var remainderWeeks;
140 var restOfYearWeeks;
142 var selecting = false;
143 var start_wyc = null;
144 var end_wyc = null;
146 function validateBirthday(birthday) {
147 var birthdayPattern = /^[0-9]{4}-[0-9]{2}-[0-9]{2}$/;
148 return birthdayPattern.test( $("#birthday").val());
149 }
151 function validateLifespan(lifespan) {
152 var lifespanPattern = /^[0-9]{1,3}$/;
153 return lifespanPattern.test( $("#lifespan").val());
154 }
156 // Read a page's GET URL variables and return them as an associative array.
157 // From http://jquery-howto.blogspot.ca/2009/09/get-url-parameters-values-with-jquery.html
158 function getQSParams()
159 {
160 var vars = [], hash;
161 var hashes = window.location.href.slice(window.location.href.indexOf('?') + 1).split('&');
162 for(var i = 0; i < hashes.length; i++)
163 {
164 hash = hashes[i].split('=');
165 vars.push(hash[0]);
166 vars[hash[0]] = hash[1];
167 }
168 return vars;
169 }
171 function isLeapYear(year) {
172 var d = new Date(year, 1, 28);
173 d.setDate(d.getDate() + 1);
174 return d.getMonth() == 1;
175 }
177 function getAge(date) {
178 var d = new Date(date), now = new Date();
179 var years = now.getFullYear() - d.getFullYear();
180 d.setFullYear(d.getFullYear() + years);
181 if (d > now) {
182 years--;
183 d.setFullYear(d.getFullYear() - 1);
184 }
185 var days = (now.getTime() - d.getTime()) / nMsPerDay;
186 return years + days / (isLeapYear(now.getFullYear()) ? 366 : 365);
187 }
189 function getCalendar(d, week, year) {
190 var c = new Date(d);
191 c.setFullYear(d.getFullYear() + year);
192 c.setTime(c.getTime() + (week * nMsPerWeek));
193 return c;
194 }
196 function getWeekYearCal(e, week) {
197 var yearParent = e.parentNode;
198 var calParent = yearParent.parentNode;
199 var year;
200 for (year = 0; year < calParent.childNodes.length; year++) {
201 if (calParent.childNodes[year] === yearParent) {
202 break;
203 }
204 }
205 return {
206 week: week,
207 year: year,
208 cal: getCalendar(birthdayDate, week, year),
209 weeks: nWeeks*year + week,
210 }
211 }
213 function inSpan(x, s, e) {
214 return x >= s && x <= e;
215 }
217 function loop(x, f) {
218 for (var i = 0; i < x; f(i++));
219 }
221 function mapLoop(x, f) {
222 var ret = [];
223 for (var i = 0; i < x; i++) {
224 ret.push(f(i));
225 }
226 return ret;
227 }
229 function setStyle(e) {
230 var styles = [];
232 if (e["_fill_color"] != undefined) {
233 styles.push("background-color: " + e._fill_color);
234 }
235 if (e["_border_color"] != undefined) {
236 styles.push("border-color: " + e._border_color);
237 }
239 if (styles.length == 0) {
240 e.setAttribute("style", undefined);
241 } else {
242 e.setAttribute("style", styles.join('; '));
243 }
244 }
246 function highlightDistance(e0, e1_wyc, e2_wyc) {
247 var calParent = e0.parentNode.parentNode;
248 var min_wyc = (e1_wyc.weeks <= e2_wyc.weeks) ? e1_wyc : e2_wyc;
249 var max_wyc = (e2_wyc == min_wyc) ? e1_wyc : e2_wyc;
250 for (var year = 0; year < lifespan; year++) {
251 for (var week = 0; week < nWeeks; week++) {
252 w = nWeeks*year + week;
253 e = calParent.childNodes[year].childNodes[week]
254 if (inSpan(w, min_wyc.weeks, max_wyc.weeks)) {
255 e._border_color = "black";
256 } else {
257 e._border_color = undefined;
258 }
259 setStyle(e);
260 }
261 }
262 }
264 function cycleColors(e) {
265 var colors = [
266 undefined,
267 "black",
268 "red",
269 "green",
270 "blue",
271 "white",
272 ];
274 var i = 0;
275 for (; i < colors.length, colors[i] != e._fill_color; i++);
276 e._fill_color = colors[(i + 1) % colors.length];
277 setStyle(e);
278 }
280 function _createElement(tagName, className) {
281 var e = document.createElement(tagName);
282 if (className) {
283 e.className = className;
284 }
285 return e;
286 }
288 function weekElem(className) {
289 return _createElement("td", className);
290 }
292 function yearElem(className) {
293 return _createElement("tr", className);
294 }
296 function yearWeekElems() {
297 return mapLoop(nWeeks, function(i) {
298 var e = weekElem();
299 addWeekMouseEvents(e, i);
300 return e;
301 });
302 }
304 function addWeekMouseEvents(e, i) {
305 e.addEventListener("mouseover", function(evt) {
306 var wyc = getWeekYearCal(e, i);
307 var calYear = wyc.cal.getFullYear();
308 var calMonth = wyc.cal.getMonth() + 1;
309 var calDay = wyc.cal.getDate();
310 var title = calYear + '-' + calMonth + '-' + calDay;
311 var text = "(Week: " + wyc.week + ", Year: " + wyc.year + ")";
312 var subtext = null;
313 if (selecting) {
314 end_wyc = wyc;
315 highlightDistance(e, start_wyc, end_wyc);
316 }
317 if (start_wyc && end_wyc) {
318 var min_wyc = (start_wyc.weeks <= end_wyc.weeks) ? start_wyc : end_wyc;
319 var max_wyc = (end_wyc == min_wyc) ? start_wyc : end_wyc;
320 if (inSpan(wyc.weeks, min_wyc.weeks, max_wyc.weeks)) {
321 var sel_weeks = max_wyc.weeks - min_wyc.weeks;
322 var sel_years = sel_weeks/nWeeks;
323 subtext = "[Selected: weeks: " + sel_weeks + " years: " + sel_years.toFixed(2) + "]";
324 }
325 }
326 createTooltip(evt, title, text, subtext);
327 });
328 e.addEventListener("click", function() {
329 var wyc = getWeekYearCal(e, i);
330 highlightDistance(e, wyc, wyc);
331 if (start_wyc && end_wyc) {
332 start_wyc = null;
333 end_wyc = null;
334 } else {
335 cycleColors(e);
336 }
337 });
338 e.addEventListener("mousedown", function() {
339 selecting = true;
340 start_wyc = getWeekYearCal(e, i);
341 });
342 e.addEventListener("mouseup", function() {
343 selecting = false;
344 });
345 }
347 $( document ).ready(function() {
348 $( "#settingsForm" ).isHappy({
349 fields: {
350 '#birthday': {
351 required: true,
352 test: validateBirthday,
353 message: 'Please enter your birthday (like YYYY-MM-DD).'
354 },
355 '#lifespan': {
356 required: true,
357 test: validateLifespan,
358 message: 'Please enter your expected lifespan (like 90)'
359 }
360 }
361 });
363 QSParams = getQSParams();
365 if(!("birthday" in QSParams) | !("lifespan" in QSParams)) {
366 // Show the form
367 $("#welcome").css("display", "block");
369 // Give the first field focus
370 $("#birthday").focus();
371 return;
372 }
374 $("body").append( '<table id="calendar"></table>' );
376 birthday = QSParams["birthday"];
377 birthdayString = birthday + "T00:00:00";
378 birthdayDate = new Date(birthdayString);
380 lifespan = QSParams["lifespan"];
382 age = getAge(birthdayDate);
383 ageYears = Math.floor(age);
384 remainder = age - ageYears;
386 previousYears = (ageYears).toFixed(0);
388 if( lifespan > ageYears) {
389 futureYears = lifespan - ageYears - 1;
390 remainderWeeks = Math.floor(remainder * nWeeks);
391 restOfYearWeeks = Math.ceil(nWeeks - remainderWeeks) - 1;
392 } else {
393 futureYears = 0;
394 remainderWeeks = nWeeks;
395 restOfYearWeeks = 0;
396 }
398 // Fill in lived years
399 loop(previousYears, function() {
400 var tr = yearElem("previous");
401 $(tr).append(yearWeekElems());
402 $("#calendar").append(tr);
403 })
405 // Fill in the current birth-year (the number of weeks elapsed since the most recent birthday.)
406 var current_tr = yearElem("current");
407 loop(remainderWeeks, function(i) {
408 var e = weekElem("partial");
409 addWeekMouseEvents(e, i);
410 $(current_tr).append(e);
411 })
412 var e = weekElem("today");
413 addWeekMouseEvents(e, remainderWeeks);
414 $(current_tr).append(e);
415 loop(restOfYearWeeks, function(i) {
416 var e = weekElem("future");
417 addWeekMouseEvents(e, remainderWeeks + 1 + i);
418 $(current_tr).append(e);
419 })
420 $("#calendar").append(current_tr);
422 // Fill in future years
423 loop(futureYears, function() {
424 var tr = yearElem("future");
425 $(tr).append(yearWeekElems());
426 $("#calendar").append(tr);
427 })
428 });
429 </script>
430 </head>
431 <body>
432 <div id="welcome">
433 <h1>Life Calendar</h1>
434 <p>
435 A minimalist life calendar. Shows the number of weeks you've lived and the number of weeks you
436 have left.
437 </p>
438 <form method="get" action="" id="settingsForm">
439 <p>
440 <label for="birthday">Birthday:
441 <input class="settings" id="birthday" name="birthday" placeholder="1985-01-01"/>
442 </label>
443 </p>
444 <p>
445 <label for="lifespan">Life expectancy:
446 <input class="settings" id="lifespan" name="lifespan" placeholder="90"/>
447 </label>
448 </p>
449 <p>
450 <input type="submit" id="submit" value="Show calendar" />
451 </p>
452 </form>
453 <div id="footer">
454 <p>
455 Based on <a href="http://waitbutwhy.com/2014/05/life-weeks.html">Your Life in Weeks</a>
456 and <a href="http://count.life">count.life</a>.
457 </p>
458 </div>
459 </div>
460 </body>
461 </html>