8 Commits
Author SHA1 Message Date
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
tiennm99 d7659129a5 ci(release): auto-move floating major tag after successful release
Adds an update-major-tag job that runs after test/docker/binaries all
pass. Extracts the major component from the pushed tag (v1.2.3 → v1)
and force-moves that tag to the current commit, then pushes.

Consumers using `uses: tiennm99/ghstats@v1` now pick up each new
minor/patch release automatically without a workflow edit on their
end. The v1 tag only moves if every artifact job succeeded, so it
never points at a commit whose binaries or Docker image failed to
publish.
2026-04-18 23:58:15 +07:00
tiennm99 729f1a0611 Revert "chore: rename module + references to tiennm99/ghstats-cards"
This reverts commit accc4b6. Repo stays as tiennm99/ghstats; the
Marketplace display name ("ghstats-cards" in action.yml) is the only
place the new name remains, since that field requires uniqueness on
the Marketplace.

plans/reports/* were added alongside the rename in the same commit
and are preserved by not deleting them in this revert.
2026-04-18 23:51:33 +07:00
tiennm99 accc4b62b2 chore: rename module + references to tiennm99/ghstats-cards
Matches the Marketplace name; repo is being renamed in lockstep.

- go.mod module path: github.com/tiennm99/ghstats →
  github.com/tiennm99/ghstats-cards
- Import paths across every .go file updated.
- README badges, install snippets, and the 'go install' line point
  to the new URL/path.
- docs/deployment-guide.md workflow template, Docker image path, and
  release edit URL updated.

Breaking for consumers pinned to the old URL; they need to swap
tiennm99/ghstats → tiennm99/ghstats-cards in workflows and switch
Docker pulls to ghcr.io/tiennm99/ghstats-cards. GitHub's HTTP
redirect covers git clones but GHCR does NOT redirect — users must
update image URIs manually.
2026-04-18 23:40:29 +07:00
tiennm99 289d00c227 docs(readme): link back to Marketplace listing + add badges
- Marketplace / Release / License badges at the top.
- Explicit Marketplace + source links under the intro so visitors
  arriving from the Marketplace page can jump to the repo and vice
  versa.

Also set repo topics via `gh repo edit`: ghstats, ghstats-cards,
github-action, github-marketplace, profile-readme, profile-stats,
stats-cards, svg-cards, go, github-stats.
2026-04-18 23:36:28 +07:00
tiennm99 52864bdd18 chore(action): rename Marketplace display to ghstats-cards
"ghstats" is already taken by an existing action/user/org on the
GitHub Marketplace, blocking publication. Rename the action's Marketplace
display name to ghstats-cards (still more descriptive of what it
produces). The repo URL, Docker image tag, and import path stay
tiennm99/ghstats — only the Marketplace listing label changes.
2026-04-18 23:30:21 +07:00
tiennm99 2fa661f9f5 build: refresh dracula samples at 340x200 2026-04-18 23:26:55 +07:00
tiennm99 ce70dcf87c refactor(card): shrink cards from 500x220 to 340x200
Two ghstats cards side-by-side at 500px overflow GitHub's README column
width (~816px). Matching github-profile-summary-cards' 340x200 lets
consumers place two cards per row cleanly.

- Shared header() rect corner radius 8→6, title font 18→15, title anchor
  (25,35)→(20,30).
- All card layouts reworked:
  * profile + stats: row padding 70→55 / 24→20, icon 14→12, value
    font 13→12, stats right-anchor x=475→320.
  * donut_chart: centre (380,120)→(250,110), outerR 70→55, innerR 38→30,
    legend y0 70→55, swatch 12→10, topN 6→5.
  * productive-time: leftAxis 50→35, topPad 60→45, barGap 2→1.
  * contributions: leftPad/rightPad 35→28, topPad 60→45, chartH 120→105.

Visual parity with github-profile-summary-cards; two-per-row layouts in
profile READMEs now fit.

Also: document the one-time GitHub Marketplace publishing step in
deployment-guide (no CLI flag exists — UI checkbox only).
2026-04-18 23:26:48 +07:00
28 changed files with 1082 additions and 383 deletions

No files matched your search

+23
View File
@@ -92,3 +92,26 @@ jobs:
- uses: softprops/action-gh-release@3bb12739c298aeb8a4eeaf626c5b8d85266b0e65 # v2
with:
files: dist/ghstats_*.*
# Move the floating major-version tag (e.g. v1) to this release's commit so
# consumers using `uses: tiennm99/ghstats@v1` automatically pick up each new
# minor/patch release. Force-push is expected here — that's the point of a
# floating tag.
update-major-tag:
needs: [test, docker, binaries]
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
fetch-depth: 0
- name: Retag major version
env:
TAG: ${{ github.ref_name }}
run: |
major="${TAG%%.*}"
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git tag -f "$major" "$TAG"
git push -f origin "$major"
+9 -1
View File
@@ -2,9 +2,17 @@
> Generate SVG cards summarizing a GitHub user's profile — written in Go.
[![Marketplace](https://img.shields.io/badge/Marketplace-ghstats--cards-2f81f7?logo=github)](https://github.com/marketplace/actions/ghstats-cards)
[![Release](https://img.shields.io/github/v/release/tiennm99/ghstats?color=blue)](https://github.com/tiennm99/ghstats/releases/latest)
[![License](https://img.shields.io/github/license/tiennm99/ghstats?color=green)](./LICENSE)
`ghstats` is a single-binary CLI (and a GitHub Action wrapping it) that fetches
data for a GitHub user and writes a themed set of SVGs you can embed in your
profile README:
profile README.
Marketplace listing: **[ghstats-cards](https://github.com/marketplace/actions/ghstats-cards)** · Source: [`tiennm99/ghstats`](https://github.com/tiennm99/ghstats)
Cards rendered:
| # | Card | What it shows |
| --- | --- | --- |
+2 -2
View File
@@ -1,5 +1,5 @@
name: ghstats
description: Generate GitHub profile summary SVG cards (profile, languages, stats, productive time)
name: ghstats-cards
description: Generate GitHub profile summary SVG cards (profile, languages, stats, productive time, contributions)
author: tiennm99
branding:
icon: bar-chart-2
+2 -2
View File
@@ -30,7 +30,7 @@ Applied in order: **YAGNI → KISS → DRY**.
### No hidden state
- Profile fetchers mutate the `*Profile` argument in-place (`FetchContributionsAllTime(p, opts)`) — explicit ownership, no package-level caches.
- Profile fetchers mutate the `*Profile` argument in-place (`FetchContributionsAllTime(ctx, p, opts)`) — explicit ownership, no package-level caches.
- Renderers are pure functions of `(*Profile, theme.Theme)`. No side effects, no goroutines.
### Comments
@@ -58,7 +58,7 @@ Never write comments that describe what well-named code does (`// increment coun
- Always XML-escape user-controlled strings through `escapeXML` (`&`, `<`, `>`, `"`, `'`).
- Numbers formatted via `formatInt` with thousands separators.
- Stable viewbox per card (`500×220` for most, `500×220` for profile too).
- Stable viewbox per card (`340×200`) matching github-profile-summary-cards.
- No `<script>` tags, no event handlers. Cards are pure markup.
## Testing
+3 -2
View File
@@ -97,13 +97,14 @@ commitHistoryQuery ──► Productive + CommitsByLanguage (+ AllTime variants)
## Test coverage
- `internal/card/card_test.go` — RenderAll produces 9 valid SVGs; escape + formatInt spot-checks.
- `internal/card/card_test.go` — `RenderAll` produces 9 valid SVGs; XML escape through real render pipeline; `formatInt` cases; `TestDonutSingleSlice` (guards the empty-arc regression); `TestDonutEmpty` (no-data fallback).
- `internal/github/profile_test.go` — `sortLangStats` ordering and tiebreak.
- `main_test.go` — `TestUTCOffsetLabel` covers UTC, Asia/Saigon, half-hour (Kolkata), quarter-hour (Kathmandu) zones.
No network-touching tests; real runs verified via `-token` + local build.
## Naming conventions
- Go files use snake_case for multi-word names (`repos_per_language.go`, `contributions_all_time.go`).
- Cards' `Filename()` returns the numbered SVG output name — consumers sort lexicographically.
- Cards' `Filename()` returns a plain kebab-case name (`profile-details.svg`, `most-commit-language-all-time.svg`, …). Embedders reference by name, so no numeric prefix is needed.
- Themes in snake_case to match upstream (`github_dark`, `nord_bright`).
+13 -2
View File
@@ -108,9 +108,20 @@ runs:
2. `release.yml` runs `go vet` + `go test` as a gate before the docker and
binaries jobs. If tests fail, no artifacts ship.
3. On green, GHCR push + cross-platform binary artifacts happen automatically.
4. Docker base images and third-party actions are SHA-pinned (with version
4. The `update-major-tag` job force-moves the floating major tag (e.g. `v1`)
to this release's commit after test + docker + binaries all pass.
Consumers pinned to `tiennm99/ghstats@v1` pick up the release on their
next Action run without a workflow edit.
5. Docker base images and third-party actions are SHA-pinned (with version
comments) so mutable-tag changes upstream can't rewrite a released image.
5. Update any public Actions marketplace metadata if the major version changed.
6. **Marketplace publishing (one-time per repo):** GitHub only exposes the
"Publish this Action to the GitHub Marketplace" toggle on the Release
web UI — there is no CLI flag. Open the newly created release at
`https://github.com/tiennm99/ghstats/releases/tag/vX.Y.Z/edit`, tick the
marketplace checkbox, accept the terms, and re-publish. Subsequent
releases inherit marketplace visibility automatically. The Marketplace
listing name is `ghstats-cards` (set in `action.yml`) because the bare
`ghstats` is already taken on the Marketplace.
## Rollback
+26 -26
View File
@@ -1,17 +1,17 @@
# Design Guidelines
Visual conventions for ghstats SVG cards. All cards share a single frame shape so they stack cleanly in a README.
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 | **500 × 220** |
| Corner radius | 8 px |
| 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 | 18 px, weight 600, `theme.Title`, anchored at `(25, 35)` |
| 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`.
@@ -32,30 +32,30 @@ Cards MUST NOT hardcode colors outside these fields. If a new visual needs a sha
Single-column rows of `icon + label` or `icon + label + value`.
| Metric | Value |
| --- | --- |
| First row baseline (y) | 70 |
| Row spacing | 24 px |
| Icon scale | `14/16 = 0.875` from 16×16 Octicon viewBox |
| Icon color | `theme.Muted` |
| Right-aligned value anchor (stats) | `x = 475`, `text-anchor="end"` |
| Value font weight | 600 |
| Value color | `theme.Accent` |
| 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: 7 for profile, 7 for stats (commits row splits into lifetime + last-year).
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 | `(380, 120)` |
| Outer radius | 70 |
| Inner radius | 38 |
| Top-N entries shown | 6 (overflow collapses into "Other") |
| 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 | `(30, 70)` |
| Legend row height | 22 px |
| Swatch size | 12 × 12 |
| 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`.
@@ -65,8 +65,8 @@ When there's **exactly one slice** (one language at 100%), the renderer emits tw
| Metric | Value |
| --- | --- |
| Chart area | `x ∈ [50, 475]`, `y ∈ [60, 170]` (110 tall) |
| Bars | 24 bars, 2 px gap |
| 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 |
@@ -78,7 +78,7 @@ When there's **exactly one slice** (one language at 100%), the renderer emits tw
| Metric | Value |
| --- | --- |
| Chart area | `x ∈ [35, 465]`, `y ∈ [60, 180]` (120 tall) |
| 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 |
@@ -93,7 +93,7 @@ Missing months in the `[first, last]` range are inserted as zero-count rows to k
- 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.875)" fill="muted">…</g>`.
- 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.
@@ -107,4 +107,4 @@ Add new icons by copying the `<path>` from Octicons and appending to `icons.go`.
## Text overflow
- Long strings (bio, repo names) are **not truncated**; they're XML-escaped and printed as-is.
- If a card looks crowded at 500 px width, that's a card design problem — fix the layout, not the data.
- If a card looks crowded at 340 px width, that's a card design problem — fix the layout, not the data.
+1 -1
View File
@@ -28,7 +28,7 @@ Distinguishing traits:
- **Single binary**: no Node, no Ruby, no Docker needed for CLI usage.
- **Seed-list sampling**: commit-history probes land on repos the user actually committed in (via `contributionsCollection.commitContributionsByRepository`), not top-starred or owned-only.
- **All-time variants**: for every time-bounded card (most-commit-language, productive-time, contributions), there's a lifetime counterpart.
- **Public-safe defaults**: forks and private repos are **off** by default; users opt in.
- **Accurate defaults**: forks and private repos are **on** by default so cards reflect real activity; `-include-private` silently no-ops if the token lacks `repo` scope, so the default is safe for public runs too.
## Functional requirements
+18 -6
View File
@@ -26,7 +26,7 @@
## Phase 4 — Accurate repo sampling (✅ done)
- Seed list built from `commitContributionsByRepository` across every active year.
- `-include-forks` / `-include-private` visibility flags (default off).
- `-include-forks` / `-include-private` visibility flags (defaults later flipped on in Phase 7).
- `-top-repos` demoted to an optional cap (default 0 = unlimited).
- Commit-history query takes `$owner` so forks and non-owned repos are probeable.
@@ -50,9 +50,21 @@ Follow-up after the full-project review (`plans/reports/code-review-260418-2223-
- Stats card label "Contributed to (non-fork)" corrected to "Contributed to" (the query doesn't filter forks).
- Tests: fixed stale XML-escape assertion, added `TestDonutSingleSlice`, added `TestUTCOffsetLabel` for half-hour zones.
## Phase 7 — Release polish & Marketplace publish (✅ done)
- Visibility defaults flipped on: `-include-forks`, `-include-private` now default `true` (private silently no-ops if token lacks scope).
- Output filenames dropped the numeric prefix: `0-profile-details.svg` → `profile-details.svg` etc. Embedders reference by name.
- Card dimensions shrunk `500×220` → `340×200` to match github-profile-summary-cards so two cards fit per row in a README.
- Action `action.yml` name set to `ghstats-cards` for Marketplace (the bare `ghstats` is taken); repo stays `tiennm99/ghstats`.
- `v1.0.0`, `v1.1.0`, `v1.1.1` tagged and released. Prebuilt binaries (linux/darwin/windows × amd64/arm64) ship with each; Docker image pushed to `ghcr.io/tiennm99/ghstats`.
- Floating `v1` major tag created; `release.yml` has an `update-major-tag` job that force-moves `v1` to the latest patch after test+docker+binaries pass, so consumers pinned to `tiennm99/ghstats@v1` auto-pick new releases.
- README badges (Marketplace / Release / License) + direct Marketplace link for cross-navigation.
- Repo topics expanded for Marketplace discoverability (`ghstats-cards`, `profile-readme`, `stats-cards`, etc.).
- An attempted repo rename to `tiennm99/ghstats-cards` was committed and reverted (commits `399a3dc` + `8bd2128` on record) — GHCR path immutability and the cost of breaking pinned consumers outweighed the Marketplace-name cosmetic benefit.
---
## Phase 7 — Per-commit file classification (planned)
## Phase 8 — Per-commit file classification (planned)
**Goal**: fix the Markdown-blog misattribution case (and any repo where linguist's byte view disagrees with what files user actually edited).
@@ -64,7 +76,7 @@ Follow-up after the full-project review (`plans/reports/code-review-260418-2223-
**Status**: designed, not implemented.
## Phase 8 — Partial bare clone for lifetime all-repo stats (planned)
## Phase 9 — Partial bare clone for lifetime all-repo stats (planned)
**Goal**: lifetime language stats across **every** repo a user has committed in, without the 500-commits-per-repo cap.
@@ -76,7 +88,7 @@ Follow-up after the full-project review (`plans/reports/code-review-260418-2223-
**Status**: researched only; behind `-deep` flag when landed.
## Phase 9 — User-configurable repo exclusion (planned)
## Phase 10 — User-configurable repo exclusion (planned)
**Goal**: let users drop throwaway repos (experiments, forks they stashed) from stats without disabling forks globally.
@@ -86,7 +98,7 @@ Follow-up after the full-project review (`plans/reports/code-review-260418-2223-
**Status**: pending user demand.
## Phase 10 — Expand ownerAffiliations (planned)
## Phase 11 — Expand ownerAffiliations (planned)
**Goal**: catch work done in org repos where user is a collaborator, not owner (e.g., company monorepos).
@@ -104,7 +116,7 @@ Follow-up after the full-project review (`plans/reports/code-review-260418-2223-
| No real-time API | Scope: scheduled batch renderer, not a server |
| No WakaTime integration | Out of scope — WakaTime cards already exist (athul/waka-readme, anmol098/waka-readme-stats) |
| No heatmap (7×24) variant of productive time | Simplified to 24-hour bar chart to match reference project |
| Hard width of 500 px per card | Keeps README layout predictable; customizing width would cascade through every chart math |
| Hard width of 340 px per card | Matches github-profile-summary-cards; customising would cascade through every chart's geometry. |
## Tracked research reports
+6 -6
View File
@@ -37,12 +37,12 @@ type monthBucket struct {
// contribution cards; only title + data differ.
func renderContributions(title string, days []github.DailyContribution, t theme.Theme) []byte {
const (
width = 500
height = 220
leftPad = 35
rightPad = 35
topPad = 60
chartH = 120
width = 340
height = 200
leftPad = 28
rightPad = 28
topPad = 45
chartH = 105
)
chartW := width - leftPad - rightPad
+13 -13
View File
@@ -14,17 +14,17 @@ import (
// shown; smaller slices are grouped into "Other".
func renderDonutCard(title string, stats []github.LangStat, t theme.Theme) []byte {
const (
width = 500
height = 220
topN = 6
cx = 380 // donut centre x
cy = 120 // donut centre y
outerR = 70.0 // donut outer radius
innerR = 38.0 // donut hole
legendX = 30
legendY0 = 70
legendDY = 22
swatchSz = 12
width = 340
height = 200
topN = 5
cx = 250 // donut centre x
cy = 110 // donut centre y
outerR = 55.0 // donut outer radius
innerR = 30.0 // donut hole
legendX = 20
legendY0 = 55
legendDY = 20
swatchSz = 10
)
stats = collapseOther(stats, topN)
@@ -50,10 +50,10 @@ func renderDonutCard(title string, stats []github.LangStat, t theme.Theme) []byt
y := legendY0 + i*legendDY
fmt.Fprintf(&b, `
<rect x="%d" y="%d" width="%d" height="%d" fill="%s" stroke="%s" stroke-width="1"/>
<text x="%d" y="%d" font-size="13" fill="%s">%s %.2f%%</text>`,
<text x="%d" y="%d" font-size="11" fill="%s">%s %.2f%%</text>`,
legendX, y-swatchSz+2, swatchSz, swatchSz,
colorOrAccent(s.Color, t.Accent), t.Background,
legendX+swatchSz+8, y, t.Text,
legendX+swatchSz+6, y, t.Text,
escapeXML(s.Name), pct)
}
+6 -6
View File
@@ -40,13 +40,13 @@ var xTickHours = [...]int{0, 6, 12, 18, 23}
// hover titles. Shared by the last-year and all-time productive-time cards.
func renderProductiveTime(title string, data [24]int, t theme.Theme) []byte {
const (
width = 500
height = 220
leftAxis = 50
rightPad = 25
topPad = 60
width = 340
height = 200
leftAxis = 35
rightPad = 15
topPad = 45
chartH = 110
barGap = 2
barGap = 1
)
chartW := width - leftAxis - rightPad
barW := float64(chartW-barGap*23) / 24.0
+8 -8
View File
@@ -21,12 +21,12 @@ type profileRow struct {
func (profileCard) SVG(p *github.Profile, t theme.Theme) ([]byte, error) {
const (
width = 500
height = 220
rowX = 25
rowY0 = 70
rowDY = 24
iconSize = 14
width = 340
height = 200
rowX = 20
rowY0 = 60
rowDY = 20
iconSize = 12
maxRows = 7
)
@@ -46,9 +46,9 @@ func (profileCard) SVG(p *github.Profile, t theme.Theme) ([]byte, error) {
// icon glyph: translate to row position, scale down, fill with muted.
fmt.Fprintf(&b, `
<g transform="translate(%d,%.2f) scale(%.3f)" fill="%s">%s</g>
<text x="%d" y="%d" font-size="13" fill="%s">%s</text>`,
<text x="%d" y="%d" font-size="12" fill="%s">%s</text>`,
rowX, float64(y-iconSize+2), scale, t.Muted, r.icon,
rowX+iconSize+10, y, t.Text, escapeXML(r.value))
rowX+iconSize+8, y, t.Text, escapeXML(r.value))
}
b.WriteString(footer)
+10 -10
View File
@@ -21,13 +21,13 @@ type statRow struct {
func (statsCard) SVG(p *github.Profile, t theme.Theme) ([]byte, error) {
const (
width = 500
height = 220
rowX = 25
rowY0 = 70
rowDY = 24
iconSize = 14
valueX = 475 // right-aligned x anchor
width = 340
height = 200
rowX = 20
rowY0 = 55
rowDY = 20
iconSize = 12
valueX = 320 // right-aligned x anchor
)
rows := []statRow{
@@ -48,10 +48,10 @@ func (statsCard) SVG(p *github.Profile, t theme.Theme) ([]byte, error) {
y := rowY0 + i*rowDY
fmt.Fprintf(&b, `
<g transform="translate(%d,%.2f) scale(%.3f)" fill="%s">%s</g>
<text x="%d" y="%d" font-size="13" fill="%s">%s</text>
<text x="%d" y="%d" font-size="13" font-weight="600" fill="%s" text-anchor="end">%s</text>`,
<text x="%d" y="%d" font-size="12" fill="%s">%s</text>
<text x="%d" y="%d" font-size="12" font-weight="600" fill="%s" text-anchor="end">%s</text>`,
rowX, float64(y-iconSize+2), scale, t.Muted, r.icon,
rowX+iconSize+10, y, t.Text, escapeXML(r.label),
rowX+iconSize+8, y, t.Text, escapeXML(r.label),
valueX, y, t.Accent, escapeXML(r.value))
}
+2 -2
View File
@@ -55,8 +55,8 @@ func formatInt(n int) string {
// header returns the opening <svg> tag + background rect + title text.
func header(width, height int, bg, stroke string, strokeOpacity float64, titleColor, title string) string {
return fmt.Sprintf(`<svg xmlns="http://www.w3.org/2000/svg" width="%d" height="%d" viewBox="0 0 %d %d" font-family="'Segoe UI', Ubuntu, Sans-Serif">
<rect x="0.5" y="0.5" width="%d" height="%d" rx="8" fill="%s" stroke="%s" stroke-opacity="%.2f"/>
<text x="25" y="35" font-size="18" font-weight="600" fill="%s">%s</text>`,
<rect x="0.5" y="0.5" width="%d" height="%d" rx="6" fill="%s" stroke="%s" stroke-opacity="%.2f"/>
<text x="20" y="30" font-size="15" font-weight="600" fill="%s">%s</text>`,
width, height, width, height,
width-1, height-1, bg, stroke, strokeOpacity,
titleColor, escapeXML(title))
+45 -45
View File
@@ -1,46 +1,46 @@
<svg xmlns="http://www.w3.org/2000/svg" width="500" height="220" viewBox="0 0 500 220" font-family="'Segoe UI', Ubuntu, Sans-Serif">
<rect x="0.5" y="0.5" width="499" height="219" rx="8" fill="#282a36" stroke="#282a36" stroke-opacity="1.00"/>
<text x="25" y="35" font-size="18" font-weight="600" fill="#ff79c6">Contributions (all time)</text>
<line x1="35" y1="60" x2="35" y2="180" stroke="#6272a4"/>
<line x1="465" y1="60" x2="465" y2="180" stroke="#6272a4"/>
<line x1="31" y1="180" x2="35" y2="180" stroke="#6272a4"/>
<text x="29" y="183" font-size="10" fill="#6272a4" text-anchor="end">0</text>
<line x1="465" y1="180" x2="469" y2="180" stroke="#6272a4"/>
<text x="471" y="183" font-size="10" fill="#6272a4" text-anchor="start">0</text>
<line x1="31" y1="156" x2="35" y2="156" stroke="#6272a4"/>
<text x="29" y="159" font-size="10" fill="#6272a4" text-anchor="end">100</text>
<line x1="465" y1="156" x2="469" y2="156" stroke="#6272a4"/>
<text x="471" y="159" font-size="10" fill="#6272a4" text-anchor="start">100</text>
<line x1="31" y1="132" x2="35" y2="132" stroke="#6272a4"/>
<text x="29" y="135" font-size="10" fill="#6272a4" text-anchor="end">200</text>
<line x1="465" y1="132" x2="469" y2="132" stroke="#6272a4"/>
<text x="471" y="135" font-size="10" fill="#6272a4" text-anchor="start">200</text>
<line x1="31" y1="108" x2="35" y2="108" stroke="#6272a4"/>
<text x="29" y="111" font-size="10" fill="#6272a4" text-anchor="end">300</text>
<line x1="465" y1="108" x2="469" y2="108" stroke="#6272a4"/>
<text x="471" y="111" font-size="10" fill="#6272a4" text-anchor="start">300</text>
<line x1="31" y1="84" x2="35" y2="84" stroke="#6272a4"/>
<text x="29" y="87" font-size="10" fill="#6272a4" text-anchor="end">400</text>
<line x1="465" y1="84" x2="469" y2="84" stroke="#6272a4"/>
<text x="471" y="87" font-size="10" fill="#6272a4" text-anchor="start">400</text>
<line x1="31" y1="60" x2="35" y2="60" stroke="#6272a4"/>
<text x="29" y="63" font-size="10" fill="#6272a4" text-anchor="end">500</text>
<line x1="465" y1="60" x2="469" y2="60" stroke="#6272a4"/>
<text x="471" y="63" font-size="10" fill="#6272a4" text-anchor="start">500</text>
<line x1="35" y1="180" x2="465" y2="180" stroke="#6272a4"/>
<line x1="35" y1="180" x2="35" y2="184" stroke="#6272a4"/>
<text x="35" y="196" font-size="10" fill="#6272a4" text-anchor="middle">01/18</text>
<line x1="117" y1="180" x2="117" y2="184" stroke="#6272a4"/>
<text x="117" y="196" font-size="10" fill="#6272a4" text-anchor="middle">08/19</text>
<line x1="200" y1="180" x2="200" y2="184" stroke="#6272a4"/>
<text x="200" y="196" font-size="10" fill="#6272a4" text-anchor="middle">03/21</text>
<line x1="282" y1="180" x2="282" y2="184" stroke="#6272a4"/>
<text x="282" y="196" font-size="10" fill="#6272a4" text-anchor="middle">10/22</text>
<line x1="365" y1="180" x2="365" y2="184" stroke="#6272a4"/>
<text x="365" y="196" font-size="10" fill="#6272a4" text-anchor="middle">05/24</text>
<line x1="465" y1="180" x2="465" y2="184" stroke="#6272a4"/>
<text x="465" y="196" font-size="10" fill="#6272a4" text-anchor="middle">04/26</text>
<path d="M35.00,180.00 C35.72,180.00 37.90,180.00 39.34,180.00 C40.79,180.00 42.24,180.00 43.69,180.00 C45.13,180.00 46.58,180.04 48.03,180.00 C49.48,179.96 50.93,180.36 52.37,179.76 C53.82,179.16 55.27,176.36 56.72,176.40 C58.16,176.44 59.61,179.40 61.06,180.00 C62.51,180.60 63.96,180.16 65.40,180.00 C66.85,179.84 68.30,179.88 69.75,179.04 C71.20,178.20 72.64,175.16 74.09,174.96 C75.54,174.76 76.99,177.00 78.43,177.84 C79.88,178.68 81.33,179.68 82.78,180.00 C84.23,180.32 85.67,179.80 87.12,179.76 C88.57,179.72 90.02,179.72 91.46,179.76 C92.91,179.80 94.36,179.96 95.81,180.00 C97.26,180.04 98.70,180.00 100.15,180.00 C101.60,180.00 103.05,180.00 104.49,180.00 C105.94,180.00 107.39,180.04 108.84,180.00 C110.29,179.96 111.73,180.36 113.18,179.76 C114.63,179.16 116.08,176.36 117.53,176.40 C118.97,176.44 120.42,179.40 121.87,180.00 C123.32,180.60 124.76,180.12 126.21,180.00 C127.66,179.88 129.11,179.28 130.56,179.28 C132.00,179.28 133.45,179.88 134.90,180.00 C136.35,180.12 137.79,180.00 139.24,180.00 C140.69,180.00 142.14,180.24 143.59,180.00 C145.03,179.76 146.48,178.56 147.93,178.56 C149.38,178.56 150.82,179.84 152.27,180.00 C153.72,180.16 155.17,179.76 156.62,179.52 C158.06,179.28 159.51,178.52 160.96,178.56 C162.41,178.60 163.86,180.56 165.30,179.76 C166.75,178.96 168.20,173.76 169.65,173.76 C171.09,173.76 172.54,179.52 173.99,179.76 C175.44,180.00 176.89,175.20 178.33,175.20 C179.78,175.20 181.23,178.96 182.68,179.76 C184.12,180.56 185.57,179.96 187.02,180.00 C188.47,180.04 189.92,180.12 191.36,180.00 C192.81,179.88 194.26,179.80 195.71,179.28 C197.15,178.76 198.60,177.04 200.05,176.88 C201.50,176.72 202.95,177.80 204.39,178.32 C205.84,178.84 207.29,179.72 208.74,180.00 C210.19,180.28 211.63,180.00 213.08,180.00 C214.53,180.00 215.98,180.20 217.42,180.00 C218.87,179.80 220.32,179.24 221.77,178.80 C223.22,178.36 224.66,177.44 226.11,177.36 C227.56,177.28 229.01,177.88 230.45,178.32 C231.90,178.76 233.35,179.76 234.80,180.00 C236.25,180.24 237.69,179.92 239.14,179.76 C240.59,179.60 242.04,179.28 243.48,179.04 C244.93,178.80 246.38,178.60 247.83,178.32 C249.28,178.04 250.72,177.40 252.17,177.36 C253.62,177.32 255.07,177.64 256.52,178.08 C257.96,178.52 259.41,180.52 260.86,180.00 C262.31,179.48 263.75,175.84 265.20,174.96 C266.65,174.08 268.10,173.92 269.55,174.72 C270.99,175.52 272.44,179.64 273.89,179.76 C275.34,179.88 276.78,176.72 278.23,175.44 C279.68,174.16 281.13,174.48 282.58,172.08 C284.02,169.68 285.47,164.00 286.92,161.04 C288.37,158.08 289.81,151.16 291.26,154.32 C292.71,157.48 294.16,178.04 295.61,180.00 C297.05,181.96 298.50,167.28 299.95,166.08 C301.40,164.88 302.85,171.32 304.29,172.80 C305.74,174.28 307.19,175.96 308.64,174.96 C310.08,173.96 311.53,167.36 312.98,166.80 C314.43,166.24 315.88,170.04 317.32,171.60 C318.77,173.16 320.22,176.96 321.67,176.16 C323.11,175.36 324.56,172.12 326.01,166.80 C327.46,161.48 328.91,144.32 330.35,144.24 C331.80,144.16 333.25,160.84 334.70,166.32 C336.14,171.80 337.59,175.24 339.04,177.12 C340.49,179.00 341.94,177.16 343.38,177.60 C344.83,178.04 346.28,179.36 347.73,179.76 C349.18,180.16 350.62,180.88 352.07,180.00 C353.52,179.12 354.97,178.96 356.41,174.48 C357.86,170.00 359.31,155.84 360.76,153.12 C362.21,150.40 363.65,154.24 365.10,158.16 C366.55,162.08 368.00,173.28 369.44,176.64 C370.89,180.00 372.34,178.84 373.79,178.32 C375.24,177.80 376.68,174.36 378.13,173.52 C379.58,172.68 381.03,173.68 382.47,173.28 C383.92,172.88 385.37,170.60 386.82,171.12 C388.27,171.64 389.71,175.52 391.16,176.40 C392.61,177.28 394.06,176.20 395.51,176.40 C396.95,176.60 398.40,179.68 399.85,177.60 C401.30,175.52 402.74,167.44 404.19,163.92 C405.64,160.40 407.09,156.84 408.54,156.48 C409.98,156.12 411.43,161.68 412.88,161.76 C414.33,161.84 415.77,155.72 417.22,156.96 C418.67,158.20 420.12,167.40 421.57,169.20 C423.01,171.00 424.46,168.84 425.91,167.76 C427.36,166.68 428.80,162.20 430.25,162.72 C431.70,163.24 433.15,172.72 434.60,170.88 C436.04,169.04 437.49,160.20 438.94,151.68 C440.39,143.16 441.84,122.00 443.28,119.76 C444.73,117.52 446.18,129.64 447.63,138.24 C449.07,146.84 450.52,168.64 451.97,171.36 C453.42,174.08 454.87,164.40 456.31,154.56 C457.76,144.72 459.21,129.04 460.66,112.32 C462.10,95.60 464.28,63.92 465.00,54.24 L465.00,180.00 L35.00,180.00 Z" fill="#bd93f9" fill-opacity="0.25" stroke="none"/>
<path d="M35.00,180.00 C35.72,180.00 37.90,180.00 39.34,180.00 C40.79,180.00 42.24,180.00 43.69,180.00 C45.13,180.00 46.58,180.04 48.03,180.00 C49.48,179.96 50.93,180.36 52.37,179.76 C53.82,179.16 55.27,176.36 56.72,176.40 C58.16,176.44 59.61,179.40 61.06,180.00 C62.51,180.60 63.96,180.16 65.40,180.00 C66.85,179.84 68.30,179.88 69.75,179.04 C71.20,178.20 72.64,175.16 74.09,174.96 C75.54,174.76 76.99,177.00 78.43,177.84 C79.88,178.68 81.33,179.68 82.78,180.00 C84.23,180.32 85.67,179.80 87.12,179.76 C88.57,179.72 90.02,179.72 91.46,179.76 C92.91,179.80 94.36,179.96 95.81,180.00 C97.26,180.04 98.70,180.00 100.15,180.00 C101.60,180.00 103.05,180.00 104.49,180.00 C105.94,180.00 107.39,180.04 108.84,180.00 C110.29,179.96 111.73,180.36 113.18,179.76 C114.63,179.16 116.08,176.36 117.53,176.40 C118.97,176.44 120.42,179.40 121.87,180.00 C123.32,180.60 124.76,180.12 126.21,180.00 C127.66,179.88 129.11,179.28 130.56,179.28 C132.00,179.28 133.45,179.88 134.90,180.00 C136.35,180.12 137.79,180.00 139.24,180.00 C140.69,180.00 142.14,180.24 143.59,180.00 C145.03,179.76 146.48,178.56 147.93,178.56 C149.38,178.56 150.82,179.84 152.27,180.00 C153.72,180.16 155.17,179.76 156.62,179.52 C158.06,179.28 159.51,178.52 160.96,178.56 C162.41,178.60 163.86,180.56 165.30,179.76 C166.75,178.96 168.20,173.76 169.65,173.76 C171.09,173.76 172.54,179.52 173.99,179.76 C175.44,180.00 176.89,175.20 178.33,175.20 C179.78,175.20 181.23,178.96 182.68,179.76 C184.12,180.56 185.57,179.96 187.02,180.00 C188.47,180.04 189.92,180.12 191.36,180.00 C192.81,179.88 194.26,179.80 195.71,179.28 C197.15,178.76 198.60,177.04 200.05,176.88 C201.50,176.72 202.95,177.80 204.39,178.32 C205.84,178.84 207.29,179.72 208.74,180.00 C210.19,180.28 211.63,180.00 213.08,180.00 C214.53,180.00 215.98,180.20 217.42,180.00 C218.87,179.80 220.32,179.24 221.77,178.80 C223.22,178.36 224.66,177.44 226.11,177.36 C227.56,177.28 229.01,177.88 230.45,178.32 C231.90,178.76 233.35,179.76 234.80,180.00 C236.25,180.24 237.69,179.92 239.14,179.76 C240.59,179.60 242.04,179.28 243.48,179.04 C244.93,178.80 246.38,178.60 247.83,178.32 C249.28,178.04 250.72,177.40 252.17,177.36 C253.62,177.32 255.07,177.64 256.52,178.08 C257.96,178.52 259.41,180.52 260.86,180.00 C262.31,179.48 263.75,175.84 265.20,174.96 C266.65,174.08 268.10,173.92 269.55,174.72 C270.99,175.52 272.44,179.64 273.89,179.76 C275.34,179.88 276.78,176.72 278.23,175.44 C279.68,174.16 281.13,174.48 282.58,172.08 C284.02,169.68 285.47,164.00 286.92,161.04 C288.37,158.08 289.81,151.16 291.26,154.32 C292.71,157.48 294.16,178.04 295.61,180.00 C297.05,181.96 298.50,167.28 299.95,166.08 C301.40,164.88 302.85,171.32 304.29,172.80 C305.74,174.28 307.19,175.96 308.64,174.96 C310.08,173.96 311.53,167.36 312.98,166.80 C314.43,166.24 315.88,170.04 317.32,171.60 C318.77,173.16 320.22,176.96 321.67,176.16 C323.11,175.36 324.56,172.12 326.01,166.80 C327.46,161.48 328.91,144.32 330.35,144.24 C331.80,144.16 333.25,160.84 334.70,166.32 C336.14,171.80 337.59,175.24 339.04,177.12 C340.49,179.00 341.94,177.16 343.38,177.60 C344.83,178.04 346.28,179.36 347.73,179.76 C349.18,180.16 350.62,180.88 352.07,180.00 C353.52,179.12 354.97,178.96 356.41,174.48 C357.86,170.00 359.31,155.84 360.76,153.12 C362.21,150.40 363.65,154.24 365.10,158.16 C366.55,162.08 368.00,173.28 369.44,176.64 C370.89,180.00 372.34,178.84 373.79,178.32 C375.24,177.80 376.68,174.36 378.13,173.52 C379.58,172.68 381.03,173.68 382.47,173.28 C383.92,172.88 385.37,170.60 386.82,171.12 C388.27,171.64 389.71,175.52 391.16,176.40 C392.61,177.28 394.06,176.20 395.51,176.40 C396.95,176.60 398.40,179.68 399.85,177.60 C401.30,175.52 402.74,167.44 404.19,163.92 C405.64,160.40 407.09,156.84 408.54,156.48 C409.98,156.12 411.43,161.68 412.88,161.76 C414.33,161.84 415.77,155.72 417.22,156.96 C418.67,158.20 420.12,167.40 421.57,169.20 C423.01,171.00 424.46,168.84 425.91,167.76 C427.36,166.68 428.80,162.20 430.25,162.72 C431.70,163.24 433.15,172.72 434.60,170.88 C436.04,169.04 437.49,160.20 438.94,151.68 C440.39,143.16 441.84,122.00 443.28,119.76 C444.73,117.52 446.18,129.64 447.63,138.24 C449.07,146.84 450.52,168.64 451.97,171.36 C453.42,174.08 454.87,164.40 456.31,154.56 C457.76,144.72 459.21,129.04 460.66,112.32 C462.10,95.60 464.28,63.92 465.00,54.24" fill="none" stroke="#bd93f9" stroke-width="2"/>
<text x="250" y="214" font-size="11" fill="#6272a4" text-anchor="middle">mm/yy</text>
<svg xmlns="http://www.w3.org/2000/svg" width="340" height="200" viewBox="0 0 340 200" font-family="'Segoe UI', Ubuntu, Sans-Serif">
<rect x="0.5" y="0.5" width="339" height="199" rx="6" fill="#282a36" stroke="#282a36" stroke-opacity="1.00"/>
<text x="20" y="30" font-size="15" font-weight="600" fill="#ff79c6">Contributions (all time)</text>
<line x1="28" y1="45" x2="28" y2="150" stroke="#6272a4"/>
<line x1="312" y1="45" x2="312" y2="150" stroke="#6272a4"/>
<line x1="24" y1="150" x2="28" y2="150" stroke="#6272a4"/>
<text x="22" y="153" font-size="10" fill="#6272a4" text-anchor="end">0</text>
<line x1="312" y1="150" x2="316" y2="150" stroke="#6272a4"/>
<text x="318" y="153" font-size="10" fill="#6272a4" text-anchor="start">0</text>
<line x1="24" y1="129" x2="28" y2="129" stroke="#6272a4"/>
<text x="22" y="132" font-size="10" fill="#6272a4" text-anchor="end">100</text>
<line x1="312" y1="129" x2="316" y2="129" stroke="#6272a4"/>
<text x="318" y="132" font-size="10" fill="#6272a4" text-anchor="start">100</text>
<line x1="24" y1="108" x2="28" y2="108" stroke="#6272a4"/>
<text x="22" y="111" font-size="10" fill="#6272a4" text-anchor="end">200</text>
<line x1="312" y1="108" x2="316" y2="108" stroke="#6272a4"/>
<text x="318" y="111" font-size="10" fill="#6272a4" text-anchor="start">200</text>
<line x1="24" y1="87" x2="28" y2="87" stroke="#6272a4"/>
<text x="22" y="90" font-size="10" fill="#6272a4" text-anchor="end">300</text>
<line x1="312" y1="87" x2="316" y2="87" stroke="#6272a4"/>
<text x="318" y="90" font-size="10" fill="#6272a4" text-anchor="start">300</text>
<line x1="24" y1="66" x2="28" y2="66" stroke="#6272a4"/>
<text x="22" y="69" font-size="10" fill="#6272a4" text-anchor="end">400</text>
<line x1="312" y1="66" x2="316" y2="66" stroke="#6272a4"/>
<text x="318" y="69" font-size="10" fill="#6272a4" text-anchor="start">400</text>
<line x1="24" y1="45" x2="28" y2="45" stroke="#6272a4"/>
<text x="22" y="48" font-size="10" fill="#6272a4" text-anchor="end">500</text>
<line x1="312" y1="45" x2="316" y2="45" stroke="#6272a4"/>
<text x="318" y="48" font-size="10" fill="#6272a4" text-anchor="start">500</text>
<line x1="28" y1="150" x2="312" y2="150" stroke="#6272a4"/>
<line x1="28" y1="150" x2="28" y2="154" stroke="#6272a4"/>
<text x="28" y="166" font-size="10" fill="#6272a4" text-anchor="middle">01/18</text>
<line x1="82" y1="150" x2="82" y2="154" stroke="#6272a4"/>
<text x="82" y="166" font-size="10" fill="#6272a4" text-anchor="middle">08/19</text>
<line x1="137" y1="150" x2="137" y2="154" stroke="#6272a4"/>
<text x="137" y="166" font-size="10" fill="#6272a4" text-anchor="middle">03/21</text>
<line x1="191" y1="150" x2="191" y2="154" stroke="#6272a4"/>
<text x="191" y="166" font-size="10" fill="#6272a4" text-anchor="middle">10/22</text>
<line x1="246" y1="150" x2="246" y2="154" stroke="#6272a4"/>
<text x="246" y="166" font-size="10" fill="#6272a4" text-anchor="middle">05/24</text>
<line x1="312" y1="150" x2="312" y2="154" stroke="#6272a4"/>
<text x="312" y="166" font-size="10" fill="#6272a4" text-anchor="middle">04/26</text>
<path d="M28.00,150.00 C28.48,150.00 29.91,150.00 30.87,150.00 C31.82,150.00 32.78,150.00 33.74,150.00 C34.69,150.00 35.65,150.03 36.61,150.00 C37.56,149.97 38.52,150.31 39.47,149.79 C40.43,149.26 41.39,146.81 42.34,146.85 C43.30,146.88 44.26,149.47 45.21,150.00 C46.17,150.53 47.12,150.14 48.08,150.00 C49.04,149.86 49.99,149.89 50.95,149.16 C51.91,148.43 52.86,145.76 53.82,145.59 C54.77,145.42 55.73,147.38 56.69,148.11 C57.64,148.85 58.60,149.72 59.56,150.00 C60.51,150.28 61.47,149.82 62.42,149.79 C63.38,149.75 64.34,149.75 65.29,149.79 C66.25,149.82 67.21,149.97 68.16,150.00 C69.12,150.03 70.07,150.00 71.03,150.00 C71.99,150.00 72.94,150.00 73.90,150.00 C74.86,150.00 75.81,150.03 76.77,150.00 C77.72,149.97 78.68,150.31 79.64,149.79 C80.59,149.26 81.55,146.81 82.51,146.85 C83.46,146.88 84.42,149.47 85.37,150.00 C86.33,150.53 87.29,150.10 88.24,150.00 C89.20,149.90 90.15,149.37 91.11,149.37 C92.07,149.37 93.02,149.90 93.98,150.00 C94.94,150.10 95.89,150.00 96.85,150.00 C97.80,150.00 98.76,150.21 99.72,150.00 C100.67,149.79 101.63,148.74 102.59,148.74 C103.54,148.74 104.50,149.86 105.45,150.00 C106.41,150.14 107.37,149.79 108.32,149.58 C109.28,149.37 110.24,148.71 111.19,148.74 C112.15,148.78 113.10,150.49 114.06,149.79 C115.02,149.09 115.97,144.54 116.93,144.54 C117.89,144.54 118.84,149.58 119.80,149.79 C120.75,150.00 121.71,145.80 122.67,145.80 C123.62,145.80 124.58,149.09 125.54,149.79 C126.49,150.49 127.45,149.97 128.40,150.00 C129.36,150.03 130.32,150.10 131.27,150.00 C132.23,149.90 133.19,149.82 134.14,149.37 C135.10,148.92 136.05,147.41 137.01,147.27 C137.97,147.13 138.92,148.07 139.88,148.53 C140.84,148.99 141.79,149.75 142.75,150.00 C143.70,150.25 144.66,150.00 145.62,150.00 C146.57,150.00 147.53,150.18 148.48,150.00 C149.44,149.82 150.40,149.33 151.35,148.95 C152.31,148.56 153.27,147.76 154.22,147.69 C155.18,147.62 156.13,148.15 157.09,148.53 C158.05,148.91 159.00,149.79 159.96,150.00 C160.92,150.21 161.87,149.93 162.83,149.79 C163.78,149.65 164.74,149.37 165.70,149.16 C166.65,148.95 167.61,148.78 168.57,148.53 C169.52,148.28 170.48,147.72 171.43,147.69 C172.39,147.66 173.35,147.94 174.30,148.32 C175.26,148.70 176.22,150.45 177.17,150.00 C178.13,149.55 179.08,146.36 180.04,145.59 C181.00,144.82 181.95,144.68 182.91,145.38 C183.87,146.08 184.82,149.69 185.78,149.79 C186.73,149.89 187.69,147.13 188.65,146.01 C189.60,144.89 190.56,145.17 191.52,143.07 C192.47,140.97 193.43,136.00 194.38,133.41 C195.34,130.82 196.30,124.77 197.25,127.53 C198.21,130.30 199.16,148.28 200.12,150.00 C201.08,151.72 202.03,138.87 202.99,137.82 C203.95,136.77 204.90,142.40 205.86,143.70 C206.81,145.00 207.77,146.47 208.73,145.59 C209.68,144.72 210.64,138.94 211.60,138.45 C212.55,137.96 213.51,141.28 214.46,142.65 C215.42,144.02 216.38,147.34 217.33,146.64 C218.29,145.94 219.25,143.10 220.20,138.45 C221.16,133.79 222.11,118.78 223.07,118.71 C224.03,118.64 224.98,133.24 225.94,138.03 C226.90,142.82 227.85,145.83 228.81,147.48 C229.76,149.12 230.72,147.52 231.68,147.90 C232.63,148.28 233.59,149.44 234.55,149.79 C235.50,150.14 236.46,150.77 237.41,150.00 C238.37,149.23 239.33,149.09 240.28,145.17 C241.24,141.25 242.20,128.86 243.15,126.48 C244.11,124.10 245.06,127.46 246.02,130.89 C246.98,134.32 247.93,144.12 248.89,147.06 C249.85,150.00 250.80,148.99 251.76,148.53 C252.71,148.07 253.67,145.06 254.63,144.33 C255.58,143.60 256.54,144.47 257.49,144.12 C258.45,143.77 259.41,141.77 260.36,142.23 C261.32,142.69 262.28,146.08 263.23,146.85 C264.19,147.62 265.14,146.67 266.10,146.85 C267.06,147.03 268.01,149.72 268.97,147.90 C269.93,146.08 270.88,139.01 271.84,135.93 C272.79,132.85 273.75,129.74 274.71,129.42 C275.66,129.11 276.62,133.97 277.58,134.04 C278.53,134.11 279.49,128.75 280.44,129.84 C281.40,130.93 282.36,138.98 283.31,140.55 C284.27,142.12 285.23,140.23 286.18,139.29 C287.14,138.34 288.09,134.42 289.05,134.88 C290.01,135.34 290.96,143.63 291.92,142.02 C292.88,140.41 293.83,132.68 294.79,125.22 C295.74,117.77 296.70,99.25 297.66,97.29 C298.61,95.33 299.57,105.94 300.53,113.46 C301.48,120.99 302.44,140.06 303.39,142.44 C304.35,144.82 305.31,136.35 306.26,127.74 C307.22,119.13 308.18,105.94 309.13,90.78 C310.09,75.62 311.52,45.80 312.00,36.81 L312.00,150.00 L28.00,150.00 Z" fill="#bd93f9" fill-opacity="0.25" stroke="none"/>
<path d="M28.00,150.00 C28.48,150.00 29.91,150.00 30.87,150.00 C31.82,150.00 32.78,150.00 33.74,150.00 C34.69,150.00 35.65,150.03 36.61,150.00 C37.56,149.97 38.52,150.31 39.47,149.79 C40.43,149.26 41.39,146.81 42.34,146.85 C43.30,146.88 44.26,149.47 45.21,150.00 C46.17,150.53 47.12,150.14 48.08,150.00 C49.04,149.86 49.99,149.89 50.95,149.16 C51.91,148.43 52.86,145.76 53.82,145.59 C54.77,145.42 55.73,147.38 56.69,148.11 C57.64,148.85 58.60,149.72 59.56,150.00 C60.51,150.28 61.47,149.82 62.42,149.79 C63.38,149.75 64.34,149.75 65.29,149.79 C66.25,149.82 67.21,149.97 68.16,150.00 C69.12,150.03 70.07,150.00 71.03,150.00 C71.99,150.00 72.94,150.00 73.90,150.00 C74.86,150.00 75.81,150.03 76.77,150.00 C77.72,149.97 78.68,150.31 79.64,149.79 C80.59,149.26 81.55,146.81 82.51,146.85 C83.46,146.88 84.42,149.47 85.37,150.00 C86.33,150.53 87.29,150.10 88.24,150.00 C89.20,149.90 90.15,149.37 91.11,149.37 C92.07,149.37 93.02,149.90 93.98,150.00 C94.94,150.10 95.89,150.00 96.85,150.00 C97.80,150.00 98.76,150.21 99.72,150.00 C100.67,149.79 101.63,148.74 102.59,148.74 C103.54,148.74 104.50,149.86 105.45,150.00 C106.41,150.14 107.37,149.79 108.32,149.58 C109.28,149.37 110.24,148.71 111.19,148.74 C112.15,148.78 113.10,150.49 114.06,149.79 C115.02,149.09 115.97,144.54 116.93,144.54 C117.89,144.54 118.84,149.58 119.80,149.79 C120.75,150.00 121.71,145.80 122.67,145.80 C123.62,145.80 124.58,149.09 125.54,149.79 C126.49,150.49 127.45,149.97 128.40,150.00 C129.36,150.03 130.32,150.10 131.27,150.00 C132.23,149.90 133.19,149.82 134.14,149.37 C135.10,148.92 136.05,147.41 137.01,147.27 C137.97,147.13 138.92,148.07 139.88,148.53 C140.84,148.99 141.79,149.75 142.75,150.00 C143.70,150.25 144.66,150.00 145.62,150.00 C146.57,150.00 147.53,150.18 148.48,150.00 C149.44,149.82 150.40,149.33 151.35,148.95 C152.31,148.56 153.27,147.76 154.22,147.69 C155.18,147.62 156.13,148.15 157.09,148.53 C158.05,148.91 159.00,149.79 159.96,150.00 C160.92,150.21 161.87,149.93 162.83,149.79 C163.78,149.65 164.74,149.37 165.70,149.16 C166.65,148.95 167.61,148.78 168.57,148.53 C169.52,148.28 170.48,147.72 171.43,147.69 C172.39,147.66 173.35,147.94 174.30,148.32 C175.26,148.70 176.22,150.45 177.17,150.00 C178.13,149.55 179.08,146.36 180.04,145.59 C181.00,144.82 181.95,144.68 182.91,145.38 C183.87,146.08 184.82,149.69 185.78,149.79 C186.73,149.89 187.69,147.13 188.65,146.01 C189.60,144.89 190.56,145.17 191.52,143.07 C192.47,140.97 193.43,136.00 194.38,133.41 C195.34,130.82 196.30,124.77 197.25,127.53 C198.21,130.30 199.16,148.28 200.12,150.00 C201.08,151.72 202.03,138.87 202.99,137.82 C203.95,136.77 204.90,142.40 205.86,143.70 C206.81,145.00 207.77,146.47 208.73,145.59 C209.68,144.72 210.64,138.94 211.60,138.45 C212.55,137.96 213.51,141.28 214.46,142.65 C215.42,144.02 216.38,147.34 217.33,146.64 C218.29,145.94 219.25,143.10 220.20,138.45 C221.16,133.79 222.11,118.78 223.07,118.71 C224.03,118.64 224.98,133.24 225.94,138.03 C226.90,142.82 227.85,145.83 228.81,147.48 C229.76,149.12 230.72,147.52 231.68,147.90 C232.63,148.28 233.59,149.44 234.55,149.79 C235.50,150.14 236.46,150.77 237.41,150.00 C238.37,149.23 239.33,149.09 240.28,145.17 C241.24,141.25 242.20,128.86 243.15,126.48 C244.11,124.10 245.06,127.46 246.02,130.89 C246.98,134.32 247.93,144.12 248.89,147.06 C249.85,150.00 250.80,148.99 251.76,148.53 C252.71,148.07 253.67,145.06 254.63,144.33 C255.58,143.60 256.54,144.47 257.49,144.12 C258.45,143.77 259.41,141.77 260.36,142.23 C261.32,142.69 262.28,146.08 263.23,146.85 C264.19,147.62 265.14,146.67 266.10,146.85 C267.06,147.03 268.01,149.72 268.97,147.90 C269.93,146.08 270.88,139.01 271.84,135.93 C272.79,132.85 273.75,129.74 274.71,129.42 C275.66,129.11 276.62,133.97 277.58,134.04 C278.53,134.11 279.49,128.75 280.44,129.84 C281.40,130.93 282.36,138.98 283.31,140.55 C284.27,142.12 285.23,140.23 286.18,139.29 C287.14,138.34 288.09,134.42 289.05,134.88 C290.01,135.34 290.96,143.63 291.92,142.02 C292.88,140.41 293.83,132.68 294.79,125.22 C295.74,117.77 296.70,99.25 297.66,97.29 C298.61,95.33 299.57,105.94 300.53,113.46 C301.48,120.99 302.44,140.06 303.39,142.44 C304.35,144.82 305.31,136.35 306.26,127.74 C307.22,119.13 308.18,105.94 309.13,90.78 C310.09,75.62 311.52,45.80 312.00,36.81" fill="none" stroke="#bd93f9" stroke-width="2"/>
<text x="170" y="184" font-size="11" fill="#6272a4" text-anchor="middle">mm/yy</text>
</svg>

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

+47 -47
View File
@@ -1,48 +1,48 @@
<svg xmlns="http://www.w3.org/2000/svg" width="500" height="220" viewBox="0 0 500 220" font-family="'Segoe UI', Ubuntu, Sans-Serif">
<rect x="0.5" y="0.5" width="499" height="219" rx="8" fill="#282a36" stroke="#282a36" stroke-opacity="1.00"/>
<text x="25" y="35" font-size="18" font-weight="600" fill="#ff79c6">Contributions (last year)</text>
<line x1="35" y1="60" x2="35" y2="180" stroke="#6272a4"/>
<line x1="465" y1="60" x2="465" y2="180" stroke="#6272a4"/>
<line x1="31" y1="180" x2="35" y2="180" stroke="#6272a4"/>
<text x="29" y="183" font-size="10" fill="#6272a4" text-anchor="end">0</text>
<line x1="465" y1="180" x2="469" y2="180" stroke="#6272a4"/>
<text x="471" y="183" font-size="10" fill="#6272a4" text-anchor="start">0</text>
<line x1="31" y1="156" x2="35" y2="156" stroke="#6272a4"/>
<text x="29" y="159" font-size="10" fill="#6272a4" text-anchor="end">100</text>
<line x1="465" y1="156" x2="469" y2="156" stroke="#6272a4"/>
<text x="471" y="159" font-size="10" fill="#6272a4" text-anchor="start">100</text>
<line x1="31" y1="132" x2="35" y2="132" stroke="#6272a4"/>
<text x="29" y="135" font-size="10" fill="#6272a4" text-anchor="end">200</text>
<line x1="465" y1="132" x2="469" y2="132" stroke="#6272a4"/>
<text x="471" y="135" font-size="10" fill="#6272a4" text-anchor="start">200</text>
<line x1="31" y1="108" x2="35" y2="108" stroke="#6272a4"/>
<text x="29" y="111" font-size="10" fill="#6272a4" text-anchor="end">300</text>
<line x1="465" y1="108" x2="469" y2="108" stroke="#6272a4"/>
<text x="471" y="111" font-size="10" fill="#6272a4" text-anchor="start">300</text>
<line x1="31" y1="84" x2="35" y2="84" stroke="#6272a4"/>
<text x="29" y="87" font-size="10" fill="#6272a4" text-anchor="end">400</text>
<line x1="465" y1="84" x2="469" y2="84" stroke="#6272a4"/>
<text x="471" y="87" font-size="10" fill="#6272a4" text-anchor="start">400</text>
<line x1="31" y1="60" x2="35" y2="60" stroke="#6272a4"/>
<text x="29" y="63" font-size="10" fill="#6272a4" text-anchor="end">500</text>
<line x1="465" y1="60" x2="469" y2="60" stroke="#6272a4"/>
<text x="471" y="63" font-size="10" fill="#6272a4" text-anchor="start">500</text>
<line x1="35" y1="180" x2="465" y2="180" stroke="#6272a4"/>
<line x1="35" y1="180" x2="35" y2="184" stroke="#6272a4"/>
<text x="35" y="196" font-size="10" fill="#6272a4" text-anchor="middle">04/25</text>
<line x1="106" y1="180" x2="106" y2="184" stroke="#6272a4"/>
<text x="106" y="196" font-size="10" fill="#6272a4" text-anchor="middle">06/25</text>
<line x1="178" y1="180" x2="178" y2="184" stroke="#6272a4"/>
<text x="178" y="196" font-size="10" fill="#6272a4" text-anchor="middle">08/25</text>
<line x1="250" y1="180" x2="250" y2="184" stroke="#6272a4"/>
<text x="250" y="196" font-size="10" fill="#6272a4" text-anchor="middle">10/25</text>
<line x1="321" y1="180" x2="321" y2="184" stroke="#6272a4"/>
<text x="321" y="196" font-size="10" fill="#6272a4" text-anchor="middle">12/25</text>
<line x1="393" y1="180" x2="393" y2="184" stroke="#6272a4"/>
<text x="393" y="196" font-size="10" fill="#6272a4" text-anchor="middle">02/26</text>
<line x1="465" y1="180" x2="465" y2="184" stroke="#6272a4"/>
<text x="465" y="196" font-size="10" fill="#6272a4" text-anchor="middle">04/26</text>
<path d="M35.00,164.16 C40.97,162.96 58.89,156.12 70.83,156.96 C82.78,157.80 94.72,167.40 106.67,169.20 C118.61,171.00 130.56,168.84 142.50,167.76 C154.44,166.68 166.39,162.20 178.33,162.72 C190.28,163.24 202.22,172.72 214.17,170.88 C226.11,169.04 238.06,160.20 250.00,151.68 C261.94,143.16 273.89,122.00 285.83,119.76 C297.78,117.52 309.72,129.64 321.67,138.24 C333.61,146.84 345.56,168.64 357.50,171.36 C369.44,174.08 381.39,164.40 393.33,154.56 C405.28,144.72 417.22,129.04 429.17,112.32 C441.11,95.60 459.03,63.92 465.00,54.24 L465.00,180.00 L35.00,180.00 Z" fill="#bd93f9" fill-opacity="0.25" stroke="none"/>
<path d="M35.00,164.16 C40.97,162.96 58.89,156.12 70.83,156.96 C82.78,157.80 94.72,167.40 106.67,169.20 C118.61,171.00 130.56,168.84 142.50,167.76 C154.44,166.68 166.39,162.20 178.33,162.72 C190.28,163.24 202.22,172.72 214.17,170.88 C226.11,169.04 238.06,160.20 250.00,151.68 C261.94,143.16 273.89,122.00 285.83,119.76 C297.78,117.52 309.72,129.64 321.67,138.24 C333.61,146.84 345.56,168.64 357.50,171.36 C369.44,174.08 381.39,164.40 393.33,154.56 C405.28,144.72 417.22,129.04 429.17,112.32 C441.11,95.60 459.03,63.92 465.00,54.24" fill="none" stroke="#bd93f9" stroke-width="2"/>
<text x="250" y="214" font-size="11" fill="#6272a4" text-anchor="middle">mm/yy</text>
<svg xmlns="http://www.w3.org/2000/svg" width="340" height="200" viewBox="0 0 340 200" font-family="'Segoe UI', Ubuntu, Sans-Serif">
<rect x="0.5" y="0.5" width="339" height="199" rx="6" fill="#282a36" stroke="#282a36" stroke-opacity="1.00"/>
<text x="20" y="30" font-size="15" font-weight="600" fill="#ff79c6">Contributions (last year)</text>
<line x1="28" y1="45" x2="28" y2="150" stroke="#6272a4"/>
<line x1="312" y1="45" x2="312" y2="150" stroke="#6272a4"/>
<line x1="24" y1="150" x2="28" y2="150" stroke="#6272a4"/>
<text x="22" y="153" font-size="10" fill="#6272a4" text-anchor="end">0</text>
<line x1="312" y1="150" x2="316" y2="150" stroke="#6272a4"/>
<text x="318" y="153" font-size="10" fill="#6272a4" text-anchor="start">0</text>
<line x1="24" y1="129" x2="28" y2="129" stroke="#6272a4"/>
<text x="22" y="132" font-size="10" fill="#6272a4" text-anchor="end">100</text>
<line x1="312" y1="129" x2="316" y2="129" stroke="#6272a4"/>
<text x="318" y="132" font-size="10" fill="#6272a4" text-anchor="start">100</text>
<line x1="24" y1="108" x2="28" y2="108" stroke="#6272a4"/>
<text x="22" y="111" font-size="10" fill="#6272a4" text-anchor="end">200</text>
<line x1="312" y1="108" x2="316" y2="108" stroke="#6272a4"/>
<text x="318" y="111" font-size="10" fill="#6272a4" text-anchor="start">200</text>
<line x1="24" y1="87" x2="28" y2="87" stroke="#6272a4"/>
<text x="22" y="90" font-size="10" fill="#6272a4" text-anchor="end">300</text>
<line x1="312" y1="87" x2="316" y2="87" stroke="#6272a4"/>
<text x="318" y="90" font-size="10" fill="#6272a4" text-anchor="start">300</text>
<line x1="24" y1="66" x2="28" y2="66" stroke="#6272a4"/>
<text x="22" y="69" font-size="10" fill="#6272a4" text-anchor="end">400</text>
<line x1="312" y1="66" x2="316" y2="66" stroke="#6272a4"/>
<text x="318" y="69" font-size="10" fill="#6272a4" text-anchor="start">400</text>
<line x1="24" y1="45" x2="28" y2="45" stroke="#6272a4"/>
<text x="22" y="48" font-size="10" fill="#6272a4" text-anchor="end">500</text>
<line x1="312" y1="45" x2="316" y2="45" stroke="#6272a4"/>
<text x="318" y="48" font-size="10" fill="#6272a4" text-anchor="start">500</text>
<line x1="28" y1="150" x2="312" y2="150" stroke="#6272a4"/>
<line x1="28" y1="150" x2="28" y2="154" stroke="#6272a4"/>
<text x="28" y="166" font-size="10" fill="#6272a4" text-anchor="middle">04/25</text>
<line x1="75" y1="150" x2="75" y2="154" stroke="#6272a4"/>
<text x="75" y="166" font-size="10" fill="#6272a4" text-anchor="middle">06/25</text>
<line x1="122" y1="150" x2="122" y2="154" stroke="#6272a4"/>
<text x="122" y="166" font-size="10" fill="#6272a4" text-anchor="middle">08/25</text>
<line x1="170" y1="150" x2="170" y2="154" stroke="#6272a4"/>
<text x="170" y="166" font-size="10" fill="#6272a4" text-anchor="middle">10/25</text>
<line x1="217" y1="150" x2="217" y2="154" stroke="#6272a4"/>
<text x="217" y="166" font-size="10" fill="#6272a4" text-anchor="middle">12/25</text>
<line x1="264" y1="150" x2="264" y2="154" stroke="#6272a4"/>
<text x="264" y="166" font-size="10" fill="#6272a4" text-anchor="middle">02/26</text>
<line x1="312" y1="150" x2="312" y2="154" stroke="#6272a4"/>
<text x="312" y="166" font-size="10" fill="#6272a4" text-anchor="middle">04/26</text>
<path d="M28.00,136.14 C31.94,135.09 43.78,129.10 51.67,129.84 C59.56,130.58 67.44,138.98 75.33,140.55 C83.22,142.12 91.11,140.23 99.00,139.29 C106.89,138.34 114.78,134.42 122.67,134.88 C130.56,135.34 138.44,143.63 146.33,142.02 C154.22,140.41 162.11,132.68 170.00,125.22 C177.89,117.77 185.78,99.25 193.67,97.29 C201.56,95.33 209.44,105.94 217.33,113.46 C225.22,120.99 233.11,140.06 241.00,142.44 C248.89,144.82 256.78,136.35 264.67,127.74 C272.56,119.13 280.44,105.94 288.33,90.78 C296.22,75.62 308.06,45.80 312.00,36.81 L312.00,150.00 L28.00,150.00 Z" fill="#bd93f9" fill-opacity="0.25" stroke="none"/>
<path d="M28.00,136.14 C31.94,135.09 43.78,129.10 51.67,129.84 C59.56,130.58 67.44,138.98 75.33,140.55 C83.22,142.12 91.11,140.23 99.00,139.29 C106.89,138.34 114.78,134.42 122.67,134.88 C130.56,135.34 138.44,143.63 146.33,142.02 C154.22,140.41 162.11,132.68 170.00,125.22 C177.89,117.77 185.78,99.25 193.67,97.29 C201.56,95.33 209.44,105.94 217.33,113.46 C225.22,120.99 233.11,140.06 241.00,142.44 C248.89,144.82 256.78,136.35 264.67,127.74 C272.56,119.13 280.44,105.94 288.33,90.78 C296.22,75.62 308.06,45.80 312.00,36.81" fill="none" stroke="#bd93f9" stroke-width="2"/>
<text x="170" y="184" font-size="11" fill="#6272a4" text-anchor="middle">mm/yy</text>
</svg>

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

@@ -1,22 +1,19 @@
<svg xmlns="http://www.w3.org/2000/svg" width="500" height="220" viewBox="0 0 500 220" font-family="'Segoe UI', Ubuntu, Sans-Serif">
<rect x="0.5" y="0.5" width="499" height="219" rx="8" fill="#282a36" stroke="#282a36" stroke-opacity="1.00"/>
<text x="25" y="35" font-size="18" font-weight="600" fill="#ff79c6">Most Commit Language (all time)</text>
<rect x="30" y="60" width="12" height="12" fill="#f1e05a" stroke="#282a36" stroke-width="1"/>
<text x="50" y="70" font-size="13" fill="#ffb86c">JavaScript 32.81%</text>
<rect x="30" y="82" width="12" height="12" fill="#e34c26" stroke="#282a36" stroke-width="1"/>
<text x="50" y="92" font-size="13" fill="#ffb86c">HTML 20.44%</text>
<rect x="30" y="104" width="12" height="12" fill="#b07219" stroke="#282a36" stroke-width="1"/>
<text x="50" y="114" font-size="13" fill="#ffb86c">Java 17.44%</text>
<rect x="30" y="126" width="12" height="12" fill="#f34b7d" stroke="#282a36" stroke-width="1"/>
<text x="50" y="136" font-size="13" fill="#ffb86c">C++ 5.35%</text>
<rect x="30" y="148" width="12" height="12" fill="#00ADD8" stroke="#282a36" stroke-width="1"/>
<text x="50" y="158" font-size="13" fill="#ffb86c">Go 5.12%</text>
<rect x="30" y="170" width="12" height="12" fill="#bd93f9" stroke="#282a36" stroke-width="1"/>
<text x="50" y="180" font-size="13" fill="#ffb86c">Other 18.84%</text>
<path d="M380.00,50.00 A70.00,70.00 0 0 1 441.74,152.99 L413.51,137.91 A38.00,38.00 0 0 0 380.00,82.00 Z" fill="#f1e05a" stroke="#282a36" stroke-width="1.5"/>
<path d="M441.74,152.99 A70.00,70.00 0 0 1 365.82,188.55 L372.30,157.21 A38.00,38.00 0 0 0 413.51,137.91 Z" fill="#e34c26" stroke="#282a36" stroke-width="1.5"/>
<path d="M365.82,188.55 A70.00,70.00 0 0 1 312.56,138.75 L343.39,130.18 A38.00,38.00 0 0 0 372.30,157.21 Z" fill="#b07219" stroke="#282a36" stroke-width="1.5"/>
<path d="M312.56,138.75 A70.00,70.00 0 0 1 310.15,115.45 L342.08,117.53 A38.00,38.00 0 0 0 343.39,130.18 Z" fill="#f34b7d" stroke="#282a36" stroke-width="1.5"/>
<path d="M310.15,115.45 A70.00,70.00 0 0 1 315.18,93.58 L344.81,105.66 A38.00,38.00 0 0 0 342.08,117.53 Z" fill="#00ADD8" stroke="#282a36" stroke-width="1.5"/>
<path d="M315.18,93.58 A70.00,70.00 0 0 1 380.00,50.00 L380.00,82.00 A38.00,38.00 0 0 0 344.81,105.66 Z" fill="#bd93f9" stroke="#282a36" stroke-width="1.5"/>
<svg xmlns="http://www.w3.org/2000/svg" width="340" height="200" viewBox="0 0 340 200" font-family="'Segoe UI', Ubuntu, Sans-Serif">
<rect x="0.5" y="0.5" width="339" height="199" rx="6" fill="#282a36" stroke="#282a36" stroke-opacity="1.00"/>
<text x="20" y="30" font-size="15" font-weight="600" fill="#ff79c6">Most Commit Language (all time)</text>
<rect x="20" y="47" width="10" height="10" fill="#f1e05a" stroke="#282a36" stroke-width="1"/>
<text x="36" y="55" font-size="11" fill="#ffb86c">JavaScript 32.66%</text>
<rect x="20" y="67" width="10" height="10" fill="#e34c26" stroke="#282a36" stroke-width="1"/>
<text x="36" y="75" font-size="11" fill="#ffb86c">HTML 20.34%</text>
<rect x="20" y="87" width="10" height="10" fill="#b07219" stroke="#282a36" stroke-width="1"/>
<text x="36" y="95" font-size="11" fill="#ffb86c">Java 17.36%</text>
<rect x="20" y="107" width="10" height="10" fill="#00ADD8" stroke="#282a36" stroke-width="1"/>
<text x="36" y="115" font-size="11" fill="#ffb86c">Go 5.54%</text>
<rect x="20" y="127" width="10" height="10" fill="#bd93f9" stroke="#282a36" stroke-width="1"/>
<text x="36" y="135" font-size="11" fill="#ffb86c">Other 24.09%</text>
<path d="M250.00,55.00 A55.00,55.00 0 0 1 298.75,135.47 L276.59,123.89 A30.00,30.00 0 0 0 250.00,80.00 Z" fill="#f1e05a" stroke="#282a36" stroke-width="1.5"/>
<path d="M298.75,135.47 A55.00,55.00 0 0 1 239.68,164.02 L244.37,139.47 A30.00,30.00 0 0 0 276.59,123.89 Z" fill="#e34c26" stroke="#282a36" stroke-width="1.5"/>
<path d="M239.68,164.02 A55.00,55.00 0 0 1 197.32,125.80 L221.26,118.62 A30.00,30.00 0 0 0 244.37,139.47 Z" fill="#b07219" stroke="#282a36" stroke-width="1.5"/>
<path d="M197.32,125.80 A55.00,55.00 0 0 1 195.09,106.87 L220.05,108.29 A30.00,30.00 0 0 0 221.26,118.62 Z" fill="#00ADD8" stroke="#282a36" stroke-width="1.5"/>
<path d="M195.09,106.87 A55.00,55.00 0 0 1 250.00,55.00 L250.00,80.00 A30.00,30.00 0 0 0 220.05,108.29 Z" fill="#bd93f9" stroke="#282a36" stroke-width="1.5"/>
</svg>

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

+18 -21
View File
@@ -1,22 +1,19 @@
<svg xmlns="http://www.w3.org/2000/svg" width="500" height="220" viewBox="0 0 500 220" font-family="'Segoe UI', Ubuntu, Sans-Serif">
<rect x="0.5" y="0.5" width="499" height="219" rx="8" fill="#282a36" stroke="#282a36" stroke-opacity="1.00"/>
<text x="25" y="35" font-size="18" font-weight="600" fill="#ff79c6">Most Commit Language (last year)</text>
<rect x="30" y="60" width="12" height="12" fill="#f1e05a" stroke="#282a36" stroke-width="1"/>
<text x="50" y="70" font-size="13" fill="#ffb86c">JavaScript 48.92%</text>
<rect x="30" y="82" width="12" height="12" fill="#e34c26" stroke="#282a36" stroke-width="1"/>
<text x="50" y="92" font-size="13" fill="#ffb86c">HTML 11.18%</text>
<rect x="30" y="104" width="12" height="12" fill="#00ADD8" stroke="#282a36" stroke-width="1"/>
<text x="50" y="114" font-size="13" fill="#ffb86c">Go 9.11%</text>
<rect x="30" y="126" width="12" height="12" fill="#b07219" stroke="#282a36" stroke-width="1"/>
<text x="50" y="136" font-size="13" fill="#ffb86c">Java 7.92%</text>
<rect x="30" y="148" width="12" height="12" fill="#384d54" stroke="#282a36" stroke-width="1"/>
<text x="50" y="158" font-size="13" fill="#ffb86c">Dockerfile 3.92%</text>
<rect x="30" y="170" width="12" height="12" fill="#bd93f9" stroke="#282a36" stroke-width="1"/>
<text x="50" y="180" font-size="13" fill="#ffb86c">Other 18.95%</text>
<path d="M380.00,50.00 A70.00,70.00 0 0 1 384.75,189.84 L382.58,157.91 A38.00,38.00 0 0 0 380.00,82.00 Z" fill="#f1e05a" stroke="#282a36" stroke-width="1.5"/>
<path d="M384.75,189.84 A70.00,70.00 0 0 1 338.50,176.38 L357.47,150.60 A38.00,38.00 0 0 0 382.58,157.91 Z" fill="#e34c26" stroke="#282a36" stroke-width="1.5"/>
<path d="M338.50,176.38 A70.00,70.00 0 0 1 314.58,144.91 L344.49,133.52 A38.00,38.00 0 0 0 357.47,150.60 Z" fill="#00ADD8" stroke="#282a36" stroke-width="1.5"/>
<path d="M314.58,144.91 A70.00,70.00 0 0 1 310.63,110.64 L342.34,114.92 A38.00,38.00 0 0 0 344.49,133.52 Z" fill="#b07219" stroke="#282a36" stroke-width="1.5"/>
<path d="M310.63,110.64 A70.00,70.00 0 0 1 315.00,94.02 L344.71,105.90 A38.00,38.00 0 0 0 342.34,114.92 Z" fill="#384d54" stroke="#282a36" stroke-width="1.5"/>
<path d="M315.00,94.02 A70.00,70.00 0 0 1 380.00,50.00 L380.00,82.00 A38.00,38.00 0 0 0 344.71,105.90 Z" fill="#bd93f9" stroke="#282a36" stroke-width="1.5"/>
<svg xmlns="http://www.w3.org/2000/svg" width="340" height="200" viewBox="0 0 340 200" font-family="'Segoe UI', Ubuntu, Sans-Serif">
<rect x="0.5" y="0.5" width="339" height="199" rx="6" fill="#282a36" stroke="#282a36" stroke-opacity="1.00"/>
<text x="20" y="30" font-size="15" font-weight="600" fill="#ff79c6">Most Commit Language (last year)</text>
<rect x="20" y="47" width="10" height="10" fill="#f1e05a" stroke="#282a36" stroke-width="1"/>
<text x="36" y="55" font-size="11" fill="#ffb86c">JavaScript 48.50%</text>
<rect x="20" y="67" width="10" height="10" fill="#e34c26" stroke="#282a36" stroke-width="1"/>
<text x="36" y="75" font-size="11" fill="#ffb86c">HTML 11.08%</text>
<rect x="20" y="87" width="10" height="10" fill="#00ADD8" stroke="#282a36" stroke-width="1"/>
<text x="36" y="95" font-size="11" fill="#ffb86c">Go 9.87%</text>
<rect x="20" y="107" width="10" height="10" fill="#b07219" stroke="#282a36" stroke-width="1"/>
<text x="36" y="115" font-size="11" fill="#ffb86c">Java 7.86%</text>
<rect x="20" y="127" width="10" height="10" fill="#bd93f9" stroke="#282a36" stroke-width="1"/>
<text x="36" y="135" font-size="11" fill="#ffb86c">Other 22.69%</text>
<path d="M250.00,55.00 A55.00,55.00 0 0 1 255.18,164.76 L252.82,139.87 A30.00,30.00 0 0 0 250.00,80.00 Z" fill="#f1e05a" stroke="#282a36" stroke-width="1.5"/>
<path d="M255.18,164.76 A55.00,55.00 0 0 1 218.85,155.33 L233.01,134.72 A30.00,30.00 0 0 0 252.82,139.87 Z" fill="#e34c26" stroke="#282a36" stroke-width="1.5"/>
<path d="M218.85,155.33 A55.00,55.00 0 0 1 198.31,128.79 L221.81,120.25 A30.00,30.00 0 0 0 233.01,134.72 Z" fill="#00ADD8" stroke="#282a36" stroke-width="1.5"/>
<path d="M198.31,128.79 A55.00,55.00 0 0 1 195.58,102.06 L220.31,105.67 A30.00,30.00 0 0 0 221.81,120.25 Z" fill="#b07219" stroke="#282a36" stroke-width="1.5"/>
<path d="M195.58,102.06 A55.00,55.00 0 0 1 250.00,55.00 L250.00,80.00 A30.00,30.00 0 0 0 220.31,105.67 Z" fill="#bd93f9" stroke="#282a36" stroke-width="1.5"/>
</svg>

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

+54 -54
View File
@@ -1,55 +1,55 @@
<svg xmlns="http://www.w3.org/2000/svg" width="500" height="220" viewBox="0 0 500 220" font-family="'Segoe UI', Ubuntu, Sans-Serif">
<rect x="0.5" y="0.5" width="499" height="219" rx="8" fill="#282a36" stroke="#282a36" stroke-opacity="1.00"/>
<text x="25" y="35" font-size="18" font-weight="600" fill="#ff79c6">Commits by Hour (all time, UTC+7.00)</text>
<line x1="50" y1="60" x2="50" y2="170" stroke="#6272a4"/>
<line x1="46" y1="170" x2="50" y2="170" stroke="#6272a4"/>
<text x="44" y="173" font-size="10" fill="#6272a4" text-anchor="end">0</text>
<line x1="46" y1="152" x2="50" y2="152" stroke="#6272a4"/>
<text x="44" y="155" font-size="10" fill="#6272a4" text-anchor="end">50</text>
<line x1="46" y1="134" x2="50" y2="134" stroke="#6272a4"/>
<text x="44" y="137" font-size="10" fill="#6272a4" text-anchor="end">100</text>
<line x1="46" y1="115" x2="50" y2="115" stroke="#6272a4"/>
<text x="44" y="118" font-size="10" fill="#6272a4" text-anchor="end">150</text>
<line x1="46" y1="97" x2="50" y2="97" stroke="#6272a4"/>
<text x="44" y="100" font-size="10" fill="#6272a4" text-anchor="end">200</text>
<line x1="46" y1="79" x2="50" y2="79" stroke="#6272a4"/>
<text x="44" y="82" font-size="10" fill="#6272a4" text-anchor="end">250</text>
<line x1="46" y1="60" x2="50" y2="60" stroke="#6272a4"/>
<text x="44" y="63" font-size="10" fill="#6272a4" text-anchor="end">300</text>
<line x1="50" y1="170" x2="475" y2="170" stroke="#6272a4"/>
<line x1="57" y1="170" x2="57" y2="174" stroke="#6272a4"/>
<text x="57" y="186" font-size="10" fill="#6272a4" text-anchor="middle">0</text>
<line x1="164" y1="170" x2="164" y2="174" stroke="#6272a4"/>
<text x="164" y="186" font-size="10" fill="#6272a4" text-anchor="middle">6</text>
<line x1="271" y1="170" x2="271" y2="174" stroke="#6272a4"/>
<text x="271" y="186" font-size="10" fill="#6272a4" text-anchor="middle">12</text>
<line x1="378" y1="170" x2="378" y2="174" stroke="#6272a4"/>
<text x="378" y="186" font-size="10" fill="#6272a4" text-anchor="middle">18</text>
<line x1="467" y1="170" x2="467" y2="174" stroke="#6272a4"/>
<text x="467" y="186" font-size="10" fill="#6272a4" text-anchor="middle">23</text>
<rect x="50.00" y="117.20" width="15.79" height="52.80" rx="2" fill="#bd93f9"><title>00:00 — 144 commits</title></rect>
<rect x="67.79" y="149.47" width="15.79" height="20.53" rx="2" fill="#bd93f9"><title>01:00 — 56 commits</title></rect>
<rect x="85.58" y="165.60" width="15.79" height="4.40" rx="2" fill="#bd93f9"><title>02:00 — 12 commits</title></rect>
<rect x="103.38" y="165.97" width="15.79" height="4.03" rx="2" fill="#bd93f9"><title>03:00 — 11 commits</title></rect>
<rect x="121.17" y="166.70" width="15.79" height="3.30" rx="2" fill="#bd93f9"><title>04:00 — 9 commits</title></rect>
<rect x="138.96" y="167.43" width="15.79" height="2.57" rx="2" fill="#bd93f9"><title>05:00 — 7 commits</title></rect>
<rect x="156.75" y="167.43" width="15.79" height="2.57" rx="2" fill="#bd93f9"><title>06:00 — 7 commits</title></rect>
<rect x="174.54" y="167.43" width="15.79" height="2.57" rx="2" fill="#bd93f9"><title>07:00 — 7 commits</title></rect>
<rect x="192.33" y="154.97" width="15.79" height="15.03" rx="2" fill="#bd93f9"><title>08:00 — 41 commits</title></rect>
<rect x="210.12" y="136.27" width="15.79" height="33.73" rx="2" fill="#bd93f9"><title>09:00 — 92 commits</title></rect>
<rect x="227.92" y="117.93" width="15.79" height="52.07" rx="2" fill="#bd93f9"><title>10:00 — 142 commits</title></rect>
<rect x="245.71" y="109.87" width="15.79" height="60.13" rx="2" fill="#bd93f9"><title>11:00 — 164 commits</title></rect>
<rect x="263.50" y="124.17" width="15.79" height="45.83" rx="2" fill="#bd93f9"><title>12:00 — 125 commits</title></rect>
<rect x="281.29" y="126.37" width="15.79" height="43.63" rx="2" fill="#bd93f9"><title>13:00 — 119 commits</title></rect>
<rect x="299.08" y="124.53" width="15.79" height="45.47" rx="2" fill="#bd93f9"><title>14:00 — 124 commits</title></rect>
<rect x="316.88" y="142.13" width="15.79" height="27.87" rx="2" fill="#bd93f9"><title>15:00 — 76 commits</title></rect>
<rect x="334.67" y="137.37" width="15.79" height="32.63" rx="2" fill="#bd93f9"><title>16:00 — 89 commits</title></rect>
<rect x="352.46" y="134.80" width="15.79" height="35.20" rx="2" fill="#bd93f9"><title>17:00 — 96 commits</title></rect>
<rect x="370.25" y="132.60" width="15.79" height="37.40" rx="2" fill="#bd93f9"><title>18:00 — 102 commits</title></rect>
<rect x="388.04" y="138.10" width="15.79" height="31.90" rx="2" fill="#bd93f9"><title>19:00 — 87 commits</title></rect>
<rect x="405.83" y="86.40" width="15.79" height="83.60" rx="2" fill="#bd93f9"><title>20:00 — 228 commits</title></rect>
<rect x="423.62" y="54.87" width="15.79" height="115.13" rx="2" fill="#bd93f9"><title>21:00 — 314 commits</title></rect>
<rect x="441.42" y="74.30" width="15.79" height="95.70" rx="2" fill="#bd93f9"><title>22:00 — 261 commits</title></rect>
<rect x="459.21" y="82.00" width="15.79" height="88.00" rx="2" fill="#bd93f9"><title>23:00 — 240 commits</title></rect>
<text x="262" y="204" font-size="11" fill="#6272a4" text-anchor="middle">hour of day</text>
<svg xmlns="http://www.w3.org/2000/svg" width="340" height="200" viewBox="0 0 340 200" font-family="'Segoe UI', Ubuntu, Sans-Serif">
<rect x="0.5" y="0.5" width="339" height="199" rx="6" fill="#282a36" stroke="#282a36" stroke-opacity="1.00"/>
<text x="20" y="30" font-size="15" font-weight="600" fill="#ff79c6">Commits by Hour (all time, UTC+7.00)</text>
<line x1="35" y1="45" x2="35" y2="155" stroke="#6272a4"/>
<line x1="31" y1="155" x2="35" y2="155" stroke="#6272a4"/>
<text x="29" y="158" font-size="10" fill="#6272a4" text-anchor="end">0</text>
<line x1="31" y1="137" x2="35" y2="137" stroke="#6272a4"/>
<text x="29" y="140" font-size="10" fill="#6272a4" text-anchor="end">50</text>
<line x1="31" y1="119" x2="35" y2="119" stroke="#6272a4"/>
<text x="29" y="122" font-size="10" fill="#6272a4" text-anchor="end">100</text>
<line x1="31" y1="100" x2="35" y2="100" stroke="#6272a4"/>
<text x="29" y="103" font-size="10" fill="#6272a4" text-anchor="end">150</text>
<line x1="31" y1="82" x2="35" y2="82" stroke="#6272a4"/>
<text x="29" y="85" font-size="10" fill="#6272a4" text-anchor="end">200</text>
<line x1="31" y1="64" x2="35" y2="64" stroke="#6272a4"/>
<text x="29" y="67" font-size="10" fill="#6272a4" text-anchor="end">250</text>
<line x1="31" y1="45" x2="35" y2="45" stroke="#6272a4"/>
<text x="29" y="48" font-size="10" fill="#6272a4" text-anchor="end">300</text>
<line x1="35" y1="155" x2="325" y2="155" stroke="#6272a4"/>
<line x1="40" y1="155" x2="40" y2="159" stroke="#6272a4"/>
<text x="40" y="171" font-size="10" fill="#6272a4" text-anchor="middle">0</text>
<line x1="113" y1="155" x2="113" y2="159" stroke="#6272a4"/>
<text x="113" y="171" font-size="10" fill="#6272a4" text-anchor="middle">6</text>
<line x1="186" y1="155" x2="186" y2="159" stroke="#6272a4"/>
<text x="186" y="171" font-size="10" fill="#6272a4" text-anchor="middle">12</text>
<line x1="258" y1="155" x2="258" y2="159" stroke="#6272a4"/>
<text x="258" y="171" font-size="10" fill="#6272a4" text-anchor="middle">18</text>
<line x1="319" y1="155" x2="319" y2="159" stroke="#6272a4"/>
<text x="319" y="171" font-size="10" fill="#6272a4" text-anchor="middle">23</text>
<rect x="35.00" y="102.20" width="11.12" height="52.80" rx="2" fill="#bd93f9"><title>00:00 — 144 commits</title></rect>
<rect x="47.12" y="134.47" width="11.12" height="20.53" rx="2" fill="#bd93f9"><title>01:00 — 56 commits</title></rect>
<rect x="59.25" y="150.60" width="11.12" height="4.40" rx="2" fill="#bd93f9"><title>02:00 — 12 commits</title></rect>
<rect x="71.38" y="150.97" width="11.12" height="4.03" rx="2" fill="#bd93f9"><title>03:00 — 11 commits</title></rect>
<rect x="83.50" y="151.70" width="11.12" height="3.30" rx="2" fill="#bd93f9"><title>04:00 — 9 commits</title></rect>
<rect x="95.62" y="152.43" width="11.12" height="2.57" rx="2" fill="#bd93f9"><title>05:00 — 7 commits</title></rect>
<rect x="107.75" y="152.43" width="11.12" height="2.57" rx="2" fill="#bd93f9"><title>06:00 — 7 commits</title></rect>
<rect x="119.88" y="152.43" width="11.12" height="2.57" rx="2" fill="#bd93f9"><title>07:00 — 7 commits</title></rect>
<rect x="132.00" y="139.97" width="11.12" height="15.03" rx="2" fill="#bd93f9"><title>08:00 — 41 commits</title></rect>
<rect x="144.12" y="121.27" width="11.12" height="33.73" rx="2" fill="#bd93f9"><title>09:00 — 92 commits</title></rect>
<rect x="156.25" y="102.93" width="11.12" height="52.07" rx="2" fill="#bd93f9"><title>10:00 — 142 commits</title></rect>
<rect x="168.38" y="94.87" width="11.12" height="60.13" rx="2" fill="#bd93f9"><title>11:00 — 164 commits</title></rect>
<rect x="180.50" y="109.17" width="11.12" height="45.83" rx="2" fill="#bd93f9"><title>12:00 — 125 commits</title></rect>
<rect x="192.62" y="111.37" width="11.12" height="43.63" rx="2" fill="#bd93f9"><title>13:00 — 119 commits</title></rect>
<rect x="204.75" y="109.53" width="11.12" height="45.47" rx="2" fill="#bd93f9"><title>14:00 — 124 commits</title></rect>
<rect x="216.88" y="127.13" width="11.12" height="27.87" rx="2" fill="#bd93f9"><title>15:00 — 76 commits</title></rect>
<rect x="229.00" y="122.37" width="11.12" height="32.63" rx="2" fill="#bd93f9"><title>16:00 — 89 commits</title></rect>
<rect x="241.12" y="119.80" width="11.12" height="35.20" rx="2" fill="#bd93f9"><title>17:00 — 96 commits</title></rect>
<rect x="253.25" y="117.60" width="11.12" height="37.40" rx="2" fill="#bd93f9"><title>18:00 — 102 commits</title></rect>
<rect x="265.38" y="123.10" width="11.12" height="31.90" rx="2" fill="#bd93f9"><title>19:00 — 87 commits</title></rect>
<rect x="277.50" y="71.40" width="11.12" height="83.60" rx="2" fill="#bd93f9"><title>20:00 — 228 commits</title></rect>
<rect x="289.62" y="39.87" width="11.12" height="115.13" rx="2" fill="#bd93f9"><title>21:00 — 314 commits</title></rect>
<rect x="301.75" y="55.27" width="11.12" height="99.73" rx="2" fill="#bd93f9"><title>22:00 — 272 commits</title></rect>
<rect x="313.88" y="65.53" width="11.12" height="89.47" rx="2" fill="#bd93f9"><title>23:00 — 244 commits</title></rect>
<text x="180" y="189" font-size="11" fill="#6272a4" text-anchor="middle">hour of day</text>
</svg>

Before

Width:  |  Height:  |  Size: 5.2 KiB

After

Width:  |  Height:  |  Size: 5.1 KiB

+48 -48
View File
@@ -1,49 +1,49 @@
<svg xmlns="http://www.w3.org/2000/svg" width="500" height="220" viewBox="0 0 500 220" font-family="'Segoe UI', Ubuntu, Sans-Serif">
<rect x="0.5" y="0.5" width="499" height="219" rx="8" fill="#282a36" stroke="#282a36" stroke-opacity="1.00"/>
<text x="25" y="35" font-size="18" font-weight="600" fill="#ff79c6">Commits by Hour (last year, UTC+7.00)</text>
<line x1="50" y1="60" x2="50" y2="170" stroke="#6272a4"/>
<line x1="46" y1="170" x2="50" y2="170" stroke="#6272a4"/>
<text x="44" y="173" font-size="10" fill="#6272a4" text-anchor="end">0</text>
<line x1="46" y1="134" x2="50" y2="134" stroke="#6272a4"/>
<text x="44" y="137" font-size="10" fill="#6272a4" text-anchor="end">50</text>
<line x1="46" y1="97" x2="50" y2="97" stroke="#6272a4"/>
<text x="44" y="100" font-size="10" fill="#6272a4" text-anchor="end">100</text>
<line x1="46" y1="60" x2="50" y2="60" stroke="#6272a4"/>
<text x="44" y="63" font-size="10" fill="#6272a4" text-anchor="end">150</text>
<line x1="50" y1="170" x2="475" y2="170" stroke="#6272a4"/>
<line x1="57" y1="170" x2="57" y2="174" stroke="#6272a4"/>
<text x="57" y="186" font-size="10" fill="#6272a4" text-anchor="middle">0</text>
<line x1="164" y1="170" x2="164" y2="174" stroke="#6272a4"/>
<text x="164" y="186" font-size="10" fill="#6272a4" text-anchor="middle">6</text>
<line x1="271" y1="170" x2="271" y2="174" stroke="#6272a4"/>
<text x="271" y="186" font-size="10" fill="#6272a4" text-anchor="middle">12</text>
<line x1="378" y1="170" x2="378" y2="174" stroke="#6272a4"/>
<text x="378" y="186" font-size="10" fill="#6272a4" text-anchor="middle">18</text>
<line x1="467" y1="170" x2="467" y2="174" stroke="#6272a4"/>
<text x="467" y="186" font-size="10" fill="#6272a4" text-anchor="middle">23</text>
<rect x="50.00" y="113.53" width="15.79" height="56.47" rx="2" fill="#bd93f9"><title>00:00 — 77 commits</title></rect>
<rect x="67.79" y="150.20" width="15.79" height="19.80" rx="2" fill="#bd93f9"><title>01:00 — 27 commits</title></rect>
<rect x="85.58" y="169.27" width="15.79" height="0.73" rx="2" fill="#bd93f9"><title>02:00 — 1 commits</title></rect>
<rect x="103.38" y="167.07" width="15.79" height="2.93" rx="2" fill="#bd93f9"><title>03:00 — 4 commits</title></rect>
<rect x="121.17" y="170.00" width="15.79" height="0.00" rx="2" fill="#bd93f9"><title>04:00 — 0 commits</title></rect>
<rect x="138.96" y="170.00" width="15.79" height="0.00" rx="2" fill="#bd93f9"><title>05:00 — 0 commits</title></rect>
<rect x="156.75" y="168.53" width="15.79" height="1.47" rx="2" fill="#bd93f9"><title>06:00 — 2 commits</title></rect>
<rect x="174.54" y="170.00" width="15.79" height="0.00" rx="2" fill="#bd93f9"><title>07:00 — 0 commits</title></rect>
<rect x="192.33" y="148.73" width="15.79" height="21.27" rx="2" fill="#bd93f9"><title>08:00 — 29 commits</title></rect>
<rect x="210.12" y="117.20" width="15.79" height="52.80" rx="2" fill="#bd93f9"><title>09:00 — 72 commits</title></rect>
<rect x="227.92" y="104.73" width="15.79" height="65.27" rx="2" fill="#bd93f9"><title>10:00 — 89 commits</title></rect>
<rect x="245.71" y="111.33" width="15.79" height="58.67" rx="2" fill="#bd93f9"><title>11:00 — 80 commits</title></rect>
<rect x="263.50" y="131.13" width="15.79" height="38.87" rx="2" fill="#bd93f9"><title>12:00 — 53 commits</title></rect>
<rect x="281.29" y="122.33" width="15.79" height="47.67" rx="2" fill="#bd93f9"><title>13:00 — 65 commits</title></rect>
<rect x="299.08" y="128.93" width="15.79" height="41.07" rx="2" fill="#bd93f9"><title>14:00 — 56 commits</title></rect>
<rect x="316.88" y="137.00" width="15.79" height="33.00" rx="2" fill="#bd93f9"><title>15:00 — 45 commits</title></rect>
<rect x="334.67" y="131.87" width="15.79" height="38.13" rx="2" fill="#bd93f9"><title>16:00 — 52 commits</title></rect>
<rect x="352.46" y="128.20" width="15.79" height="41.80" rx="2" fill="#bd93f9"><title>17:00 — 57 commits</title></rect>
<rect x="370.25" y="138.47" width="15.79" height="31.53" rx="2" fill="#bd93f9"><title>18:00 — 43 commits</title></rect>
<rect x="388.04" y="144.33" width="15.79" height="25.67" rx="2" fill="#bd93f9"><title>19:00 — 35 commits</title></rect>
<rect x="405.83" y="82.00" width="15.79" height="88.00" rx="2" fill="#bd93f9"><title>20:00 — 120 commits</title></rect>
<rect x="423.62" y="62.93" width="15.79" height="107.07" rx="2" fill="#bd93f9"><title>21:00 — 146 commits</title></rect>
<rect x="441.42" y="51.93" width="15.79" height="118.07" rx="2" fill="#bd93f9"><title>22:00 — 161 commits</title></rect>
<rect x="459.21" y="66.60" width="15.79" height="103.40" rx="2" fill="#bd93f9"><title>23:00 — 141 commits</title></rect>
<text x="262" y="204" font-size="11" fill="#6272a4" text-anchor="middle">hour of day</text>
<svg xmlns="http://www.w3.org/2000/svg" width="340" height="200" viewBox="0 0 340 200" font-family="'Segoe UI', Ubuntu, Sans-Serif">
<rect x="0.5" y="0.5" width="339" height="199" rx="6" fill="#282a36" stroke="#282a36" stroke-opacity="1.00"/>
<text x="20" y="30" font-size="15" font-weight="600" fill="#ff79c6">Commits by Hour (last year, UTC+7.00)</text>
<line x1="35" y1="45" x2="35" y2="155" stroke="#6272a4"/>
<line x1="31" y1="155" x2="35" y2="155" stroke="#6272a4"/>
<text x="29" y="158" font-size="10" fill="#6272a4" text-anchor="end">0</text>
<line x1="31" y1="119" x2="35" y2="119" stroke="#6272a4"/>
<text x="29" y="122" font-size="10" fill="#6272a4" text-anchor="end">50</text>
<line x1="31" y1="82" x2="35" y2="82" stroke="#6272a4"/>
<text x="29" y="85" font-size="10" fill="#6272a4" text-anchor="end">100</text>
<line x1="31" y1="45" x2="35" y2="45" stroke="#6272a4"/>
<text x="29" y="48" font-size="10" fill="#6272a4" text-anchor="end">150</text>
<line x1="35" y1="155" x2="325" y2="155" stroke="#6272a4"/>
<line x1="40" y1="155" x2="40" y2="159" stroke="#6272a4"/>
<text x="40" y="171" font-size="10" fill="#6272a4" text-anchor="middle">0</text>
<line x1="113" y1="155" x2="113" y2="159" stroke="#6272a4"/>
<text x="113" y="171" font-size="10" fill="#6272a4" text-anchor="middle">6</text>
<line x1="186" y1="155" x2="186" y2="159" stroke="#6272a4"/>
<text x="186" y="171" font-size="10" fill="#6272a4" text-anchor="middle">12</text>
<line x1="258" y1="155" x2="258" y2="159" stroke="#6272a4"/>
<text x="258" y="171" font-size="10" fill="#6272a4" text-anchor="middle">18</text>
<line x1="319" y1="155" x2="319" y2="159" stroke="#6272a4"/>
<text x="319" y="171" font-size="10" fill="#6272a4" text-anchor="middle">23</text>
<rect x="35.00" y="98.53" width="11.12" height="56.47" rx="2" fill="#bd93f9"><title>00:00 — 77 commits</title></rect>
<rect x="47.12" y="135.20" width="11.12" height="19.80" rx="2" fill="#bd93f9"><title>01:00 — 27 commits</title></rect>
<rect x="59.25" y="154.27" width="11.12" height="0.73" rx="2" fill="#bd93f9"><title>02:00 — 1 commits</title></rect>
<rect x="71.38" y="152.07" width="11.12" height="2.93" rx="2" fill="#bd93f9"><title>03:00 — 4 commits</title></rect>
<rect x="83.50" y="155.00" width="11.12" height="0.00" rx="2" fill="#bd93f9"><title>04:00 — 0 commits</title></rect>
<rect x="95.62" y="155.00" width="11.12" height="0.00" rx="2" fill="#bd93f9"><title>05:00 — 0 commits</title></rect>
<rect x="107.75" y="153.53" width="11.12" height="1.47" rx="2" fill="#bd93f9"><title>06:00 — 2 commits</title></rect>
<rect x="119.88" y="155.00" width="11.12" height="0.00" rx="2" fill="#bd93f9"><title>07:00 — 0 commits</title></rect>
<rect x="132.00" y="133.73" width="11.12" height="21.27" rx="2" fill="#bd93f9"><title>08:00 — 29 commits</title></rect>
<rect x="144.12" y="102.20" width="11.12" height="52.80" rx="2" fill="#bd93f9"><title>09:00 — 72 commits</title></rect>
<rect x="156.25" y="89.73" width="11.12" height="65.27" rx="2" fill="#bd93f9"><title>10:00 — 89 commits</title></rect>
<rect x="168.38" y="96.33" width="11.12" height="58.67" rx="2" fill="#bd93f9"><title>11:00 — 80 commits</title></rect>
<rect x="180.50" y="116.13" width="11.12" height="38.87" rx="2" fill="#bd93f9"><title>12:00 — 53 commits</title></rect>
<rect x="192.62" y="107.33" width="11.12" height="47.67" rx="2" fill="#bd93f9"><title>13:00 — 65 commits</title></rect>
<rect x="204.75" y="113.93" width="11.12" height="41.07" rx="2" fill="#bd93f9"><title>14:00 — 56 commits</title></rect>
<rect x="216.88" y="122.00" width="11.12" height="33.00" rx="2" fill="#bd93f9"><title>15:00 — 45 commits</title></rect>
<rect x="229.00" y="116.87" width="11.12" height="38.13" rx="2" fill="#bd93f9"><title>16:00 — 52 commits</title></rect>
<rect x="241.12" y="113.20" width="11.12" height="41.80" rx="2" fill="#bd93f9"><title>17:00 — 57 commits</title></rect>
<rect x="253.25" y="123.47" width="11.12" height="31.53" rx="2" fill="#bd93f9"><title>18:00 — 43 commits</title></rect>
<rect x="265.38" y="129.33" width="11.12" height="25.67" rx="2" fill="#bd93f9"><title>19:00 — 35 commits</title></rect>
<rect x="277.50" y="67.00" width="11.12" height="88.00" rx="2" fill="#bd93f9"><title>20:00 — 120 commits</title></rect>
<rect x="289.62" y="47.93" width="11.12" height="107.07" rx="2" fill="#bd93f9"><title>21:00 — 146 commits</title></rect>
<rect x="301.75" y="28.87" width="11.12" height="126.13" rx="2" fill="#bd93f9"><title>22:00 — 172 commits</title></rect>
<rect x="313.88" y="48.67" width="11.12" height="106.33" rx="2" fill="#bd93f9"><title>23:00 — 145 commits</title></rect>
<text x="180" y="189" font-size="11" fill="#6272a4" text-anchor="middle">hour of day</text>
</svg>

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 4.7 KiB

+15 -15
View File
@@ -1,16 +1,16 @@
<svg xmlns="http://www.w3.org/2000/svg" width="500" height="220" viewBox="0 0 500 220" font-family="'Segoe UI', Ubuntu, Sans-Serif">
<rect x="0.5" y="0.5" width="499" height="219" rx="8" fill="#282a36" stroke="#282a36" stroke-opacity="1.00"/>
<text x="25" y="35" font-size="18" font-weight="600" fill="#ff79c6">tiennm99 (Tien Nguyen Minh)</text>
<g transform="translate(25,58.00) scale(0.875)" fill="#6272a4"><path fill-rule="evenodd" d="M1.5 14.25c0 .138.112.25.25.25H4v-1.25a.75.75 0 01.75-.75h2.5a.75.75 0 01.75.75v1.25h2.25a.25.25 0 00.25-.25V1.75a.25.25 0 00-.25-.25h-8.5a.25.25 0 00-.25.25v12.5zM1.75 16A1.75 1.75 0 010 14.25V1.75C0 .784.784 0 1.75 0h8.5C11.216 0 12 .784 12 1.75v12.5c0 .085-.006.168-.018.25h2.268a.25.25 0 00.25-.25V8.285a.25.25 0 00-.111-.208l-1.055-.703a.75.75 0 11.832-1.248l1.055.703c.487.325.779.871.779 1.456v5.965A1.75 1.75 0 0114.25 16h-3.5a.75.75 0 01-.197-.026c-.099.017-.2.026-.303.026h-3a.75.75 0 01-.75-.75V14h-1v1.25a.75.75 0 01-.75.75h-3zM3 3.75A.75.75 0 013.75 3h.5a.75.75 0 010 1.5h-.5A.75.75 0 013 3.75zM3.75 6a.75.75 0 000 1.5h.5a.75.75 0 000-1.5h-.5zM3 9.75A.75.75 0 013.75 9h.5a.75.75 0 010 1.5h-.5A.75.75 0 013 9.75zM7.75 9a.75.75 0 000 1.5h.5a.75.75 0 000-1.5h-.5zM7 6.75A.75.75 0 017.75 6h.5a.75.75 0 010 1.5h-.5A.75.75 0 017 6.75zM7.75 3a.75.75 0 000 1.5h.5a.75.75 0 000-1.5h-.5z"/></g>
<text x="49" y="70" font-size="13" fill="#ffb86c">VNG Corporation</text>
<g transform="translate(25,82.00) scale(0.875)" fill="#6272a4"><path fill-rule="evenodd" d="M11.536 3.464a5 5 0 010 7.072L8 14.07l-3.536-3.535a5 5 0 117.072-7.072v.001zm1.06 8.132a6.5 6.5 0 10-9.192 0l3.535 3.536a1.5 1.5 0 002.122 0l3.535-3.536zM8 9a2 2 0 100-4 2 2 0 000 4z"/></g>
<text x="49" y="94" font-size="13" fill="#ffb86c">Ho Chi Minh - Viet Nam</text>
<g transform="translate(25,106.00) scale(0.875)" fill="#6272a4"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"/></g>
<text x="49" y="118" font-size="13" fill="#ffb86c">miti99.com</text>
<g transform="translate(25,130.00) scale(0.875)" fill="#6272a4"><path fill-rule="evenodd" d="M1.5 8a6.5 6.5 0 1113 0 6.5 6.5 0 01-13 0zM8 0a8 8 0 100 16A8 8 0 008 0zm.5 4.75a.75.75 0 00-1.5 0v3.5a.75.75 0 00.471.696l2.5 1a.75.75 0 00.557-1.392L8.5 7.742V4.75z"/></g>
<text x="49" y="142" font-size="13" fill="#ffb86c">2018-05-07 (7 years ago)</text>
<g transform="translate(25,154.00) scale(0.875)" fill="#6272a4"><path fill-rule="evenodd" d="M5.5 3.5a2 2 0 100 4 2 2 0 000-4zM2 5.5a3.5 3.5 0 115.898 2.549 5.507 5.507 0 013.034 4.084.75.75 0 11-1.482.235 4.001 4.001 0 00-7.9 0 .75.75 0 01-1.482-.236A5.507 5.507 0 013.102 8.05 3.49 3.49 0 012 5.5zM11 4a.75.75 0 100 1.5 1.5 1.5 0 01.666 2.844.75.75 0 00-.416.672v.352a.75.75 0 00.574.73c1.2.289 2.162 1.2 2.522 2.372a.75.75 0 101.434-.44 5.01 5.01 0 00-2.56-3.012A3 3 0 0011 4z"/></g>
<text x="49" y="166" font-size="13" fill="#ffb86c">10 followers · 14 following</text>
<g transform="translate(25,178.00) scale(0.875)" fill="#6272a4"><path fill-rule="evenodd" d="M2 2.5A2.5 2.5 0 014.5 0h8.75a.75.75 0 01.75.75v12.5a.75.75 0 01-.75.75h-2.5a.75.75 0 110-1.5h1.75v-2h-8a1 1 0 00-.714 1.7.75.75 0 01-1.072 1.05A2.495 2.495 0 012 11.5v-9zm10.5-1V9h-8c-.356 0-.694.074-1 .208V2.5a1 1 0 011-1h8zM5 12.25v3.25a.25.25 0 00.4.2l1.45-1.087a.25.25 0 01.3 0L8.6 15.7a.25.25 0 00.4-.2v-3.25a.25.25 0 00-.25-.25h-3.5a.25.25 0 00-.25.25z"/></g>
<text x="49" y="190" font-size="13" fill="#ffb86c">145 public repos</text>
<svg xmlns="http://www.w3.org/2000/svg" width="340" height="200" viewBox="0 0 340 200" font-family="'Segoe UI', Ubuntu, Sans-Serif">
<rect x="0.5" y="0.5" width="339" height="199" rx="6" fill="#282a36" stroke="#282a36" stroke-opacity="1.00"/>
<text x="20" y="30" font-size="15" font-weight="600" fill="#ff79c6">tiennm99 (Tien Nguyen Minh)</text>
<g transform="translate(20,50.00) scale(0.750)" fill="#6272a4"><path fill-rule="evenodd" d="M1.5 14.25c0 .138.112.25.25.25H4v-1.25a.75.75 0 01.75-.75h2.5a.75.75 0 01.75.75v1.25h2.25a.25.25 0 00.25-.25V1.75a.25.25 0 00-.25-.25h-8.5a.25.25 0 00-.25.25v12.5zM1.75 16A1.75 1.75 0 010 14.25V1.75C0 .784.784 0 1.75 0h8.5C11.216 0 12 .784 12 1.75v12.5c0 .085-.006.168-.018.25h2.268a.25.25 0 00.25-.25V8.285a.25.25 0 00-.111-.208l-1.055-.703a.75.75 0 11.832-1.248l1.055.703c.487.325.779.871.779 1.456v5.965A1.75 1.75 0 0114.25 16h-3.5a.75.75 0 01-.197-.026c-.099.017-.2.026-.303.026h-3a.75.75 0 01-.75-.75V14h-1v1.25a.75.75 0 01-.75.75h-3zM3 3.75A.75.75 0 013.75 3h.5a.75.75 0 010 1.5h-.5A.75.75 0 013 3.75zM3.75 6a.75.75 0 000 1.5h.5a.75.75 0 000-1.5h-.5zM3 9.75A.75.75 0 013.75 9h.5a.75.75 0 010 1.5h-.5A.75.75 0 013 9.75zM7.75 9a.75.75 0 000 1.5h.5a.75.75 0 000-1.5h-.5zM7 6.75A.75.75 0 017.75 6h.5a.75.75 0 010 1.5h-.5A.75.75 0 017 6.75zM7.75 3a.75.75 0 000 1.5h.5a.75.75 0 000-1.5h-.5z"/></g>
<text x="40" y="60" font-size="12" fill="#ffb86c">VNG Corporation</text>
<g transform="translate(20,70.00) scale(0.750)" fill="#6272a4"><path fill-rule="evenodd" d="M11.536 3.464a5 5 0 010 7.072L8 14.07l-3.536-3.535a5 5 0 117.072-7.072v.001zm1.06 8.132a6.5 6.5 0 10-9.192 0l3.535 3.536a1.5 1.5 0 002.122 0l3.535-3.536zM8 9a2 2 0 100-4 2 2 0 000 4z"/></g>
<text x="40" y="80" font-size="12" fill="#ffb86c">Ho Chi Minh - Viet Nam</text>
<g transform="translate(20,90.00) scale(0.750)" fill="#6272a4"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"/></g>
<text x="40" y="100" font-size="12" fill="#ffb86c">miti99.com</text>
<g transform="translate(20,110.00) scale(0.750)" fill="#6272a4"><path fill-rule="evenodd" d="M1.5 8a6.5 6.5 0 1113 0 6.5 6.5 0 01-13 0zM8 0a8 8 0 100 16A8 8 0 008 0zm.5 4.75a.75.75 0 00-1.5 0v3.5a.75.75 0 00.471.696l2.5 1a.75.75 0 00.557-1.392L8.5 7.742V4.75z"/></g>
<text x="40" y="120" font-size="12" fill="#ffb86c">2018-05-07 (7 years ago)</text>
<g transform="translate(20,130.00) scale(0.750)" fill="#6272a4"><path fill-rule="evenodd" d="M5.5 3.5a2 2 0 100 4 2 2 0 000-4zM2 5.5a3.5 3.5 0 115.898 2.549 5.507 5.507 0 013.034 4.084.75.75 0 11-1.482.235 4.001 4.001 0 00-7.9 0 .75.75 0 01-1.482-.236A5.507 5.507 0 013.102 8.05 3.49 3.49 0 012 5.5zM11 4a.75.75 0 100 1.5 1.5 1.5 0 01.666 2.844.75.75 0 00-.416.672v.352a.75.75 0 00.574.73c1.2.289 2.162 1.2 2.522 2.372a.75.75 0 101.434-.44 5.01 5.01 0 00-2.56-3.012A3 3 0 0011 4z"/></g>
<text x="40" y="140" font-size="12" fill="#ffb86c">10 followers · 14 following</text>
<g transform="translate(20,150.00) scale(0.750)" fill="#6272a4"><path fill-rule="evenodd" d="M2 2.5A2.5 2.5 0 014.5 0h8.75a.75.75 0 01.75.75v12.5a.75.75 0 01-.75.75h-2.5a.75.75 0 110-1.5h1.75v-2h-8a1 1 0 00-.714 1.7.75.75 0 01-1.072 1.05A2.495 2.495 0 012 11.5v-9zm10.5-1V9h-8c-.356 0-.694.074-1 .208V2.5a1 1 0 011-1h8zM5 12.25v3.25a.25.25 0 00.4.2l1.45-1.087a.25.25 0 01.3 0L8.6 15.7a.25.25 0 00.4-.2v-3.25a.25.25 0 00-.25-.25h-3.5a.25.25 0 00-.25.25z"/></g>
<text x="40" y="160" font-size="12" fill="#ffb86c">145 public repos</text>
</svg>

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

+18 -21
View File
@@ -1,22 +1,19 @@
<svg xmlns="http://www.w3.org/2000/svg" width="500" height="220" viewBox="0 0 500 220" font-family="'Segoe UI', Ubuntu, Sans-Serif">
<rect x="0.5" y="0.5" width="499" height="219" rx="8" fill="#282a36" stroke="#282a36" stroke-opacity="1.00"/>
<text x="25" y="35" font-size="18" font-weight="600" fill="#ff79c6">Repos Per Language</text>
<rect x="30" y="60" width="12" height="12" fill="#f1e05a" stroke="#282a36" stroke-width="1"/>
<text x="50" y="70" font-size="13" fill="#ffb86c">JavaScript 20.00%</text>
<rect x="30" y="82" width="12" height="12" fill="#b07219" stroke="#282a36" stroke-width="1"/>
<text x="50" y="92" font-size="13" fill="#ffb86c">Java 16.92%</text>
<rect x="30" y="104" width="12" height="12" fill="#00ADD8" stroke="#282a36" stroke-width="1"/>
<text x="50" y="114" font-size="13" fill="#ffb86c">Go 14.62%</text>
<rect x="30" y="126" width="12" height="12" fill="#3572A5" stroke="#282a36" stroke-width="1"/>
<text x="50" y="136" font-size="13" fill="#ffb86c">Python 10.00%</text>
<rect x="30" y="148" width="12" height="12" fill="#f34b7d" stroke="#282a36" stroke-width="1"/>
<text x="50" y="158" font-size="13" fill="#ffb86c">C++ 9.23%</text>
<rect x="30" y="170" width="12" height="12" fill="#bd93f9" stroke="#282a36" stroke-width="1"/>
<text x="50" y="180" font-size="13" fill="#ffb86c">Other 29.23%</text>
<path d="M380.00,50.00 A70.00,70.00 0 0 1 446.57,98.37 L416.14,108.26 A38.00,38.00 0 0 0 380.00,82.00 Z" fill="#f1e05a" stroke="#282a36" stroke-width="1.5"/>
<path d="M446.57,98.37 A70.00,70.00 0 0 1 431.26,167.67 L407.83,145.88 A38.00,38.00 0 0 0 416.14,108.26 Z" fill="#b07219" stroke="#282a36" stroke-width="1.5"/>
<path d="M431.26,167.67 A70.00,70.00 0 0 1 373.24,189.67 L376.33,157.82 A38.00,38.00 0 0 0 407.83,145.88 Z" fill="#00ADD8" stroke="#282a36" stroke-width="1.5"/>
<path d="M373.24,189.67 A70.00,70.00 0 0 1 333.58,172.40 L354.80,148.44 A38.00,38.00 0 0 0 376.33,157.82 Z" fill="#3572A5" stroke="#282a36" stroke-width="1.5"/>
<path d="M333.58,172.40 A70.00,70.00 0 0 1 312.46,138.39 L343.33,129.98 A38.00,38.00 0 0 0 354.80,148.44 Z" fill="#f34b7d" stroke="#282a36" stroke-width="1.5"/>
<path d="M312.46,138.39 A70.00,70.00 0 0 1 380.00,50.00 L380.00,82.00 A38.00,38.00 0 0 0 343.33,129.98 Z" fill="#bd93f9" stroke="#282a36" stroke-width="1.5"/>
<svg xmlns="http://www.w3.org/2000/svg" width="340" height="200" viewBox="0 0 340 200" font-family="'Segoe UI', Ubuntu, Sans-Serif">
<rect x="0.5" y="0.5" width="339" height="199" rx="6" fill="#282a36" stroke="#282a36" stroke-opacity="1.00"/>
<text x="20" y="30" font-size="15" font-weight="600" fill="#ff79c6">Repos Per Language</text>
<rect x="20" y="47" width="10" height="10" fill="#f1e05a" stroke="#282a36" stroke-width="1"/>
<text x="36" y="55" font-size="11" fill="#ffb86c">JavaScript 20.00%</text>
<rect x="20" y="67" width="10" height="10" fill="#b07219" stroke="#282a36" stroke-width="1"/>
<text x="36" y="75" font-size="11" fill="#ffb86c">Java 16.92%</text>
<rect x="20" y="87" width="10" height="10" fill="#00ADD8" stroke="#282a36" stroke-width="1"/>
<text x="36" y="95" font-size="11" fill="#ffb86c">Go 14.62%</text>
<rect x="20" y="107" width="10" height="10" fill="#3572A5" stroke="#282a36" stroke-width="1"/>
<text x="36" y="115" font-size="11" fill="#ffb86c">Python 10.00%</text>
<rect x="20" y="127" width="10" height="10" fill="#bd93f9" stroke="#282a36" stroke-width="1"/>
<text x="36" y="135" font-size="11" fill="#ffb86c">Other 38.46%</text>
<path d="M250.00,55.00 A55.00,55.00 0 0 1 302.31,93.00 L278.53,100.73 A30.00,30.00 0 0 0 250.00,80.00 Z" fill="#f1e05a" stroke="#282a36" stroke-width="1.5"/>
<path d="M302.31,93.00 A55.00,55.00 0 0 1 290.27,147.46 L271.97,130.43 A30.00,30.00 0 0 0 278.53,100.73 Z" fill="#b07219" stroke="#282a36" stroke-width="1.5"/>
<path d="M290.27,147.46 A55.00,55.00 0 0 1 244.69,164.74 L247.10,139.86 A30.00,30.00 0 0 0 271.97,130.43 Z" fill="#00ADD8" stroke="#282a36" stroke-width="1.5"/>
<path d="M244.69,164.74 A55.00,55.00 0 0 1 213.53,151.17 L230.11,132.46 A30.00,30.00 0 0 0 247.10,139.86 Z" fill="#3572A5" stroke="#282a36" stroke-width="1.5"/>
<path d="M213.53,151.17 A55.00,55.00 0 0 1 250.00,55.00 L250.00,80.00 A30.00,30.00 0 0 0 230.11,132.46 Z" fill="#bd93f9" stroke="#282a36" stroke-width="1.5"/>
</svg>

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

+24 -24
View File
@@ -1,25 +1,25 @@
<svg xmlns="http://www.w3.org/2000/svg" width="500" height="220" viewBox="0 0 500 220" font-family="'Segoe UI', Ubuntu, Sans-Serif">
<rect x="0.5" y="0.5" width="499" height="219" rx="8" fill="#282a36" stroke="#282a36" stroke-opacity="1.00"/>
<text x="25" y="35" font-size="18" font-weight="600" fill="#ff79c6">Stats</text>
<g transform="translate(25,58.00) scale(0.875)" fill="#6272a4"><path fill-rule="evenodd" d="M8 .25a.75.75 0 01.673.418l1.882 3.815 4.21.612a.75.75 0 01.416 1.279l-3.046 2.97.719 4.192a.75.75 0 01-1.088.791L8 12.347l-3.766 1.98a.75.75 0 01-1.088-.79l.72-4.194L.818 6.374a.75.75 0 01.416-1.28l4.21-.611L7.327.668A.75.75 0 018 .25zm0 2.445L6.615 5.5a.75.75 0 01-.564.41l-3.097.45 2.24 2.184a.75.75 0 01.216.664l-.528 3.084 2.769-1.456a.75.75 0 01.698 0l2.77 1.456-.53-3.084a.75.75 0 01.216-.664l2.24-2.183-3.096-.45a.75.75 0 01-.564-.41L8 2.694v.001z"/></g>
<text x="49" y="70" font-size="13" fill="#ffb86c">Total Stars</text>
<text x="475" y="70" font-size="13" font-weight="600" fill="#bd93f9" text-anchor="end">4</text>
<g transform="translate(25,82.00) scale(0.875)" fill="#6272a4"><path fill-rule="evenodd" d="M10.5 7.75a2.5 2.5 0 11-5 0 2.5 2.5 0 015 0zm1.43.75a4.002 4.002 0 01-7.86 0H.75a.75.75 0 110-1.5h3.32a4.001 4.001 0 017.86 0h3.32a.75.75 0 110 1.5h-3.32z"/></g>
<text x="49" y="94" font-size="13" fill="#ffb86c">Total Commits (all time)</text>
<text x="475" y="94" font-size="13" font-weight="600" fill="#bd93f9" text-anchor="end">3,106</text>
<g transform="translate(25,106.00) scale(0.875)" fill="#6272a4"><path fill-rule="evenodd" d="M10.5 7.75a2.5 2.5 0 11-5 0 2.5 2.5 0 015 0zm1.43.75a4.002 4.002 0 01-7.86 0H.75a.75.75 0 110-1.5h3.32a4.001 4.001 0 017.86 0h3.32a.75.75 0 110 1.5h-3.32z"/></g>
<text x="49" y="118" font-size="13" fill="#ffb86c">Total Commits (last year)</text>
<text x="475" y="118" font-size="13" font-weight="600" fill="#bd93f9" text-anchor="end">1,734</text>
<g transform="translate(25,130.00) scale(0.875)" fill="#6272a4"><path fill-rule="evenodd" d="M7.177 3.073L9.573.677A.25.25 0 0110 .854v4.792a.25.25 0 01-.427.177L7.177 3.427a.25.25 0 010-.354zM3.75 2.5a.75.75 0 100 1.5.75.75 0 000-1.5zm-2.25.75a2.25 2.25 0 113 2.122v5.256a2.251 2.251 0 11-1.5 0V5.372A2.25 2.25 0 011.5 3.25zM11 2.5h-1V4h1a1 1 0 011 1v5.628a2.251 2.251 0 101.5 0V5A2.5 2.5 0 0011 2.5zm1 10.25a.75.75 0 111.5 0 .75.75 0 01-1.5 0zM3.75 12a.75.75 0 100 1.5.75.75 0 000-1.5z"/></g>
<text x="49" y="142" font-size="13" fill="#ffb86c">Total PRs</text>
<text x="475" y="142" font-size="13" font-weight="600" fill="#bd93f9" text-anchor="end">69</text>
<g transform="translate(25,154.00) scale(0.875)" fill="#6272a4"><path fill-rule="evenodd" d="M8 1.5a6.5 6.5 0 100 13 6.5 6.5 0 000-13zM0 8a8 8 0 1116 0A8 8 0 010 8zm9 3a1 1 0 11-2 0 1 1 0 012 0zm-.25-6.25a.75.75 0 00-1.5 0v3.5a.75.75 0 001.5 0v-3.5z"/></g>
<text x="49" y="166" font-size="13" fill="#ffb86c">Total Issues</text>
<text x="475" y="166" font-size="13" font-weight="600" fill="#bd93f9" text-anchor="end">7</text>
<g transform="translate(25,178.00) scale(0.875)" fill="#6272a4"><path fill-rule="evenodd" d="M1.5 2.75a.25.25 0 01.25-.25h12.5a.25.25 0 01.25.25v8.5a.25.25 0 01-.25.25h-6.5a.75.75 0 00-.53.22L4.5 14.44v-2.19a.75.75 0 00-.75-.75h-2a.25.25 0 01-.25-.25v-8.5zM1.75 1A1.75 1.75 0 000 2.75v8.5C0 12.216.784 13 1.75 13H3v1.543a1.457 1.457 0 002.487 1.03L8.061 13h6.189A1.75 1.75 0 0016 11.25v-8.5A1.75 1.75 0 0014.25 1H1.75zm5.03 3.47a.75.75 0 010 1.06L5.31 7l1.47 1.47a.75.75 0 01-1.06 1.06l-2-2a.75.75 0 010-1.06l2-2a.75.75 0 011.06 0zm2.44 0a.75.75 0 000 1.06L10.69 7 9.22 8.47a.75.75 0 001.06 1.06l2-2a.75.75 0 000-1.06l-2-2a.75.75 0 00-1.06 0z"/></g>
<text x="49" y="190" font-size="13" fill="#ffb86c">Total PR Reviews</text>
<text x="475" y="190" font-size="13" font-weight="600" fill="#bd93f9" text-anchor="end">0</text>
<g transform="translate(25,202.00) scale(0.875)" fill="#6272a4"><path fill-rule="evenodd" d="M2 2.5A2.5 2.5 0 014.5 0h8.75a.75.75 0 01.75.75v12.5a.75.75 0 01-.75.75h-2.5a.75.75 0 110-1.5h1.75v-2h-8a1 1 0 00-.714 1.7.75.75 0 01-1.072 1.05A2.495 2.495 0 012 11.5v-9zm10.5-1V9h-8c-.356 0-.694.074-1 .208V2.5a1 1 0 011-1h8zM5 12.25v3.25a.25.25 0 00.4.2l1.45-1.087a.25.25 0 01.3 0L8.6 15.7a.25.25 0 00.4-.2v-3.25a.25.25 0 00-.25-.25h-3.5a.25.25 0 00-.25.25z"/></g>
<text x="49" y="214" font-size="13" fill="#ffb86c">Contributed to</text>
<text x="475" y="214" font-size="13" font-weight="600" fill="#bd93f9" text-anchor="end">15</text>
<svg xmlns="http://www.w3.org/2000/svg" width="340" height="200" viewBox="0 0 340 200" font-family="'Segoe UI', Ubuntu, Sans-Serif">
<rect x="0.5" y="0.5" width="339" height="199" rx="6" fill="#282a36" stroke="#282a36" stroke-opacity="1.00"/>
<text x="20" y="30" font-size="15" font-weight="600" fill="#ff79c6">Stats</text>
<g transform="translate(20,45.00) scale(0.750)" fill="#6272a4"><path fill-rule="evenodd" d="M8 .25a.75.75 0 01.673.418l1.882 3.815 4.21.612a.75.75 0 01.416 1.279l-3.046 2.97.719 4.192a.75.75 0 01-1.088.791L8 12.347l-3.766 1.98a.75.75 0 01-1.088-.79l.72-4.194L.818 6.374a.75.75 0 01.416-1.28l4.21-.611L7.327.668A.75.75 0 018 .25zm0 2.445L6.615 5.5a.75.75 0 01-.564.41l-3.097.45 2.24 2.184a.75.75 0 01.216.664l-.528 3.084 2.769-1.456a.75.75 0 01.698 0l2.77 1.456-.53-3.084a.75.75 0 01.216-.664l2.24-2.183-3.096-.45a.75.75 0 01-.564-.41L8 2.694v.001z"/></g>
<text x="40" y="55" font-size="12" fill="#ffb86c">Total Stars</text>
<text x="320" y="55" font-size="12" font-weight="600" fill="#bd93f9" text-anchor="end">4</text>
<g transform="translate(20,65.00) scale(0.750)" fill="#6272a4"><path fill-rule="evenodd" d="M10.5 7.75a2.5 2.5 0 11-5 0 2.5 2.5 0 015 0zm1.43.75a4.002 4.002 0 01-7.86 0H.75a.75.75 0 110-1.5h3.32a4.001 4.001 0 017.86 0h3.32a.75.75 0 110 1.5h-3.32z"/></g>
<text x="40" y="75" font-size="12" fill="#ffb86c">Total Commits (all time)</text>
<text x="320" y="75" font-size="12" font-weight="600" fill="#bd93f9" text-anchor="end">3,121</text>
<g transform="translate(20,85.00) scale(0.750)" fill="#6272a4"><path fill-rule="evenodd" d="M10.5 7.75a2.5 2.5 0 11-5 0 2.5 2.5 0 015 0zm1.43.75a4.002 4.002 0 01-7.86 0H.75a.75.75 0 110-1.5h3.32a4.001 4.001 0 017.86 0h3.32a.75.75 0 110 1.5h-3.32z"/></g>
<text x="40" y="95" font-size="12" fill="#ffb86c">Total Commits (last year)</text>
<text x="320" y="95" font-size="12" font-weight="600" fill="#bd93f9" text-anchor="end">1,749</text>
<g transform="translate(20,105.00) scale(0.750)" fill="#6272a4"><path fill-rule="evenodd" d="M7.177 3.073L9.573.677A.25.25 0 0110 .854v4.792a.25.25 0 01-.427.177L7.177 3.427a.25.25 0 010-.354zM3.75 2.5a.75.75 0 100 1.5.75.75 0 000-1.5zm-2.25.75a2.25 2.25 0 113 2.122v5.256a2.251 2.251 0 11-1.5 0V5.372A2.25 2.25 0 011.5 3.25zM11 2.5h-1V4h1a1 1 0 011 1v5.628a2.251 2.251 0 101.5 0V5A2.5 2.5 0 0011 2.5zm1 10.25a.75.75 0 111.5 0 .75.75 0 01-1.5 0zM3.75 12a.75.75 0 100 1.5.75.75 0 000-1.5z"/></g>
<text x="40" y="115" font-size="12" fill="#ffb86c">Total PRs</text>
<text x="320" y="115" font-size="12" font-weight="600" fill="#bd93f9" text-anchor="end">69</text>
<g transform="translate(20,125.00) scale(0.750)" fill="#6272a4"><path fill-rule="evenodd" d="M8 1.5a6.5 6.5 0 100 13 6.5 6.5 0 000-13zM0 8a8 8 0 1116 0A8 8 0 010 8zm9 3a1 1 0 11-2 0 1 1 0 012 0zm-.25-6.25a.75.75 0 00-1.5 0v3.5a.75.75 0 001.5 0v-3.5z"/></g>
<text x="40" y="135" font-size="12" fill="#ffb86c">Total Issues</text>
<text x="320" y="135" font-size="12" font-weight="600" fill="#bd93f9" text-anchor="end">7</text>
<g transform="translate(20,145.00) scale(0.750)" fill="#6272a4"><path fill-rule="evenodd" d="M1.5 2.75a.25.25 0 01.25-.25h12.5a.25.25 0 01.25.25v8.5a.25.25 0 01-.25.25h-6.5a.75.75 0 00-.53.22L4.5 14.44v-2.19a.75.75 0 00-.75-.75h-2a.25.25 0 01-.25-.25v-8.5zM1.75 1A1.75 1.75 0 000 2.75v8.5C0 12.216.784 13 1.75 13H3v1.543a1.457 1.457 0 002.487 1.03L8.061 13h6.189A1.75 1.75 0 0016 11.25v-8.5A1.75 1.75 0 0014.25 1H1.75zm5.03 3.47a.75.75 0 010 1.06L5.31 7l1.47 1.47a.75.75 0 01-1.06 1.06l-2-2a.75.75 0 010-1.06l2-2a.75.75 0 011.06 0zm2.44 0a.75.75 0 000 1.06L10.69 7 9.22 8.47a.75.75 0 001.06 1.06l2-2a.75.75 0 000-1.06l-2-2a.75.75 0 00-1.06 0z"/></g>
<text x="40" y="155" font-size="12" fill="#ffb86c">Total PR Reviews</text>
<text x="320" y="155" font-size="12" font-weight="600" fill="#bd93f9" text-anchor="end">0</text>
<g transform="translate(20,165.00) scale(0.750)" fill="#6272a4"><path fill-rule="evenodd" d="M2 2.5A2.5 2.5 0 014.5 0h8.75a.75.75 0 01.75.75v12.5a.75.75 0 01-.75.75h-2.5a.75.75 0 110-1.5h1.75v-2h-8a1 1 0 00-.714 1.7.75.75 0 01-1.072 1.05A2.495 2.495 0 012 11.5v-9zm10.5-1V9h-8c-.356 0-.694.074-1 .208V2.5a1 1 0 011-1h8zM5 12.25v3.25a.25.25 0 00.4.2l1.45-1.087a.25.25 0 01.3 0L8.6 15.7a.25.25 0 00.4-.2v-3.25a.25.25 0 00-.25-.25h-3.5a.25.25 0 00-.25.25z"/></g>
<text x="40" y="175" font-size="12" fill="#ffb86c">Contributed to</text>
<text x="320" y="175" font-size="12" font-weight="600" fill="#bd93f9" text-anchor="end">15</text>
</svg>

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

@@ -0,0 +1,94 @@
# Why Your Most-Commit-Language (All Time) Looks Like This
Reconstructed from live GraphQL data for user `tiennm99` on 2026-04-18.
## The card says
| Rank | Language | % |
|---|---|---|
| 1 | JavaScript | 24.96 |
| 2 | Python | 22.68 |
| 3 | C# | 19.54 |
| 4 | Go | 12.65 |
| 5 | Svelte | 11.40 |
| 6 | Other | 8.77 |
## What the algorithm actually sees
Only your **top 10 starred non-fork owned repos** get probed, up to **500 commits each**. Everything else is invisible to this card.
**Your top 10 + your commits in them + linguist byte split:**
| Repo | Your commits | Primary | Linguist bytes (non-prose) |
|---|---:|---|---|
| time-mocker | 34 | C# | C# 100% |
| adventofcode | 15 | Go | Go 100% |
| export-telegram-group-members | 9 | Python | Python 100% |
| lottery-generator | 11 | Java | Java 100% |
| ghstats | 2 | Go | Go 100% |
| rplace | **44** | JavaScript | JS 56% · Svelte 43% · HTML/CSS ~0.4% |
| thptqg2016 | 9 | JavaScript | JS 72% · CSS 27% |
| go-util | 5 | Go | Go 100% |
| try-bmad | 35 | Python | **Python 87% · JS 7%** · HTML/Svelte/Groovy/CSS ~5% |
| try-claudekit | 10 | JavaScript | JS 96% |
Total commits counted: **174**.
## Per-language derivation
Each commit contributes 1 "vote" split by linguist byte share. Summed:
| Language | Vote from… | Total |
|---|---|---:|
| **JavaScript** | rplace 24.71 · try-claudekit 9.64 · thptqg2016 6.50 · try-bmad 2.57 | **43.42** |
| **Python** | try-bmad 30.47 · export-tg 9.00 | **39.47** |
| **C#** | time-mocker 34.00 | **34.00** |
| **Go** | adventofcode 15 · go-util 5 · ghstats 2 | **22.00** |
| **Svelte** | rplace 19.14 · try-bmad 0.69 | **19.83** |
| Java | lottery-generator 11.00 | 11.00 |
| CSS | thptqg2016 2.40 · small others | 2.77 |
| HTML | spread across rplace/thptqg2016/try-bmad/try-claudekit | 1.13 |
| Groovy | try-bmad 0.37 | 0.37 |
Dividing by 174 → exactly matches the card (24.96% / 22.68% / 19.54% / 12.65% / 11.40% / 8.78%).
## Why these rank where they do
| Language | Real story |
|---|---|
| **JavaScript** dominates | Three throwaway/experiment repos (rplace, try-claudekit, thptqg2016) plus a Python project that happens to ship a JS frontend (try-bmad). The 44 rplace commits alone contribute 24.7 of the 43 JS "votes" — a single weekend project is driving the #1 slot. |
| **Python** #2 | 77% of it (30.47 of 39.47) is **try-bmad**, a 35-commit scaffolding project. The 87% Python byte share there means every commit — even a README edit — gets 87% credit to Python. |
| **C#** #3 | Every single C# vote comes from **time-mocker** (34 commits, 100% C#). This is the signal with the cleanest attribution — if you committed to that repo, it was almost certainly touching C# files. |
| **Go** surprisingly low | Only 22 votes from three repos. `ghstats` itself has **2 commits** in your window because most of this session's work hasn't been pushed yet; and `adventofcode`/`go-util` are one-off exercises. Your Go day-job probably lives in private repos we can't see. |
| **Svelte** appears out of nowhere | Linguist sees ~80KB of Svelte in **rplace** next to ~103KB of JS. Each of rplace's 44 commits credits 43% to Svelte — even commits that only touched `.js` files. That's the cost of byte-weighted attribution. |
## Why it feels wrong
Four structural reasons:
1. **Top-10 cap.** Sorted by stargazers. Your actual Go/Python dev work probably lives in repos with 0 stars but many commits — they don't make the cut.
2. **Private repos invisible.** `ownerAffiliations: OWNER` + the token's scope. Your VNG Corp code does not appear.
3. **Forks excluded.** `isFork: false`. If you hack on forks of upstream projects, those contributions vanish.
4. **Byte-weighted ≠ file-touched.** rplace credits 43% to Svelte on every commit regardless of which file you edited. A commit that fixes one line in `main.js` still counts Svelte bytes.
## What would actually fix it
Only per-commit file classification does (already scoped in earlier research — `REST /commits/{sha}` + go-enry). That path would:
- Count each commit by the **files actually touched** (Svelte only if you edited `*.svelte`).
- Support opt-in to cover all repos, not just top-10.
- Recover Markdown-heavy repos that currently vanish.
Cost: ~1000 REST calls per run vs. the current ~15 GraphQL calls. A toggleable `-accurate-languages` flag still makes sense.
## Quick wins you could take without the rewrite
- **Raise `-top-repos`** to 30–50 so less-starred but heavily-committed repos enter the sample.
- **Raise `-commits-per-repo`** past 500 if you care about lifetime depth.
- **Add `-exclude-repo rplace,try-bmad,try-claudekit`** (not implemented yet) to drop known experiment repos the way github-profile-summary-cards does.
## Unresolved questions
- Do you want the `-exclude-repo` flag landed now as a short-term fix?
- Should we expose `ownerAffiliations` so contributed-to (non-owned) repos can be included?
- Is the 500-commit cap actually binding for any of your repos, or is 174 total just "everything you've pushed"?
@@ -0,0 +1,226 @@
# Full-Project Code Review
Scope: all Go source, `action.yml`, `entrypoint.sh`, `Dockerfile`, workflows. ~2300 LOC. Adversarial pass included.
Verification harness used: Go compile + tests pass clean (`go vet ./... && go test ./...`). Real-data render against `tiennm99` with token — all 9 cards produced valid SVGs. One bug reproduced with a standalone probe.
## Verdict
Code quality is high for the scope. One real rendering bug, several correctness and hygiene issues, and a meaningful test-coverage gap. Nothing blocks merge; prioritize the **Important** list before next tag.
## Severity counts
| Critical | Important | Nice-to-have |
|---:|---:|---:|
| 0 | 6 | 10 |
---
## Important
### I1 — Donut chart renders empty when there is only 1 slice ⚠️
**File**: `internal/card/donut_chart.go:60-79`
For a user with a single language at 100%, `angle = 2π` → `start == end` → SVG `A` command degenerates to zero-length arc. The donut shows nothing.
Reproduced with a standalone probe:
```
start=(380, 50)
end=(380, 50) # identical → empty arc
same=true
```
**Fix**: when there's exactly one slice (or when `angle >= 2π - ε`), render a full ring via two half-arcs, a `<circle>` with a stroke, or a punched-out clip path. Smallest change:
```go
if len(stats) == 1 {
// Full ring: outer circle fill + inner background circle
fmt.Fprintf(&b, `<circle cx=... fill="%s"/><circle cx=... fill="%s"/>`, slice.Color, t.Background)
...
}
```
### I2 — `FetchContributionsAllTime` silently drops failed years
**File**: `internal/github/contributions_all_time.go:62-64`
```go
if resp.User == nil {
continue
}
```
If GraphQL returns an error JSON per year (e.g. permission issue, transient 5xx that somehow bypassed the HTTP error check), the year is skipped with zero logging. All 8 years of a user's history could silently vanish — cards render "No data available" with no diagnostic.
**Fix**: log at warn level when `resp.User == nil` after a successful response; or return a wrapped error so `main.go` can surface a partial-data warning.
### I3 — Stale comment on `FetchOptions` contradicts live behavior
**File**: `internal/github/profile.go:61-62`
```go
// FetchOptions tunes which repos contribute to the profile's aggregates.
// All defaults are conservative (no forks, no private) so public-facing
// READMEs don't accidentally leak work-repo signal.
```
Defaults are now **true/true** (commit `514195c`). Zero-value `FetchOptions{}` still gives `false/false`, so API callers using `FetchOptions{}` get one behavior while CLI callers get another. Subtle surprise.
**Fix**: either update the comment to reflect new posture, or flip the zero-value semantics (e.g., `ExcludeForks bool` / `ExcludePrivate bool`) so the package-level default matches the CLI. The former is simpler.
### I4 — `TestRenderAll` no longer verifies XML escape
**File**: `internal/card/card_test.go:17,63`
The test sets `Bio: "Test & <bio>"` and asserts the raw string doesn't appear in any rendered SVG. But `Bio` is **not rendered** anywhere after the profile-card redesign — it was removed along with the github-row. The assertion is trivially true for every future change.
Additionally, `TestRenderAll` only checks each file starts with `<svg` — it does not validate content. A card that renders an empty shell would pass.
**Fix**: set `Name: "Alice & <bob>"` (Name **is** rendered, in the title) and keep the assertion. Add a golden-file comparison per card for at least one theme to catch silent regressions.
### I5 — Release workflow doesn't gate on tests
**File**: `.github/workflows/release.yml`
Tagging `v1.2.3` runs the release pipeline immediately — no `go test ./...`, no `needs: [ci]`. A broken `main` tagged accidentally ships broken binaries + Docker image.
**Fix**: add a pre-release test job:
```yaml
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-go@v6
with:
go-version: "1.26"
- run: go vet ./... && go test ./...
docker:
needs: [test]
...
binaries:
needs: [test]
...
```
### I6 — `attributeCommit` recomputes `total` per-commit
**File**: `internal/github/productive.go:111-115` + `:86-92` (caller)
```go
func attributeCommit(repo RepoInfo, ...) {
var total int64
for _, l := range repo.Languages {
total += l.Bytes
}
...
}
```
Called once per commit. For 500 commits × 6-20 languages per repo, that's 3000–10000 redundant additions per repo. Not a runtime problem, but it's a correctness smell: if `repo.Languages` were ever mutated between calls, you'd get inconsistent totals.
**Fix**: precompute `total` once per repo, pass it in or cache on `RepoInfo`. Clean separation of loop-invariant work.
---
## Nice-to-have
### N1 — `joinErrs` reinvents `strings.Join`
**File**: `internal/github/client.go:102-110`
Custom loop concatenates with `"; "`. Identical to `strings.Join(ss, "; ")`. Replace.
### N2 — No total timeout / `context.Context`
**File**: `internal/github/client.go:26`, all fetchers
`http.Client.Timeout = 30s` applies per request. A fetch with 50 pages × 30s worst-case = 25 minutes. No way to set an overall budget or cancel on signal. Add `ctx context.Context` to `Client.query` and fetcher methods; respect `<-ctx.Done()` in pagination loops.
### N3 — `truncate` may split UTF-8 mid-sequence
**File**: `internal/github/client.go:95-100`
`string(b[:n])` at an arbitrary byte boundary can leave a half-character. Purely cosmetic in error messages; fine to ignore.
### N4 — Docker base images pinned to major, not digest
**File**: `Dockerfile:1,8`
`golang:1.26-alpine` and `alpine:3.21` are mutable tags. A compromised Alpine push affects builds. Pin to `@sha256:…` for hermetic builds. Acceptable risk for an OSS tool.
### N5 — Third-party GHA actions pinned to major
**File**: `.github/workflows/release.yml`
`docker/build-push-action@v6`, `softprops/action-gh-release@v2`, `actions/checkout@v6` — mutable. Pin to SHA for supply-chain safety. Same caveat as N4.
### N6 — No rate-limit header inspection
**File**: `internal/github/client.go:63-73`
`x-ratelimit-remaining` / `x-ratelimit-reset` are ignored. When near zero, the next call returns 403 and we error out. Cheap improvement: parse headers and sleep until reset.
### N7 — `xAxisLabelVisible` rules can drop the penultimate label without the cosmetic expectation
**File**: `internal/card/contributions.go:158-160`
```go
if n-1-i < stride/2 {
return false
}
```
For some `(n, stride)` pairs this drops a label that would have been `stride` apart. Result: 5 labels where you expected 6. Minor visual quirk; not a bug.
### N8 — `Profile.TotalContributions` is misnamed
**File**: `internal/github/model.go` (via `profile.go:118`)
Set from `ContributionCalendar.TotalContributions + RestrictedContributionsCount`, which is a **one-year** total, not lifetime. Field name suggests lifetime. Rename to `TotalContributionsLastYear` or compute from the AllTime loop.
### N9 — Stats card's "Contributed to (non-fork)" stays capped at the top-N
**File**: `internal/card/stats.go` + `profile.go:113`
`TotalContributedTo = u.RepositoriesContributedTo.TotalCount` queries with `first: 1`, so it only returns the **count** — that's fine. But the label says "(non-fork)" while the query doesn't actually filter by fork. Drop the "(non-fork)" qualifier or add `contributionTypes` filter that excludes forks.
### N10 — `output/` in .gitignore exception is narrow but fragile
**File**: `.gitignore`
```
output/*
!output/dracula/
```
If someone runs with `-out=output/` and a theme named `dracula`, their local changes overlay the committed sample. Usually fine. If someone runs `-themes all`, `output/*` blocks every theme dir except dracula — working as intended. No action needed; noted for future refactors.
---
## Adversarial pass — things that don't break
Tried and confirmed safe:
| Attack | Result |
| --- | --- |
| XML injection via `Bio`, `Name`, `Company`, `Location`, `Website`, language names | All flow through `escapeXML` — safe |
| Theme ID path traversal (`-themes ../../etc`) | `theme.Lookup` rejects unknown IDs before `filepath.Join` — safe |
| GraphQL injection via `$login`, `$owner`, `$repo` | Variables passed separately from query string — safe |
| Shell injection in `entrypoint.sh` via user inputs | All variables double-quoted; no `eval` — safe |
| Token exposure in logs | `entrypoint.sh:31` echoes user/themes/out only; no token echo — safe |
| Token exposure in error messages | HTTP errors truncate body to 500 bytes; GitHub bodies don't echo tokens — safe |
| Integer overflow in `scaleFactor * bytes / total` | Worst realistic case ~10^14, well under int64 max — safe |
| Panic from `Productive[tl.Hour()]` | `Hour()` always returns 0-23 — safe |
| NaN from `angle := 2 * math.Pi * value / total` when total=0 | len(stats)>0 check guarantees one non-zero value, so total>0 in practice. But... if all values happen to be 0, we'd produce NaN. Unlikely (sortLangStats wouldn't generate 0-valued entries), but defensive check wouldn't hurt. |
| Resource exhaustion via huge user | `maxPages = 10` in FetchProfile (1000 repos cap); `maxRepositories: 100` per year in seed query. Bounded. |
| Race conditions | Single-goroutine — no races possible |
| Division by zero in `xAxisLabelVisible` stride calc | Early return for `n <= xLabelTarget` prevents stride=0 — safe |
---
## Testing gaps (summary)
| Missing test | Motivation |
| --- | --- |
| Single-slice donut | Catches bug I1 |
| `catmullRomLinePath([1 point])` / `[]` | Verifies early returns |
| Half-hour timezone (`Asia/Kolkata`) in `utcOffsetLabel` | Verifies `%+.2f` formatting |
| `sortLangStats` with ties and empty color map | Already partially tested |
| Empty `DailyContributions` → "No data available" path | Already partially tested indirectly |
| Golden SVG comparison per card for one theme | Catches regressions across refactors |
---
## Not issues
- No external Go deps. Good.
- `filepath.Join(outDir, t.ID)` — `t.ID` sourced from the themes map keys, validated through `theme.Lookup`. Safe.
- GraphQL query strings are compile-time constants. No injection surface.
- Commit-time error paths (`git add`, `git commit`, `git push`) run in Action workspace only and fail closed.
---
## Unresolved questions
- Should we land `-accurate-languages` (REST-per-commit + enry) as the primary fix for Markdown-blog attribution, or invest in `-deep` (partial bare clone) first? Roadmap has both.
- Is the single-slice donut case (I1) rare enough in practice to accept a compact fix, or worth refactoring to render all donuts with a full-ring base + pie slices on top (more robust, slightly more code)?
- Release workflow: run tests in matrix (cross-platform) or on linux only before shipping? Linux-only is practical; matrix catches platform bugs but triples minutes.
@@ -0,0 +1,185 @@
# Most Commit Language — Accurate Attribution Research
## Problem & constraints
GitHub's default language detection (`repo.primaryLanguage`) counts total bytes per repo language, not weighted by commit activity. Users with mixed-language repos (e.g., blog w/ 3 JS files + 1000 Markdown files) get misattributed: every commit to `.md` gets credited to the lowest-byte language, not the language actually edited. This report evaluates methods to fix attribution by tracking which files are modified per-commit.
**Constraints:** Action runner cost (storage/time), REST API rate limits (5K/hr), accuracy trade-offs, language-detection reliability without repo access.
---
## Prior art comparison table
| Project | Method | Clone? | Accuracy | Cost | Notes |
|---------|--------|--------|----------|------|-------|
| **anuraghazra/github-readme-stats (GRS)** | GraphQL `languages(first:10, orderBy:SIZE)` per repo | No | Byte-size only; **no commit weighting** | 1 GraphQL query/100 repos | Baseline: pure size-based, can't solve commit problem |
| **lowlighter/metrics (indepth)** | Clone repos → `git log --patch` per commit → linguist-js classify each file → accumulate by type | Yes | **Per-commit, per-line** | 15 min/repo timeout; heavy | Ground truth; `categories=[programming,markup]` filter; handles `.gitattributes` |
| **lowlighter/metrics (default/recent)** | GraphQL byte-size (same as GRS) + recent event stream analysis | No | Byte-size + event heuristics | Lightweight | Not commit-weighted |
| **Proposed: REST per-commit + go-enry** | REST `/repos/{o}/{r}/commits/{sha}` for each commit → go-enry classify filenames/extensions | No | **Per-commit by filename**, no line counting | 1 REST call/commit; 5K limit = ~100 commits/hr | Fast; lightweight; no clone; accuracy limited to extension-level |
---
## How github-readme-stats handles it
**GRS does NOT solve the commit-attribution problem.** It computes language stats as:
1. **GraphQL fetch:** `repositories(ownerAffiliations: OWNER, isFork: false, first: 100)` → for each repo:
- `languages(first: 10, orderBy: {field: SIZE, direction: DESC})` → get top 10 languages by **bytes**
- Accumulate `size` values across all repos; weight by `size_weight=1` and `count_weight=0` (default)
2. **Filters offered:** `exclude_repo` list only; no per-commit filtering, no commit-count weighting
3. **Documented limitation:** Users can use `exclude_repo` to hide problematic repos; no built-in commit-weighting
**Conclusion:** GRS is optimized for byte-size ranking (good for codebases), not commit activity (good for contributor profiles). No per-commit analysis.
---
## lowlighter/metrics — further details
### Default categories
Confirmed: `plugin_languages_categories` default is `[markup, programming]` (excludes `data`, `prose`). For indepth mode, users can override to include `prose` (which includes Markdown). Markdown is classified as **TypeProse** by go-enry (type code 4).
### Per-file analysis in indepth mode
- Clones repo to temp directory
- Runs `git log --author=<user> --patch` to fetch each commit with diff
- Parses unified diff to extract file paths and line counts (added/deleted per file)
- Calls `linguist-js` (Node wrapper around linguist) to classify each file
- Accumulates `{bytes, lines}` per language per commit
- Filters by `categories` to exclude unwanted types
### Clone timeout & safety
- `plugin_languages_analysis_timeout`: 15 sec global (default); 7.5 sec per repo (default)
- **No REST-only fallback mode** — if clone fails, that repo is skipped; no graceful degradation
- Symlinks, submodules, large binaries handled by linguist; `.gitattributes` **IS** respected (checked from cloned repo)
### De-duplication & fork handling
- Indepth fetches only repos where user is OWNER (not forks, not contributed-to repos)
- Counts only commits matching `--author` regex (authoring email list fetched from GPG keys)
- Deduplicates by commit SHA within session
### REST-only mode does NOT exist
lowlighter/metrics has two modes: default (byte-size, no clone) and indepth (clone + patch analysis). No hybrid REST-per-commit approach exposed.
---
## The go-enry + REST-per-commit approach — validation
### Module details
- **Module path:** `github.com/go-enry/go-enry/v2` (Apache-2.0 license)
- **Status:** Actively maintained; last push 2026-04-04; 603 stars; imported from github/linguist
- **Go version:** 1.14+
- **Pre-compiled data:** Yes — language metadata (types, colors, patterns) embedded in `data/*.go` files; auto-generated from github/linguist
### Language type mapping — confirmed for Markdown
```go
// Type int: 0=Unknown, 1=Data, 2=Programming, 3=Markup, 4=Prose
// Markdown = Type 4 (Prose)
var LanguagesType = map[string]int{
"Markdown": 4, // Prose
"YAML": 1, // Data
"JSON": 1, // Data
"JavaScript": 2, // Programming
}
```
**Implication:** If we use go-enry and filter to `types=[2, 3]` (Programming + Markup), Markdown gets excluded. To include Markdown, must expand to `types=[2, 3, 4]` or add it to a custom whitelist.
### Extension-only vs content-based classification
go-enry has two classification modes:
1. **Extension-only** (`GetLanguageByExtension("file.md")` → "Markdown"): Fast, no file content needed
2. **Content-based** (`GetLanguageByContent(filename, content)` → "Markdown"): Slower; handles ambiguous extensions (e.g., `.txt`, `.r` for R vs reStructuredText)
**REST API provides:** Filename + additions/deletions per file, NO file content. So **we're limited to extension-only mode**, which is **~90% accurate** (fails on ambiguous extensions, but Markdown `.md` is unambiguous).
### `.gitattributes` support — NO
- go-enry does NOT parse `.gitattributes` directives (`linguist-vendored`, `linguist-generated`, `linguist-ignore`)
- Those live in the repo; without cloning, we can't access them
- **Accuracy delta:** Most users don't use `.gitattributes` heavily; estimated 5-10% false positives (counting generated/vendored code as real)
- **Mitigation:** Offer optional `.gitattributes` fetch via `GET /repos/{o}/{r}/contents/.gitattributes` if file exists (one extra REST call/repo)
### Binary/submodule/symlink handling
- go-enry's extension-based approach is safe: won't misclassify `.exe`, `.so`, etc. (no match → `OtherLanguage`)
- Submodules: filenames include submodule path; classified by extension of filename (safe)
- Symlinks: REST API lists symlink targets as files; go-enry classifies by target extension (reasonable)
### Known limitations
- **No semantic analysis:** Go comment syntax won't help distinguish `// in code` from `// in prose` (extension-based only)
- **Ambiguous extensions:** `.r`, `.tsx` can be R or reStructuredText / TypeScript or TSX. Content-based classification helps, but we don't have content.
- **Custom language aliases:** go-enry uses fixed language names from linguist; user aliases (e.g., renaming "TypeScript" to "TS") would need client-side mapping
---
## Cost estimation: REST per-commit approach
**Assumptions:** Top 10 repos, 100 commits/repo (user-configurable), 1K REST calls/run
**Rate limits:** 5K requests/hour (authenticated). At 100 commits/sec optimal rate, 1K calls ≈ 10 seconds API time.
**Safety:** Well within burst & hourly limits; safe for GitHub Actions runners.
**Breakdown per commit:**
- `GET /repos/{o}/{r}/commits/{sha}` → 1 call, returns `files[].{filename, additions, deletions}`
- go-enry local classification → negligible (microseconds per file)
- No clone, no network I/O beyond REST
**Feasibility:** High. Straightforward to parallelize (batch SHA fetches) if needed.
---
## Additional ideas
### Idea 1: Sampling + statistical estimation
Fetch 10-20 commits evenly distributed across repo history (e.g., every Nth commit). Use frequency to extrapolate total language distribution. **Trade-off:** 10x faster, ~70% accuracy (misses long-term shifts). **Verdict:** Useful for low-precision preview card, not for serious stats.
### Idea 2: GraphQL Commit history with limited file info
GitHub GraphQL `repository.ref.target.history` supports commit queries, but only returns commit count + author info. NO per-file details. Checked: `Commit` object has no `files` or `changedFilesIfAvailable` field exposing filenames. **Verdict:** Dead end; no edge advantage over REST.
### Idea 3: Docker + linguist Ruby gem
Run `linguist` CLI inside Docker during Action. Requires cloning repos into Action storage (same cost as lowlighter/metrics). More accurate than go-enry (Bayesian disambiguation). **Trade-off:** Heavy; docker image size; slower startup. **Verdict:** Over-engineered for our use case; go-enry sufficient.
### Idea 4: Fetch `.gitattributes` explicitly
One additional `GET /repos/{o}/{r}/contents/.gitattributes` per repo. Parse it locally; override go-enry classification for files tagged `linguist-vendored` or `linguist-generated`. **Cost:** 1 call/repo; minimal. **Accuracy gain:** ~5-10% fewer false positives. **Verdict:** Worth doing; low cost, reasonable gain.
### Idea 5: Combine REST per-commit with GraphQL for byte-size fallback
Query `GET /repos/{o}/{r}/languages` as secondary validation: if REST commit analysis yields unexpected results (e.g., 99% JavaScript despite repo being mostly Markdown), fall back to byte-size weighted stats. **Trade-off:** Adds complexity; not needed if go-enry is trusted. **Verdict:** Skip for v1; revisit after validation.
---
## Recommendation
**Proceed with: REST per-commit + go-enry, with optional `.gitattributes` override**
**Exact approach:**
1. For each user repo, fetch all commits (paginated, limit configurable; default 100/repo)
2. Per commit SHA: `GET /repos/{o}/{r}/commits/{sha}` → extract `files[].filename`
3. Classify each filename via `go-enry/v2` extension-based detection
4. Accumulate commit count + lines added per language per repo
5. **Optional:** Fetch `.gitattributes` per repo; override classifications for files marked `linguist-generated` / `linguist-vendored`
6. Filter output by language type (exclude `Unknown` / `Data`; include `Programming`, `Markup`, optionally `Prose`)
7. Rank by commit count (primary) or lines added (secondary weighting option)
**Why this beats alternatives:**
- **vs. byte-size (GRS):** Commit-weighted, not size-weighted → accurate for mixed-language repos
- **vs. cloning (lowlighter):** No repo clone → safe for Action runner, completes in <10 sec for typical user
- **vs. sampling:** 100% coverage, not statistical estimate
- **Cost:** ~100 REST calls (1K limit budget = 10x headroom); well-designed for scale
**Estimated accuracy:** 90-95% (limited by extension-only classification; `.gitattributes` override lifts this to 95-98%)
---
## Unresolved questions
- Should **Prose** (Markdown, AsciiDoc, reStructuredText) be included in default output, or only **Programming + Markup**? (lowlighter defaults to Programming + Markup; users opt-in to Prose)
- Do we want **lines-added weighted** language ranking, or just **commit-count weighted**? (e.g., 1-line typo fix vs. 500-line refactor)
- Should **vendored/generated code** be excluded by default, or configurable? (`.gitattributes` parsing adds 1 call/repo; users likely don't care)
- How do we handle repos with **no commits by user** (e.g., organization repos where user never committed)? (Skip? Count PR reviews? Leave blank?)
- Fallback behavior if **go-enry can't classify a file** (e.g., custom `.lisp` variant): count as "Other" or skip?
---
**Sources:**
- [anuraghazra/github-readme-stats](https://github.com/anuraghazra/github-readme-stats)
- [lowlighter/metrics](https://github.com/lowlighter/metrics)
- [go-enry/go-enry](https://github.com/go-enry/go-enry)
- [GitHub REST API: Get a commit](https://docs.github.com/en/rest/commits/commits)
- [GitHub REST API: Rate limits](https://docs.github.com/en/rest/using-the-rest-api/rate-limits-for-the-rest-api)
@@ -0,0 +1,148 @@
# Profile Stats Tools — Commit Attribution Survey (Round 2)
## Summary
Investigated 6 profile-stats projects + 2 template generators + BigQuery aggregate tool. **No project solves per-commit language attribution better than proposed REST + go-enry approach.** Two categories found: (1) **byte-size only** (GRS, jstrieb/github-stats, TraceLD), acknowledging the problem but not fixing it; (2) **WakaTime-only** (anmol098, athul), bypassing GitHub language API entirely via editor telemetry. One theoretical per-commit CLI mentioned in DEV.to discussions but repo not found in public GitHub. **Recommendation unchanged:** REST per-commit + go-enry + optional `.gitattributes` override is the frontier.
---
## Project-by-project findings
| Project | Repo URL | Primary Lang | Algorithm | Solves Commit Attribution? |
|---------|----------|--------------|-----------|---------------------------|
| **jstrieb/github-stats** | github.com/jstrieb/github-stats | Python | GraphQL `languages(orderBy:SIZE)` | No — byte-size only; explicit TODO: "Improve languages to scale by number of contributions" |
| **anmol098/waka-readme-stats** | github.com/anmol098/waka-readme-stats | Python | WakaTime API editor telemetry | Yes, but orthogonal — not GitHub stats; bypasses the problem entirely |
| **athul/waka-readme** | github.com/athul/waka-readme | Python | WakaTime API (simpler wrapper) | Yes, but orthogonal — WakaTime-only |
| **yoshi389111/github-profile-3d-contrib** | github.com/yoshi389111/github-profile-3d-contrib | TypeScript | GitHub GraphQL contributions calendar | N/A — contributions only, no languages |
| **sarthakhingankar/github-profile-readme-generator** | (repo not found / archived) | — | — | — |
| **rahul-jha98/github-profile-readme-generator** | (repo not found / archived) | — | — | — |
| **TraceLD/github-user-language-breakdown** | github.com/TraceLD/github-user-language-breakdown | TypeScript | Byte-size aggregation (`/api/langs`) | No — frontend app; backend not audited but calls generic language API |
| **madnight/githut** | github.com/madnight/githut | JavaScript | Google BigQuery public GitHub dataset | No — aggregate repo stats, not per-user commits |
---
## Detailed findings
### jstrieb/github-stats
- **Active:** Yes (last push 2026-04-18, 3.4K stars)
- **What it does:** Python CLI → GraphQL user repos → per-repo `languages(first:10, orderBy:{SIZE})` → accumulate byte-size
- **Commit attribution:** Explicitly does NOT; source has TODO comment: `# TODO: Improve languages to scale by number of contributions to`
- **Cost:** 1 GraphQL query per 100 repos
- **Novel:** Handles private repos via token; otherwise standard byte-size approach
- **Verdict:** Aware of the problem, chose not to solve it (likely due to REST rate-limit concerns)
### anmol098/waka-readme-stats
- **Active:** Yes (last push 2026-04-14, 3.9K stars)
- **What it does:** GitHub Action → fetches WakaTime API → displays editor time-in-language breakdown
- **Commit attribution:** **Yes** — but only if user has WakaTime installed and active
- **Cost:** WakaTime telemetry (user's editor plugin); no GitHub API calls for language stats
- **Solves blog repo problem?** YES, because WakaTime tracks actual editor time, not bytes. A user editing 3 JS files in a Markdown blog gets attributed to JS only if they actually spent time in JS editor.
- **Limitation:** Requires WakaTime setup; doesn't work offline; not a pure GitHub solution
- **Verdict:** Different UX. Solves the problem *orthogonally* — doesn't use GitHub language API at all.
### athul/waka-readme
- **Active:** Yes (last push 2026-02-18, 1.8K stars)
- **What it does:** Simpler WakaTime wrapper; GitHub Action fetches WakaTime API only
- **Commit attribution:** **Yes** — same as anmol098, via WakaTime editor telemetry
- **Verdict:** WakaTime alternative; no GitHub language innovation
### TraceLD/github-user-language-breakdown
- **Active:** Yes (last push 2025-02-27, 55 stars)
- **What it does:** Frontend (Vite + TypeScript) → calls `/api/langs` backend → returns byte-size breakdown
- **Commit attribution:** No — backend not audited; frontend aggregates by bytes
- **Verdict:** Small project; no novel approach
### madnight/githut
- **Active:** Inactive (last push 2024-04-03, 1K stars)
- **What it does:** Google BigQuery + GitHub public dataset → aggregate language stats across all public repos
- **Commit attribution:** No — designed for ecosystem trends, not per-user stats
- **Verdict:** Enterprise-scale analysis tool; not relevant to individual profile cards
### yoshi389111/github-profile-3d-contrib
- **Active:** Yes (last push 2026-04-15, 1.6K stars)
- **What it does:** 3D contribution calendar visualization
- **Language stats:** N/A — contributions only
- **Verdict:** Orthogonal to language problem
---
## The mysterious per-commit CLI
DEV.to post by maxfriedmann (Feb 2026): "I built a CLI to see my real GitHub language stats — does something like this already exist?"
> *"scanning every commit you've personally authored on GitHub — including private repos — and calculates how many lines you've changed per programming language"*
- **Repo:** Could not locate in public GitHub
- **Likely approach:** REST `GET /repos/{o}/{r}/commits` + parse diff → linguist/go-enry classify files → aggregate lines per language
- **If it exists:** This is exactly the REST per-commit + linguist approach proposed in prior report
- **Status:** Appears to be personal/private project or lost to time
- **Significance:** Validates that the proposed approach is feasible and novel enough to be noteworthy 4 months ago
---
## Language classification ecosystem — current state
| Approach | Maturity | Solves commit problem? | Cost | Trade-offs |
|----------|----------|----------------------|------|------------|
| **Byte-size (GitHub default)** | Stable, no code needed | No | GraphQL 1 call/100 repos | Simple; fundamentally broken for mixed-language repos |
| **Repository language count** | Stable (vn7n24fzkq) | No (only counts repo count, not commits) | Same as above | Slightly less broken; still size-biased |
| **WakaTime editor telemetry** | Requires opt-in | Yes, but not GitHub-only | User's telemetry; 0 GitHub API calls | Accurate; private; off-chain; requires user setup |
| **REST per-commit + go-enry** | Not yet packaged; proposed | Yes (90%–95% accuracy) | 1 REST call/commit (100/hr budget) | Fast; no clone; extension-limited; no `.gitattributes` support |
| **REST per-commit + go-enry + `.gitattributes`** | Proposed (this project) | Yes (95%–98% accuracy) | +1 REST call/repo for attrs | Same + minimal overhead for ~5% accuracy gain |
| **Clone + linguist Ruby gem** | Stable (lowlighter/metrics) | Yes (99% accuracy) | 15 sec timeout; storage | Accurate; slow; heavy; clones entire repo |
| **Clone + linguist-js** | Stable (lowlighter/metrics) | Yes (99% accuracy) | 15 sec timeout; storage | Same as Ruby gem |
---
## Did anyone solve it better?
**No.** The landscape is:
1. **Byte-weighted GitHub stats** — easy, broken, everyone does it (GRS, jstrieb, others)
2. **WakaTime editor telemetry** — orthogonal; requires opt-in; doesn't use GitHub API
3. **Cloning repos** — accurate but slow (lowlighter/metrics)
4. **REST per-commit + go-enry** — middle ground, not yet packaged as a standalone tool
**Null result:** No project uses `GET /repos/{o}/{r}/commits/{sha}` + go-enry/linguist for per-commit classification and packages it as a reusable tool. The DEV.to CLI mentions this exists but repo not found in public GitHub. This suggests either: (a) it's private/personal; (b) abandoned; (c) author hasn't open-sourced it.
---
## Implication for ghstats
**Prior recommendation stands.** REST per-commit + go-enry is:
- **Frontier-tier** — no packaged competitor exists yet
- **Feasible** — go-enry is performant; REST budgets fit; no cloning overhead
- **Accurate enough** — 90–95% for extension-only; 95–98% with `.gitattributes`
- **Testable** — can validate against lowlighter/metrics cloned results (regression test)
**Action:** Proceed with REST per-commit + go-enry implementation for ghstats v1. Add `.gitattributes` override as Phase 2 if accuracy feedback demands it.
---
## New ideas surfaced
- **Idea A:** Could reach out to maxfriedmann (DEV.to) to find/acquire their per-commit CLI code if it's actually been built. Might skip months of engineering.
- **Idea B:** Offer ghstats as a GitHub Action alternative to WakaTime for users who don't want editor telemetry but want accurate stats. Differentiate: "GitHub-only, no telemetry setup, REST-fast."
- **Idea C:** Add a `.gitattributes` fetcher as an optional HTTP call per repo; toggle via config. Minimal cost for significant accuracy gain on projects that use `linguist-*` directives.
---
## Unresolved questions
- **What repo is the DEV.to per-commit CLI?** Could it be claimed, forked, or improved?
- **Should ghstats include Prose (Markdown)?** Default to Programming+Markup only, with opt-in for Prose?
- **How to handle repos with zero user commits?** Skip, count PR reviews, or leave blank?
- **Fallback behavior if go-enry can't classify a file?** Count as "Other" or skip?
- **Should `.gitattributes` parsing be v1 or v2 feature?** (Adds 1 REST call/repo; ~5% accuracy gain)
---
**Sources:**
- [jstrieb/github-stats](https://github.com/jstrieb/github-stats)
- [anmol098/waka-readme-stats](https://github.com/anmol098/waka-readme-stats)
- [athul/waka-readme](https://github.com/athul/waka-readme)
- [yoshi389111/github-profile-3d-contrib](https://github.com/yoshi389111/github-profile-3d-contrib)
- [TraceLD/github-user-language-breakdown](https://github.com/TraceLD/github-user-language-breakdown)
- [madnight/githut](https://github.com/madnight/githut)
- [I built a CLI to see my real GitHub language stats (DEV.to)](https://dev.to/maxfriedmann/i-built-a-cli-to-see-my-real-github-language-stats-does-something-like-this-already-exist-1n18)
- [go-enry/go-enry](https://github.com/go-enry/go-enry)
- [GitHub Docs: About repository languages](https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-repository-languages)