Skip to content

Commit 4c45a48

Browse files
committed
feat: add 4 new themes to github thumbnail generator
1 parent 8759dab commit 4c45a48

File tree

6 files changed

+492
-0
lines changed

6 files changed

+492
-0
lines changed

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@ const THEME_OPTIONS = [
4343
{ value: 'nature', label: 'NATURE_VIBES' },
4444
{ value: 'graphicNovel', label: 'SIN_CITY_STYLE' },
4545
{ value: 'win95', label: 'RETRO_WINDOWS_95' },
46+
{ value: 'minimalDark', label: 'MINIMAL_DARK' },
47+
{ value: 'gradient', label: 'GRADIENT_MESH' },
48+
{ value: 'comic', label: 'COMIC_BOOK' },
49+
{ value: 'cybernetic', label: 'CYBERNETIC_HUD' },
4650
];
4751

4852
const GithubThumbnailGeneratorPage = () => {

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ import { abstract } from './themes/abstract';
2323
import { nature } from './themes/nature';
2424
import { graphicNovel } from './themes/graphicNovel';
2525
import { win95 } from './themes/win95';
26+
import { minimalDark } from './themes/minimalDark';
27+
import { gradient } from './themes/gradient';
28+
import { comic } from './themes/comic';
29+
import { cybernetic } from './themes/cybernetic';
2630

2731
export const themeRenderers = {
2832
modern,
@@ -50,4 +54,8 @@ export const themeRenderers = {
5054
nature,
5155
graphicNovel,
5256
win95,
57+
minimalDark,
58+
gradient,
59+
comic,
60+
cybernetic,
5361
};
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
import { wrapText } from '../utils';
2+
3+
export const comic = (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+
} = data;
15+
16+
// Background
17+
ctx.fillStyle = secondaryColor;
18+
ctx.fillRect(0, 0, width, height);
19+
20+
// Halftone Pattern Background
21+
ctx.save();
22+
ctx.fillStyle = primaryColor;
23+
ctx.globalAlpha = 0.2;
24+
const dotSpacing = 30 * scale;
25+
for (let x = 0; x < width; x += dotSpacing) {
26+
for (let y = 0; y < height; y += dotSpacing) {
27+
ctx.beginPath();
28+
ctx.arc(x, y, 4 * scale, 0, Math.PI * 2);
29+
ctx.fill();
30+
}
31+
}
32+
ctx.restore();
33+
34+
// Thick Border
35+
const borderW = 10 * scale;
36+
ctx.strokeStyle = '#000000';
37+
ctx.lineWidth = borderW;
38+
ctx.strokeRect(borderW / 2, borderW / 2, width - borderW, height - borderW);
39+
40+
// Decorative Panels
41+
ctx.fillStyle = '#ffffff';
42+
ctx.strokeStyle = '#000000';
43+
ctx.lineWidth = 6 * scale;
44+
45+
// Speech Bubble Shape for Title
46+
const pad = 100 * scale;
47+
const bubbleW = width - pad * 2;
48+
const bubbleH = height - pad * 2;
49+
const r = 40 * scale;
50+
51+
ctx.save();
52+
ctx.shadowColor = 'rgba(0,0,0,0.4)';
53+
ctx.shadowBlur = 0;
54+
ctx.shadowOffsetX = 12 * scale;
55+
ctx.shadowOffsetY = 12 * scale;
56+
57+
ctx.beginPath();
58+
ctx.roundRect(pad, pad, bubbleW, bubbleH, r);
59+
ctx.fill();
60+
ctx.stroke();
61+
ctx.restore();
62+
63+
// Content
64+
const contentPad = pad + 60 * scale;
65+
66+
// Repo Owner (Sticker Style)
67+
ctx.save();
68+
ctx.translate(contentPad + 80 * scale, pad - 20 * scale);
69+
ctx.rotate(-0.03);
70+
ctx.fillStyle = primaryColor;
71+
const ownerW = ctx.measureText(repoOwner.toUpperCase()).width + 40 * scale;
72+
ctx.fillRect(-20 * scale, -25 * scale, ownerW + 40 * scale, 50 * scale);
73+
ctx.strokeRect(-20 * scale, -25 * scale, ownerW + 40 * scale, 50 * scale);
74+
ctx.fillStyle = '#ffffff';
75+
ctx.font = `bold ${28 * scale}px "Comic Sans MS", cursive, sans-serif`;
76+
ctx.textAlign = 'center';
77+
ctx.fillText(repoOwner.toUpperCase(), ownerW / 2, 10 * scale);
78+
ctx.restore();
79+
80+
// Repo Name
81+
ctx.textAlign = 'left';
82+
ctx.fillStyle = '#000000';
83+
ctx.font = `italic 900 ${110 * scale}px "Arial Black", sans-serif`;
84+
ctx.fillText(repoName, contentPad, pad + 150 * scale);
85+
86+
// Description
87+
ctx.font = `bold ${32 * scale}px "Comic Sans MS", cursive, sans-serif`;
88+
wrapText(
89+
ctx,
90+
description,
91+
contentPad,
92+
pad + 250 * scale,
93+
bubbleW - 120 * scale,
94+
45 * scale,
95+
);
96+
97+
// Footer Stats
98+
const footerY = pad + bubbleH - 60 * scale;
99+
100+
// Stats (Bang!)
101+
const drawBang = (text, x, y, color) => {
102+
ctx.save();
103+
ctx.translate(x, y);
104+
ctx.rotate(Math.random() * 0.1 - 0.05);
105+
ctx.fillStyle = color;
106+
ctx.font = `italic 900 ${36 * scale}px "Arial Black", sans-serif`;
107+
ctx.lineWidth = 5 * scale;
108+
ctx.strokeStyle = '#000000';
109+
ctx.strokeText(text, 0, 0);
110+
ctx.fillText(text, 0, 0);
111+
ctx.restore();
112+
};
113+
114+
let statX = contentPad;
115+
if (stars) {
116+
drawBang(`★ ${stars}`, statX, footerY, '#ffd700');
117+
statX += 200 * scale;
118+
}
119+
if (forks) {
120+
drawBang(`⑂ ${forks}`, statX, footerY, '#00ffcc');
121+
statX += 200 * scale;
122+
}
123+
124+
// Language (Action style)
125+
drawBang(language.toUpperCase(), width - contentPad - 150 * scale, footerY, primaryColor);
126+
127+
// Support URL
128+
if (supportUrl) {
129+
ctx.font = `bold ${20 * scale}px "Comic Sans MS", cursive, sans-serif`;
130+
ctx.textAlign = 'right';
131+
ctx.fillStyle = 'rgba(0,0,0,0.6)';
132+
ctx.fillText(supportUrl, width - contentPad, pad + bubbleH + 40 * scale);
133+
}
134+
};
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
import { wrapText } from '../utils';
2+
3+
export const cybernetic = (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+
} = data;
15+
16+
// Background
17+
ctx.fillStyle = '#0a0a0f';
18+
ctx.fillRect(0, 0, width, height);
19+
20+
// Hexagon Pattern Background
21+
ctx.save();
22+
ctx.strokeStyle = primaryColor;
23+
ctx.globalAlpha = 0.15;
24+
ctx.lineWidth = 1 * scale;
25+
const size = 60 * scale;
26+
for (let y = 0; y < height + size; y += size * 1.5) {
27+
for (let x = 0; x < width + size; x += size * Math.sqrt(3)) {
28+
ctx.beginPath();
29+
for (let i = 0; i < 6; i++) {
30+
const angle = (i * Math.PI) / 3;
31+
ctx.lineTo(
32+
x + size * Math.cos(angle),
33+
y + size * Math.sin(angle)
34+
);
35+
}
36+
ctx.closePath();
37+
ctx.stroke();
38+
}
39+
}
40+
ctx.restore();
41+
42+
// Tech Frame
43+
const padding = 60 * scale;
44+
const borderW = 4 * scale;
45+
ctx.strokeStyle = primaryColor;
46+
ctx.lineWidth = borderW;
47+
ctx.strokeRect(padding, padding, width - padding * 2, height - padding * 2);
48+
49+
// Corner Accents
50+
const cornerSize = 40 * scale;
51+
ctx.fillStyle = primaryColor;
52+
const drawCorner = (x, y, dx, dy) => {
53+
ctx.beginPath();
54+
ctx.moveTo(x, y + dy * cornerSize);
55+
ctx.lineTo(x, y);
56+
ctx.lineTo(x + dx * cornerSize, y);
57+
ctx.lineTo(x + dx * cornerSize, y + dy * 10 * scale);
58+
ctx.lineTo(x + dx * 10 * scale, y + dy * 10 * scale);
59+
ctx.lineTo(x + dx * 10 * scale, y + dy * cornerSize);
60+
ctx.closePath();
61+
ctx.fill();
62+
};
63+
64+
drawCorner(padding, padding, 1, 1);
65+
drawCorner(width - padding, padding, -1, 1);
66+
drawCorner(padding, height - padding, 1, -1);
67+
drawCorner(width - padding, height - padding, -1, -1);
68+
69+
// Content
70+
const contentPad = padding + 80 * scale;
71+
72+
// Repo Owner (HUD style)
73+
ctx.textAlign = 'left';
74+
ctx.font = `bold ${30 * scale}px "JetBrains Mono", monospace`;
75+
ctx.fillStyle = secondaryColor;
76+
ctx.fillText(`ID: ${repoOwner.toUpperCase()}`, contentPad, padding + 100 * scale);
77+
78+
// Repo Name (Glitch Style)
79+
ctx.save();
80+
ctx.font = `900 ${110 * scale}px "Orbitron", "Arial Black", sans-serif`;
81+
ctx.shadowColor = primaryColor;
82+
ctx.shadowBlur = 20 * scale;
83+
ctx.fillStyle = '#ffffff';
84+
ctx.fillText(repoName, contentPad, padding + 220 * scale);
85+
86+
// Slight offset glow
87+
ctx.shadowColor = secondaryColor;
88+
ctx.shadowBlur = 10 * scale;
89+
ctx.fillText(repoName, contentPad + 2 * scale, padding + 220 * scale);
90+
ctx.restore();
91+
92+
// Description
93+
ctx.font = `${34 * scale}px "JetBrains Mono", monospace`;
94+
ctx.fillStyle = 'rgba(255, 255, 255, 0.8)';
95+
wrapText(
96+
ctx,
97+
description,
98+
contentPad,
99+
padding + 320 * scale,
100+
width - padding * 2 - 160 * scale,
101+
45 * scale,
102+
);
103+
104+
// Footer Stats
105+
const footerY = height - padding - 60 * scale;
106+
107+
// HUD Bars
108+
ctx.fillStyle = primaryColor;
109+
ctx.globalAlpha = 0.2;
110+
ctx.fillRect(contentPad, footerY - 40 * scale, width - padding * 2 - 160 * scale, 60 * scale);
111+
ctx.globalAlpha = 1.0;
112+
113+
// Stats
114+
ctx.font = `bold ${24 * scale}px "JetBrains Mono", monospace`;
115+
ctx.fillStyle = '#ffffff';
116+
let statX = contentPad + 30 * scale;
117+
118+
const drawHudStat = (label, value) => {
119+
if (!value) return;
120+
const text = `${label}::${value}`;
121+
ctx.fillText(text, statX, footerY);
122+
statX += ctx.measureText(text).width + 60 * scale;
123+
};
124+
125+
drawHudStat('STR', stars);
126+
drawHudStat('FRK', forks);
127+
drawHudStat('LNG', language.toUpperCase());
128+
129+
// Support URL (Scanline style)
130+
if (supportUrl) {
131+
ctx.textAlign = 'right';
132+
ctx.font = `bold ${20 * scale}px "JetBrains Mono", monospace`;
133+
ctx.fillStyle = secondaryColor;
134+
ctx.fillText(supportUrl, width - padding - 80 * scale, footerY);
135+
}
136+
137+
// Scanlines
138+
ctx.save();
139+
ctx.globalAlpha = 0.05;
140+
ctx.fillStyle = '#000000';
141+
for (let i = 0; i < height; i += 4 * scale) {
142+
ctx.fillRect(0, i, width, 1 * scale);
143+
}
144+
ctx.restore();
145+
};

0 commit comments

Comments
 (0)