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).
This commit is contained in:
2026-04-18 23:26:48 +07:00
parent 0310b7a2d9
commit d0c75d5ff1
7 changed files with 51 additions and 46 deletions
+6 -1
View File
@@ -110,7 +110,12 @@ runs:
3. On green, GHCR push + cross-platform binary artifacts happen automatically.
4. 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.
5. **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.
## Rollback
+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))