Files
ghstats/docs/design-guidelines.md
T
tiennm99 e5b6daae5a docs: resync with current state across all project docs
- design-guidelines: every dimension was stale — card frame 340x200
  (was 500x220), corner radius 6, title 15px at (20,30), row y0/dy,
  donut centre (250,110) r=55/30, topN=5, legend y0=55 dy=20, bar chart
  area [35,325]x[45,155], area chart [28,312]x[45,150], icon scale 0.75.
- code-standards: FetchContributionsAllTime signature now ctx-first,
  viewbox 500x220 → 340x200.
- codebase-summary: test coverage lists main_test.go +
  TestDonutSingleSlice/Empty; filename convention says plain kebab-case
  (no numeric prefix).
- project-overview-pdr: forks/private defaults now on, not off.
- project-roadmap: add Phase 7 (Marketplace polish — resize, numeric-
  prefix drop, v1 floating tag, rename-rollback). Renumber planned
  phases 8-11. Fix "hard width 500 px" limitation.
- deployment-guide: document update-major-tag job; note
  Marketplace listing name is `ghstats-cards`.
2026-04-19 00:09:00 +07:00

111 lines
4.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Design Guidelines
Visual conventions for ghstats SVG cards. All cards share a single frame shape so they stack cleanly in a README — two cards sit side-by-side inside GitHub's ~816 px content column.
## Card frame
| Property | Value |
| --- | --- |
| Width × Height | **340 × 200** (matches github-profile-summary-cards) |
| Corner radius | 6 px |
| Stroke | `theme.Stroke` at `theme.StrokeOpacity` |
| Fill | `theme.Background` |
| Font family | `'Segoe UI', Ubuntu, Sans-Serif` |
| Title | 15 px, weight 600, `theme.Title`, anchored at `(20, 30)` |
Generated by `header(width, height, bg, stroke, strokeOpacity, titleColor, title)` in `internal/card/svg.go`.
## Theme role mapping
| Theme field | Used for |
| --- | --- |
| `Title` | Card title text |
| `Text` | Primary content (values, names) |
| `Background` | Card fill + stroke around donut slices to separate colors |
| `Stroke` + `StrokeOpacity` | Card outline |
| `Muted` | Axis lines, axis labels, icons, legend metadata |
| `Accent` | Bars, area fills, stat values, fallback slice color |
Cards MUST NOT hardcode colors outside these fields. If a new visual needs a shade, pick the closest existing field — don't extend the schema without a strong reason.
## Row-based cards (profile, stats)
Single-column rows of `icon + label` or `icon + label + value`.
| Metric | Profile | Stats |
| --- | --- | --- |
| First row baseline (y) | 60 | 55 |
| Row spacing | 20 px | 20 px |
| Row x padding | 20 | 20 |
| Icon scale | `12/16 = 0.75` from 16×16 Octicon viewBox | same |
| Icon color | `theme.Muted` | same |
| Value font | 12 px, `theme.Text` | 12 px, weight 600, `theme.Accent`, right-anchored at `x = 320` |
Cap rows at what fits: up to 7 rows per card. Stats splits commits into lifetime + last-year rows.
## Donut cards (language breakdowns)
| Metric | Value |
| --- | --- |
| Donut centre | `(250, 110)` |
| Outer radius | 55 |
| Inner radius | 30 |
| Top-N entries shown | 5 (overflow collapses into "Other") |
| Slice stroke | `theme.Background`, 1.5 px (gap between slices) |
| Legend origin | `(20, 55)` |
| Legend row height | 20 px |
| Swatch size | 10 × 10 |
| Legend font | 11 px |
Language colors come from linguist via GraphQL (`repo.languages.edges[].node.color`). Missing colors fall back to `theme.Accent`.
When there's **exactly one slice** (one language at 100%), the renderer emits two concentric `<circle>` elements instead of a pie arc, because SVG's `A` command from point P back to the same P draws nothing. Regression guarded by `TestDonutSingleSlice`.
## Bar-chart cards (productive time)
| Metric | Value |
| --- | --- |
| Chart area | `x ∈ [35, 325]`, `y ∈ [45, 155]` (110 tall) |
| Bars | 24 bars, 1 px gap |
| Bar fill | `theme.Accent` |
| Y-axis ticks | `niceTicks(max, 5)` — 1/2/5 × 10^k ladder |
| X-axis labels | Hours 0, 6, 12, 18, 23 |
| Axis caption | "hour of day" bottom-center |
| Title format | `Commits by Hour (<window>, UTC±N.NN)` |
| Hover | `<title>HH:00 — N commits</title>` inside each bar |
## Area-chart cards (contributions)
| Metric | Value |
| --- | --- |
| Chart area | `x ∈ [28, 312]`, `y ∈ [45, 150]` (105 tall) |
| Curve | Catmull-Rom → cubic Bezier (tension 0.5) |
| Fill | `theme.Accent` at 25% opacity |
| Stroke | `theme.Accent`, 2 px |
| Y-axis | **Both sides**, mirrored, same tick values |
| X-axis labels | `mm/yy`, stride-thinned to ~6 labels regardless of bucket count |
| Axis caption | "mm/yy" bottom-center |
| First/last labels | Always printed (pinned endpoints) |
Missing months in the `[first, last]` range are inserted as zero-count rows to keep the curve time-continuous.
## Icons
- Sourced from [Primer Octicons](https://primer.style/octicons/) 16×16 set.
- Stored as raw `<path d="…"/>` strings in `internal/card/icons.go`.
- Rendered inside `<g transform="translate(x,y) scale(0.75)" fill="muted">…</g>` (scaled to fit the 12 px box).
- Used: `iconRepos`, `iconCompany`, `iconLocation`, `iconClock`, `iconLink`, `iconPeople`, `iconStar`, `iconCommit`, `iconPR`, `iconIssue`, `iconReview`.
Add new icons by copying the `<path>` from Octicons and appending to `icons.go`. Keep them to the same 16×16 viewBox so the existing scale math applies.
## Accessibility
- Contrast is the theme author's responsibility — we don't validate at runtime.
- Tooltips (`<title>`) on productive-time bars let screen readers announce counts.
- No motion, no `<animate>` elements — profile READMEs render statically.
## Text overflow
- Long strings (bio, repo names) are **not truncated**; they're XML-escaped and printed as-is.
- If a card looks crowded at 340 px width, that's a card design problem — fix the layout, not the data.