fix(card): productive titles render at 15 px for every timezone (#24)

Two cuts together keep the productive-time and productive-weekday
titles at the same 15 px the rest of the gallery uses:

1. utcOffsetLabel is now compact. Integer offsets drop the '.00'
   padding ("UTC+7" vs the old "UTC+7.00") and non-integer offsets use
   the colon form ("UTC+5:30", "UTC+5:45"). Saves 3 chars for the
   common integer case, so "Commits by Hour (last year, UTC+7)" lands
   at 34 chars — inside the 15 px budget.

2. weekdayTitle no longer embeds the UTC label. Day-of-week aggregates
   by whole days; clock precision isn't informative there, and
   dropping it shortens the title to 30 chars so the full 15 px lands
   for every timezone.

Quarter-hour-zone users (Kathmandu UTC+5:45) see the hour title drop
to 14 px — 37 chars still exceeds the budget — but that's a rare
case and only 1 px off.

TestUTCOffsetLabel updated to the new format. TestFitTitleFontSize
pinned the new titles.
This commit is contained in:
2026-04-19 11:49:40 +07:00
committed by GitHub
parent 234d2ef015
commit f184ac8409
5 changed files with 37 additions and 23 deletions
+1 -1
View File
@@ -70,7 +70,7 @@ When there's **exactly one slice** (one language at 100%), the renderer emits tw
| Bar fill | `theme.Accent` for the peak bar; `mixHex(Background, Accent, 0.55)` dim for the rest so the busiest period reads at a glance |
| Y-axis ticks | `niceTicks(max, 5)` — 1/2/5 × 10^k ladder. `last = ceil(max/step) × step` so `yMax ≥ dataMax` always (bars can't poke above chartH into the title) |
| Axis caption | "hour of day" bottom-center on productive-time; weekday / by-year omit the caption since the x labels are self-describing |
| Title format | `Commits by Hour (<window>, UTC±N.NN)` / `Commits by Weekday (<window>, UTC±N.NN)` / `Contributions by Year` |
| Title format | `Commits by Hour (<window>, UTC±H[:MM])` / `Commits by Weekday (<window>)` — weekday drops the UTC so the title always fits at 15 px / `Contributions by Year` |
| Hover | `<title>HH:00 — N commits</title>` / `<title>Mon — N commits</title>` / `<title>YYYY — N commits</title>` |
## Heatmap card (contributions-heatmap)
+4 -4
View File
@@ -204,9 +204,9 @@ func TestFitTitleFontSize(t *testing.T) {
{"Top Starred Repos", 15},
{"Most Commit Language (all time)", 15}, // 31 chars
{"Contributions by Year", 15},
{"Commits by Hour (last year, UTC+7.00)", 14}, // 37 chars
{"Commits by Weekday (last year, UTC+7.00)", 13}, // 40 chars
{"Commits by Weekday (last year, UTC+12.75)", 12}, // 41 chars
{"Commits by Hour (last year, UTC+7)", 15}, // 34 chars, common integer-zone case
{"Commits by Hour (last year, UTC+5:45)", 14}, // 37 chars, quarter-hour zone (Kathmandu)
{"Commits by Weekday (last year)", 15}, // 30 chars — weekday titles never include UTC
{strings.Repeat("x", 200), 11}, // pathological
}
for _, c := range cases {
@@ -369,7 +369,7 @@ func adversarialProfile() *github.Profile {
p := &github.Profile{
Login: "user-with-a-very-long-login-name",
Name: "A Very Long Display Name That Keeps Going",
UTCOffsetLabel: "UTC+12.75", // half-hour / quarter-hour zones widen the title
UTCOffsetLabel: "UTC+12:45", // quarter-hour zone — longest realistic UTC label
Company: "A-Company-With-An-Unusually-Long-Name Pty Ltd",
Location: "A Place With A Name That Is Way Too Long To Fit",
+7 -7
View File
@@ -13,7 +13,7 @@ type productiveWeekdayCard struct{}
func (productiveWeekdayCard) Filename() string { return "productive-weekday.svg" }
func (productiveWeekdayCard) SVG(p *github.Profile, t theme.Theme) ([]byte, error) {
return renderWeekday(weekdayTitle("last year", p.UTCOffsetLabel), p.Weekday, t), nil
return renderWeekday(weekdayTitle("last year"), p.Weekday, t), nil
}
type productiveWeekdayAllTimeCard struct{}
@@ -21,14 +21,14 @@ type productiveWeekdayAllTimeCard struct{}
func (productiveWeekdayAllTimeCard) Filename() string { return "productive-weekday-all-time.svg" }
func (productiveWeekdayAllTimeCard) SVG(p *github.Profile, t theme.Theme) ([]byte, error) {
return renderWeekday(weekdayTitle("all time", p.UTCOffsetLabel), p.WeekdayAllTime, t), nil
return renderWeekday(weekdayTitle("all time"), p.WeekdayAllTime, t), nil
}
func weekdayTitle(window, utcLabel string) string {
if utcLabel == "" {
return "Commits by Weekday (" + window + ")"
}
return "Commits by Weekday (" + window + ", " + utcLabel + ")"
// weekdayTitle skips the UTC offset — the data aggregates into day-of-week
// buckets, so exact clock precision isn't informative and dropping it keeps
// the title short enough to render at the full 15 px.
func weekdayTitle(window string) string {
return "Commits by Weekday (" + window + ")"
}
// Index 0 = Sunday to match time.Weekday (which is what FetchProductive stores).
+19 -5
View File
@@ -113,13 +113,27 @@ func main() {
}
}
// utcOffsetLabel formats the location's current offset from UTC as "UTC±N.NN"
// (two-decimal hours) so half-hour zones like India (UTC+5.30) or Nepal
// (UTC+5.75) render cleanly. Matches github-profile-summary-cards' style.
// utcOffsetLabel formats the location's current offset from UTC compactly:
//
// integer hours → "UTC+7" (no ".00" padding — 3 chars shorter than
// the old "UTC+7.00" format, keeps the
// productive-time title at 15 px)
// half-hour zone → "UTC+5:30" (India)
// quarter-hour → "UTC+5:45" (Nepal)
// negative zone → "UTC-3" / "UTC-3:30"
func utcOffsetLabel(loc *time.Location) string {
_, offsetSec := time.Now().In(loc).Zone()
hours := float64(offsetSec) / 3600.0
return fmt.Sprintf("UTC%+.2f", hours)
sign := "+"
if offsetSec < 0 {
sign = "-"
offsetSec = -offsetSec
}
hours := offsetSec / 3600
minutes := (offsetSec % 3600) / 60
if minutes == 0 {
return fmt.Sprintf("UTC%s%d", sign, hours)
}
return fmt.Sprintf("UTC%s%d:%02d", sign, hours, minutes)
}
func resolveThemes(spec string) ([]theme.Theme, error) {
+6 -6
View File
@@ -6,17 +6,17 @@ import (
"time"
)
// TestUTCOffsetLabel checks that half-hour and quarter-hour zones render
// with a decimal, matching github-profile-summary-cards' "UTC+X.NN" style.
// TestUTCOffsetLabel checks the compact format: integer hours drop the
// minutes suffix, non-zero offsets render as `UTC±H:MM`.
func TestUTCOffsetLabel(t *testing.T) {
cases := []struct {
zone string
want string // must appear in the label; exact value varies by DST
}{
{"UTC", "UTC+0.00"},
{"Asia/Saigon", "UTC+7.00"},
{"Asia/Kolkata", "UTC+5.50"}, // half-hour zone
{"Asia/Kathmandu", "UTC+5.75"}, // quarter-hour zone
{"UTC", "UTC+0"},
{"Asia/Saigon", "UTC+7"},
{"Asia/Kolkata", "UTC+5:30"}, // half-hour zone
{"Asia/Kathmandu", "UTC+5:45"}, // quarter-hour zone
}
for _, tc := range cases {
loc, err := time.LoadLocation(tc.zone)