257 lines
9.5 KiB
JavaScript
257 lines
9.5 KiB
JavaScript
(function() {
|
|
'use strict';
|
|
|
|
var form = document.getElementById('weekends-form');
|
|
var birthYearInput = document.getElementById('birth-year');
|
|
var deathAgeInput = document.getElementById('death-age');
|
|
var resultsDiv = document.getElementById('weekends-results');
|
|
var percentageRemainingEl = document.getElementById('percentage-remaining');
|
|
var usedCountEl = document.getElementById('used-count');
|
|
var totalCountEl = document.getElementById('total-count');
|
|
var wakingHoursEl = document.getElementById('waking-hours');
|
|
var gridEl = document.getElementById('weekends-grid');
|
|
var downloadBtn = document.getElementById('download-btn');
|
|
|
|
var lastCalculation = null;
|
|
|
|
function countWeekends(startDate, endDate) {
|
|
var count = 0;
|
|
var current = new Date(startDate);
|
|
current.setHours(0, 0, 0, 0);
|
|
|
|
while (current <= endDate) {
|
|
if (current.getDay() === 6) {
|
|
count++;
|
|
}
|
|
current.setDate(current.getDate() + 1);
|
|
}
|
|
return count;
|
|
}
|
|
|
|
function formatNumber(num) {
|
|
return num.toLocaleString();
|
|
}
|
|
|
|
function renderGrid(usedCount, remainingCount, birthYear) {
|
|
gridEl.innerHTML = '';
|
|
|
|
var existingMilestones = document.querySelector('.grid-milestones');
|
|
if (existingMilestones) {
|
|
existingMilestones.remove();
|
|
}
|
|
|
|
var total = usedCount + remainingCount;
|
|
var maxCells = 5000;
|
|
var scaled = total > maxCells;
|
|
|
|
var currentCellIndex;
|
|
var cellCount;
|
|
|
|
if (scaled) {
|
|
var scaleFactor = maxCells / total;
|
|
var usedToRender = Math.round(usedCount * scaleFactor);
|
|
var remainingToRender = maxCells - usedToRender;
|
|
currentCellIndex = usedToRender > 0 ? usedToRender - 1 : 0;
|
|
cellCount = maxCells;
|
|
|
|
for (var i = 0; i < usedToRender; i++) {
|
|
var cell = document.createElement('div');
|
|
cell.className = 'grid-cell used';
|
|
if (i === currentCellIndex) {
|
|
cell.className += ' current';
|
|
}
|
|
gridEl.appendChild(cell);
|
|
}
|
|
|
|
for (var j = 0; j < remainingToRender; j++) {
|
|
var cell = document.createElement('div');
|
|
cell.className = 'grid-cell';
|
|
gridEl.appendChild(cell);
|
|
}
|
|
|
|
var overflow = document.createElement('div');
|
|
overflow.className = 'grid-overflow';
|
|
overflow.textContent = '(' + formatNumber(total) + ' total weekends)';
|
|
gridEl.appendChild(overflow);
|
|
} else {
|
|
currentCellIndex = usedCount > 0 ? usedCount - 1 : 0;
|
|
cellCount = total;
|
|
|
|
for (var i = 0; i < usedCount; i++) {
|
|
var cell = document.createElement('div');
|
|
cell.className = 'grid-cell used';
|
|
if (i === currentCellIndex) {
|
|
cell.className += ' current';
|
|
}
|
|
gridEl.appendChild(cell);
|
|
}
|
|
|
|
for (var j = 0; j < remainingCount; j++) {
|
|
var cell = document.createElement('div');
|
|
cell.className = 'grid-cell';
|
|
gridEl.appendChild(cell);
|
|
}
|
|
}
|
|
|
|
var milestones = [20, 40, 60, 80];
|
|
var milestoneText = milestones.map(function(age) {
|
|
return 'Age ' + age + ': ' + (birthYear + age);
|
|
}).join(' · ');
|
|
|
|
var milestonesEl = document.createElement('div');
|
|
milestonesEl.className = 'grid-milestones';
|
|
milestonesEl.textContent = milestoneText;
|
|
gridEl.parentNode.appendChild(milestonesEl);
|
|
}
|
|
|
|
function generateImage() {
|
|
if (!lastCalculation) return;
|
|
|
|
var fontUrl = '/fonts/JetBrainsMonoNLNerdFont-Regular.ttf';
|
|
var font = new FontFace('JetBrainsMono', 'url(' + fontUrl + ')');
|
|
|
|
font.load().then(function(loadedFont) {
|
|
document.fonts.add(loadedFont);
|
|
renderImage();
|
|
}).catch(function() {
|
|
renderImage();
|
|
});
|
|
|
|
function renderImage() {
|
|
var data = lastCalculation;
|
|
var canvasWidth = 800;
|
|
var cellSize = 6;
|
|
var cellGap = 2;
|
|
var padding = 30;
|
|
var headerHeight = 100;
|
|
var footerHeight = 60;
|
|
|
|
var total = data.usedCount + data.remainingCount;
|
|
var maxCells = 5000;
|
|
var scaled = total > maxCells;
|
|
var cellCount = scaled ? maxCells : total;
|
|
|
|
var cellsPerRow = Math.floor((canvasWidth - 2 * padding) / (cellSize + cellGap));
|
|
var rows = Math.ceil(cellCount / cellsPerRow);
|
|
var gridHeight = rows * (cellSize + cellGap);
|
|
var canvasHeight = headerHeight + gridHeight + footerHeight + padding;
|
|
|
|
var canvas = document.createElement('canvas');
|
|
canvas.width = canvasWidth;
|
|
canvas.height = canvasHeight;
|
|
var ctx = canvas.getContext('2d');
|
|
|
|
var fontFamily = 'JetBrainsMono, monospace';
|
|
var bgColor = '#131a21';
|
|
var textColor = getComputedStyle(document.documentElement).getPropertyValue('--text-color').trim() || '#333333';
|
|
var primaryColor = getComputedStyle(document.documentElement).getPropertyValue('--primary-color').trim() || '#007bff';
|
|
|
|
ctx.fillStyle = bgColor;
|
|
ctx.fillRect(0, 0, canvasWidth, canvasHeight);
|
|
|
|
ctx.fillStyle = primaryColor;
|
|
ctx.font = 'bold 36px ' + fontFamily;
|
|
ctx.fillText(data.percentageRemaining + '% weekends remaining', padding, padding + 30);
|
|
|
|
ctx.fillStyle = textColor;
|
|
ctx.font = '14px ' + fontFamily;
|
|
var summary = formatNumber(data.usedCount) + ' / ' + formatNumber(data.totalCount) +
|
|
' weekends · ' + formatNumber(data.wakingHours) + ' waking hours remaining (assuming 8h sleep on saturdays)';
|
|
ctx.fillText(summary, padding, padding + 55);
|
|
|
|
var scaleFactor = scaled ? maxCells / total : 1;
|
|
var usedToRender = scaled ? Math.round(data.usedCount * scaleFactor) : data.usedCount;
|
|
var currentCellIndex = usedToRender > 0 ? usedToRender - 1 : 0;
|
|
|
|
for (var i = 0; i < cellCount; i++) {
|
|
var col = i % cellsPerRow;
|
|
var row = Math.floor(i / cellsPerRow);
|
|
var x = padding + col * (cellSize + cellGap);
|
|
var y = headerHeight + row * (cellSize + cellGap);
|
|
|
|
var isUsed = i < usedToRender;
|
|
ctx.fillStyle = primaryColor;
|
|
ctx.globalAlpha = isUsed ? 0.3 : 1;
|
|
ctx.beginPath();
|
|
ctx.roundRect(x, y, cellSize, cellSize, 1);
|
|
ctx.fill();
|
|
ctx.globalAlpha = 1;
|
|
|
|
if (i === currentCellIndex) {
|
|
ctx.strokeStyle = textColor;
|
|
ctx.lineWidth = 2;
|
|
ctx.beginPath();
|
|
ctx.roundRect(x - 2, y - 2, cellSize + 4, cellSize + 4, 2);
|
|
ctx.stroke();
|
|
}
|
|
}
|
|
|
|
ctx.fillStyle = textColor;
|
|
ctx.font = '20px ' + fontFamily;
|
|
var subtitle = 'posixlycorrect.com/lib/weekends-left';
|
|
ctx.fillText(subtitle, padding, canvasHeight - 20);
|
|
|
|
var link = document.createElement('a');
|
|
link.download = 'weekends-left.png';
|
|
link.href = canvas.toDataURL('image/png');
|
|
link.click();
|
|
}
|
|
}
|
|
|
|
function calculate() {
|
|
var birthYear = parseInt(birthYearInput.value, 10);
|
|
var deathAge = parseInt(deathAgeInput.value, 10);
|
|
|
|
if (!birthYear || !deathAge) {
|
|
resultsDiv.style.display = 'none';
|
|
return;
|
|
}
|
|
|
|
if (deathAge < 1 || deathAge > 999) {
|
|
resultsDiv.style.display = 'none';
|
|
return;
|
|
}
|
|
|
|
var deathYear = birthYear + deathAge;
|
|
|
|
var birthDate = new Date(birthYear, 0, 1);
|
|
var deathDate = new Date(deathYear, 11, 31);
|
|
var today = new Date();
|
|
today.setHours(0, 0, 0, 0);
|
|
|
|
var usedWeekends = countWeekends(birthDate, today);
|
|
var remainingWeekends = countWeekends(today, deathDate);
|
|
var totalWeekends = usedWeekends + remainingWeekends;
|
|
|
|
var percentage = Math.round((usedWeekends / totalWeekends) * 100);
|
|
var wakingHours = remainingWeekends * 40;
|
|
|
|
var percentageRemaining = 100 - percentage;
|
|
|
|
lastCalculation = {
|
|
usedCount: usedWeekends,
|
|
remainingCount: remainingWeekends,
|
|
totalCount: totalWeekends,
|
|
percentage: percentage,
|
|
percentageRemaining: percentageRemaining,
|
|
wakingHours: wakingHours,
|
|
birthDate: birthDate
|
|
};
|
|
|
|
percentageRemainingEl.textContent = percentageRemaining + '%';
|
|
usedCountEl.textContent = formatNumber(usedWeekends);
|
|
totalCountEl.textContent = formatNumber(totalWeekends);
|
|
wakingHoursEl.textContent = formatNumber(wakingHours) + ' waking hours remaining, assuming 8h of sleep on saturdays';
|
|
|
|
renderGrid(usedWeekends, remainingWeekends, birthYear);
|
|
resultsDiv.style.display = 'block';
|
|
}
|
|
|
|
birthYearInput.addEventListener('input', calculate);
|
|
deathAgeInput.addEventListener('input', calculate);
|
|
form.addEventListener('submit', function(e) {
|
|
e.preventDefault();
|
|
calculate();
|
|
});
|
|
downloadBtn.addEventListener('click', generateImage);
|
|
})();
|