(function () {
'use strict';
// -------------------------------------------------------------------------
// Constants
// -------------------------------------------------------------------------
// Birthday: Monday, December 29, 2003
const BIRTHDAY = new Date(2003, 11, 29); // month is 0-indexed
// End of the 80-year span: December 29, 2083
const LIFE_END = new Date(2083, 11, 29);
const YEARS = 80;
const WEEKS_PER_YEAR = 52;
const TOTAL_WEEKS = YEARS * WEEKS_PER_YEAR; // 4160
// A tropical season: 365.25 / 4 days
const SEASON_MS = 91.25 * 24 * 60 * 60 * 1000;
// -------------------------------------------------------------------------
// Utilities
// -------------------------------------------------------------------------
function fmtDate(d) {
return d.toLocaleDateString('en-US', {
month: 'short', day: 'numeric', year: 'numeric'
});
}
function fmtDayShort(d) {
return d.toLocaleDateString('en-US', {
weekday: 'short', month: 'short', day: 'numeric'
});
}
function currentSeasonName() {
const now = new Date();
const m = now.getMonth(); // 0-indexed
const d = now.getDate();
// Approximate meteorological seasons (northern hemisphere)
if (m < 2 || (m === 11)) return 'winter';
if (m < 5) return 'spring';
if (m < 8) return 'summer';
return 'autumn';
}
// -------------------------------------------------------------------------
// Grid
// -------------------------------------------------------------------------
function buildGrid() {
const container = document.getElementById('weeks-grid-wrapper');
if (!container) return;
const today = new Date();
today.setHours(0, 0, 0, 0);
const grid = document.createElement('div');
grid.className = 'weeks-grid';
grid.setAttribute('role', 'img');
grid.setAttribute('aria-label', 'A grid of 4,160 squares representing 80 years of life, one square per week.');
let globalWeekIndex = 1;
for (let year = 0; year < YEARS; year++) {
// Anchor the start of every row exactly to the calendar anniversary.
const yearStart = new Date(BIRTHDAY.getTime());
yearStart.setFullYear(yearStart.getFullYear() + year);
for (let weekInYear = 1; weekInYear <= WEEKS_PER_YEAR; weekInYear++) {
const start = new Date(yearStart.getTime());
start.setDate(start.getDate() + (weekInYear - 1) * 7);
const end = new Date(start.getTime());
// Stretch week 52 to the eve of the next birthday, absorbing
// the 365th/366th day so no day falls outside the grid.
if (weekInYear === WEEKS_PER_YEAR) {
const nextBday = new Date(BIRTHDAY.getTime());
nextBday.setFullYear(nextBday.getFullYear() + year + 1);
end.setTime(nextBday.getTime() - 24 * 60 * 60 * 1000);
} else {
end.setDate(end.getDate() + 6);
}
const cell = document.createElement('span');
cell.className = 'week';
if (today > end) {
cell.classList.add('week--past');
} else if (today >= start) {
cell.classList.add('week--current');
} else {
cell.classList.add('week--future');
}
if (weekInYear === 1 && year % 10 === 0) {
cell.classList.add('week--decade');
}
cell.dataset.start = fmtDate(start);
cell.dataset.end = fmtDate(end);
cell.dataset.startRaw = start.getTime();
cell.dataset.endRaw = end.getTime();
cell.dataset.year = year;
cell.dataset.weekInYear = weekInYear;
cell.dataset.index = globalWeekIndex;
grid.appendChild(cell);
globalWeekIndex++;
}
}
container.appendChild(grid);
const legend = document.createElement('p');
legend.className = 'weeks-legend';
legend.textContent =
'Each row is one year (age 0–79). Each cell is one week. ' +
'Decade boundaries are marked.';
container.appendChild(legend);
initTooltip(grid);
initWeekPopup(grid);
}
// -------------------------------------------------------------------------
// Tooltip
// -------------------------------------------------------------------------
function initTooltip(grid) {
const tip = document.createElement('div');
tip.className = 'weeks-tooltip';
tip.setAttribute('aria-hidden', 'true');
tip.hidden = true;
document.body.appendChild(tip);
grid.addEventListener('mouseover', e => {
const cell = e.target.closest('.week');
if (!cell) { tip.hidden = true; return; }
const year = parseInt(cell.dataset.year, 10);
const weekInYear = parseInt(cell.dataset.weekInYear, 10);
const index = parseInt(cell.dataset.index, 10);
const isPast = cell.classList.contains('week--past');
const isCurrent = cell.classList.contains('week--current');
const status = isPast ? 'elapsed' : isCurrent ? 'this week' : 'remaining';
tip.innerHTML =
`age ${year}` +
`year ${year + 1}, week ${weekInYear}` +
`${cell.dataset.start} – ${cell.dataset.end}` +
`` +
`week ${index.toLocaleString()} of ${TOTAL_WEEKS.toLocaleString()}` +
` · ${status}` +
``;
tip.hidden = false;
});
grid.addEventListener('mouseleave', () => { tip.hidden = true; });
document.addEventListener('mousemove', e => {
if (tip.hidden) return;
const tw = tip.offsetWidth;
const th = tip.offsetHeight;
let x = e.clientX + 14;
let y = e.clientY - th - 10;
if (y < 8) y = e.clientY + 16;
if (x + tw > window.innerWidth - 8) x = e.clientX - tw - 14;
tip.style.left = x + 'px';
tip.style.top = y + 'px';
});
}
// -------------------------------------------------------------------------
// Week popup — dynamic day count
// -------------------------------------------------------------------------
function initWeekPopup(grid) {
const backdrop = document.createElement('div');
backdrop.className = 'week-popup-backdrop';
backdrop.hidden = true;
const card = document.createElement('div');
card.className = 'week-popup-card';
card.setAttribute('role', 'dialog');
card.setAttribute('aria-modal', 'true');
backdrop.appendChild(card);
document.body.appendChild(backdrop);
function openPopup(cell) {
const startMs = parseInt(cell.dataset.startRaw, 10);
const endMs = parseInt(cell.dataset.endRaw, 10);
const year = parseInt(cell.dataset.year, 10);
const weekInYear = parseInt(cell.dataset.weekInYear, 10);
const index = parseInt(cell.dataset.index, 10);
const today = new Date();
today.setHours(0, 0, 0, 0);
// Dynamically calculate how many days are in this week (7, 8, or 9).
const daysInWeek = Math.round((endMs - startMs) / (24 * 60 * 60 * 1000)) + 1;
const header = document.createElement('div');
header.className = 'week-popup-header';
const title = document.createElement('div');
title.className = 'week-popup-title';
title.textContent = `Age ${year} — year ${year + 1}, week ${weekInYear}`;
const sub = document.createElement('div');
sub.className = 'week-popup-sub';
sub.textContent = `week ${index.toLocaleString()} of ${TOTAL_WEEKS.toLocaleString()}`;
const closeBtn = document.createElement('button');
closeBtn.className = 'week-popup-close';
closeBtn.setAttribute('aria-label', 'Close');
closeBtn.innerHTML = '×';
closeBtn.addEventListener('click', closePopup);
header.appendChild(title);
header.appendChild(sub);
header.appendChild(closeBtn);
const days = document.createElement('div');
days.className = 'week-popup-days';
for (let d = 0; d < daysInWeek; d++) {
const dayDate = new Date(startMs);
dayDate.setDate(dayDate.getDate() + d);
dayDate.setHours(0, 0, 0, 0);
const wday = document.createElement('div');
wday.className = 'wday';
if (dayDate < today) {
wday.classList.add('wday--past');
} else if (dayDate.getTime() === today.getTime()) {
wday.classList.add('wday--today');
} else {
wday.classList.add('wday--future');
}
const dot = document.createElement('div');
dot.className = 'wday-dot';
const name = document.createElement('div');
name.className = 'wday-name';
// Derive day name from the date itself — never misaligned.
name.textContent = dayDate.toLocaleDateString('en-US', { weekday: 'short' });
const date = document.createElement('div');
date.className = 'wday-date';
date.textContent = dayDate.toLocaleDateString('en-US', {
month: 'short', day: 'numeric'
});
const dayNum = Math.floor(
(dayDate.getTime() - BIRTHDAY.getTime()) / (24 * 60 * 60 * 1000)
) + 1;
const numEl = document.createElement('div');
numEl.className = 'wday-daynum';
numEl.textContent = 'day ' + dayNum.toLocaleString();
wday.appendChild(dot);
wday.appendChild(name);
wday.appendChild(date);
wday.appendChild(numEl);
days.appendChild(wday);
}
card.innerHTML = '';
card.appendChild(header);
card.appendChild(days);
backdrop.hidden = false;
closeBtn.focus();
}
function closePopup() {
backdrop.hidden = true;
}
grid.addEventListener('click', e => {
const cell = e.target.closest('.week');
if (!cell) return;
openPopup(cell);
});
backdrop.addEventListener('click', e => {
if (e.target === backdrop) closePopup();
});
document.addEventListener('keydown', e => {
if (e.key === 'Escape' && !backdrop.hidden) closePopup();
});
}
// -------------------------------------------------------------------------
// Countdown
// -------------------------------------------------------------------------
function initCountdown() {
const wrapper = document.getElementById('countdown-wrapper');
if (!wrapper) return;
let unit = localStorage.getItem('mm-unit') || 'seconds';
const labelEl = document.createElement('div');
labelEl.className = 'countdown-label';
const valueEl = document.createElement('div');
valueEl.className = 'countdown-value';
const switcherEl = document.createElement('div');
switcherEl.className = 'countdown-switcher';
['seconds', 'hours', 'seasons'].forEach(u => {
const btn = document.createElement('button');
btn.className = 'countdown-btn';
btn.dataset.unit = u;
btn.textContent = u;
if (u === unit) btn.classList.add('is-active');
btn.addEventListener('click', () => {
unit = u;
localStorage.setItem('mm-unit', u);
switcherEl.querySelectorAll('.countdown-btn').forEach(b =>
b.classList.toggle('is-active', b.dataset.unit === u));
tick();
});
switcherEl.appendChild(btn);
});
wrapper.appendChild(labelEl);
wrapper.appendChild(valueEl);
wrapper.appendChild(switcherEl);
function format(ms) {
if (ms <= 0) return '0';
if (unit === 'seconds') return Math.floor(ms / 1000).toLocaleString();
if (unit === 'hours') return Math.floor(ms / (1000 * 60 * 60)).toLocaleString();
return Math.floor(ms / SEASON_MS).toLocaleString(); // seasons — whole integer
}
function label() {
if (unit === 'seconds') return 'seconds remaining';
if (unit === 'hours') return 'hours remaining';
// seasons: poetic label with current season name
return 'seasons remaining — it is ' + currentSeasonName();
}
function tick() {
const ms = Math.max(0, LIFE_END.getTime() - Date.now());
labelEl.textContent = label();
valueEl.textContent = format(ms);
}
tick();
setInterval(tick, 1000);
}
// -------------------------------------------------------------------------
// Init
// -------------------------------------------------------------------------
function init() {
buildGrid();
initCountdown();
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
}());