annotate life_calendar/index.html @ 115:2b5d93c5628a

merge heads
author paulo
date Mon, 03 Aug 2020 02:02:02 -0600
parents 256b8df1c686
children
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@86 82 div#tooltip h3 {
paulo@86 83 color: #333333;
paulo@86 84 font-size: 0.8em;
paulo@86 85 margin: 2px;
paulo@86 86 }
paulo@86 87
paulo@81 88 td {
paulo@81 89 border-color: #FFFFFF;
paulo@81 90 }
paulo@81 91
paulo@81 92 table {
paulo@81 93 border-color: #FFFFFF;
paulo@81 94 }
paulo@81 95
paulo@81 96 tr.previous td {
paulo@81 97 background-color: #BBBBBB;
paulo@81 98 }
paulo@81 99
paulo@81 100 tr.current td.partial {
paulo@81 101 background-color: #CCDDCC;
paulo@81 102 }
paulo@81 103
paulo@81 104 tr.current td.today {
paulo@81 105 background-color: #DDDDCC;
paulo@81 106 }
paulo@81 107
paulo@81 108 tr.current td.future {
paulo@81 109 background-color: #DDCCCC;
paulo@81 110 }
paulo@81 111
paulo@81 112 tr.future {
paulo@81 113 background-color: #DDDDDD;
paulo@81 114 }
paulo@81 115
paulo@81 116 </style>
paulo@81 117 <script src="jquery-2.1.3.min.js"></script>
paulo@81 118 <script src="happy.js"></script>
paulo@81 119 <script src="tooltip.js"></script>
paulo@81 120
paulo@81 121 <script type="text/javascript">
paulo@81 122 var nWeeks = 52;
paulo@81 123 var nMsPerDay = 3600 * 24 * 1000;
paulo@81 124 var nMsPerWeek = 7 * nMsPerDay;
paulo@81 125 var QSParams;
paulo@81 126
paulo@81 127 var birthday;
paulo@81 128 var birthdayString;
paulo@81 129 var birthdayDate;
paulo@81 130
paulo@81 131 var lifespan;
paulo@81 132
paulo@81 133 var age;
paulo@81 134 var ageYears;
paulo@81 135 var remainder;
paulo@81 136
paulo@81 137 var previousYears;
paulo@81 138 var futureYears;
paulo@81 139 var remainderWeeks;
paulo@81 140 var restOfYearWeeks;
paulo@81 141
paulo@86 142 var selecting = false;
paulo@86 143 var start_wyc = null;
paulo@86 144 var end_wyc = null;
paulo@86 145
paulo@81 146 function validateBirthday(birthday) {
paulo@81 147 var birthdayPattern = /^[0-9]{4}-[0-9]{2}-[0-9]{2}$/;
paulo@81 148 return birthdayPattern.test( $("#birthday").val());
paulo@81 149 }
paulo@81 150
paulo@81 151 function validateLifespan(lifespan) {
paulo@81 152 var lifespanPattern = /^[0-9]{1,3}$/;
paulo@81 153 return lifespanPattern.test( $("#lifespan").val());
paulo@81 154 }
paulo@81 155
paulo@81 156 // Read a page's GET URL variables and return them as an associative array.
paulo@81 157 // From http://jquery-howto.blogspot.ca/2009/09/get-url-parameters-values-with-jquery.html
paulo@81 158 function getQSParams()
paulo@81 159 {
paulo@81 160 var vars = [], hash;
paulo@81 161 var hashes = window.location.href.slice(window.location.href.indexOf('?') + 1).split('&');
paulo@81 162 for(var i = 0; i < hashes.length; i++)
paulo@81 163 {
paulo@81 164 hash = hashes[i].split('=');
paulo@81 165 vars.push(hash[0]);
paulo@81 166 vars[hash[0]] = hash[1];
paulo@81 167 }
paulo@81 168 return vars;
paulo@81 169 }
paulo@81 170
paulo@81 171 function isLeapYear(year) {
paulo@81 172 var d = new Date(year, 1, 28);
paulo@81 173 d.setDate(d.getDate() + 1);
paulo@81 174 return d.getMonth() == 1;
paulo@81 175 }
paulo@81 176
paulo@81 177 function getAge(date) {
paulo@81 178 var d = new Date(date), now = new Date();
paulo@81 179 var years = now.getFullYear() - d.getFullYear();
paulo@81 180 d.setFullYear(d.getFullYear() + years);
paulo@81 181 if (d > now) {
paulo@81 182 years--;
paulo@81 183 d.setFullYear(d.getFullYear() - 1);
paulo@81 184 }
paulo@81 185 var days = (now.getTime() - d.getTime()) / nMsPerDay;
paulo@81 186 return years + days / (isLeapYear(now.getFullYear()) ? 366 : 365);
paulo@81 187 }
paulo@81 188
paulo@81 189 function getCalendar(d, week, year) {
paulo@81 190 var c = new Date(d);
paulo@81 191 c.setFullYear(d.getFullYear() + year);
paulo@81 192 c.setTime(c.getTime() + (week * nMsPerWeek));
paulo@81 193 return c;
paulo@81 194 }
paulo@81 195
paulo@81 196 function getWeekYearCal(e, week) {
paulo@81 197 var yearParent = e.parentNode;
paulo@81 198 var calParent = yearParent.parentNode;
paulo@81 199 var year;
paulo@81 200 for (year = 0; year < calParent.childNodes.length; year++) {
paulo@81 201 if (calParent.childNodes[year] === yearParent) {
paulo@81 202 break;
paulo@81 203 }
paulo@81 204 }
paulo@81 205 return {
paulo@81 206 week: week,
paulo@81 207 year: year,
paulo@86 208 cal: getCalendar(birthdayDate, week, year),
paulo@86 209 weeks: nWeeks*year + week,
paulo@81 210 }
paulo@81 211 }
paulo@81 212
paulo@86 213 function inSpan(x, s, e) {
paulo@86 214 return x >= s && x <= e;
paulo@86 215 }
paulo@86 216
paulo@81 217 function loop(x, f) {
paulo@81 218 for (var i = 0; i < x; f(i++));
paulo@81 219 }
paulo@81 220
paulo@81 221 function mapLoop(x, f) {
paulo@81 222 var ret = [];
paulo@81 223 for (var i = 0; i < x; i++) {
paulo@81 224 ret.push(f(i));
paulo@81 225 }
paulo@81 226 return ret;
paulo@81 227 }
paulo@81 228
paulo@86 229 function setStyle(e) {
paulo@86 230 var styles = [];
paulo@86 231
paulo@86 232 if (e["_fill_color"] != undefined) {
paulo@86 233 styles.push("background-color: " + e._fill_color);
paulo@86 234 }
paulo@86 235 if (e["_border_color"] != undefined) {
paulo@86 236 styles.push("border-color: " + e._border_color);
paulo@86 237 }
paulo@86 238
paulo@86 239 if (styles.length == 0) {
paulo@86 240 e.setAttribute("style", undefined);
paulo@86 241 } else {
paulo@86 242 e.setAttribute("style", styles.join('; '));
paulo@86 243 }
paulo@86 244 }
paulo@86 245
paulo@86 246 function highlightDistance(e0, e1_wyc, e2_wyc) {
paulo@86 247 var calParent = e0.parentNode.parentNode;
paulo@86 248 var min_wyc = (e1_wyc.weeks <= e2_wyc.weeks) ? e1_wyc : e2_wyc;
paulo@86 249 var max_wyc = (e2_wyc == min_wyc) ? e1_wyc : e2_wyc;
paulo@86 250 for (var year = 0; year < lifespan; year++) {
paulo@86 251 for (var week = 0; week < nWeeks; week++) {
paulo@86 252 w = nWeeks*year + week;
paulo@86 253 e = calParent.childNodes[year].childNodes[week]
paulo@86 254 if (inSpan(w, min_wyc.weeks, max_wyc.weeks)) {
paulo@86 255 e._border_color = "black";
paulo@86 256 } else {
paulo@86 257 e._border_color = undefined;
paulo@86 258 }
paulo@86 259 setStyle(e);
paulo@86 260 }
paulo@86 261 }
paulo@86 262 }
paulo@86 263
paulo@81 264 function cycleColors(e) {
paulo@81 265 var colors = [
paulo@81 266 undefined,
paulo@81 267 "black",
paulo@81 268 "red",
paulo@81 269 "green",
paulo@81 270 "blue",
paulo@81 271 "white",
paulo@81 272 ];
paulo@81 273
paulo@81 274 var i = 0;
paulo@81 275 for (; i < colors.length, colors[i] != e._fill_color; i++);
paulo@81 276 e._fill_color = colors[(i + 1) % colors.length];
paulo@86 277 setStyle(e);
paulo@81 278 }
paulo@81 279
paulo@81 280 function _createElement(tagName, className) {
paulo@81 281 var e = document.createElement(tagName);
paulo@81 282 if (className) {
paulo@81 283 e.className = className;
paulo@81 284 }
paulo@81 285 return e;
paulo@81 286 }
paulo@81 287
paulo@81 288 function weekElem(className) {
paulo@81 289 return _createElement("td", className);
paulo@81 290 }
paulo@81 291
paulo@81 292 function yearElem(className) {
paulo@81 293 return _createElement("tr", className);
paulo@81 294 }
paulo@81 295
paulo@81 296 function yearWeekElems() {
paulo@81 297 return mapLoop(nWeeks, function(i) {
paulo@81 298 var e = weekElem();
paulo@81 299 addWeekMouseEvents(e, i);
paulo@81 300 return e;
paulo@81 301 });
paulo@81 302 }
paulo@81 303
paulo@81 304 function addWeekMouseEvents(e, i) {
paulo@81 305 e.addEventListener("mouseover", function(evt) {
paulo@81 306 var wyc = getWeekYearCal(e, i);
paulo@81 307 var calYear = wyc.cal.getFullYear();
paulo@81 308 var calMonth = wyc.cal.getMonth() + 1;
paulo@81 309 var calDay = wyc.cal.getDate();
paulo@86 310 var title = calYear + '-' + calMonth + '-' + calDay;
paulo@86 311 var text = "(Week: " + wyc.week + ", Year: " + wyc.year + ")";
paulo@86 312 var subtext = null;
paulo@86 313 if (selecting) {
paulo@86 314 end_wyc = wyc;
paulo@86 315 highlightDistance(e, start_wyc, end_wyc);
paulo@86 316 }
paulo@86 317 if (start_wyc && end_wyc) {
paulo@86 318 var min_wyc = (start_wyc.weeks <= end_wyc.weeks) ? start_wyc : end_wyc;
paulo@86 319 var max_wyc = (end_wyc == min_wyc) ? start_wyc : end_wyc;
paulo@86 320 if (inSpan(wyc.weeks, min_wyc.weeks, max_wyc.weeks)) {
paulo@86 321 var sel_weeks = max_wyc.weeks - min_wyc.weeks;
paulo@86 322 var sel_years = sel_weeks/nWeeks;
paulo@86 323 subtext = "[Selected: weeks: " + sel_weeks + " years: " + sel_years.toFixed(2) + "]";
paulo@86 324 }
paulo@86 325 }
paulo@86 326 createTooltip(evt, title, text, subtext);
paulo@81 327 });
paulo@81 328 e.addEventListener("click", function() {
paulo@86 329 var wyc = getWeekYearCal(e, i);
paulo@86 330 highlightDistance(e, wyc, wyc);
paulo@86 331 if (start_wyc && end_wyc) {
paulo@86 332 start_wyc = null;
paulo@86 333 end_wyc = null;
paulo@86 334 } else {
paulo@86 335 cycleColors(e);
paulo@86 336 }
paulo@86 337 });
paulo@86 338 e.addEventListener("mousedown", function() {
paulo@86 339 selecting = true;
paulo@86 340 start_wyc = getWeekYearCal(e, i);
paulo@86 341 });
paulo@86 342 e.addEventListener("mouseup", function() {
paulo@86 343 selecting = false;
paulo@81 344 });
paulo@81 345 }
paulo@81 346
paulo@81 347 $( document ).ready(function() {
paulo@81 348 $( "#settingsForm" ).isHappy({
paulo@81 349 fields: {
paulo@81 350 '#birthday': {
paulo@81 351 required: true,
paulo@81 352 test: validateBirthday,
paulo@81 353 message: 'Please enter your birthday (like YYYY-MM-DD).'
paulo@81 354 },
paulo@81 355 '#lifespan': {
paulo@81 356 required: true,
paulo@81 357 test: validateLifespan,
paulo@81 358 message: 'Please enter your expected lifespan (like 90)'
paulo@81 359 }
paulo@81 360 }
paulo@81 361 });
paulo@81 362
paulo@81 363 QSParams = getQSParams();
paulo@81 364
paulo@81 365 if(!("birthday" in QSParams) | !("lifespan" in QSParams)) {
paulo@81 366 // Show the form
paulo@81 367 $("#welcome").css("display", "block");
paulo@81 368
paulo@81 369 // Give the first field focus
paulo@81 370 $("#birthday").focus();
paulo@81 371 return;
paulo@81 372 }
paulo@81 373
paulo@81 374 $("body").append( '<table id="calendar"></table>' );
paulo@81 375
paulo@81 376 birthday = QSParams["birthday"];
paulo@81 377 birthdayString = birthday + "T00:00:00";
paulo@81 378 birthdayDate = new Date(birthdayString);
paulo@81 379
paulo@81 380 lifespan = QSParams["lifespan"];
paulo@81 381
paulo@81 382 age = getAge(birthdayDate);
paulo@81 383 ageYears = Math.floor(age);
paulo@81 384 remainder = age - ageYears;
paulo@81 385
paulo@81 386 previousYears = (ageYears).toFixed(0);
paulo@81 387
paulo@81 388 if( lifespan > ageYears) {
paulo@81 389 futureYears = lifespan - ageYears - 1;
paulo@81 390 remainderWeeks = Math.floor(remainder * nWeeks);
paulo@81 391 restOfYearWeeks = Math.ceil(nWeeks - remainderWeeks) - 1;
paulo@81 392 } else {
paulo@81 393 futureYears = 0;
paulo@81 394 remainderWeeks = nWeeks;
paulo@81 395 restOfYearWeeks = 0;
paulo@81 396 }
paulo@81 397
paulo@81 398 // Fill in lived years
paulo@81 399 loop(previousYears, function() {
paulo@81 400 var tr = yearElem("previous");
paulo@81 401 $(tr).append(yearWeekElems());
paulo@81 402 $("#calendar").append(tr);
paulo@81 403 })
paulo@81 404
paulo@81 405 // Fill in the current birth-year (the number of weeks elapsed since the most recent birthday.)
paulo@81 406 var current_tr = yearElem("current");
paulo@81 407 loop(remainderWeeks, function(i) {
paulo@81 408 var e = weekElem("partial");
paulo@81 409 addWeekMouseEvents(e, i);
paulo@81 410 $(current_tr).append(e);
paulo@81 411 })
paulo@81 412 var e = weekElem("today");
paulo@81 413 addWeekMouseEvents(e, remainderWeeks);
paulo@81 414 $(current_tr).append(e);
paulo@81 415 loop(restOfYearWeeks, function(i) {
paulo@81 416 var e = weekElem("future");
paulo@81 417 addWeekMouseEvents(e, remainderWeeks + 1 + i);
paulo@81 418 $(current_tr).append(e);
paulo@81 419 })
paulo@81 420 $("#calendar").append(current_tr);
paulo@81 421
paulo@81 422 // Fill in future years
paulo@81 423 loop(futureYears, function() {
paulo@81 424 var tr = yearElem("future");
paulo@81 425 $(tr).append(yearWeekElems());
paulo@81 426 $("#calendar").append(tr);
paulo@81 427 })
paulo@81 428 });
paulo@81 429 </script>
paulo@81 430 </head>
paulo@81 431 <body>
paulo@81 432 <div id="welcome">
paulo@81 433 <h1>Life Calendar</h1>
paulo@81 434 <p>
paulo@81 435 A minimalist life calendar. Shows the number of weeks you've lived and the number of weeks you
paulo@81 436 have left.
paulo@81 437 </p>
paulo@81 438 <form method="get" action="" id="settingsForm">
paulo@81 439 <p>
paulo@81 440 <label for="birthday">Birthday:
paulo@81 441 <input class="settings" id="birthday" name="birthday" placeholder="1985-01-01"/>
paulo@81 442 </label>
paulo@81 443 </p>
paulo@81 444 <p>
paulo@81 445 <label for="lifespan">Life expectancy:
paulo@81 446 <input class="settings" id="lifespan" name="lifespan" placeholder="90"/>
paulo@81 447 </label>
paulo@81 448 </p>
paulo@81 449 <p>
paulo@81 450 <input type="submit" id="submit" value="Show calendar" />
paulo@81 451 </p>
paulo@81 452 </form>
paulo@81 453 <div id="footer">
paulo@81 454 <p>
paulo@81 455 Based on <a href="http://waitbutwhy.com/2014/05/life-weeks.html">Your Life in Weeks</a>
paulo@81 456 and <a href="http://count.life">count.life</a>.
paulo@81 457 </p>
paulo@81 458 </div>
paulo@81 459 </div>
paulo@81 460 </body>
paulo@81 461 </html>