Skip to content

Commit a2291dd

Browse files
authored
fix(svg): add accessible title and desc tags (JhaSourav07#116)
Fixes JhaSourav07#70 ## Summary Added semantic `<title>` and `<desc>` tags inside generated SVGs to improve accessibility for screen readers. ## Changes Made * Added `role="img"` to SVG elements * Added dynamic `<title>` tags * Added dynamic `<desc>` tags using contribution stats * Updated both standard and auto-theme SVG generators ## Pillar * [ ] 🎨 Pillar 1 — New Theme Design * [ ] 📐 Pillar 2 — Geometric SVG Improvement * [ ] 🕐 Pillar 3 — Timezone Logic Optimization * [x] 🛠️ Other (Bug fix, refactoring, docs) ## Visual Preview N/A — accessibility-focused update with no visual rendering changes. ## Checklist before requesting a review: * [x] I have read the `CONTRIBUTING.md` file. * [ ] I have tested these changes locally (`localhost:3000/api/streak?user=YOUR_USERNAME`). * [ ] I have run `npm run format` and `npm run lint` locally and resolved all errors (CI will fail otherwise). * [x] My commits follow the Conventional Commits format (e.g., `feat(themes): ...`, `fix(calculate): ...`). * [ ] I have updated `README.md` if I added a new theme or URL parameter. * [x] I have started the repo. * [x] I have made sure that i have only one commit to merge in this PR. * [x] The SVG output matches the CommitPulse "premium quality" aesthetic standard (no raw elements, smooth animations, correct fonts). ## Result Screen readers can now interpret contribution statistics without affecting visual rendering.
2 parents 73bdb22 + bc65568 commit a2291dd

1 file changed

Lines changed: 36 additions & 5 deletions

File tree

lib/svg/generator.ts

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,14 @@ function deterministicRandom(seed: string): number {
1515
}
1616
return (hash >>> 0) / 4294967296;
1717
}
18-
18+
function escapeXML(str: string): string {
19+
return str
20+
.replace(/&/g, '&amp;')
21+
.replace(/</g, '&lt;')
22+
.replace(/>/g, '&gt;')
23+
.replace(/"/g, '&quot;')
24+
.replace(/'/g, '&#39;');
25+
}
1926
function generateParticles(
2027
x: number,
2128
y: number,
@@ -148,6 +155,7 @@ export function generateSVG(
148155
if (params.autoTheme) {
149156
return generateAutoThemeSVG(stats, params, calendar);
150157
}
158+
const safeUser = escapeXML(params.user || 'GitHub User');
151159

152160
const bg = `#${(params.bg || '0d1117').replace('#', '')}`;
153161
const accent = `#${(params.accent || '00ffaa').replace('#', '')}`;
@@ -216,7 +224,18 @@ export function generateSVG(
216224
: '';
217225

218226
return `
219-
<svg xmlns="http://www.w3.org/2000/svg" width="600" height="420" viewBox="0 0 600 420" fill="none">
227+
<svg
228+
xmlns="http://www.w3.org/2000/svg"
229+
width="600"
230+
height="420"
231+
viewBox="0 0 600 420"
232+
fill="none"
233+
role="img"
234+
>
235+
<title>CommitPulse Stats for ${safeUser}</title>
236+
<desc>
237+
${params.user || 'This user'} has ${stats.totalContributions} total contributions and a longest streak of ${stats.longestStreak} days.
238+
</desc>
220239
<defs>
221240
<filter id="glow" x="-50%" y="-50%" width="200%" height="200%">
222241
<feGaussianBlur stdDeviation="5" result="blur" />
@@ -287,7 +306,7 @@ export function generateSVG(
287306
<text y="40" class="stats">${stats.longestStreak}</text>
288307
</g>
289308
290-
<text x="300" y="50" text-anchor="middle" class="title">${params.user?.toUpperCase() || ''}</text>
309+
<text x="300" y="50" text-anchor="middle" class="title">${safeUser.toUpperCase()}</text>
291310
292311
<rect x="100" y="60" width="400" height="1" fill="${accent}" fill-opacity="0.3">
293312
<animate attributeName="y" values="80;320;80" dur="${params.speed || '8s'}" repeatCount="indefinite" />
@@ -313,6 +332,7 @@ function generateAutoThemeSVG(
313332
): string {
314333
const light = AUTO_LIGHT_THEME;
315334
const dark = AUTO_DARK_THEME;
335+
const safeUser = escapeXML(params.user || 'GitHub User');
316336

317337
const selectedFont = params.font
318338
? FONT_MAP[params.font.toLowerCase()] || '"JetBrains Mono", monospace'
@@ -363,7 +383,18 @@ function generateAutoThemeSVG(
363383
}
364384

365385
return `
366-
<svg xmlns="http://www.w3.org/2000/svg" width="600" height="420" viewBox="0 0 600 420" fill="none">
386+
<svg
387+
xmlns="http://www.w3.org/2000/svg"
388+
width="600"
389+
height="420"
390+
viewBox="0 0 600 420"
391+
fill="none"
392+
role="img"
393+
>
394+
<title>CommitPulse Stats for ${safeUser} </title>
395+
<desc>
396+
${params.user || 'This user'} has ${stats.totalContributions} total contributions and a longest streak of ${stats.longestStreak} days.
397+
</desc>
367398
<defs>
368399
<filter id="glow" x="-50%" y="-50%" width="200%" height="200%">
369400
<feGaussianBlur stdDeviation="5" result="blur" />
@@ -455,7 +486,7 @@ function generateAutoThemeSVG(
455486
<text y="40" class="stats">${stats.longestStreak}</text>
456487
</g>
457488
458-
<text x="300" y="50" text-anchor="middle" class="title">${params.user?.toUpperCase() || ''}</text>
489+
<text x="300" y="50" text-anchor="middle" class="title">${safeUser.toUpperCase()}</text>
459490
460491
<rect x="100" y="60" width="400" height="1" class="cp-accent-fill" fill-opacity="0.3">
461492
<animate attributeName="y" values="80;320;80" dur="${params.speed || '8s'}" repeatCount="indefinite" />

0 commit comments

Comments
 (0)