summaryrefslogtreecommitdiffstats
path: root/docs/superpowers/specs
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-05-08 17:34:17 +0200
committerDanilo M. <danix@danix.xyz>2026-05-08 17:34:17 +0200
commitc76daa409c88b86185b9cb83894f1149254967a3 (patch)
tree45c92356ee926077229426fc5feed4abce34c2fc /docs/superpowers/specs
parent15e428fe0fd3246cadfec9adb6d6261b479c9965 (diff)
downloaddanixxyz-c76daa409c88b86185b9cb83894f1149254967a3.tar.gz
danixxyz-c76daa409c88b86185b9cb83894f1149254967a3.zip
docs: add contribution graph widget design spec
Spec for sidebar (/is pages) + footer contribution graph widget. Vanilla JS fetch from /contributions.json, split-cell rendering, WCAG 2.1 AA compliant, full i18n (IT/EN). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Diffstat (limited to 'docs/superpowers/specs')
-rw-r--r--docs/superpowers/specs/2026-05-08-contribution-graph-design.md286
1 files changed, 286 insertions, 0 deletions
diff --git a/docs/superpowers/specs/2026-05-08-contribution-graph-design.md b/docs/superpowers/specs/2026-05-08-contribution-graph-design.md
new file mode 100644
index 0000000..e5aec14
--- /dev/null
+++ b/docs/superpowers/specs/2026-05-08-contribution-graph-design.md
@@ -0,0 +1,286 @@
+# Contribution Graph Widget — Design Spec
+
+**Date:** 2026-05-08
+**Status:** Approved
+**Scope:** Hugo partial + vanilla JS + CSS additions to theme repo
+
+---
+
+## Overview
+
+A sidebar/footer widget that renders a 53×7 GitHub-style contribution graph fetched from `/contributions.json` on danix.xyz. Each cell is split left/right representing gitolite (green) and GitHub (purple) activity independently. Placed in two locations: sidebar on `/is` and `/it/is` pages only, and footer right column sitewide.
+
+---
+
+## Data Source
+
+- **URL:** `https://danix.xyz/contributions.json`
+- **Format:** Flat object keyed by ISO date strings `"YYYY-MM-DD"`, each with `{ "github": N, "gitolite": N }`
+- **Coverage:** Always exactly the last 365 days, all dates present, zero counts included
+- **Already served:** No generation needed, endpoint is live
+
+---
+
+## Placement
+
+### 1. Sidebar — `/is` and `/it/is` only
+
+- Injected in `layouts/is/list.html` after the stats widget (before social share)
+- Cell size: 8px, gap: 2px
+- Sidebar is `md:col-span-1` (~200–220px) — graph fits at 8px × 53 cols + gaps = ~530px total grid width; horizontal scroll allowed via `overflow-x: auto`
+
+### 2. Footer — right column, sitewide
+
+- Appended below existing `<dl>` terminal readout in `layouts/partials/footer.html`
+- Cell size: 6px, gap: 1px
+- Fits the narrower right column
+
+### Caller Pattern
+
+Both call sites use:
+```hugo
+{{- partial "contribution-graph.html" (dict "cellSize" 8) -}}
+{{- partial "contribution-graph.html" (dict "cellSize" 6) -}}
+```
+
+---
+
+## Cell Rendering
+
+**Approach:** Single `div` per cell, `linear-gradient(90deg, green 50%, purple 50%)`.
+
+- Left half = gitolite = `--accent2` (green: `#00ff88` dark / `#008f5a` light)
+- Right half = github = `--accent` (purple: `#a855f7` dark / `#7c3aed` light)
+- Empty cell background = `var(--border)`
+
+**Intensity levels** (evaluated independently per source):
+
+| Count | Opacity |
+|-------|---------|
+| 0 | transparent (`var(--border)` side) |
+| 1–3 | 0.35 |
+| 4–9 | 0.60 |
+| 10–19 | 0.80 |
+| 20+ | 1.00 |
+
+If both sources are zero: solid `var(--border)` background (no gradient).
+If one source is zero: that half uses `var(--border)` color, other half uses accent with opacity.
+
+Opacity against `--accent`/`--accent2` automatically adapts to dark/light theme — no extra theme rules needed.
+
+---
+
+## Grid Structure
+
+- 53 columns × 7 rows
+- Sunday = row 0 (top), Saturday = row 6 (bottom)
+- Newest date = rightmost column
+- CSS: `grid-template-rows: repeat(7, var(--contrib-cell-size, 8px)); grid-auto-flow: column`
+- Month labels row above grid: abbreviated month name (`Jan`, `Feb`, …) placed at the column index where that month first appears. Labels may be 1–2 columns off at year boundary — accepted cosmetic limitation.
+
+---
+
+## Tooltip
+
+- Triggered on `mouseover` / `mouseout` per cell (mouse-only; cells are not tab-focusable)
+- Content: date (`Mon DD, YYYY`), gitolite count, github count, total
+- Implementation: single shared `div.contrib-tooltip` injected into `<body>`, positioned via `position: fixed` + JS mouse coordinates (avoids sidebar overflow clipping)
+- `aria-hidden="true"` — decorative; screen readers use cell `aria-label` instead
+- No CSS transition (instant show/hide — no `prefers-reduced-motion` concern)
+
+---
+
+## Summary Line
+
+Below the grid:
+```
+N commits in the last year · G from gitolite · H from GitHub
+```
+Uses i18n key `contrib_summary` with Hugo-style params.
+
+---
+
+## Architecture
+
+### Files Changed (all in `~/Programming/GIT/danix2-hugo-theme/`)
+
+| File | Change |
+|------|--------|
+| `layouts/partials/contribution-graph.html` | New — container + script tag |
+| `assets/js/contribution-graph.js` | New — fetch, build, render, tooltip |
+| `assets/css/main.css` | Edit — add `.contrib-*` classes |
+| `layouts/is/list.html` | Edit — call partial (cellSize 8) after stats widget |
+| `layouts/partials/footer.html` | Edit — call partial (cellSize 6) at end of right column |
+| `i18n/en.yaml` | Edit — add contrib keys |
+| `i18n/it.yaml` | Edit — add contrib keys (Italian) |
+
+Then in content repo: `npm run build` → commit compiled CSS → bump submodule pointer.
+
+### JS Flow (`contribution-graph.js`)
+
+1. Read `cellSize` from container dataset attribute
+2. Fetch `https://danix.xyz/contributions.json`
+3. On any fetch/parse error → hide container element, return
+4. Sort dates oldest→newest; assign to 53-col × 7-row grid slots (Sun=0 … Sat=6)
+5. Compute gitolite level and github level independently per date → derive gradient string
+6. Build month label row: track first column index per calendar month
+7. Build grid: one `div.contrib-cell` per date, set `background` inline, set `aria-label`
+8. Inject tooltip `div` into `<body>` once (shared across all cells on page)
+9. Attach `mouseover`/`mouseout` listeners on grid (event delegation) for tooltip positioning
+10. Inject summary line below grid
+11. Reveal container (was hidden via inline `display:none` until ready)
+
+### HTML Structure (rendered by JS)
+
+```html
+<div id="contrib-graph" style="display:none" data-cell-size="8" data-lang="en">
+ <figure role="img" aria-label="Contribution activity for the last year">
+ <div class="contrib-graph-wrap">
+ <div class="contrib-month-row">…month labels…</div>
+ <div class="contrib-grid">
+ <div class="contrib-cell" aria-label="May 8, 2026: 12 gitolite, 3 github"></div>
+ …
+ </div>
+ </div>
+ <figcaption class="contrib-summary">342 commits in the last year · 270 from gitolite · 72 from GitHub</figcaption>
+ </figure>
+</div>
+<!-- injected once into body: -->
+<div class="contrib-tooltip" aria-hidden="true"></div>
+```
+
+---
+
+## CSS Classes
+
+```css
+.contrib-graph-wrap { overflow-x: auto; }
+
+.contrib-month-row {
+ display: grid;
+ grid-auto-flow: column;
+ gap: 2px;
+ margin-bottom: 2px;
+}
+.contrib-month-label {
+ font-size: 0.55rem;
+ color: var(--text-dim);
+ font-family: var(--font-mono);
+}
+
+.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;
+ color: var(--text);
+ font-family: var(--font-mono);
+ 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: var(--font-mono);
+}
+```
+
+JS sets `--contrib-cell-size` and `--contrib-gap` as inline CSS vars on the grid element based on `cellSize` param.
+
+---
+
+## i18n Keys
+
+Hugo renders only the widget heading and the `aria-label` at build time. The summary line is built by JS after fetch, so it uses a bundled JS translation map (not Hugo i18n).
+
+### `i18n/en.yaml` and `i18n/it.yaml` (Hugo-rendered strings)
+```yaml
+# en.yaml
+contrib_widget_label: "Contribution activity for the last year"
+contrib_heading: "contributions"
+
+# it.yaml
+contrib_widget_label: "Attività di contribuzione nell'ultimo anno"
+contrib_heading: "contribuzioni"
+```
+
+### JS translation map (bundled in `contribution-graph.js`)
+```js
+const I18N = {
+ en: {
+ summary: (t, g, h) => `${t} commits in the last year · ${g} from gitolite · ${h} from GitHub`,
+ gitolite: 'gitolite',
+ github: 'GitHub',
+ },
+ it: {
+ summary: (t, g, h) => `${t} commit nell'ultimo anno · ${g} da gitolite · ${h} da GitHub`,
+ gitolite: 'gitolite',
+ github: 'GitHub',
+ },
+};
+```
+
+The partial passes `.Lang` as `data-lang` on the container; JS reads it to pick the correct translation.
+
+---
+
+## Accessibility
+
+- Grid wrapped in `<figure role="img" aria-label="[contrib_widget_label]">`
+- Summary line in `<figcaption>` — provides text equivalent of graph (WCAG 1.1.1)
+- Each cell: `aria-label="[date]: [g] gitolite, [h] github"` — keyboard/SR navigable
+- Tooltip: `aria-hidden="true"` — decorative only
+- No color as sole information carrier — aria-labels and summary line provide all data textually
+- No tooltip transition → no `prefers-reduced-motion` concern
+- Cells are NOT individually tab-focusable (365 tab stops is unusable); tooltip is mouse-only
+- Screen readers navigate cells via virtual cursor using each cell's `aria-label`
+- `<figure>` `aria-label` + `<figcaption>` summary provide complete information without requiring cell-level interaction
+
+---
+
+## Error Handling
+
+- Fetch fails or JSON parse fails → container hidden silently (`display: none` stays)
+- No error message shown to user
+
+---
+
+## Theming Compliance
+
+- Zero hardcoded hex values — all colors via `--accent`, `--accent2`, `--border`, `--bg2`, `--text`, `--text-dim`, `--font-mono`
+- Dark/light mode handled automatically by existing CSS var system
+- `prefers-color-scheme` no-JS fallback: graph not shown (JS required) — acceptable for a JS-rendered widget
+- Follows existing JS-per-partial pattern (same as `fortune.js`)
+
+---
+
+## Out of Scope
+
+- JSON endpoint generation (already live)
+- Canvas rendering
+- Any Hugo template pre-rendering of grid cells
+- Alpine.js usage
+- CSS animations on cells or tooltip