Skip to content

Bug: rating circle arc renders incorrectly when CSS animations are frozen (GitHub README img context) #75

Description

@davencode-dev

Description

When the SVG card is embedded as an <img> in a GitHub profile README, GitHub renders it through a sandboxed proxy (camo) where CSS animations do not execute. The card is frozen at currentTime: 0 — the very start of all @keyframe animations.

This causes the .rating-circle to render with stroke-dashoffset: 250 (the from value), which is equal to the stroke-dasharray: 250. Because the stroke-dasharray value (250) doesn't precisely match the circle's actual circumference (2π × 38 ≈ 238.76 px), the arc appears at an incorrect position — showing a strangely clipped or offset partial arc rather than being invisible (as intended before animation runs) or fully drawn (as intended after animation completes).

Steps to Reproduce

  1. Add ![stats](https://github-contribution-stats.vercel.app/api/?username=<any_user>) to a GitHub profile README.
    1. View the profile page on GitHub.
    1. The rating circle arc appears broken/misaligned.
      You can also observe it by loading the SVG URL directly and checking animation state:
document.querySelector('.rating-circle').getAnimations()
// → [{ animationName: 'ratingProgressAnimation', playState: 'running', currentTime: 0 }]
// currentTime never advances — animations are frozen

Root Cause

In src/CardTemplates.js, the .rating-circle CSS class has:

.rating-circle {
  stroke-dasharray: 250;
  /* No stroke-dashoffset set here — defaults to 0 */
  animation: ratingProgressAnimation 1s forwards ease-in-out;
}

@keyframes ratingProgressAnimation {
  from { stroke-dashoffset: 250; }
  to   { stroke-dashoffset: <rating-progress-value>; }
}

When animations don't run, the element uses its base CSS stroke-dashoffset: 0 (default), NOT the from keyframe value. So depending on the renderer, the arc either shows fully drawn (dashoffset 0 = full circle) or at the wrong position.

Additionally, stroke-dasharray: 250 does not match the real circumference of the circle (r=38, circumference ≈ 238.76), compounding the visual glitch.

Proposed Fix

Set the pre-animation final state directly on the element so the frozen/static rendering already looks correct. Move the stroke-dashoffset out of @keyframes from {} and into the base class, using the per-grade final value:

.rating-circle {
  stroke-dasharray: 238.76;           /* match actual circumference: 2π × 38 */
  stroke-dashoffset: <ratingsProgress>; /* set directly — no need for a "from" keyframe */
  animation: ratingProgressAnimation 1s forwards ease-in-out;
}

@keyframes ratingProgressAnimation {
  from { stroke-dashoffset: 238.76; } /* start fully hidden */
  to   { stroke-dashoffset: <ratingsProgress>; }
}

Since ratingsProgress is already a template variable injected per-request (e.g. ${ratingsProgress}), simply adding stroke-dashoffset: ${ratingsProgress}; directly to .rating-circle in CardTemplates.js means the static/frozen state already shows the correct arc fill.

The same fix also applies to the transform-origin for the rotation — using cx/cy coordinates of 0 0 (or matching the actual circle center) would make the geometry cleaner.

Impact

All users embedding this card in GitHub READMEs see a broken rating circle. The fix is a one-line addition to CardTemplates.js with no breaking changes.

Reference

  • src/CardTemplates.js.rating-circle CSS class and ratingProgressAnimation keyframes
    • Circle element: <circle cx="-10" cy="8" r="38" />
      • Actual circumference: 2 × π × 38 ≈ 238.76

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions