Skip to content

Commit 6d3c7f2

Browse files
committed
feat: add Modern Nature theme to github thumbnail generator
1 parent 341b3d3 commit 6d3c7f2

File tree

3 files changed

+185
-0
lines changed

3 files changed

+185
-0
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ const THEME_OPTIONS = [
6666
{ value: 'aeroGlass', label: 'AERO_GLASS_7' },
6767
{ value: 'circlesBg', label: 'CIRCLES_BG' },
6868
{ value: 'cassetteJCard', label: 'CASSETTE_J_CARD' },
69+
{ value: 'modernNature', label: 'MODERN_NATURE' },
6970
];
7071

7172
const GithubThumbnailGeneratorPage = () => {

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ import { macosGlass } from './themes/macosGlass';
4646
import { aeroGlass } from './themes/aeroGlass';
4747
import { circlesBg } from './themes/circlesBg';
4848
import { cassetteJCard } from './themes/cassetteJCard';
49+
import { modernNature } from './themes/modernNature';
4950

5051
export const themeRenderers = {
5152
modern,
@@ -96,4 +97,5 @@ export const themeRenderers = {
9697
aeroGlass,
9798
circlesBg,
9899
cassetteJCard,
100+
modernNature,
99101
};
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
import { wrapText, drawPill } from '../utils';
2+
3+
export const modernNature = (ctx, width, height, scale, data) => {
4+
const {
5+
primaryColor,
6+
secondaryColor,
7+
repoOwner,
8+
repoName,
9+
description,
10+
language,
11+
stars,
12+
forks,
13+
supportUrl,
14+
bgColor,
15+
showPattern,
16+
} = data;
17+
18+
// Background
19+
ctx.fillStyle = bgColor;
20+
ctx.fillRect(0, 0, width, height);
21+
22+
// Organic gradient orbs
23+
ctx.save();
24+
const grad1 = ctx.createRadialGradient(
25+
width * 0.75, height * 0.2, 0,
26+
width * 0.75, height * 0.2, 500 * scale,
27+
);
28+
grad1.addColorStop(0, primaryColor);
29+
grad1.addColorStop(1, 'transparent');
30+
ctx.globalAlpha = 0.12;
31+
ctx.fillStyle = grad1;
32+
ctx.fillRect(0, 0, width, height);
33+
34+
const grad2 = ctx.createRadialGradient(
35+
width * 0.15, height * 0.85, 0,
36+
width * 0.15, height * 0.85, 400 * scale,
37+
);
38+
grad2.addColorStop(0, secondaryColor);
39+
grad2.addColorStop(1, 'transparent');
40+
ctx.fillStyle = grad2;
41+
ctx.fillRect(0, 0, width, height);
42+
ctx.restore();
43+
44+
// Leaf vein pattern (if showPattern)
45+
if (showPattern) {
46+
ctx.save();
47+
ctx.globalAlpha = 0.06;
48+
ctx.strokeStyle = primaryColor;
49+
ctx.lineWidth = 1.5 * scale;
50+
51+
// Flowing organic lines
52+
for (let i = 0; i < 8; i++) {
53+
const startY = (height / 8) * i;
54+
ctx.beginPath();
55+
ctx.moveTo(0, startY);
56+
for (let x = 0; x <= width; x += 40 * scale) {
57+
const y = startY + Math.sin((x / width) * Math.PI * 2 + i) * 60 * scale;
58+
ctx.lineTo(x, y);
59+
}
60+
ctx.stroke();
61+
}
62+
63+
// Scattered organic dots
64+
for (let i = 0; i < 30; i++) {
65+
const x = (width * ((i * 7 + 13) % 31)) / 31;
66+
const y = (height * ((i * 11 + 5) % 23)) / 23;
67+
const r = (3 + (i % 5)) * scale;
68+
ctx.beginPath();
69+
ctx.arc(x, y, r, 0, Math.PI * 2);
70+
ctx.fillStyle = i % 2 === 0 ? primaryColor : secondaryColor;
71+
ctx.fill();
72+
}
73+
ctx.restore();
74+
}
75+
76+
// Abstract leaf silhouette top-right
77+
ctx.save();
78+
ctx.globalAlpha = 0.08;
79+
ctx.fillStyle = primaryColor;
80+
ctx.beginPath();
81+
ctx.moveTo(width * 0.95, 0);
82+
ctx.bezierCurveTo(
83+
width * 0.7, height * 0.05,
84+
width * 0.75, height * 0.35,
85+
width * 0.85, height * 0.45,
86+
);
87+
ctx.bezierCurveTo(
88+
width * 0.95, height * 0.35,
89+
width, height * 0.15,
90+
width, 0,
91+
);
92+
ctx.fill();
93+
ctx.restore();
94+
95+
// Abstract leaf silhouette bottom-left
96+
ctx.save();
97+
ctx.globalAlpha = 0.06;
98+
ctx.fillStyle = secondaryColor;
99+
ctx.beginPath();
100+
ctx.moveTo(0, height);
101+
ctx.bezierCurveTo(
102+
width * 0.1, height * 0.75,
103+
width * 0.25, height * 0.7,
104+
width * 0.2, height * 0.6,
105+
);
106+
ctx.bezierCurveTo(
107+
width * 0.05, height * 0.65,
108+
0, height * 0.8,
109+
0, height,
110+
);
111+
ctx.fill();
112+
ctx.restore();
113+
114+
// Content
115+
const padding = 80 * scale;
116+
117+
// Owner line with botanical separator
118+
ctx.fillStyle = primaryColor;
119+
ctx.font = `500 ${26 * scale}px "JetBrains Mono"`;
120+
ctx.textAlign = 'left';
121+
const ownerText = `${repoOwner} /`;
122+
ctx.fillText(ownerText, padding, padding + 20 * scale);
123+
124+
// Repo name
125+
ctx.fillStyle = '#ffffff';
126+
ctx.font = `bold ${76 * scale}px "Inter", sans-serif`;
127+
ctx.fillText(repoName, padding, padding + 108 * scale);
128+
129+
// Thin accent line under title
130+
const lineGrad = ctx.createLinearGradient(padding, 0, padding + 300 * scale, 0);
131+
lineGrad.addColorStop(0, primaryColor);
132+
lineGrad.addColorStop(1, secondaryColor);
133+
ctx.fillStyle = lineGrad;
134+
ctx.fillRect(padding, padding + 125 * scale, 200 * scale, 3 * scale);
135+
136+
// Description
137+
ctx.fillStyle = 'rgba(255, 255, 255, 0.75)';
138+
ctx.font = `${30 * scale}px "Inter", sans-serif`;
139+
const maxWidth = width - padding * 2;
140+
wrapText(ctx, description, padding, padding + 180 * scale, maxWidth, 44 * scale);
141+
142+
// Bottom section
143+
const bottomY = height - padding;
144+
145+
// Language pill
146+
drawPill(ctx, padding, bottomY - 20 * scale, language, primaryColor, scale);
147+
148+
// Right-aligned stats and support URL
149+
ctx.textAlign = 'right';
150+
let currentX = width - padding;
151+
152+
// Support URL
153+
if (supportUrl) {
154+
ctx.fillStyle = 'rgba(255, 255, 255, 0.35)';
155+
ctx.font = `${18 * scale}px "JetBrains Mono"`;
156+
ctx.fillText(supportUrl, currentX, bottomY);
157+
currentX -= ctx.measureText(supportUrl).width + 40 * scale;
158+
}
159+
160+
// Stats
161+
ctx.font = `bold ${22 * scale}px "JetBrains Mono"`;
162+
ctx.fillStyle = 'rgba(255, 255, 255, 0.55)';
163+
const statGap = 36 * scale;
164+
165+
if (forks) {
166+
ctx.fillText(`${forks} Forks`, currentX, bottomY);
167+
currentX -= ctx.measureText(`${forks} Forks`).width + statGap;
168+
}
169+
170+
if (stars) {
171+
ctx.fillText(`${stars} Stars`, currentX, bottomY);
172+
currentX -= ctx.measureText(`${stars} Stars`).width + statGap;
173+
}
174+
175+
// Small botanical accent near stats — a tiny leaf icon
176+
ctx.save();
177+
ctx.fillStyle = primaryColor;
178+
ctx.globalAlpha = 0.4;
179+
ctx.font = `${20 * scale}px serif`;
180+
ctx.fillText('🌿', currentX, bottomY + 2 * scale);
181+
ctx.restore();
182+
};

0 commit comments

Comments
 (0)