Skip to content

Commit f640df4

Browse files
committed
feat(apps): add 4 new themes to github thumbnail generator
1 parent 36634bb commit f640df4

File tree

6 files changed

+333
-1
lines changed

6 files changed

+333
-1
lines changed

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ const THEME_OPTIONS = [
3535
{ value: 'sketch', label: 'HAND_DRAWN' },
3636
{ value: 'bauhaus', label: 'BAUHAUS_GEO' },
3737
{ value: 'popart', label: 'POP_ART_COMIC' },
38+
{ value: 'cod', label: 'TACTICAL_OPS' },
39+
{ value: 'crtAmber', label: 'RETRO_AMBER_CRT' },
40+
{ value: 'basketball', label: 'COURT_SIDE' },
41+
{ value: 'rich', label: 'LUXURY_GOLD' },
3842
];
3943

4044
const GithubThumbnailGeneratorPage = () => {

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

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ import { cyberpunk } from './themes/cyberpunk';
1515
import { sketch } from './themes/sketch';
1616
import { bauhaus } from './themes/bauhaus';
1717
import { popart } from './themes/popart';
18+
import { cod } from './themes/callofduty';
19+
import { crtAmber } from './themes/crtAmber';
20+
import { basketball } from './themes/basketball';
21+
import { rich } from './themes/rich';
1822

1923
export const themeRenderers = {
2024
modern,
@@ -34,4 +38,8 @@ export const themeRenderers = {
3438
sketch,
3539
bauhaus,
3640
popart,
37-
};
41+
cod,
42+
crtAmber,
43+
basketball,
44+
rich,
45+
};
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import { wrapText } from '../utils';
2+
3+
export const basketball = (ctx, width, height, scale, data) => {
4+
const { repoOwner, repoName, description, stars, forks } = data;
5+
// BASKETBALL / COURT SIDE Style
6+
7+
// Wood Floor Background
8+
ctx.fillStyle = '#e8b576'; // Light wood
9+
ctx.fillRect(0, 0, width, height);
10+
11+
// Wood Planks
12+
ctx.strokeStyle = '#d49b5e';
13+
ctx.lineWidth = 2 * scale;
14+
for(let x=0; x<width; x+=60*scale) {
15+
ctx.beginPath();
16+
ctx.moveTo(x, 0); ctx.lineTo(x, height);
17+
ctx.stroke();
18+
}
19+
20+
// Court Lines (White & Black)
21+
ctx.lineWidth = 10 * scale;
22+
ctx.strokeStyle = '#ffffff';
23+
ctx.beginPath();
24+
// Center Circle
25+
ctx.arc(width/2, height/2, 250 * scale, 0, Math.PI * 2);
26+
ctx.stroke();
27+
// Mid court line
28+
ctx.beginPath();
29+
ctx.moveTo(width/2, 0); ctx.lineTo(width/2, height);
30+
ctx.stroke();
31+
32+
// Key Area (Left side decoration)
33+
ctx.strokeStyle = '#000000';
34+
ctx.strokeRect(0, height * 0.3, 200 * scale, height * 0.4);
35+
36+
const primaryColor = '#ff6b00'; // Basketball Orange
37+
const black = '#111';
38+
39+
// Main Title: Varsity Style
40+
ctx.fillStyle = primaryColor;
41+
ctx.font = `900 ${120 * scale}px "Arial Black", "Impact", sans-serif`;
42+
ctx.textAlign = 'center';
43+
ctx.lineWidth = 8 * scale;
44+
ctx.strokeStyle = black; // Outline
45+
46+
// Shadow
47+
ctx.shadowColor = 'rgba(0,0,0,0.3)';
48+
ctx.shadowBlur = 10 * scale;
49+
ctx.strokeText(repoName.toUpperCase(), width/2, height * 0.45);
50+
ctx.fillText(repoName.toUpperCase(), width/2, height * 0.45);
51+
ctx.shadowBlur = 0;
52+
53+
// "VS" or "BY"
54+
ctx.fillStyle = black;
55+
ctx.font = `bold ${40 * scale}px "Arial", sans-serif`;
56+
ctx.fillText(`COACH: ${repoOwner.toUpperCase()}`, width/2, height * 0.25);
57+
58+
// Scoreboard (Stats)
59+
const boardW = 600 * scale;
60+
const boardH = 150 * scale;
61+
const boardX = (width - boardW) / 2;
62+
const boardY = height * 0.7;
63+
64+
ctx.fillStyle = '#222';
65+
ctx.fillRect(boardX, boardY, boardW, boardH);
66+
ctx.strokeStyle = '#555';
67+
ctx.lineWidth = 5 * scale;
68+
ctx.strokeRect(boardX, boardY, boardW, boardH);
69+
70+
// LED Digits
71+
ctx.font = `bold ${50 * scale}px "Courier New", monospace`;
72+
ctx.textAlign = 'center';
73+
74+
// Home (Stars)
75+
ctx.fillStyle = '#f00'; // LED Red
76+
ctx.fillText(stars || '0', boardX + boardW * 0.25, boardY + boardH * 0.6);
77+
ctx.font = `bold ${20 * scale}px "Arial", sans-serif`;
78+
ctx.fillStyle = '#fff';
79+
ctx.fillText("STARS", boardX + boardW * 0.25, boardY + boardH * 0.85);
80+
81+
// Away (Forks)
82+
ctx.font = `bold ${50 * scale}px "Courier New", monospace`;
83+
ctx.fillStyle = '#f00';
84+
ctx.fillText(forks || '0', boardX + boardW * 0.75, boardY + boardH * 0.6);
85+
ctx.font = `bold ${20 * scale}px "Arial", sans-serif`;
86+
ctx.fillStyle = '#fff';
87+
ctx.fillText("FORKS", boardX + boardW * 0.75, boardY + boardH * 0.85);
88+
89+
// Description text (Playbook)
90+
ctx.fillStyle = '#000';
91+
ctx.font = `italic bold ${24 * scale}px "Arial", sans-serif`;
92+
wrapText(ctx, description, width/2, height * 0.55, width * 0.8, 30 * scale);
93+
};
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import { wrapText } from '../utils';
2+
3+
export const cod = (ctx, width, height, scale, data) => {
4+
const { repoOwner, repoName, description, language, stars, forks } = data;
5+
// CALL OF DUTY / TACTICAL OPS Style
6+
7+
// Background: Dark Camo / Night Vision Green tint
8+
ctx.fillStyle = '#0a0f0a'; // Very dark green/black
9+
ctx.fillRect(0, 0, width, height);
10+
11+
// Grid Overlay (Tactical Map)
12+
ctx.strokeStyle = 'rgba(100, 255, 100, 0.1)';
13+
ctx.lineWidth = 1 * scale;
14+
const gridSize = 50 * scale;
15+
ctx.beginPath();
16+
for(let x=0; x<width; x+=gridSize) {
17+
ctx.moveTo(x, 0); ctx.lineTo(x, height);
18+
}
19+
for(let y=0; y<height; y+=gridSize) {
20+
ctx.moveTo(0, y); ctx.lineTo(width, y);
21+
}
22+
ctx.stroke();
23+
24+
const padding = 60 * scale;
25+
const primaryColor = '#7fff00'; // Radar Green
26+
const secondaryColor = '#ffffff';
27+
28+
// Top Left: Mission Info
29+
ctx.fillStyle = primaryColor;
30+
ctx.font = `bold ${24 * scale}px "Courier New", monospace`;
31+
ctx.fillText(`OPERATOR: ${repoOwner.toUpperCase()}`, padding, padding);
32+
ctx.fillText(`MISSION: ${repoName.toUpperCase()}`, padding, padding + 30 * scale);
33+
34+
// Top Right: Coordinates / Time
35+
ctx.textAlign = 'right';
36+
ctx.fillText(`LOC: ${stars || '0'}°N ${forks || '0'}°E`, width - padding, padding);
37+
ctx.fillText(`LANG: ${language.toUpperCase()}`, width - padding, padding + 30 * scale);
38+
39+
// Center: Main Title with Glitch/Scan effect
40+
ctx.textAlign = 'center';
41+
ctx.font = `900 ${100 * scale}px "Impact", sans-serif`;
42+
ctx.fillStyle = 'rgba(255, 255, 255, 0.1)';
43+
ctx.fillText(repoName.toUpperCase(), width/2 + 5*scale, height/2 + 5*scale); // Shadow
44+
45+
ctx.fillStyle = secondaryColor;
46+
ctx.fillText(repoName.toUpperCase(), width/2, height/2);
47+
48+
// Crosshair in center
49+
ctx.strokeStyle = primaryColor;
50+
ctx.lineWidth = 2 * scale;
51+
const chSize = 40 * scale;
52+
ctx.beginPath();
53+
ctx.moveTo(width/2 - chSize, height/2); ctx.lineTo(width/2 + chSize, height/2);
54+
ctx.moveTo(width/2, height/2 - chSize); ctx.lineTo(width/2, height/2 + chSize);
55+
ctx.stroke();
56+
ctx.beginPath();
57+
ctx.arc(width/2, height/2, 200 * scale, 0, Math.PI * 2); // Large circle
58+
ctx.setLineDash([10 * scale, 15 * scale]);
59+
ctx.stroke();
60+
ctx.setLineDash([]);
61+
62+
// Description Box (Tactical Intel)
63+
const descY = height * 0.65;
64+
ctx.fillStyle = 'rgba(0, 50, 0, 0.5)';
65+
ctx.strokeStyle = primaryColor;
66+
ctx.lineWidth = 2 * scale;
67+
ctx.fillRect(width * 0.15, descY, width * 0.7, 120 * scale);
68+
ctx.strokeRect(width * 0.15, descY, width * 0.7, 120 * scale);
69+
70+
ctx.fillStyle = primaryColor;
71+
ctx.font = `normal ${28 * scale}px "Courier New", monospace`;
72+
ctx.textAlign = 'center';
73+
ctx.fillText("/// BRIEFING ///", width/2, descY - 10 * scale);
74+
75+
ctx.fillStyle = secondaryColor;
76+
wrapText(ctx, description.toUpperCase(), width/2, descY + 40 * scale, width * 0.65, 35 * scale);
77+
78+
// Bottom Corners: Tech Decoration
79+
ctx.fillStyle = primaryColor;
80+
ctx.fillRect(padding, height - padding, 100 * scale, 10 * scale);
81+
ctx.fillRect(padding, height - padding - 50 * scale, 10 * scale, 60 * scale);
82+
83+
ctx.fillRect(width - padding - 100 * scale, height - padding, 100 * scale, 10 * scale);
84+
ctx.fillRect(width - padding, height - padding - 50 * scale, 10 * scale, 60 * scale);
85+
};
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import { wrapText } from '../utils';
2+
3+
export const crtAmber = (ctx, width, height, scale, data) => {
4+
const { repoOwner, repoName, description, language, stars, forks } = data;
5+
// RETRO AMBER CRT Style
6+
7+
const amber = '#ffb000';
8+
9+
// Background: Deep Black
10+
ctx.fillStyle = '#050505';
11+
ctx.fillRect(0, 0, width, height);
12+
13+
// Scanlines
14+
ctx.fillStyle = 'rgba(0, 0, 0, 0.5)';
15+
for(let i=0; i<height; i+=4*scale) {
16+
ctx.fillRect(0, i, width, 2*scale);
17+
}
18+
19+
// Vignette (Simulate curved screen corners darkening)
20+
const grad = ctx.createRadialGradient(width/2, height/2, width*0.3, width/2, height/2, width*0.8);
21+
grad.addColorStop(0, 'rgba(0,0,0,0)');
22+
grad.addColorStop(1, 'rgba(0,0,0,0.8)');
23+
ctx.fillStyle = grad;
24+
ctx.fillRect(0, 0, width, height);
25+
26+
// Glow Effect
27+
ctx.shadowBlur = 10 * scale;
28+
ctx.shadowColor = amber;
29+
30+
const padding = 80 * scale;
31+
32+
// Header
33+
ctx.fillStyle = amber;
34+
ctx.font = `bold ${30 * scale}px "Courier New", monospace`;
35+
ctx.textAlign = 'left';
36+
ctx.fillText(`> SYSTEM.LOGIN: ${repoOwner}`, padding, padding);
37+
ctx.fillText(`> MOUNTING REPO: ${repoName}... OK`, padding, padding + 40 * scale);
38+
39+
// Main Title (ASCII Art style simulation or just big blocky text)
40+
ctx.font = `900 ${100 * scale}px "Courier New", monospace`;
41+
ctx.fillText(repoName.toUpperCase(), padding, height * 0.4);
42+
43+
// Line
44+
ctx.strokeStyle = amber;
45+
ctx.lineWidth = 2 * scale;
46+
ctx.beginPath();
47+
ctx.moveTo(padding, height * 0.45);
48+
ctx.lineTo(width - padding, height * 0.45);
49+
ctx.stroke();
50+
51+
// Description
52+
ctx.font = `normal ${30 * scale}px "Courier New", monospace`;
53+
wrapText(ctx, description, padding, height * 0.55, width - padding*2, 40 * scale);
54+
55+
// Stats as System Stats
56+
const bottomY = height - padding;
57+
ctx.font = `bold ${24 * scale}px "Courier New", monospace`;
58+
let stats = `[ MEMORY: ${stars || 0} KB ]`; // Stars as memory
59+
stats += ` [ PROCS: ${forks || 0} ]`; // Forks as processes
60+
stats += ` [ LANG: ${language.toUpperCase()} ]`;
61+
62+
ctx.fillText(stats, padding, bottomY);
63+
64+
// Blinking Cursor
65+
ctx.fillRect(padding + ctx.measureText(stats).width + 10 * scale, bottomY - 25 * scale, 15 * scale, 30 * scale);
66+
};
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import { wrapText } from '../utils';
2+
3+
export const rich = (ctx, width, height, scale, data) => {
4+
const { repoOwner, repoName, description, language, stars } = data;
5+
// RICH / LUXURY GOLD Style
6+
7+
// Marble/Paper Background
8+
ctx.fillStyle = '#f8f8f8';
9+
ctx.fillRect(0, 0, width, height);
10+
11+
// Gold Gradient
12+
const goldGrad = ctx.createLinearGradient(0, 0, width, height);
13+
goldGrad.addColorStop(0, '#bf953f');
14+
goldGrad.addColorStop(0.25, '#fcf6ba');
15+
goldGrad.addColorStop(0.5, '#b38728');
16+
goldGrad.addColorStop(0.75, '#fbf5b7');
17+
goldGrad.addColorStop(1, '#aa771c');
18+
19+
// Border Frame
20+
const padding = 50 * scale;
21+
ctx.strokeStyle = goldGrad;
22+
ctx.lineWidth = 10 * scale;
23+
ctx.strokeRect(padding, padding, width - padding*2, height - padding*2);
24+
25+
// Inner thin line
26+
ctx.lineWidth = 2 * scale;
27+
ctx.strokeRect(padding + 15*scale, padding + 15*scale, width - padding*2 - 30*scale, height - padding*2 - 30*scale);
28+
29+
// Decorative Flourish (Simple lines for now)
30+
ctx.beginPath();
31+
ctx.moveTo(width/2 - 100*scale, height * 0.35);
32+
ctx.lineTo(width/2 + 100*scale, height * 0.35);
33+
ctx.stroke();
34+
35+
// Typography
36+
const serifFont = '"Playfair Display", "Times New Roman", serif';
37+
38+
// Title
39+
ctx.fillStyle = '#1a1a1a'; // Dark Charcoal
40+
ctx.font = `italic 900 ${100 * scale}px ${serifFont}`;
41+
ctx.textAlign = 'center';
42+
ctx.fillText(repoName, width/2, height * 0.3);
43+
44+
// Owner (The Brand)
45+
ctx.font = `bold ${30 * scale}px ${serifFont}`;
46+
ctx.letterSpacing = '5px';
47+
ctx.fillStyle = '#aa771c'; // Gold text
48+
ctx.fillText(repoOwner.toUpperCase(), width/2, height * 0.15);
49+
50+
// Description
51+
ctx.fillStyle = '#333';
52+
ctx.font = `normal ${32 * scale}px ${serifFont}`;
53+
wrapText(ctx, description, width/2, height * 0.5, width * 0.6, 45 * scale);
54+
55+
// "Seal" of Quality (Stats)
56+
const sealX = width - padding - 100 * scale;
57+
const sealY = height - padding - 100 * scale;
58+
const sealR = 80 * scale;
59+
60+
ctx.beginPath();
61+
ctx.arc(sealX, sealY, sealR, 0, Math.PI * 2);
62+
ctx.fillStyle = goldGrad;
63+
ctx.fill();
64+
65+
// Seal text
66+
ctx.fillStyle = '#000';
67+
ctx.font = `bold ${24 * scale}px ${serifFont}`;
68+
ctx.fillText(stars || '0', sealX, sealY);
69+
ctx.font = `normal ${16 * scale}px ${serifFont}`;
70+
ctx.fillText("STARS", sealX, sealY + 20 * scale);
71+
72+
// Lang at bottom center
73+
ctx.fillStyle = '#666';
74+
ctx.font = `italic ${24 * scale}px ${serifFont}`;
75+
ctx.fillText(`~ ${language} ~`, width/2, height - padding - 30 * scale);
76+
};

0 commit comments

Comments
 (0)