Skip to content

Commit fe9a91f

Browse files
committed
feat(community): add brand kit section
1 parent e6963dc commit fe9a91f

File tree

2 files changed

+210
-1
lines changed

2 files changed

+210
-1
lines changed

.github/workflows/version-bump.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ jobs:
1616
- name: Checkout repository
1717
uses: actions/checkout@v4
1818
with:
19-
fetch-depth: 0 # Required to get all tags
19+
fetch-depth: 0
2020

2121
- name: Setup Node.js
2222
uses: actions/setup-node@v4

src/app/brand-kit/page.tsx

Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
/* eslint-disable @typescript-eslint/ban-ts-comment, react/no-unescaped-entities */
2+
// @ts-nocheck
3+
'use client';
4+
5+
import { useState, useRef, type ComponentType } from 'react';
6+
import { Download, Copy, Check, XCircle, ShieldCheck } from 'lucide-react';
7+
8+
9+
const generateSvgString = (type: 'complete' | 'symbol', theme: 'light' | 'dark'): string => {
10+
const isDark = theme === 'dark';
11+
const primaryColor = isDark ? '#FFFFFF' : '#1e293b';
12+
const secondaryColor = isDark ? '#cbd5e1' : '#64748b';
13+
14+
if (type === 'symbol') {
15+
return `<svg viewBox="0 0 130 100" xmlns="http://www.w3.org/2000/svg"><g><path d="M 5 20 L 40 50 L 5 80 Z" fill="#E6007A"/><text x="60" y="80" font-family="Inter, sans-serif" font-size="80" font-weight="800" fill="${primaryColor}">P</text><circle cx="88" cy="50" r="10" fill="#E6007A" /></g></svg>`;
16+
}
17+
18+
return `<svg viewBox="0 0 400 100" xmlns="http://www.w3.org/2000/svg"><g id="logomark"><path d="M 5 20 L 40 50 L 5 80 Z" fill="#E6007A"/><text x="60" y="80" font-family="Inter, sans-serif" font-size="80" font-weight="800" fill="${primaryColor}">P</text><circle cx="88" cy="50" r="10" fill="#E6007A" /></g><g id="logotype" transform="translate(170, 0)"><text x="0" y="55" font-family="Inter, sans-serif" font-size="40" font-weight="800" fill="${primaryColor}">PAPI</text><text x="0" y="85" font-family="Inter, sans-serif" font-size="18" font-weight="500" fill="${secondaryColor}" letter-spacing="0.2em">SIMULATOR</text></g></svg>`;
19+
};
20+
21+
22+
23+
type LogoProps = { theme?: 'light' | 'dark' };
24+
25+
const LogoCompleto: React.FC<LogoProps> = ({ theme = 'light' }) => {
26+
const isDark = theme === 'dark';
27+
return (
28+
<svg viewBox="0 0 400 100" xmlns="http://www.w3.org/2000/svg" aria-label={`PAPI Simulator Primary Logo ${isDark ? 'White Version' : ''}`}>
29+
<g id="logomark"><path d="M 5 20 L 40 50 L 5 80 Z" fill="#E6007A" /><text x="60" y="80" fontFamily="Inter, sans-serif" fontSize="80" fontWeight="800" fill={isDark ? "#FFFFFF" : "#1e293b"}>P</text><circle cx="88" cy="50" r="10" fill="#E6007A" /></g>
30+
<g id="logotype" transform="translate(170, 0)"><text x="0" y="55" fontFamily="Inter, sans-serif" fontSize="40" fontWeight="800" fill={isDark ? "#FFFFFF" : "#1e293b"}>PAPI</text><text x="0" y="85" fontFamily="Inter, sans-serif" fontSize="18" fontWeight="500" fill={isDark ? "#cbd5e1" : "#64748b"} letterSpacing="0.2em">SIMULATOR</text></g>
31+
</svg>
32+
);
33+
};
34+
35+
const Simbolo: React.FC<LogoProps> = ({ theme = 'light' }) => {
36+
const isDark = theme === 'dark';
37+
return (
38+
<svg viewBox="0 0 130 100" xmlns="http://www.w3.org/2000/svg" aria-label={`PAPI Simulator Symbol ${isDark ? 'White Version' : ''}`}>
39+
<g><path d="M 5 20 L 40 50 L 5 80 Z" fill="#E6007A" /><text x="60" y="80" fontFamily="Inter, sans-serif" fontSize="80" fontWeight="800" fill={isDark ? "#FFFFFF" : "#1e293b"}>P</text><circle cx="88" cy="50" r="10" fill="#E6007A" /></g>
40+
</svg>
41+
);
42+
};
43+
44+
45+
interface LogoCardProps {
46+
title: string;
47+
LogoComponent: ComponentType<LogoProps>;
48+
svgContent: string;
49+
fileName: string;
50+
isDark?: boolean;
51+
}
52+
53+
const LogoCard: React.FC<LogoCardProps> = ({ title, LogoComponent, svgContent, fileName, isDark = false }) => {
54+
const [copied, setCopied] = useState(false);
55+
const canvasRef = useRef<HTMLCanvasElement>(null);
56+
const theme = isDark ? 'dark' : 'light';
57+
58+
const handleCopy = () => {
59+
navigator.clipboard.writeText(svgContent.trim());
60+
setCopied(true);
61+
setTimeout(() => setCopied(false), 2000);
62+
};
63+
64+
const handleDownload = (format: 'svg' | 'png', size?: number) => {
65+
if (format === 'svg') {
66+
const blob = new Blob([svgContent], { type: 'image/svg+xml' });
67+
const url = URL.createObjectURL(blob);
68+
const a = document.createElement('a');
69+
a.href = url;
70+
a.download = `${fileName}.svg`;
71+
document.body.appendChild(a);
72+
a.click();
73+
document.body.removeChild(a);
74+
URL.revokeObjectURL(url);
75+
} else if (format === 'png' && size) {
76+
const canvas = canvasRef.current;
77+
const ctx = canvas?.getContext('2d');
78+
if (!ctx || !canvas) return;
79+
const img = new Image();
80+
const svgBlob = new Blob([svgContent], { type: 'image/svg+xml' });
81+
const url = URL.createObjectURL(svgBlob);
82+
img.onload = () => {
83+
const tempViewBox = svgContent.match(/viewBox="([^"]+)"/);
84+
const dims = tempViewBox ? tempViewBox[1].split(' ').map(Number) : [0, 0, 400, 100];
85+
const originalWidth = dims[2];
86+
const originalHeight = dims[3];
87+
const aspectRatio = originalWidth / originalHeight;
88+
canvas.width = size;
89+
canvas.height = size / aspectRatio;
90+
ctx.clearRect(0, 0, canvas.width, canvas.height);
91+
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
92+
const pngUrl = canvas.toDataURL('image/png');
93+
const a = document.createElement('a');
94+
a.href = pngUrl;
95+
a.download = `${fileName}_${size}px.png`;
96+
document.body.appendChild(a);
97+
a.click();
98+
document.body.removeChild(a);
99+
URL.revokeObjectURL(url);
100+
};
101+
img.onerror = () => { console.error("Failed to load SVG image for canvas conversion."); URL.revokeObjectURL(url); };
102+
img.src = url;
103+
}
104+
};
105+
106+
return (
107+
<div className="border border-slate-200 dark:border-slate-700 rounded-2xl overflow-hidden shadow-sm bg-white dark:bg-slate-800">
108+
<div className={`p-8 sm:p-12 flex items-center justify-center ${isDark ? 'bg-slate-900' : 'bg-slate-50'}`}>
109+
<div className="w-48 h-24"> <LogoComponent theme={theme} /> </div>
110+
</div>
111+
<div className="p-4">
112+
<h3 className="font-semibold text-slate-800 dark:text-slate-200">{title}</h3>
113+
<div className="mt-4 flex flex-wrap gap-2">
114+
<button onClick={() => handleDownload('svg')} className="flex items-center gap-2 text-sm bg-slate-100 dark:bg-slate-700 hover:bg-slate-200 dark:hover:bg-slate-600 text-slate-700 dark:text-slate-300 font-medium py-2 px-3 rounded-lg transition-colors"> <Download size={16} /> SVG </button>
115+
<button onClick={() => handleDownload('png', 512)} className="flex items-center gap-2 text-sm bg-slate-100 dark:bg-slate-700 hover:bg-slate-200 dark:hover:bg-slate-600 text-slate-700 dark:text-slate-300 font-medium py-2 px-3 rounded-lg transition-colors"> <Download size={16} /> PNG (512px) </button>
116+
<button onClick={() => handleDownload('png', 1024)} className="flex items-center gap-2 text-sm bg-slate-100 dark:bg-slate-700 hover:bg-slate-200 dark:hover:bg-slate-600 text-slate-700 dark:text-slate-300 font-medium py-2 px-3 rounded-lg transition-colors"> <Download size={16} /> PNG (1024px) </button>
117+
<button onClick={handleCopy} className={`flex items-center gap-2 text-sm ${copied ? 'bg-green-100 text-green-700' : 'bg-slate-100 dark:bg-slate-700 hover:bg-slate-200 dark:hover:bg-slate-600 text-slate-700 dark:text-slate-300'} font-medium py-2 px-3 rounded-lg transition-colors`}> {copied ? <Check size={16} /> : <Copy size={16} />} {copied ? 'Copied!' : 'Copy SVG'} </button>
118+
</div>
119+
</div>
120+
<canvas ref={canvasRef} style={{ display: 'none' }} />
121+
</div>
122+
);
123+
};
124+
125+
126+
const ColorPalette = () => {
127+
const colors = [
128+
{ name: 'Polkadot Pink', hex: '#E6007A', text: 'white' }, { name: 'Slate Dark', hex: '#1e293b', text: 'white' },
129+
{ name: 'Slate Medium', hex: '#64748b', text: 'white' }, { name: 'Slate Light', hex: '#f8fafc', text: 'black' },
130+
];
131+
const [copiedHex, setCopiedHex] = useState('');
132+
const handleCopy = (hex: string) => { navigator.clipboard.writeText(hex); setCopiedHex(hex); setTimeout(() => setCopiedHex(''), 2000); };
133+
return (
134+
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-4 gap-6">
135+
{colors.map(color => (
136+
<div key={color.name} className="rounded-2xl shadow-sm overflow-hidden border border-slate-200 dark:border-slate-700 bg-white dark:bg-slate-800">
137+
<div style={{ backgroundColor: color.hex, color: `var(--color-${color.text})` }} className="h-24 sm:h-32 p-4 flex flex-col justify-end"> <h3 className="font-bold text-lg">{color.name}</h3> </div>
138+
<div className="p-4 flex justify-between items-center"> <span className="font-mono text-slate-600 dark:text-slate-400">{color.hex}</span> <button onClick={() => handleCopy(color.hex)} title={`Copy ${color.hex}`}>{copiedHex === color.hex ? <Check size={18} className="text-green-600" /> : <Copy size={18} className="text-slate-400 hover:text-slate-600" />}</button> </div>
139+
</div>
140+
))}
141+
</div>
142+
);
143+
};
144+
145+
146+
export default function BrandKitPage() {
147+
148+
const logoAssets: Omit<LogoCardProps, 'svgContent'>[] = [
149+
{ title: 'Primary Logo', LogoComponent: LogoCompleto, fileName: 'papi_simulator_logo_primary' },
150+
{ title: 'Primary Logo (White Version)', LogoComponent: LogoCompleto, fileName: 'papi_simulator_logo_primary_white', isDark: true },
151+
{ title: 'Symbol (Icon)', LogoComponent: Simbolo, fileName: 'papi_simulator_symbol' },
152+
{ title: 'Symbol (White Version)', LogoComponent: Simbolo, fileName: 'papi_simulator_symbol_white', isDark: true },
153+
];
154+
155+
return (
156+
<main className="bg-slate-50 dark:bg-slate-900 min-h-screen text-slate-800 dark:text-slate-200 font-sans">
157+
<div className="container mx-auto px-4 py-12 sm:py-16 md:py-24">
158+
<header className="text-center max-w-3xl mx-auto mb-16">
159+
<div className="w-48 mx-auto mb-6 text-slate-900 dark:text-white"> <Simbolo theme="light" /> </div>
160+
<h1 className="text-4xl sm:text-5xl font-extrabold text-slate-900 dark:text-slate-50 tracking-tight">PAPI Simulator Brand Kit</h1>
161+
<p className="mt-4 text-lg text-slate-600 dark:text-slate-400"> Welcome to the PAPI Simulator brand resource hub. Here you'll find our logos, colors, and guidelines to ensure our brand is represented consistently. </p>
162+
</header>
163+
164+
<section className="mb-16">
165+
<h2 className="text-3xl font-bold text-slate-900 dark:text-slate-100 mb-8 border-b border-slate-200 dark:border-slate-700 pb-4">Logos</h2>
166+
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
167+
{logoAssets.map(asset => {
168+
const theme = asset.isDark ? 'dark' : 'light';
169+
const type = asset.title.includes('Symbol') ? 'symbol' : 'complete';
170+
return <LogoCard key={asset.fileName} {...asset} svgContent={generateSvgString(type, theme)} />;
171+
})}
172+
</div>
173+
</section>
174+
175+
<section className="mb-16">
176+
<h2 className="text-3xl font-bold text-slate-900 dark:text-slate-100 mb-8 border-b border-slate-200 dark:border-slate-700 pb-4">Color Palette</h2>
177+
<ColorPalette />
178+
</section>
179+
180+
<section className="mb-16">
181+
<h2 className="text-3xl font-bold text-slate-900 dark:text-slate-100 mb-8 border-b border-slate-200 dark:border-slate-700 pb-4">Typography</h2>
182+
<div className="bg-white dark:bg-slate-800 p-8 rounded-2xl border border-slate-200 dark:border-slate-700 shadow-sm">
183+
<h3 className="text-2xl font-bold text-slate-800 dark:text-slate-200">Inter</h3>
184+
<p className="mt-2 text-slate-600 dark:text-slate-400 mb-6">Our visual identity uses the Inter font family, known for its excellent legibility in user interfaces. It's an open-source font and can be obtained for free from Google Fonts.</p>
185+
<div className="flex items-baseline gap-x-8 gap-y-4 flex-wrap text-slate-700 dark:text-slate-300"> <div className="font-medium">Regular</div> <div className="font-semibold">SemiBold</div> <div className="font-bold">Bold</div> <div className="font-extrabold">ExtraBold</div> </div>
186+
<a href="https://fonts.google.com/specimen/Inter" target="_blank" rel="noopener noreferrer" className="inline-block mt-6 bg-slate-900 dark:bg-slate-100 text-white dark:text-slate-900 font-semibold py-2 px-4 rounded-lg hover:bg-slate-700 dark:hover:bg-slate-300 transition-colors"> Get Inter on Google Fonts </a>
187+
</div>
188+
</section>
189+
190+
<section>
191+
<h2 className="text-3xl font-bold text-slate-900 dark:text-slate-100 mb-8 border-b border-slate-200 dark:border-slate-700 pb-4">Usage Guidelines</h2>
192+
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
193+
<div className="bg-white dark:bg-slate-800 p-8 rounded-2xl border border-slate-200 dark:border-slate-700 shadow-sm">
194+
<div className="flex items-center gap-3 mb-4"> <div className="bg-green-100 dark:bg-green-900/30 p-2 rounded-full"> <ShieldCheck className="text-green-600 dark:text-green-400" size={24} /> </div> <h3 className="text-2xl font-bold text-slate-800 dark:text-slate-200">Do's</h3> </div>
195+
<ul className="space-y-3 text-slate-600 dark:text-slate-400 list-disc list-inside"> <li>Use the primary logo whenever space permits.</li> <li>Maintain a clear space around the logo (at least 50% of the logo's height).</li> <li>Use the white version on dark backgrounds to ensure contrast.</li> <li>Always maintain the original proportions of the logo.</li> </ul>
196+
</div>
197+
<div className="bg-white dark:bg-slate-800 p-8 rounded-2xl border border-slate-200 dark:border-slate-700 shadow-sm">
198+
<div className="flex items-center gap-3 mb-4"> <div className="bg-red-100 dark:bg-red-900/30 p-2 rounded-full"> <XCircle className="text-red-600 dark:text-red-400" size={24} /> </div> <h3 className="text-2xl font-bold text-slate-800 dark:text-slate-200">Don'ts</h3> </div>
199+
<ul className="space-y-3 text-slate-600 dark:text-slate-400 list-disc list-inside"> <li>Do not distort or alter the logo's proportions.</li> <li>Do not change the logo's colors.</li> <li>Do not add shadows, gradients, or other visual effects.</li> <li>Do not use the logo on backgrounds that compromise its legibility.</li> </ul>
200+
</div>
201+
</div>
202+
</section>
203+
</div>
204+
<footer className="text-center py-8 border-t border-slate-200 dark:border-slate-700">
205+
<p className="text-slate-500 dark:text-slate-400">© {new Date().getFullYear()} PAPI Simulator. All rights reserved.</p>
206+
</footer>
207+
</main>
208+
);
209+
}

0 commit comments

Comments
 (0)