(function () {
'use strict';
var MONTHS = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
var I18N = {
en: {
summary: function(t, g, h) { return t + ' commits in the last year \xb7 ' + g + ' from gitolite \xb7 ' + h + ' from GitHub'; },
tooltip_gitolite: 'gitolite',
tooltip_github: 'GitHub',
tooltip_total: 'total',
},
it: {
summary: function(t, g, h) { return t + ' commit nell’ultimo anno \xb7 ' + g + ' da gitolite \xb7 ' + h + ' da GitHub'; },
tooltip_gitolite: 'gitolite',
tooltip_github: 'GitHub',
tooltip_total: 'totale',
},
};
function level(count) {
if (count === 0) return 0;
if (count <= 3) return 0.35;
if (count <= 9) return 0.60;
if (count <= 19) return 0.80;
return 1.0;
}
function hexWithOpacity(hex, opacity) {
hex = hex.replace('#', '').trim();
if (hex.length === 3) {
hex = hex[0]+hex[0]+hex[1]+hex[1]+hex[2]+hex[2];
}
var r = parseInt(hex.substring(0, 2), 16);
var g = parseInt(hex.substring(2, 4), 16);
var b = parseInt(hex.substring(4, 6), 16);
return 'rgba(' + r + ',' + g + ',' + b + ',' + opacity + ')';
}
function buildCellStyle(gitoliteCount, githubCount, rootStyle) {
var gLevel = level(gitoliteCount);
var hLevel = level(githubCount);
if (gLevel === 0 && hLevel === 0) return '';
var accent2 = rootStyle.getPropertyValue('--accent2').trim() || '#00ff88';
var accent = rootStyle.getPropertyValue('--accent').trim() || '#a855f7';
var border = rootStyle.getPropertyValue('--border').trim() || '#182840';
var leftColor = gLevel === 0 ? border : hexWithOpacity(accent2, gLevel);
var rightColor = hLevel === 0 ? border : hexWithOpacity(accent, hLevel);
return 'linear-gradient(90deg, ' + leftColor + ' 50%, ' + rightColor + ' 50%)';
}
function formatDate(dateStr) {
// dateStr: "YYYY-MM-DD"
var parts = dateStr.split('-');
var d = new Date(parseInt(parts[0]), parseInt(parts[1]) - 1, parseInt(parts[2]));
var months = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
var day = d.getDate();
return months[d.getMonth()] + ' ' + day + ', ' + d.getFullYear();
}
function buildGraph(container, data) {
var cellSize = parseInt(container.dataset.cellSize, 10) || 8;
var lang = container.dataset.lang || 'en';
var ariaLabel = container.dataset.ariaLabel || 'Contribution graph';
var i18n = I18N[lang] || I18N.en;
var gap = cellSize <= 6 ? 1 : 2;
var rootStyle = getComputedStyle(document.documentElement);
// Sort dates oldest → newest
var dates = Object.keys(data).sort();
// Build 53-col × 7-row grid
// Find the day-of-week of the oldest date (Sun=0)
var firstParts = dates[0].split('-');
var firstDate = new Date(parseInt(firstParts[0]), parseInt(firstParts[1]) - 1, parseInt(firstParts[2]));
var startDow = firstDate.getDay(); // 0=Sun
// Assign each date to (col, row)
// col 0 starts at row=startDow
var cells = []; // { col, row, date, gitolite, github }
for (var i = 0; i < dates.length; i++) {
var slot = startDow + i;
var col = Math.floor(slot / 7);
var row = slot % 7;
cells.push({ col: col, row: row, date: dates[i], gitolite: data[dates[i]].gitolite || 0, github: data[dates[i]].github || 0 });
}
var totalCols = cells[cells.length - 1].col + 1;
// Month labels: track first col per month
var monthCols = {}; // "YYYY-MM" → col index
for (var j = 0; j < cells.length; j++) {
var c = cells[j];
var ym = c.date.substring(0, 7);
if (!(ym in monthCols)) monthCols[ym] = c.col;
}
// Build figure
var figure = document.createElement('figure');
figure.setAttribute('role', 'img');
figure.setAttribute('aria-label', ariaLabel);
var wrap = document.createElement('div');
wrap.className = 'contrib-graph-wrap';
// Month label row
var monthRow = document.createElement('div');
monthRow.className = 'contrib-month-row';
monthRow.style.setProperty('--contrib-cell-size', cellSize + 'px');
monthRow.style.setProperty('--contrib-gap', gap + 'px');
monthRow.style.gridTemplateColumns = 'repeat(' + totalCols + ', ' + cellSize + 'px)';
var monthKeys = Object.keys(monthCols).sort();
for (var m = 0; m < monthKeys.length; m++) {
var ym2 = monthKeys[m];
var colIdx = monthCols[ym2];
var monthNum = parseInt(ym2.split('-')[1], 10) - 1;
var span = document.createElement('span');
span.className = 'contrib-month-label';
span.textContent = MONTHS[monthNum];
span.style.gridColumn = (colIdx + 1).toString();
monthRow.appendChild(span);
}
// Graph grid
var grid = document.createElement('div');
grid.className = 'contrib-grid';
grid.style.setProperty('--contrib-cell-size', cellSize + 'px');
grid.style.setProperty('--contrib-gap', gap + 'px');
grid.style.gridTemplateColumns = 'repeat(' + totalCols + ', ' + cellSize + 'px)';
// Build a map of col+row → cell data for quick lookup
var cellMap = {};
for (var k = 0; k < cells.length; k++) {
cellMap[cells[k].col + '_' + cells[k].row] = cells[k];
}
// Fill grid col by col, row by row
for (var col2 = 0; col2 < totalCols; col2++) {
for (var row2 = 0; row2 < 7; row2++) {
var div = document.createElement('div');
div.className = 'contrib-cell';
var key = col2 + '_' + row2;
var cellData = cellMap[key];
if (cellData) {
var bg = buildCellStyle(cellData.gitolite, cellData.github, rootStyle);
if (bg) div.style.background = bg;
var label = formatDate(cellData.date) + ': ' + cellData.gitolite + ' ' + i18n.tooltip_gitolite + ', ' + cellData.github + ' ' + i18n.tooltip_github;
div.setAttribute('aria-label', label);
div.dataset.gitolite = cellData.gitolite;
div.dataset.github = cellData.github;
div.dataset.date = cellData.date;
}
grid.appendChild(div);
}
}
wrap.appendChild(monthRow);
wrap.appendChild(grid);
figure.appendChild(wrap);
// Totals for summary
var totalCommits = 0, totalGitolite = 0, totalGithub = 0;
for (var n = 0; n < cells.length; n++) {
totalGitolite += cells[n].gitolite;
totalGithub += cells[n].github;
}
totalCommits = totalGitolite + totalGithub;
// Summary figcaption
var caption = document.createElement('figcaption');
caption.className = 'contrib-summary';
caption.textContent = i18n.summary(totalCommits, totalGitolite, totalGithub);
figure.appendChild(caption);
container.appendChild(figure);
container.style.display = '';
// Tooltip (inject once)
var tooltip = document.querySelector('.contrib-tooltip');
if (!tooltip) {
tooltip = document.createElement('div');
tooltip.className = 'contrib-tooltip';
tooltip.setAttribute('aria-hidden', 'true');
document.body.appendChild(tooltip);
}
// Event delegation on grid
grid.addEventListener('mouseover', function(e) {
var cell = e.target.closest('.contrib-cell');
if (!cell || !cell.dataset.date) return;
var g = parseInt(cell.dataset.gitolite, 10);
var h = parseInt(cell.dataset.github, 10);
tooltip.innerHTML =
formatDate(cell.dataset.date) + '
' +
i18n.tooltip_gitolite + ': ' + g + '
' +
i18n.tooltip_github + ': ' + h + '
' +
i18n.tooltip_total + ': ' + (g + h);
tooltip.classList.add('visible');
});
grid.addEventListener('mousemove', function(e) {
var x = e.clientX + 12;
var y = e.clientY + 12;
var tw = tooltip.offsetWidth;
var th = tooltip.offsetHeight;
if (x + tw > window.innerWidth - 8) x = e.clientX - tw - 12;
if (y + th > window.innerHeight - 8) y = e.clientY - th - 12;
tooltip.style.left = x + 'px';
tooltip.style.top = y + 'px';
});
grid.addEventListener('mouseout', function(e) {
if (!e.relatedTarget || !grid.contains(e.relatedTarget)) {
tooltip.classList.remove('visible');
}
});
}
document.addEventListener('DOMContentLoaded', function() {
var containers = document.querySelectorAll('.contrib-container');
if (!containers.length) return;
fetch('https://danix.xyz/contributions.json')
.then(function(res) {
if (!res.ok) throw new Error('fetch failed');
return res.json();
})
.then(function(data) {
containers.forEach(function(container) {
buildGraph(container, data);
});
})
.catch(function() {
// hide silently — containers already display:none
});
});
}());