landing updates
All checks were successful
Apply Kuberentes Configs / update-repo (push) Successful in 1s
Apply Kuberentes Configs / update-infrastructure (push) Successful in 4s
Apply Kuberentes Configs / notify-on-failure (push) Has been skipped

This commit is contained in:
2026-03-05 10:56:38 -07:00
parent 01b92733c3
commit ee5966306a
3 changed files with 179 additions and 4 deletions

View File

@@ -329,3 +329,30 @@
white-space: nowrap;
padding-top: 3px;
}
/* ── Heatmap ─────────────────────────────────────────────── */
.heatmap-section {
max-width: 1100px;
margin: 0 auto;
padding: 0 var(--space-lg) var(--space-xl);
}
.activity-heatmap {
background: var(--color-bg-card);
border: 1px solid var(--color-border);
border-radius: var(--radius-lg);
padding: var(--space-md) var(--space-lg);
overflow-x: auto;
}
.heatmap-svg {
display: block;
}
.heatmap-month {
font-size: 9px;
fill: var(--color-text-muted, #8b949e);
font-family: inherit;
}
.heatmap-day {
font-size: 9px;
fill: var(--color-text-muted, #8b949e);
font-family: inherit;
}

View File

@@ -1,6 +1,5 @@
{{template "base/head" .}}
<div class="page-content home" id="alex-landing">
<section class="hero">
<div class="hero-inner">
<h1>Alex Mickelson</h1>
@@ -21,6 +20,13 @@
</div>
</section>
<section class="heatmap-section">
<div class="section-header">
<h2>Activity</h2>
</div>
<div id="activity-heatmap" class="activity-heatmap"></div>
</section>
<section class="activity-section">
<div class="section-header">
<h2>Recent Activity</h2>
@@ -34,13 +40,12 @@
<div class="skeleton-activity"></div>
</div>
</section>
</div>
<script>
window.GITEA_APP_URL = "{{AppUrl}}";
window.GITEA_SUB_URL = "{{AppSubUrl}}";
</script>
<!-- update version when changed to reset cloudflare cache -->
<script src="{{AppSubUrl}}/assets/js/custom-landing.js?v=7"></script>
<link rel="stylesheet" href="{{AppSubUrl}}/assets/css/custom-landing.css?v=7">
<script src="{{AppSubUrl}}/assets/js/custom-landing.js?v=8"></script>
<link href="{{AppSubUrl}}/assets/css/custom-landing.css?v=8" rel="stylesheet" />
{{template "base/footer" .}}

View File

@@ -102,6 +102,19 @@ const dataDomain = {
return Array.from(seen.values());
},
parseAllActivityDates(xmlDoc) {
const counts = new Map();
for (const item of Array.from(xmlDoc.querySelectorAll("channel > item"))) {
const pubDate = item.querySelector("pubDate")?.textContent || "";
if (!pubDate) continue;
const d = new Date(pubDate);
if (isNaN(d)) continue;
const key = d.toISOString().slice(0, 10);
counts.set(key, (counts.get(key) || 0) + 1);
}
return counts;
},
parseActivity(xmlDoc, limit = 20) {
return Array.from(xmlDoc.querySelectorAll("channel > item"))
.slice(0, limit)
@@ -208,6 +221,135 @@ const uiRendering = {
}
},
activityMapRender(xmlDoc) {
const container = document.getElementById("activity-heatmap");
if (!container) return;
const counts = dataDomain.parseAllActivityDates(xmlDoc);
const today = new Date();
today.setHours(0, 0, 0, 0);
// Align start to Sunday 52 weeks ago
const startDate = new Date(today);
startDate.setDate(startDate.getDate() - 52 * 7);
startDate.setDate(startDate.getDate() - startDate.getDay());
const cellSize = 11;
const gap = 2;
const step = cellSize + gap;
const cols = 53;
const rows = 7;
const padLeft = 28;
const padTop = 20;
const svgW = padLeft + cols * step;
const svgH = padTop + rows * step;
const LEVELS = ["#161b22", "#0e4429", "#006d32", "#26a641", "#39d353"];
const countToLevel = (n) =>
n === 0 ? 0 : n === 1 ? 1 : n <= 3 ? 2 : n <= 6 ? 3 : 4;
// Collect month labels (one per column where the month changes)
const monthLabels = new Map();
let lastMonth = -1;
for (let col = 0; col < cols; col++) {
const d = new Date(startDate);
d.setDate(d.getDate() + col * 7);
if (d.getMonth() !== lastMonth) {
lastMonth = d.getMonth();
monthLabels.set(col, d.toLocaleString("default", { month: "short" }));
}
}
const ns = "http://www.w3.org/2000/svg";
const svg = document.createElementNS(ns, "svg");
svg.setAttribute("width", svgW);
svg.setAttribute("height", svgH);
svg.setAttribute("class", "heatmap-svg");
svg.setAttribute("aria-label", "Activity heatmap");
// Month labels
for (const [col, name] of monthLabels) {
const t = document.createElementNS(ns, "text");
t.setAttribute("x", padLeft + col * step);
t.setAttribute("y", 12);
t.setAttribute("class", "heatmap-month");
t.textContent = name;
svg.appendChild(t);
}
// Day-of-week labels (Sun / Tue / Thu / Sat)
["Sun", "", "Tue", "", "Thu", "", "Sat"].forEach((label, i) => {
if (!label) return;
const t = document.createElementNS(ns, "text");
t.setAttribute("x", 0);
t.setAttribute("y", padTop + i * step + cellSize - 2);
t.setAttribute("class", "heatmap-day");
t.textContent = label;
svg.appendChild(t);
});
// Day cells
for (let col = 0; col < cols; col++) {
for (let row = 0; row < rows; row++) {
const d = new Date(startDate);
d.setDate(d.getDate() + col * 7 + row);
if (d > today) continue;
const key = d.toISOString().slice(0, 10);
const count = counts.get(key) || 0;
const rect = document.createElementNS(ns, "rect");
rect.setAttribute("x", padLeft + col * step);
rect.setAttribute("y", padTop + row * step);
rect.setAttribute("width", cellSize);
rect.setAttribute("height", cellSize);
rect.setAttribute("rx", 2);
rect.setAttribute("fill", LEVELS[countToLevel(count)]);
rect.setAttribute("data-date", key);
rect.setAttribute("data-count", count);
const title = document.createElementNS(ns, "title");
title.textContent = count > 0
? `${count} activit${count === 1 ? "y" : "ies"} on ${key}`
: `No activity on ${key}`;
rect.appendChild(title);
svg.appendChild(rect);
}
}
// Legend
const legendY = svgH + 6;
const legendG = document.createElementNS(ns, "g");
const legendLabel = document.createElementNS(ns, "text");
legendLabel.setAttribute("x", padLeft);
legendLabel.setAttribute("y", legendY + cellSize - 2);
legendLabel.setAttribute("class", "heatmap-day");
legendLabel.textContent = "Less";
legendG.appendChild(legendLabel);
LEVELS.forEach((color, i) => {
const r = document.createElementNS(ns, "rect");
r.setAttribute("x", padLeft + 32 + i * step);
r.setAttribute("y", legendY);
r.setAttribute("width", cellSize);
r.setAttribute("height", cellSize);
r.setAttribute("rx", 2);
r.setAttribute("fill", color);
legendG.appendChild(r);
});
const moreLabel = document.createElementNS(ns, "text");
moreLabel.setAttribute("x", padLeft + 32 + LEVELS.length * step + 4);
moreLabel.setAttribute("y", legendY + cellSize - 2);
moreLabel.setAttribute("class", "heatmap-day");
moreLabel.textContent = "More";
legendG.appendChild(moreLabel);
svg.setAttribute("height", svgH + cellSize + 12);
svg.appendChild(legendG);
container.innerHTML = "";
container.appendChild(svg);
},
async render() {
const baseUrl = httpService.baseUrl;
@@ -216,6 +358,7 @@ const uiRendering = {
await Promise.all([
this.renderRepos(xmlDoc),
this.renderActivity(xmlDoc),
this.activityMapRender(xmlDoc),
]);
} catch (e) {
console.error("Gitea landing: RSS fetch failed", e);