summaryrefslogtreecommitdiffstats
path: root/assets
diff options
context:
space:
mode:
Diffstat (limited to 'assets')
-rw-r--r--assets/css/main.css69
-rw-r--r--assets/js/contribution-graph.js243
2 files changed, 312 insertions, 0 deletions
diff --git a/assets/css/main.css b/assets/css/main.css
index 3aafd4f..04f737d 100644
--- a/assets/css/main.css
+++ b/assets/css/main.css
@@ -1923,3 +1923,72 @@ html.theme-light .prose-invert .cta-block a {
vertical-align: -0.1em;
margin: 0;
}
+
+/* ============================================================
+ Contribution Graph Widget
+ ============================================================ */
+
+.contrib-graph-wrap {
+ overflow-x: auto;
+}
+
+.contrib-month-row {
+ display: grid;
+ grid-auto-flow: column;
+ gap: var(--contrib-gap, 2px);
+ margin-bottom: 2px;
+ width: fit-content;
+ min-width: 100%;
+}
+
+.contrib-month-label {
+ font-size: 0.55rem;
+ color: var(--text-dim);
+ font-family: monospace;
+ white-space: nowrap;
+ overflow: hidden;
+}
+
+.contrib-grid {
+ display: grid;
+ grid-template-rows: repeat(7, var(--contrib-cell-size, 8px));
+ grid-auto-flow: column;
+ gap: var(--contrib-gap, 2px);
+ width: fit-content;
+}
+
+.contrib-cell {
+ width: var(--contrib-cell-size, 8px);
+ height: var(--contrib-cell-size, 8px);
+ border-radius: 1px;
+ background: var(--border);
+ cursor: default;
+}
+
+.contrib-tooltip {
+ position: fixed;
+ background: var(--bg2);
+ border: 1px solid var(--border);
+ border-radius: 4px;
+ padding: 5px 8px;
+ font-size: 0.65rem;
+ line-height: 1.5;
+ color: var(--text);
+ font-family: monospace;
+ pointer-events: none;
+ z-index: 50;
+ white-space: nowrap;
+ display: none;
+}
+
+.contrib-tooltip.visible {
+ display: block;
+}
+
+.contrib-summary {
+ font-size: 0.65rem;
+ color: var(--text-dim);
+ margin-top: 6px;
+ font-family: monospace;
+ display: block;
+}
diff --git a/assets/js/contribution-graph.js b/assets/js/contribution-graph.js
new file mode 100644
index 0000000..ccf4a7f
--- /dev/null
+++ b/assets/js/contribution-graph.js
@@ -0,0 +1,243 @@
+(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) + '<br>' +
+ i18n.tooltip_gitolite + ': ' + g + '<br>' +
+ i18n.tooltip_github + ': ' + h + '<br>' +
+ 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
+ });
+ });
+}());