Skip to content

Commit fa58e90

Browse files
author
ChetanSenta
committed
fix(generator): replace fixed width/height with width=100% for responsive SVG scaling on mobile
1 parent 6ede310 commit fa58e90

3 files changed

Lines changed: 50 additions & 48 deletions

File tree

lib/svg/generator.additional.test.ts

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -285,10 +285,32 @@ describe('[Issue] generateVersusSVG — zero existing test coverage', () => {
285285
expect(svg).toContain('stroke-dasharray="4 4"');
286286
});
287287

288-
it('total width is double the single card width for medium size', () => {
288+
it('total width viewBox is double the single card width for medium size', () => {
289289
const svg = generateVersusSVG(stats1, stats2, versusParams, calendar1, calendar2);
290-
// Medium size: SVG_WIDTH=600, versus = 600*2=1200
291-
expect(svg).toContain('width="1200"');
290+
expect(svg).toContain('viewBox="0 0 1200 420"');
291+
expect(svg).toContain('width="100%"');
292+
});
293+
294+
it('renders correct viewBox for small size in versus SVG', () => {
295+
const svg = generateVersusSVG(
296+
stats1,
297+
stats2,
298+
{ ...versusParams, size: 'small' },
299+
calendar1,
300+
calendar2
301+
);
302+
expect(svg).toContain('viewBox="0 0 800 280"');
303+
});
304+
305+
it('renders correct viewBox for large size in versus SVG', () => {
306+
const svg = generateVersusSVG(
307+
stats1,
308+
stats2,
309+
{ ...versusParams, size: 'large' },
310+
calendar1,
311+
calendar2
312+
);
313+
expect(svg).toContain('viewBox="0 0 1600 560"');
292314
});
293315

294316
it('includes total contribution counts from both users in <desc>', () => {

lib/svg/generator.test.ts

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -748,37 +748,35 @@ describe('generateSVG', () => {
748748
});
749749

750750
describe('SVG dimensions per size', () => {
751-
it('renders width="600" and height="420" for medium size (default)', () => {
751+
it('renders responsive width="100%" for medium size (default)', () => {
752752
const svg = generateSVG(
753753
mockStats,
754754
{ user: 'avi', size: 'medium' } as unknown as BadgeParams,
755755
mockCalendar
756756
);
757-
758-
expect(svg).toContain('width="600"');
759-
expect(svg).toContain('height="420"');
757+
expect(svg).toContain('width="100%"');
758+
// viewBox should still carry the correct pixel dimensions
759+
expect(svg).toContain('viewBox="0 0 600 420"');
760760
});
761761

762-
it('renders width="400" and height="280" for small size', () => {
762+
it('renders responsive width="100%" for small size with correct viewBox', () => {
763763
const svg = generateSVG(
764764
mockStats,
765765
{ user: 'avi', size: 'small' } as unknown as BadgeParams,
766766
mockCalendar
767767
);
768-
769-
expect(svg).toContain('width="400"');
770-
expect(svg).toContain('height="280"');
768+
expect(svg).toContain('width="100%"');
769+
expect(svg).toContain('viewBox="0 0 400 280"');
771770
});
772771

773-
it('renders width="800" and height="560" for large size', () => {
772+
it('renders responsive width="100%" for large size with correct viewBox', () => {
774773
const svg = generateSVG(
775774
mockStats,
776775
{ user: 'avi', size: 'large' } as unknown as BadgeParams,
777776
mockCalendar
778777
);
779-
780-
expect(svg).toContain('width="800"');
781-
expect(svg).toContain('height="560"');
778+
expect(svg).toContain('width="100%"');
779+
expect(svg).toContain('viewBox="0 0 800 560"');
782780
});
783781
});
784782

lib/svg/generator.ts

Lines changed: 15 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -500,7 +500,7 @@ export function generateSVG(
500500
const mainAccentHex = mainAccent.startsWith('#') ? mainAccent : `#${mainAccent}`;
501501

502502
return `
503-
<svg xmlns="http://www.w3.org/2000/svg" width="${W}" height="${H}" viewBox="0 0 ${W} ${H}" fill="none" role="img">
503+
<svg xmlns="http://www.w3.org/2000/svg" width="100%" viewBox="0 0 ${W} ${H}" fill="none" role="img">
504504
${renderHeader(safeUser, stats, sf, params)}
505505
${renderStyle(selectedFont, statsFont, googleFontsImport, text, mainAccentHex, sf)}
506506
<rect width="${W}" height="${H}" rx="${radius}" fill="${params.hideBackground ? 'transparent' : bg}" ${borderAttr} />
@@ -546,8 +546,7 @@ function generateAutoThemeSVG(
546546
return `
547547
<svg
548548
xmlns="http://www.w3.org/2000/svg"
549-
width="${W}"
550-
height="${H}"
549+
width="100%"
551550
viewBox="0 0 ${W} ${H}"
552551
fill="none"
553552
role="img"
@@ -891,13 +890,13 @@ export function generateWrappedSVG(
891890
892891
.title-user { font-family: ${selectedFont || '"Syncopate", sans-serif'}; font-weight: 700; font-size: 13px; letter-spacing: 2.5px; opacity: 0.85; }
893892
.title-wrapped { font-family: ${selectedFont || '"Syncopate", sans-serif'}; font-weight: 700; font-size: 13px; letter-spacing: 2.5px; }
894-
893+
895894
.total-commits { font-family: ${statsFont}; font-size: 46px; font-weight: 700; }
896895
.total-label { font-family: "Roboto", sans-serif; font-size: 9.5px; font-weight: 700; letter-spacing: 1.5px; opacity: 0.5; }
897-
896+
898897
.grid-label { font-family: "Roboto", sans-serif; font-size: 9px; font-weight: 700; letter-spacing: 1.5px; opacity: 0.5; }
899898
.grid-val { font-family: ${statsFont}; font-size: 14.5px; font-weight: 600; }
900-
899+
901900
.scan-line {
902901
animation: scan-sweep var(--scan-speed, 8s) linear infinite;
903902
transform-box: fill-box;
@@ -914,12 +913,10 @@ export function generateWrappedSVG(
914913
915914
<rect width="420" height="260" rx="${radius}" ${rectFill} ${borderStroke} />
916915
917-
<!-- Background holographic mini-monolith -->
918916
<g opacity="0.8">
919917
${bgTowersMarkup}
920918
</g>
921919
922-
<!-- Horizontal holographic scan-line -->
923920
<rect
924921
x="15"
925922
y="15"
@@ -931,48 +928,37 @@ export function generateWrappedSVG(
931928
style="--scan-speed: ${params.speed || '8s'};"
932929
/>
933930
934-
<!-- Header -->
935931
<g transform="translate(25, 45)">
936932
<text x="0" y="0" class="title-user" ${textClass}>${safeUser.toUpperCase()}'S GRIND</text>
937933
<text x="370" y="0" text-anchor="end" class="title-wrapped" ${accentClass}>${year} WRAPPED</text>
938934
<line x1="0" y1="12" x2="370" y2="12" stroke="${params.autoTheme ? 'var(--cp-accent)' : accent}" stroke-opacity="0.15" stroke-width="1" />
939935
</g>
940936
941-
<!-- Left Stats Block (Total contributions) -->
942937
<g transform="translate(25, 120)">
943938
<text x="0" y="15" class="total-commits" ${accentClass} filter="url(#glow)">${stats.totalContributions}</text>
944939
<text x="2" y="38" class="total-label" ${textClass}>TOTAL CONTRIBUTIONS</text>
945940
</g>
946941
947-
<!-- Vertical Divider -->
948942
<line x1="185" y1="80" x2="185" y2="230" stroke="${params.autoTheme ? 'var(--cp-accent)' : accent}" stroke-opacity="0.12" stroke-width="1" stroke-dasharray="3 3" />
949943
950-
<!-- Right Section: 2x2 Spaced Grid -->
951-
<!-- Row 1: Top Language (Col 1) and Weekend Grind (Col 2) -->
952944
<g transform="translate(210, 80)">
953-
<!-- Top Language -->
954945
<g transform="translate(0, 20)">
955946
<text x="0" y="0" class="grid-label" ${textClass}>TOP LANGUAGE</text>
956947
<text x="0" y="20" class="grid-val" ${accentClass}>${stats.topLanguage || 'Unknown'}</text>
957948
</g>
958949
959-
<!-- Weekend Grind Progress Arc -->
960950
<g transform="translate(130, 20)">
961951
<text x="0" y="0" class="grid-label" ${textClass}>WEEKEND GRIND</text>
962952
<g transform="translate(25, 24)">
963-
<!-- Background Track -->
964953
<circle cx="0" cy="0" r="14" stroke="${params.autoTheme ? 'var(--cp-text)' : text}" stroke-opacity="0.1" stroke-width="2.5" fill="none" />
965-
<!-- Progress Bar -->
966954
<circle cx="0" cy="0" r="14" stroke="${params.autoTheme ? 'var(--cp-accent)' : accent}" stroke-width="3" fill="none"
967955
stroke-dasharray="${circ.toFixed(2)}" stroke-dashoffset="${strokeDashoffset.toFixed(2)}"
968956
stroke-linecap="round" transform="rotate(-90)" />
969-
<!-- Text inside progress circle -->
970957
<text x="0" y="3.5" text-anchor="middle" font-family="${statsFont}" font-size="9" font-weight="700" ${textClass}>${clampedRatio}%</text>
971958
</g>
972959
</g>
973960
</g>
974961
975-
<!-- Row 2: Peak Day -->
976962
<g transform="translate(210, 150)">
977963
<text x="0" y="0" class="grid-label" ${textClass}>PEAK DAY</text>
978964
<text x="0" y="20" class="grid-val" ${textClass}>
@@ -981,7 +967,6 @@ export function generateWrappedSVG(
981967
</text>
982968
</g>
983969
984-
<!-- Row 3: Busiest Month -->
985970
<g transform="translate(210, 205)">
986971
<text x="0" y="0" class="grid-label" ${textClass}>BUSIEST MONTH</text>
987972
<text x="0" y="20" class="grid-val" ${textClass}>
@@ -1065,7 +1050,7 @@ function generateAutoThemeMonthlySVG(stats: MonthlyStats, params: BadgeParams):
10651050
.cp-text-fill { fill: var(--cp-text); color: var(--cp-text); }
10661051
.cp-accent-fill { fill: var(--cp-accent); color: var(--cp-accent); }
10671052
.cp-delta-fill { fill: ${stats.deltaAbsolute >= 0 ? 'var(--cp-accent)' : 'var(--cp-negative)'}; }
1068-
1053+
10691054
.title { font-family: ${selectedFont || '"Syncopate", sans-serif'}; fill: var(--cp-text); font-size: 14px; letter-spacing: 2px; font-weight: 400; opacity: 0.8; }
10701055
.stats { font-family: ${statsFont}; fill: var(--cp-accent); font-size: 36px; font-weight: 600; letter-spacing: 0; }
10711056
.label { font-family: "Roboto", sans-serif; fill: var(--cp-text); font-size: 10px; font-weight: 400; letter-spacing: 1px; opacity: 0.7; }
@@ -1171,8 +1156,7 @@ export function generateNotFoundSVG(
11711156

11721157
return `<svg
11731158
xmlns="http://www.w3.org/2000/svg"
1174-
width="${SVG_WIDTH}"
1175-
height="${SVG_HEIGHT}"
1159+
width="100%"
11761160
viewBox="0 0 ${SVG_WIDTH} ${SVG_HEIGHT}"
11771161
fill="none"
11781162
role="img"
@@ -1321,13 +1305,13 @@ export function generateVersusSVG(
13211305
const unit = params.mode === 'loc' ? 'lines of code' : 'total contributions';
13221306

13231307
return `
1324-
<svg xmlns="http://www.w3.org/2000/svg" width="${W}" height="${H}" viewBox="0 0 ${W} ${H}" fill="none" role="img">
1308+
<svg xmlns="http://www.w3.org/2000/svg" width="100%" viewBox="0 0 ${W} ${H}" fill="none" role="img">
13251309
<title>CommitPulse Versus Stats: ${safeUser1} vs ${safeUser2}</title>
13261310
<desc>${safeUser1} has ${stats1.totalContributions} ${unit}. ${safeUser2} has ${stats2.totalContributions} ${unit}.</desc>
13271311
${renderDefs(sf, params)}
13281312
${renderStyle(selectedFont, statsFont, googleFontsImport, text, accent, sf)}
13291313
<rect width="${W}" height="${H}" rx="${radius}" fill="${params.hideBackground ? 'transparent' : bg}" />
1330-
1314+
13311315
<g transform="translate(0, 0)">
13321316
<g transform="translate(0, ${Math.round(20 * sf)})">${towers1}</g>
13331317
${renderIsometricLabels(calendar1, params, text, sf)}
@@ -1341,7 +1325,7 @@ export function generateVersusSVG(
13411325
</g>
13421326
13431327
<line x1="${singleW}" y1="${s(40)}" x2="${singleW}" y2="${H - s(40)}" stroke="${text}" stroke-opacity="0.2" stroke-width="2" stroke-dasharray="4 4" />
1344-
1328+
13451329
<g transform="translate(${singleW}, ${H / 2})">
13461330
<circle cx="0" cy="0" r="${s(24)}" fill="${bg}" stroke="${accent}" stroke-width="2" />
13471331
<text x="0" y="${s(6)}" text-anchor="middle" font-family="${statsFont}" fill="${accent}" font-size="${s(16)}" font-weight="bold">VS</text>
@@ -1454,11 +1438,11 @@ function generateAutoThemeVersusSVG(
14541438
const unit = params.mode === 'loc' ? 'lines of code' : 'total contributions';
14551439

14561440
return `
1457-
<svg xmlns="http://www.w3.org/2000/svg" width="${W}" height="${H}" viewBox="0 0 ${W} ${H}" fill="none" role="img">
1441+
<svg xmlns="http://www.w3.org/2000/svg" width="100%" viewBox="0 0 ${W} ${H}" fill="none" role="img">
14581442
<title>CommitPulse Versus Stats: ${safeUser1} vs ${safeUser2}</title>
14591443
<desc>${safeUser1} has ${stats1.totalContributions} ${unit}. ${safeUser2} has ${stats2.totalContributions} ${unit}.</desc>
14601444
${renderDefs(sf, params)}
1461-
1445+
14621446
<style>
14631447
@import url('https://fonts.googleapis.com/css2?family=Fira+Code&amp;family=JetBrains+Mono&amp;family=Roboto&amp;family=Syncopate:wght@400;700&amp;family=Space+Grotesk:wght@400;500;600;700&amp;display=swap');
14641448
:root { --cp-bg: #${light.bg}; --cp-text: #${light.text}; --cp-accent: #${light.accent}; }
@@ -1491,7 +1475,7 @@ function generateAutoThemeVersusSVG(
14911475
</style>
14921476
14931477
<rect width="${W}" height="${H}" rx="${radius}" class="${params.hideBackground ? '' : 'cp-bg-fill'}" fill="${params.hideBackground ? 'transparent' : ''}" />
1494-
1478+
14951479
<g transform="translate(0, 0)">
14961480
<g transform="translate(0, ${Math.round(20 * sf)})">${towers1}</g>
14971481
${renderIsometricLabels(calendar1, params, '', sf)}
@@ -1505,7 +1489,7 @@ function generateAutoThemeVersusSVG(
15051489
</g>
15061490
15071491
<line x1="${singleW}" y1="${s(40)}" x2="${singleW}" y2="${H - s(40)}" stroke="var(--cp-text)" stroke-opacity="0.2" stroke-width="2" stroke-dasharray="4 4" />
1508-
1492+
15091493
<g transform="translate(${singleW}, ${H / 2})">
15101494
<circle cx="0" cy="0" r="${s(24)}" class="cp-bg-fill" stroke="var(--cp-accent)" stroke-width="2" />
15111495
<text x="0" y="${s(6)}" text-anchor="middle" font-family="${statsFont}" class="cp-accent-fill" font-size="${s(16)}" font-weight="bold">VS</text>
@@ -1571,8 +1555,7 @@ export function generateRateLimitSVG(
15711555

15721556
return `<svg
15731557
xmlns="http://www.w3.org/2000/svg"
1574-
width="${SVG_WIDTH}"
1575-
height="${SVG_HEIGHT}"
1558+
width="100%"
15761559
viewBox="0 0 ${SVG_WIDTH} ${SVG_HEIGHT}"
15771560
fill="none"
15781561
role="img"
@@ -1619,7 +1602,6 @@ export function generateRateLimitSVG(
16191602
16201603
<rect x="180" y="62" width="240" height="1" fill="${accent}" fill-opacity="0.15"/>
16211604
1622-
<!-- Warning mark -->
16231605
<circle cx="300" cy="190" r="32" fill="none"
16241606
stroke="${accent}" stroke-width="1.2" stroke-opacity="0.3" filter="url(#softglow)"/>
16251607
<path d="M300 172 V200 M300 210 V210.1"

0 commit comments

Comments
 (0)