Skip to content

feat: Add ?opacity= URL parameter to control tower fill opacity globally #1936

@ChetanSenta

Description

@ChetanSenta

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:

  1. types/index.ts
    Add opacity?: number to the BadgeParams interface

  2. 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
  3. 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

  4. app/api/streak/route.ts
    Add opacity to the destructured parseResult.data and pass it into params

  5. README.md
    Add opacity row to the parameter reference table

Describe alternatives you've considered

  1. 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.

  2. 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.

  3. 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.

  4. 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.

Metadata

Metadata

Assignees

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions