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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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