Skip to content

Commit e8333d5

Browse files
committed
✨ Refactor CardRenderer for improved SVG rendering precision and animation effects
1 parent a658143 commit e8333d5

File tree

1 file changed

+53
-46
lines changed

1 file changed

+53
-46
lines changed

src/card-renderer.ts

Lines changed: 53 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -32,38 +32,43 @@ export class CardRenderer {
3232
const x = Math.random() * width;
3333
const y = Math.random() * height;
3434
const r = Math.random() * 1.5 + 0.5;
35-
const opacity = Math.random() * 0.7 + 0.3;
35+
const opacity = +(Math.random() * 0.7 + 0.3).toFixed(2);
36+
const opacityLow = +(opacity * 0.3).toFixed(2);
3637
const twinkleDur = (Math.random() * 3 + 2).toFixed(1); // 2-5 seconds
3738
const moveX = (Math.random() * 4 - 2).toFixed(1); // -2 to +2
3839
const moveY = (Math.random() * 4 - 2).toFixed(1); // -2 to +2
3940
const delay = (Math.random() * 3).toFixed(1);
40-
return `<circle cx="${x}" cy="${y}" r="${r}" fill="#fff" opacity="${opacity}">
41-
<animate attributeName="opacity" values="${opacity};${opacity * 0.3};${opacity}" dur="${twinkleDur}s" begin="${delay}s" repeatCount="indefinite"/>
42-
<animateTransform attributeName="transform" type="translate" values="0,0; ${moveX},${moveY}; 0,0" dur="${parseFloat(twinkleDur) * 2}s" begin="${delay}s" repeatCount="indefinite"/>
41+
return `<circle cx="${x.toFixed(1)}" cy="${y.toFixed(1)}" r="${r.toFixed(1)}" fill="#fff" opacity="${opacity}">
42+
<animate attributeName="opacity" values="${opacity};${opacityLow};${opacity}" dur="${twinkleDur}s" begin="${delay}s" repeatCount="indefinite" />
43+
<animateTransform attributeName="transform" type="translate" values="0,0; ${moveX},${moveY}; 0,0" dur="${parseFloat(twinkleDur) * 2}s" begin="${delay}s" repeatCount="indefinite" />
4344
</circle>`;
4445
}).join('');
4546

4647
// Generate shooting stars
4748
const shootingStars = Array.from({ length: 3 }, (_, i) => {
48-
const startX = Math.random() * width;
49-
const startY = Math.random() * (height / 2);
50-
const endX = startX + 200 + Math.random() * 100;
51-
const endY = startY + 150 + Math.random() * 50;
49+
const startX = (Math.random() * width).toFixed(1);
50+
const startY = (Math.random() * (height / 2)).toFixed(1);
51+
const endX = (parseFloat(startX) + 200 + Math.random() * 100).toFixed(1);
52+
const endY = (parseFloat(startY) + 150 + Math.random() * 50).toFixed(1);
53+
const startX2 = (parseFloat(startX) + 30).toFixed(1);
54+
const startY2 = (parseFloat(startY) + 20).toFixed(1);
55+
const endX2 = (parseFloat(endX) + 30).toFixed(1);
56+
const endY2 = (parseFloat(endY) + 20).toFixed(1);
5257
const delay = (i * 8 + Math.random() * 4).toFixed(1);
5358
return `
54-
<line x1="${startX}" y1="${startY}" x2="${startX + 30}" y2="${startY + 20}" stroke="url(#shootingStarGradient)" stroke-width="2" opacity="0" stroke-linecap="round">
55-
<animate attributeName="x1" values="${startX};${endX}" dur="1.5s" begin="${delay}s" repeatCount="indefinite"/>
56-
<animate attributeName="y1" values="${startY};${endY}" dur="1.5s" begin="${delay}s" repeatCount="indefinite"/>
57-
<animate attributeName="x2" values="${startX + 30};${endX + 30}" dur="1.5s" begin="${delay}s" repeatCount="indefinite"/>
58-
<animate attributeName="y2" values="${startY + 20};${endY + 20}" dur="1.5s" begin="${delay}s" repeatCount="indefinite"/>
59-
<animate attributeName="opacity" values="0;0.8;0" dur="1.5s" begin="${delay}s" repeatCount="indefinite"/>
59+
<line x1="${startX}" y1="${startY}" x2="${startX2}" y2="${startY2}" stroke="url(#shootingStarGradient)" stroke-width="2" opacity="0" stroke-linecap="round">
60+
<animate attributeName="x1" values="${startX};${endX}" dur="1.5s" begin="${delay}s" repeatCount="indefinite" />
61+
<animate attributeName="y1" values="${startY};${endY}" dur="1.5s" begin="${delay}s" repeatCount="indefinite" />
62+
<animate attributeName="x2" values="${startX2};${endX2}" dur="1.5s" begin="${delay}s" repeatCount="indefinite" />
63+
<animate attributeName="y2" values="${startY2};${endY2}" dur="1.5s" begin="${delay}s" repeatCount="indefinite" />
64+
<animate attributeName="opacity" values="0;0.8;0" dur="1.5s" begin="${delay}s" repeatCount="indefinite" />
6065
</line>
6166
`;
6267
}).join('');
6368

6469
// Generate orbital rings
6570
const orbitRings = [120, 160, 200, 240].map((r, i) =>
66-
`<circle cx="${centerX}" cy="${centerY}" r="${r}" fill="none" stroke="rgba(0, 200, 255, ${0.15 - i * 0.03})" stroke-width="1" stroke-dasharray="10,8"/>`
71+
`<circle cx="${centerX}" cy="${centerY}" r="${r}" fill="none" stroke="rgba(0, 200, 255, ${(0.15 - i * 0.03).toFixed(2)})" stroke-width="1" stroke-dasharray="10,8" />`
6772
).join('');
6873

6974
// Generate data beams radiating from center
@@ -81,36 +86,38 @@ export class CardRenderer {
8186
const angle = (stat.angle * Math.PI) / 180;
8287
const intensity = maxValue > 0 ? stat.value / maxValue : 0;
8388
const beamLength = 100 + (intensity * 140);
84-
const endX = centerX + Math.cos(angle) * beamLength;
85-
const endY = centerY + Math.sin(angle) * beamLength;
89+
const endX = (centerX + Math.cos(angle) * beamLength).toFixed(1);
90+
const endY = (centerY + Math.sin(angle) * beamLength).toFixed(1);
8691

8792
// Data point position
88-
const dotX = centerX + Math.cos(angle) * (beamLength + 20);
89-
const dotY = centerY + Math.sin(angle) * (beamLength + 20);
93+
const dotX = (centerX + Math.cos(angle) * (beamLength + 20)).toFixed(1);
94+
const dotY = (centerY + Math.sin(angle) * (beamLength + 20)).toFixed(1);
9095

9196
// Label position (further out)
92-
const labelX = centerX + Math.cos(angle) * (beamLength + 60);
97+
const labelX = (centerX + Math.cos(angle) * (beamLength + 60)).toFixed(1);
9398
const labelY = centerY + Math.sin(angle) * (beamLength + 60);
99+
const labelYTop = (labelY - 12).toFixed(1);
100+
const labelYBottom = (labelY + 6).toFixed(1);
94101

95102
return `
96103
<!-- Beam line -->
97-
<line x1="${centerX}" y1="${centerY}" x2="${endX}" y2="${endY}" stroke="url(#beamGradient${i})" stroke-width="2" opacity="0.6"/>
104+
<line x1="${centerX}" y1="${centerY}" x2="${endX}" y2="${endY}" stroke="url(#beamGradient${i})" stroke-width="2" opacity="0.6" />
98105
99106
<!-- Glow effect beam -->
100-
<line x1="${centerX}" y1="${centerY}" x2="${endX}" y2="${endY}" stroke="rgba(0, 200, 255, 0.3)" stroke-width="6" filter="url(#glow)"/>
107+
<line x1="${centerX}" y1="${centerY}" x2="${endX}" y2="${endY}" stroke="rgba(0, 200, 255, 0.3)" stroke-width="6" filter="url(#glow)" />
101108
102109
<!-- Data point -->
103110
<circle cx="${dotX}" cy="${dotY}" r="6" fill="#00c8ff" filter="url(#glow)">
104-
<animate attributeName="r" values="6;8;6" dur="2s" repeatCount="indefinite"/>
111+
<animate attributeName="r" values="6;8;6" dur="2s" repeatCount="indefinite" />
105112
</circle>
106113
<circle cx="${dotX}" cy="${dotY}" r="12" fill="none" stroke="#00c8ff" stroke-width="1" opacity="0.4">
107-
<animate attributeName="r" values="12;16;12" dur="2s" repeatCount="indefinite"/>
108-
<animate attributeName="opacity" values="0.4;0.1;0.4" dur="2s" repeatCount="indefinite"/>
114+
<animate attributeName="r" values="12;16;12" dur="2s" repeatCount="indefinite" />
115+
<animate attributeName="opacity" values="0.4;0.1;0.4" dur="2s" repeatCount="indefinite" />
109116
</circle>
110117
111118
<!-- Stat label -->
112-
<text x="${labelX}" y="${labelY - 12}" text-anchor="middle" fill="#00c8ff" font-size="11" font-weight="600" filter="url(#glow)">${stat.label}</text>
113-
<text x="${labelX}" y="${labelY + 6}" text-anchor="middle" fill="#fff" font-size="20" font-weight="700" class="number" filter="url(#glow)">${formatNumber(stat.value)}</text>
119+
<text x="${labelX}" y="${labelYTop}" text-anchor="middle" fill="#00c8ff" font-size="11" font-weight="600" filter="url(#glow)">${stat.label}</text>
120+
<text x="${labelX}" y="${labelYBottom}" text-anchor="middle" fill="#fff" font-size="20" font-weight="700" class="number" filter="url(#glow)">${formatNumber(stat.value)}</text>
114121
`;
115122
}).join('');
116123

@@ -219,7 +226,7 @@ export class CardRenderer {
219226
</style>
220227
221228
<!-- Space background -->
222-
<rect width="${width}" height="${height}" fill="url(#spaceGradient)"/>
229+
<rect width="${width}" height="${height}" fill="url(#spaceGradient)" />
223230
224231
<!-- Starfield -->
225232
<g opacity="0.8">
@@ -232,7 +239,7 @@ export class CardRenderer {
232239
</g>
233240
234241
<!-- Scan lines -->
235-
<rect width="${width}" height="${height}" fill="url(#scanlines)" opacity="0.3"/>
242+
<rect width="${width}" height="${height}" fill="url(#scanlines)" opacity="0.3" />
236243
237244
<!-- Rotating orbital rings -->
238245
<g class="rotating">
@@ -259,34 +266,34 @@ export class CardRenderer {
259266
<!-- Center sphere (Earth-like) -->
260267
<g filter="url(#strongGlow)">
261268
<!-- Avatar image -->
262-
<image href="${stats.avatarUrl}" x="${centerX - 65}" y="${centerY - 65}" width="130" height="130" clip-path="url(#avatarClip)" opacity="0.9"/>
269+
<image href="${stats.avatarUrl}" x="${centerX - 65}" y="${centerY - 65}" width="130" height="130" clip-path="url(#avatarClip)" opacity="0.9" />
263270
264271
<!-- Avatar border and effects -->
265-
<circle cx="${centerX}" cy="${centerY}" r="65" fill="none" stroke="#00c8ff" stroke-width="3" opacity="0.8"/>
266-
<circle cx="${centerX}" cy="${centerY}" r="70" fill="none" stroke="#00c8ff" stroke-width="1" opacity="0.5"/>
267-
<circle cx="${centerX}" cy="${centerY}" r="80" fill="none" stroke="#00c8ff" stroke-width="2" opacity="0.3"/>
268-
<circle cx="${centerX}" cy="${centerY}" r="75" fill="none" stroke="#00c8ff" stroke-width="1" opacity="0.4" stroke-dasharray="4,4"/>
272+
<circle cx="${centerX}" cy="${centerY}" r="65" fill="none" stroke="#00c8ff" stroke-width="3" opacity="0.8" />
273+
<circle cx="${centerX}" cy="${centerY}" r="70" fill="none" stroke="#00c8ff" stroke-width="1" opacity="0.5" />
274+
<circle cx="${centerX}" cy="${centerY}" r="80" fill="none" stroke="#00c8ff" stroke-width="2" opacity="0.3" />
275+
<circle cx="${centerX}" cy="${centerY}" r="75" fill="none" stroke="#00c8ff" stroke-width="1" opacity="0.4" stroke-dasharray="4,4" />
269276
270277
<!-- Ping animation rings -->
271278
<circle cx="${centerX}" cy="${centerY}" r="80" fill="none" stroke="#00c8ff54" stroke-width="2" opacity="0">
272-
<animate attributeName="r" values="80;150;220" dur="5s" repeatCount="indefinite"/>
273-
<animate attributeName="opacity" values="0.7;0.3;0" dur="5s" repeatCount="indefinite"/>
279+
<animate attributeName="r" values="80;150;220" dur="5s" repeatCount="indefinite" />
280+
<animate attributeName="opacity" values="0.7;0.3;0" dur="5s" repeatCount="indefinite" />
274281
</circle>
275282
<circle cx="${centerX}" cy="${centerY}" r="80" fill="none" stroke="#00c8ff54" stroke-width="2" opacity="0">
276-
<animate attributeName="r" values="80;150;220" dur="5s" begin="1s" repeatCount="indefinite"/>
277-
<animate attributeName="opacity" values="0.7;0.3;0" dur="5s" begin="1s" repeatCount="indefinite"/>
283+
<animate attributeName="r" values="80;150;220" dur="5s" begin="1s" repeatCount="indefinite" />
284+
<animate attributeName="opacity" values="0.7;0.3;0" dur="5s" begin="1s" repeatCount="indefinite" />
278285
</circle>
279286
<circle cx="${centerX}" cy="${centerY}" r="80" fill="none" stroke="#00c8ff54" stroke-width="2" opacity="0">
280-
<animate attributeName="r" values="80;150;220" dur="5s" begin="2s" repeatCount="indefinite"/>
281-
<animate attributeName="opacity" values="0.7;0.3;0" dur="5s" begin="2s" repeatCount="indefinite"/>
287+
<animate attributeName="r" values="80;150;220" dur="5s" begin="2s" repeatCount="indefinite" />
288+
<animate attributeName="opacity" values="0.7;0.3;0" dur="5s" begin="2s" repeatCount="indefinite" />
282289
</circle>
283290
</g>
284291
285292
<!-- Top left panel - User info -->
286293
${!hideTitle ? `
287294
<g transform="translate(40, 40)">
288-
<rect width="280" height="120" rx="8" fill="url(#panelGradient)" stroke="#00c8ff" stroke-width="1" opacity="0.8"/>
289-
<line x1="0" y1="35" x2="280" y2="35" stroke="#00c8ff" stroke-width="1" opacity="0.3"/>
295+
<rect width="280" height="120" rx="8" fill="url(#panelGradient)" stroke="#00c8ff" stroke-width="1" opacity="0.8" />
296+
<line x1="0" y1="35" x2="280" y2="35" stroke="#00c8ff" stroke-width="1" opacity="0.3" />
290297
291298
<text x="15" y="25" fill="#00c8ff" font-size="14" font-weight="600" letter-spacing="1">${customTitle}</text>
292299
<text x="15" y="55" fill="#888" font-size="11" font-weight="500">TOTAL CONTRIBUTIONS</text>
@@ -312,8 +319,8 @@ export class CardRenderer {
312319
313320
<!-- Bottom left panel - Activity -->
314321
<g transform="translate(40, ${height - 160})">
315-
<rect width="280" height="120" rx="8" fill="url(#panelGradient)" stroke="#00c8ff" stroke-width="1" opacity="0.8"/>
316-
<line x1="0" y1="35" x2="280" y2="35" stroke="#00c8ff" stroke-width="1" opacity="0.3"/>
322+
<rect width="280" height="120" rx="8" fill="url(#panelGradient)" stroke="#00c8ff" stroke-width="1" opacity="0.8" />
323+
<line x1="0" y1="35" x2="280" y2="35" stroke="#00c8ff" stroke-width="1" opacity="0.3" />
317324
318325
<text x="15" y="25" fill="#00c8ff" font-size="14" font-weight="600" letter-spacing="1">REPOSITORY ACTIVITY</text>
319326
@@ -327,8 +334,8 @@ export class CardRenderer {
327334
328335
<!-- Bottom right panel - Terminal style data stream -->
329336
<g transform="translate(${width - 320}, ${height - 160})">
330-
<rect width="280" height="120" rx="8" fill="url(#panelGradient)" stroke="#00c8ff" stroke-width="1" opacity="0.8"/>
331-
<line x1="0" y1="35" x2="280" y2="35" stroke="#00c8ff" stroke-width="1" opacity="0.3"/>
337+
<rect width="280" height="120" rx="8" fill="url(#panelGradient)" stroke="#00c8ff" stroke-width="1" opacity="0.8" />
338+
<line x1="0" y1="35" x2="280" y2="35" stroke="#00c8ff" stroke-width="1" opacity="0.3" />
332339
333340
<text x="15" y="25" fill="#00c8ff" font-size="14" font-weight="600" letter-spacing="1">DATA STREAM</text>
334341
<text x="15" y="55" fill="#0f0" font-size="9" opacity="0.8">> Analyzing contribution patterns...</text>

0 commit comments

Comments
 (0)