Overview
The Nucleate Dev journey page tracks months of iteration in prose—and, under Development stats, in numbers. The original presentation was a screenshot exported from Excel: accurate, but static, off-palette, and impossible to explore.
This page documents the replacement: an embedded accumulated-hours chart that matches the site’s charcoal-and-amber workshop aesthetic, reads real session data at build time, and surfaces milestone events on hover without leaving the page.
Why Not a Screenshot—or a Chart Library?
Three constraints drove the design:
- Brand — The plot had to feel like the Observatory and Section Map: restrained, amber-accented, subservient to reading. A default Chart.js or Plotly theme would fight the rest of the docs.
- Weight — One page, one chart. Pulling in a general-purpose charting library for a single SVG line plot felt disproportionate.
- Data ownership — Hours live in a spreadsheet I already maintain (
static/nucleate_dev_stats.xlsx). The site should consume that source of truth, not a manually re-exported PNG.
The result is vanilla SVG + ~550 lines of JavaScript, the same pattern as the Section Map: geometry and interaction in the client, structure and copy baked in at Hugo build time.
What Visitors Experience
On the Dev journey page, the Accumulated development hoursshortcode renders:
- A header with title and a live summary (total hours and day span, e.g.
966.8 h across 429 days). - An area + line chart of accumulated development hours from first commit through today.
- Dim horizontal grid and bi-monthly date ticks—enough context without chartjunk.
- Milestone markers — vertical dashed amber lines with dots where project events occurred (repo creation, Echo, Nucleate rebrand, PySide6 migration, Gumroad release, first download, and others).
- A legend row of milestone labels below the chart—each pill is keyboard-focusable.
Hover and focus behavior
Interaction is intentionally simple:
| Input | Result |
|---|---|
| Pointer over chart | Vertical crosshair; dot on the line; tooltip with date and interpolated hours at that x-position |
| Pointer near a milestone | Snaps to the milestone column; tooltip adds label and detail text for that event |
| Pointer over milestone dot | Same as above, with a larger invisible hit target (14px radius) for easier targeting |
| Hover / focus legend pill | Highlights the corresponding marker and shows the milestone tooltip |
| Pointer leave | Crosshair, dot, and tooltip hide; active states clear |
Between logged sessions, hours are linearly interpolated along the time axis so the crosshair always returns a sensible cumulative value—even on days with no explicit row in the spreadsheet.
Milestone copy is not inferred from the xlsx. Dates come from the data series; labels and descriptions are authored in the export script (see below).
Accessibility
- The SVG carries an
aria-labelsummarizing the date range and total hours. - A visually hidden
<ul>lists every milestone for screen readers. - Legend buttons are real
<button>elements with focus handlers mirroring hover. prefers-reduced-motion: reducedisables CSS transitions on tooltips and markers (the chart itself has no animation loop).
Architecture: Build Time vs Runtime
| Layer | Responsibility |
|---|---|
static/nucleate_dev_stats.xlsx | Source spreadsheet (dates + accumulated hours) |
scripts/export-nucleate-dev-stats.py | Reads xlsx, writes data/nucleate-dev-stats.json; milestones defined here |
data/nucleate-dev-stats.json | Hugo data file: series array + milestones array |
layouts/shortcodes/dev-stats-chart.html | Embeds JSON in data-* attributes; sets Scratch flag for assets |
assets/js/dev-stats-chart.js | Parse data, layout SVG, wire pointer + legend interaction |
assets/css/dev-stats-chart.css | Chart chrome using --wl-* tokens |
layouts/partials/custom/head-end.html | Loads chart CSS when shortcode used |
layouts/partials/scripts.html | Loads chart JS when shortcode used |
No runtime fetch. The chart is fully determined at build time—same philosophy as the Section Map’s data-graph blob.
Data Pipeline
Spreadsheet → JSON
The export script uses only the Python standard library (zipfile + XML parse of the xlsx internals—no openpyxl dependency). Each row becomes { "date": "YYYY-MM-DD", "hours": <float> }.
Milestones are a separate list in the script:
{
"date": "2025-10-26",
"label": "Nucleate",
"detail": 'Rebranding under a product name, "Nucleate"',
}After editing the spreadsheet or milestone list:
python scripts/export-nucleate-dev-stats.pyHugo exposes the file as site.Data under the key nucleate-dev-stats (hyphenated filename).
Shortcode → page
The shortcode jsonifies series and milestones onto the figure element:
<figure class="dev-stats-chart"
data-series="[...]"
data-milestones="[...]">It also calls .Page.Scratch.Set "devStatsChart" true so CSS/JS load only on pages that embed the chart—not on every docs page.
Rendering (SVG)
On DOMContentLoaded, the script:
- Parses embedded JSON and sorts the series by date.
- Computes
minDate,maxDate, and a padded y-axis ceiling (nice 100- or 200-hour ticks). - Builds an SVG with a vertical amber gradient for the area fill under the line.
- Draws grid lines, the area path, the stroke path, milestone groups (line + marker + hit circle), and axis labels.
- Attaches a transparent
rectoverlay for pointer tracking.
Layout is recomputed on resize via ResizeObserver—content layers rebuild; the interaction overlay persists so listeners are not lost.
Scales are plain functions:
xForDate/dateAtX— linear map between plot margins and the time span.yForHours— invert y because SVG coordinates grow downward.interpolateHours— walk adjacent series points and lerp.
Milestone y-positions use hoursAtDate: the last logged hours at or before the milestone date, so markers sit on the actual curve even when the milestone falls between sessions.
Nearest-milestone detection
When the pointer moves, the script scores each milestone by pixel distance plus a small day penalty, and promotes the winner if within a threshold. That is why hovering near Gumroad release feels magnetic without requiring pixel-perfect aim on the dot.
Styling
Colors come from assets/css/wl-tokens.css:
- Background:
--wl-charcoal-mid - Line and accents:
--wl-amber-soft - Grid and borders:
--wl-amber-dim,--wl-line - Text:
--wl-text,--wl-text-dim
The tooltip is an HTML div positioned absolutely over the canvas (not SVG <foreignObject>), which keeps typography and wrapping straightforward.
Relationship to Other Site Systems
| Observatory | Section Map | Dev Stats Chart | |
|---|---|---|---|
| Scope | Whole studio hub | One docs branch | One product timeline |
| Motion | Rotation, dust, comets | Static | Static |
| Library | None | None | None |
| Data at build | Graph partial | Walk partial | Hugo data JSON |
| Primary job | Identity + entry | In-branch orientation | Quantified journey |
Same workshop: quiet instruments, amber ink, no gratuitous animation.
Extending
- New hours — Update the xlsx, run the export script, rebuild.
- New milestone — Add an entry to
MILESTONESin the export script withdate,label, anddetail. - Another page — Drop
anywhere; assets load automatically.Accumulated development hours
- 2025-04-16: Repo established for "LLM Scribe"
- 2025-06-28: Development for general release begins under the "Echo" project name
- 2025-09-01: Addition of multiple user modes and supporting system prompts
- 2025-10-26: Rebranding under a product name, "Nucleate"
- 2025-11-16: Branch for Tkinter → PySide6 conversion
- 2025-12-21: Removed Tkinter legacy code from repo, consolidation
- 2026-01-01: macOS (x86) first development round complete
- 2026-02-16: macOS (Apple Silicon) first development round complete
- 2026-02-25: Windows w/ CUDA, Windows w/ CPU, and macOS w/ Apple Silicon packaged successfully
- 2026-03-13: Mac (Metal) packaged .dmg successfully
- 2026-04-07: Alpha feedback implemented, beta solicitation
- 2026-04-14: Mac notarization and signing
- 2026-04-21: Beta solicitation closed—unfortunately zero users
- 2026-05-09: Version updated to 1.0 for first packaged release
- 2026-05-19: First time using agentic AI to help review code before beta
- 2026-05-25: v1.0 release and publication to Gumroad
- 2026-06-07: First download—$0 from Germany!
- 2026-06-21: Version updated to 1.1 for packaged release
- 2026-08-01: Public visibility release
- Different dataset — Generalizing would mean parameterizing the shortcode (
data="other-stats") and duplicating or templating the export script. Not needed yet.
Reflections
The Dev journey page is narrative first; the chart is evidence, not the headline. Keeping it as a single shortcode with conditional assets means the rest of the Nucleate docs stay untouched.
Replacing a screenshot also removed a maintenance trap: every spreadsheet update used to mean re-cropping and re-uploading an image. Now the pipeline is one command.
The milestone layer is the part I care about most. Cumulative hours tell how much; the hover copy tells what happened. That pairing is what the Excel chart could never do on a static site.
See It Live
The chart is embedded on Nucleate → Overview → Dev journey. Hover the line, the milestone dots, or the legend labels below the plot.
AI-generated Hugo documentation—describes how this website is built and customized.