Skip to content

Commit 4f8cf2a

Browse files
committed
feat: new github thumbnails
1 parent 11e5fb9 commit 4f8cf2a

File tree

3 files changed

+208
-0
lines changed

3 files changed

+208
-0
lines changed

src/pages/apps/github-thumbnail/GithubThumbnailGeneratorPage.jsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ const THEME_OPTIONS = [
5656
{ value: 'darkMedieval', label: 'DARK_MEDIEVAL' },
5757
{ value: 'tacticalMap', label: 'TACTICAL_MAP' },
5858
{ value: 'modernEdge', label: 'MODERN_EDGE' },
59+
{ value: 'auroraWave', label: 'AURORA_WAVE' },
5960
];
6061

6162
const GithubThumbnailGeneratorPage = () => {

src/pages/apps/github-thumbnail/themes.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import { retroDos } from './themes/retroDos';
3636
import { darkMedieval } from './themes/darkMedieval';
3737
import { tacticalMap } from './themes/tacticalMap';
3838
import { modernEdge } from './themes/modernEdge';
39+
import { auroraWave } from './themes/auroraWave';
3940

4041
export const themeRenderers = {
4142
modern,
@@ -76,4 +77,5 @@ export const themeRenderers = {
7677
darkMedieval,
7778
tacticalMap,
7879
modernEdge,
80+
auroraWave,
7981
};
Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
import { wrapText } from '../utils';
2+
3+
export const auroraWave = (ctx, width, height, scale, data) => {
4+
const {
5+
primaryColor,
6+
secondaryColor,
7+
bgColor,
8+
showPattern,
9+
repoOwner,
10+
repoName,
11+
description,
12+
language,
13+
stars,
14+
forks,
15+
supportUrl,
16+
} = data;
17+
18+
// --- Aurora wave bands ---
19+
ctx.save();
20+
const waveCount = 5;
21+
for (let i = 0; i < waveCount; i++) {
22+
const yBase = height * 0.25 + i * 50 * scale;
23+
const grad = ctx.createLinearGradient(0, yBase, width, yBase);
24+
const alpha = 0.06 - i * 0.008;
25+
grad.addColorStop(0, 'transparent');
26+
grad.addColorStop(0.2, primaryColor);
27+
grad.addColorStop(0.5, secondaryColor);
28+
grad.addColorStop(0.8, primaryColor);
29+
grad.addColorStop(1, 'transparent');
30+
31+
ctx.globalAlpha = Math.max(alpha, 0.015);
32+
ctx.beginPath();
33+
ctx.moveTo(0, yBase);
34+
for (let x = 0; x <= width; x += 4) {
35+
const wave = Math.sin((x / width) * Math.PI * 3 + i * 1.2) * (40 * scale);
36+
ctx.lineTo(x, yBase + wave);
37+
}
38+
ctx.lineTo(width, height);
39+
ctx.lineTo(0, height);
40+
ctx.closePath();
41+
ctx.fillStyle = grad;
42+
ctx.fill();
43+
}
44+
ctx.restore();
45+
46+
// --- Noise texture dots (when showPattern is on) ---
47+
if (showPattern) {
48+
ctx.save();
49+
ctx.globalAlpha = 0.025;
50+
ctx.fillStyle = '#ffffff';
51+
const seed = 42;
52+
for (let i = 0; i < 600; i++) {
53+
const px = ((seed * (i + 1) * 7919) % width);
54+
const py = ((seed * (i + 1) * 6271) % height);
55+
const r = ((i % 3) + 1) * scale;
56+
ctx.beginPath();
57+
ctx.arc(px, py, r, 0, Math.PI * 2);
58+
ctx.fill();
59+
}
60+
ctx.restore();
61+
}
62+
63+
const padding = 80 * scale;
64+
65+
// --- Vertical accent line on the left ---
66+
ctx.save();
67+
const lineGrad = ctx.createLinearGradient(0, padding, 0, height - padding);
68+
lineGrad.addColorStop(0, primaryColor);
69+
lineGrad.addColorStop(0.5, secondaryColor);
70+
lineGrad.addColorStop(1, 'transparent');
71+
ctx.fillStyle = lineGrad;
72+
ctx.globalAlpha = 0.6;
73+
ctx.fillRect(padding - 30 * scale, padding, 3 * scale, height - padding * 2);
74+
ctx.restore();
75+
76+
// --- Owner ---
77+
const contentX = padding;
78+
ctx.textAlign = 'left';
79+
ctx.fillStyle = 'rgba(255,255,255,0.4)';
80+
ctx.font = `500 ${20 * scale}px "JetBrains Mono", monospace`;
81+
ctx.fillText(repoOwner, contentX, padding + 14 * scale);
82+
83+
// --- Repo name ---
84+
ctx.fillStyle = '#ffffff';
85+
ctx.font = `900 ${76 * scale}px "Inter", sans-serif`;
86+
ctx.fillText(repoName, contentX, padding + 100 * scale);
87+
88+
// --- Soft glow behind the name ---
89+
ctx.save();
90+
ctx.globalAlpha = 0.08;
91+
ctx.filter = 'blur(40px)';
92+
ctx.fillStyle = primaryColor;
93+
const nameW = ctx.measureText(repoName).width;
94+
ctx.fillRect(contentX, padding + 40 * scale, nameW, 70 * scale);
95+
ctx.restore();
96+
97+
// --- Description ---
98+
ctx.fillStyle = 'rgba(255,255,255,0.55)';
99+
ctx.font = `300 ${26 * scale}px "Inter", sans-serif`;
100+
wrapText(ctx, description, contentX, padding + 160 * scale, width * 0.52, 38 * scale);
101+
102+
// --- Stats bar at the bottom ---
103+
const bottomY = height - padding;
104+
105+
// Language tag with dot indicator
106+
ctx.fillStyle = primaryColor;
107+
ctx.beginPath();
108+
ctx.arc(contentX + 8 * scale, bottomY - 6 * scale, 6 * scale, 0, Math.PI * 2);
109+
ctx.fill();
110+
111+
ctx.fillStyle = '#ffffff';
112+
ctx.font = `700 ${20 * scale}px "JetBrains Mono", monospace`;
113+
ctx.fillText(language, contentX + 24 * scale, bottomY);
114+
115+
// Stars
116+
let statX = contentX + 24 * scale + ctx.measureText(language).width + 40 * scale;
117+
118+
if (stars) {
119+
ctx.fillStyle = 'rgba(255,255,255,0.35)';
120+
ctx.font = `400 ${16 * scale}px "JetBrains Mono", monospace`;
121+
ctx.fillText('★', statX, bottomY);
122+
statX += 20 * scale;
123+
ctx.fillStyle = 'rgba(255,255,255,0.6)';
124+
ctx.font = `700 ${20 * scale}px "JetBrains Mono", monospace`;
125+
ctx.fillText(stars, statX, bottomY);
126+
statX += ctx.measureText(stars).width + 30 * scale;
127+
}
128+
129+
// Forks
130+
if (forks) {
131+
ctx.fillStyle = 'rgba(255,255,255,0.35)';
132+
ctx.font = `400 ${16 * scale}px "JetBrains Mono", monospace`;
133+
ctx.fillText('⑂', statX, bottomY);
134+
statX += 20 * scale;
135+
ctx.fillStyle = 'rgba(255,255,255,0.6)';
136+
ctx.font = `700 ${20 * scale}px "JetBrains Mono", monospace`;
137+
ctx.fillText(forks, statX, bottomY);
138+
}
139+
140+
// --- Right-side info panel ---
141+
const panelW = 280 * scale;
142+
const panelH = 180 * scale;
143+
const panelX = width - padding - panelW;
144+
const panelY = height * 0.35;
145+
146+
// Panel bg
147+
ctx.save();
148+
ctx.globalAlpha = 0.04;
149+
ctx.fillStyle = '#ffffff';
150+
ctx.beginPath();
151+
ctx.roundRect(panelX, panelY, panelW, panelH, 12 * scale);
152+
ctx.fill();
153+
ctx.restore();
154+
155+
// Panel left accent
156+
ctx.save();
157+
const panelAccent = ctx.createLinearGradient(0, panelY, 0, panelY + panelH);
158+
panelAccent.addColorStop(0, primaryColor);
159+
panelAccent.addColorStop(1, secondaryColor);
160+
ctx.fillStyle = panelAccent;
161+
ctx.globalAlpha = 0.7;
162+
ctx.fillRect(panelX, panelY, 3 * scale, panelH);
163+
ctx.restore();
164+
165+
const innerX = panelX + 22 * scale;
166+
let innerY = panelY + 30 * scale;
167+
168+
// Color palette row
169+
const swatchR = 10 * scale;
170+
[bgColor, primaryColor, secondaryColor].forEach((color, i) => {
171+
ctx.save();
172+
ctx.fillStyle = color;
173+
ctx.beginPath();
174+
ctx.arc(innerX + i * (swatchR * 2.8), innerY, swatchR, 0, Math.PI * 2);
175+
ctx.fill();
176+
ctx.strokeStyle = 'rgba(255,255,255,0.15)';
177+
ctx.lineWidth = 1 * scale;
178+
ctx.stroke();
179+
ctx.restore();
180+
});
181+
182+
innerY += 36 * scale;
183+
ctx.fillStyle = 'rgba(255,255,255,0.3)';
184+
ctx.font = `500 ${12 * scale}px "JetBrains Mono", monospace`;
185+
ctx.textAlign = 'left';
186+
ctx.fillText('THEME PALETTE', innerX, innerY);
187+
188+
innerY += 28 * scale;
189+
ctx.fillStyle = 'rgba(255,255,255,0.45)';
190+
ctx.font = `600 ${14 * scale}px "JetBrains Mono", monospace`;
191+
ctx.fillText(language.toUpperCase(), innerX, innerY);
192+
193+
innerY += 26 * scale;
194+
ctx.fillStyle = showPattern ? secondaryColor : 'rgba(255,255,255,0.2)';
195+
ctx.font = `500 ${11 * scale}px "JetBrains Mono", monospace`;
196+
ctx.fillText(showPattern ? '◆ TEXTURE ACTIVE' : '◇ TEXTURE OFF', innerX, innerY);
197+
198+
// --- Support URL bottom-right ---
199+
if (supportUrl) {
200+
ctx.textAlign = 'right';
201+
ctx.fillStyle = 'rgba(255,255,255,0.25)';
202+
ctx.font = `300 ${16 * scale}px "JetBrains Mono", monospace`;
203+
ctx.fillText(supportUrl, width - padding, bottomY);
204+
}
205+
};

0 commit comments

Comments
 (0)