diff options
| author | Paul Buetow <paul@buetow.org> | 2026-05-27 21:50:05 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-05-27 21:50:05 +0300 |
| commit | c5c14267fe4f991654251437ed8523a43067f8ac (patch) | |
| tree | 8577375a5df87ab289cdb70bd1849de7017f7bd4 /internal | |
| parent | a38e9b8e94d0b242cd6091078e9f7dac010dc08f (diff) | |
showcase: grey out inactive projects in rank history SVG
Inactive projects (avgCommitAge > 730 days AND last commit > 365 days ago,
matching the gemtext inactivity notice) are now visually distinguished:
Default state
- Plot line: rendered as #555 grey at 0.3 opacity so it is visible but does
not compete with active coloured lines.
- Legend entry: dimmed to 0.35 opacity.
On legend-entry hover
- The line snaps to its project colour and is bolded (stroke-width 3) at
full opacity — identical behaviour to active projects.
- Other inactive lines remain at their grey resting state (not dimmed to
0.08 alongside active ones) so they stay readable.
On leave
- Both stroke colour and opacity are restored per-project: grey+0.3 for
inactive, colour+0.55 for active.
The inactivity flag is stored in the PROJECTS JSON (inactive:true/false) so
all JS state transitions can consult it without touching the DOM attributes.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Diffstat (limited to 'internal')
| -rw-r--r-- | internal/showcase/rank_history_svg.go | 84 |
1 files changed, 68 insertions, 16 deletions
diff --git a/internal/showcase/rank_history_svg.go b/internal/showcase/rank_history_svg.go index d932edc..a761f7f 100644 --- a/internal/showcase/rank_history_svg.go +++ b/internal/showcase/rank_history_svg.go @@ -5,6 +5,7 @@ import ( "fmt" "math" "strings" + "time" ) // SVG canvas and margin constants (pixels in viewBox coordinates). @@ -37,10 +38,15 @@ type svgTimePoint struct { } // svgProjectData carries per-project metadata used by the interactive JS layer. +// Inactive is true when the project's average commit age exceeds 730 days AND +// the last commit was over a year ago — matching the gemtext inactivity notice. +// Inactive projects are rendered as grey lines by default and only switch to +// their project colour when the user mouses over their legend entry. type svgProjectData struct { - Name string `json:"name"` - Color string `json:"color"` - Points []svgTimePoint `json:"points"` + Name string `json:"name"` + Color string `json:"color"` + Points []svgTimePoint `json:"points"` + Inactive bool `json:"inactive"` } // projectColor returns a visually distinct CSS hex color for project index i. @@ -201,10 +207,24 @@ func GenerateRankHistorySVG(summaries []ProjectSummary) string { continue // skip projects that have never appeared in any snapshot } + // Mirror the inactivity check from formatGemtext: avg commit age > 730 days + // AND the most recent commit was also over a year ago. Inactive projects + // are still drawn but as grey lines so they do not compete visually with + // active ones; they turn coloured only on legend-entry hover. + inactive := false + if s.Metadata != nil && s.Metadata.AvgCommitAge > 730 && s.Metadata.LastCommitDate != "" { + if last, err := time.Parse("2006-01-02", s.Metadata.LastCommitDate); err == nil { + if time.Since(last).Hours()/24 > 365 { + inactive = true + } + } + } + allProjects = append(allProjects, svgProjectData{ - Name: s.Name, - Color: projectColor(colorIdx), - Points: pts, + Name: s.Name, + Color: projectColor(colorIdx), + Points: pts, + Inactive: inactive, }) colorIdx++ } @@ -436,8 +456,25 @@ function rescale(){ window.addEventListener('resize',rescale); rescale(); +// Initialise inactive projects: grey plot line, dimmed legend entry. +// This runs once after DOM and rescale() are ready. +for(var i=0;i<allPG.length;i++){ + if(PROJECTS[i].inactive){ + allPG[i].style.opacity='0.3'; + allPG[i].querySelector('.pl').style.stroke='#555'; + } +} +for(var i=0;i<allLG.length;i++){ + if(PROJECTS[i].inactive) allLG[i].style.opacity='0.35'; +} + +// defaultPGOpacity returns the resting opacity for a plot group. +function defaultPGOpacity(i){return PROJECTS[i].inactive?'0.3':'0.55';} +// defaultLGOpacity returns the resting opacity for a legend entry. +function defaultLGOpacity(i){return PROJECTS[i].inactive?'0.35':'1';} + // onEnter is called when the cursor enters a project group or legend entry. -// It dims all other groups, shows the tooltip, and marks the project active. +// It dims/greys all other groups, shows the tooltip, and marks the project active. function onEnter(idx,evt){ activeIdx=idx; var p=PROJECTS[idx]; @@ -470,14 +507,26 @@ function onEnter(idx,evt){ ttbg.setAttribute('width',w); ttbg.setAttribute('height',h); - // Highlight hovered plot line; dim all others. + // Apply per-project opacity for plot lines: + // - hovered project → full opacity + project colour (restores inactive grey) + // - active others → dimmed to 0.08 + // - inactive others → stay at their grey resting state (not dimmed further) for(var i=0;i<allPG.length;i++){ - allPG[i].style.opacity=(i===idx)?'1':'0.08'; + var pl=allPG[i].querySelector('.pl'); + if(i===idx){ + allPG[i].style.opacity='1'; + pl.style.stroke=PROJECTS[i].color; // restore colour for inactive projects + pl.style.strokeWidth='3'; + } else if(PROJECTS[i].inactive){ + // Keep inactive lines at their grey resting state so they do not compete. + allPG[i].style.opacity='0.3'; + pl.style.stroke='#555'; + } else { + allPG[i].style.opacity='0.08'; + } } - // Bold the hovered line. - allPG[idx].querySelector('.pl').style.strokeWidth='3'; - // Highlight hovered legend entry; dim all others. + // Highlight hovered legend entry; dim all others uniformly. for(var i=0;i<allLG.length;i++){ allLG[i].style.opacity=(i===idx)?'1':'0.2'; } @@ -486,15 +535,18 @@ function onEnter(idx,evt){ tt.style.display='block'; } -// onLeave restores all groups to their default opacity and hides the tooltip. +// onLeave restores all groups to their per-project resting state. function onLeave(){ tt.style.display='none'; for(var i=0;i<allPG.length;i++){ - allPG[i].style.opacity='0.55'; - allPG[i].querySelector('.pl').style.strokeWidth=''; + var pl=allPG[i].querySelector('.pl'); + allPG[i].style.opacity=defaultPGOpacity(i); + pl.style.strokeWidth=''; + // Restore grey stroke for inactive projects; clear override for active ones. + pl.style.stroke=PROJECTS[i].inactive?'#555':''; } for(var i=0;i<allLG.length;i++){ - allLG[i].style.opacity='1'; + allLG[i].style.opacity=defaultLGOpacity(i); } activeIdx=-1; } |
