Is your feature request related to a problem? Please describe.
I'm always frustrated when I embed the CommitPulse badge on a light background
or a custom portfolio page and the towers look too dense and opaque — there is
absolutely no way to reduce the tower fill opacity without forking the project
and hardcoding a value myself.
The tower face opacities in lib/svg/generator.ts are hardcoded constants
(faceOpacity.left, faceOpacity.right, faceOpacity.top) computed in
lib/svg/layout.ts. Every user gets the exact same opacity values regardless
of their embedding context:
- Developers embedding on dark GitHub READMEs → fine as-is
- Developers embedding on light portfolio sites → towers look too heavy
- Developers who want a "ghost city" semi-transparent aesthetic → impossible
without a fork
All other visual properties (bg, text, accent, radius, size, speed, scale,
border, gradient, shading) are controllable via URL parameters — opacity is
the only major visual property with no override.
Describe the solution you'd like
Add a ?opacity= URL parameter that applies a global scalar multiplier to all
tower face fill-opacity values, giving users full control over monolith density.
Accepted values: floats between 0.1 and 1.0
Default: 1.0 (no change to current behavior — fully backward compatible)
Usage examples:
?opacity=1.0 → default, fully opaque towers (current behavior)
?opacity=0.8 → slightly faded, great for light background embeds
?opacity=0.5 → semi-transparent ghost city aesthetic
?opacity=0.1 → near-invisible wireframe, purely architectural look
Example URLs:
https://commitpulse.vercel.app/api/streak?user=YOUR_USERNAME&opacity=0.5
https://commitpulse.vercel.app/api/streak?user=YOUR_USERNAME&opacity=0.8
Validation rules:
- Values below 0.1 → clamp to 0.1 (prevent fully invisible towers)
- Values above 1.0 → clamp to 1.0 (prevent values > 1 which are invalid SVG)
- Non-numeric or missing → fall back to 1.0 (default, backward compatible)
Implementation plan:
-
types/index.ts
Add opacity?: number to the BadgeParams interface
-
lib/validations.ts
Add toOpacityValue() transform function:
- Parse as float
- Clamp to [0.1, 1.0]
- Default to 1.0 for missing/invalid
Add opacity field to streakParamsSchema
-
lib/svg/generator.ts
In renderTowers(), apply the opacity scalar to all three face opacities:
leftFaceOpacity = Math.round(t.faceOpacity.left * opacity * 100) / 100
rightFaceOpacity = Math.round(t.faceOpacity.right * opacity * 100) / 100
topFaceOpacity = Math.round(t.faceOpacity.top * opacity * 100) / 100
Pass opacity through from generateSVG → renderTowers
-
app/api/streak/route.ts
Add opacity to the destructured parseResult.data and pass it into params
-
README.md
Add opacity row to the parameter reference table
Describe alternatives you've considered
-
Add opacity as part of the existing ?shading= boolean
→ Rejected: shading is an on/off flag for intensity-based face multipliers.
Merging opacity into it would make the param do two unrelated things and
break the single-responsibility principle of each URL parameter.
-
Add separate ?opacity_left=, ?opacity_right=, ?opacity_top= params
→ Rejected: three separate params for one visual concept is poor API design.
It adds complexity for users and maintenance burden for contributors.
A single scalar applied to all three faces covers 99% of use cases.
-
Only apply opacity to the top face (the most visible)
→ Rejected: partial opacity creates an inconsistent look where the tower
sides stay opaque but the top fades — the opposite of what users want.
The scalar must apply uniformly to all three faces.
-
Use CSS opacity on the entire tower element instead of fill-opacity
→ Rejected: CSS opacity affects the entire group including stroke and
particle effects, which breaks the ghost city wireframe aesthetic.
fill-opacity on individual faces gives surgical control.
Is your feature request related to a problem? Please describe.
I'm always frustrated when I embed the CommitPulse badge on a light background
or a custom portfolio page and the towers look too dense and opaque — there is
absolutely no way to reduce the tower fill opacity without forking the project
and hardcoding a value myself.
The tower face opacities in
lib/svg/generator.tsare hardcoded constants(
faceOpacity.left,faceOpacity.right,faceOpacity.top) computed inlib/svg/layout.ts. Every user gets the exact same opacity values regardlessof their embedding context:
without a fork
All other visual properties (bg, text, accent, radius, size, speed, scale,
border, gradient, shading) are controllable via URL parameters — opacity is
the only major visual property with no override.
Describe the solution you'd like
Add a ?opacity= URL parameter that applies a global scalar multiplier to all
tower face fill-opacity values, giving users full control over monolith density.
Accepted values: floats between 0.1 and 1.0
Default: 1.0 (no change to current behavior — fully backward compatible)
Usage examples:
?opacity=1.0 → default, fully opaque towers (current behavior)
?opacity=0.8 → slightly faded, great for light background embeds
?opacity=0.5 → semi-transparent ghost city aesthetic
?opacity=0.1 → near-invisible wireframe, purely architectural look
Example URLs:
https://commitpulse.vercel.app/api/streak?user=YOUR_USERNAME&opacity=0.5
https://commitpulse.vercel.app/api/streak?user=YOUR_USERNAME&opacity=0.8
Validation rules:
Implementation plan:
types/index.ts
Add opacity?: number to the BadgeParams interface
lib/validations.ts
Add toOpacityValue() transform function:
Add opacity field to streakParamsSchema
lib/svg/generator.ts
In renderTowers(), apply the opacity scalar to all three face opacities:
leftFaceOpacity = Math.round(t.faceOpacity.left * opacity * 100) / 100
rightFaceOpacity = Math.round(t.faceOpacity.right * opacity * 100) / 100
topFaceOpacity = Math.round(t.faceOpacity.top * opacity * 100) / 100
Pass opacity through from generateSVG → renderTowers
app/api/streak/route.ts
Add opacity to the destructured parseResult.data and pass it into params
README.md
Add opacity row to the parameter reference table
Describe alternatives you've considered
Add opacity as part of the existing ?shading= boolean
→ Rejected: shading is an on/off flag for intensity-based face multipliers.
Merging opacity into it would make the param do two unrelated things and
break the single-responsibility principle of each URL parameter.
Add separate ?opacity_left=, ?opacity_right=, ?opacity_top= params
→ Rejected: three separate params for one visual concept is poor API design.
It adds complexity for users and maintenance burden for contributors.
A single scalar applied to all three faces covers 99% of use cases.
Only apply opacity to the top face (the most visible)
→ Rejected: partial opacity creates an inconsistent look where the tower
sides stay opaque but the top fades — the opposite of what users want.
The scalar must apply uniformly to all three faces.
Use CSS opacity on the entire tower element instead of fill-opacity
→ Rejected: CSS opacity affects the entire group including stroke and
particle effects, which breaks the ghost city wireframe aesthetic.
fill-opacity on individual faces gives surgical control.