|
| 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 | + let url: string | null = null; |
| 66 | + try { |
| 67 | + if (format === 'svg') { |
| 68 | + const blob = new Blob([svgContent], { type: 'image/svg+xml' }); |
| 69 | + url = URL.createObjectURL(blob); |
| 70 | + const a = document.createElement('a'); |
| 71 | + a.href = url; |
| 72 | + a.download = `${fileName}.svg`; |
| 73 | + document.body.appendChild(a); |
| 74 | + a.click(); |
| 75 | + document.body.removeChild(a); |
| 76 | + } else if (format === 'png' && size) { |
| 77 | + const canvas = canvasRef.current; |
| 78 | + const ctx = canvas?.getContext('2d'); |
| 79 | + if (!ctx || !canvas) { |
| 80 | + console.error("Canvas context is not available."); |
| 81 | + return; |
| 82 | + } |
| 83 | + const img = new Image(); |
| 84 | + const svgBlob = new Blob([svgContent], { type: 'image/svg+xml' }); |
| 85 | + url = URL.createObjectURL(svgBlob); |
| 86 | + |
| 87 | + img.onload = () => { |
| 88 | + // Validate image dimensions before calculations |
| 89 | + if (img.width > 0 && img.height > 0) { |
| 90 | + const aspectRatio = img.width / img.height; |
| 91 | + canvas.width = size; |
| 92 | + canvas.height = size / aspectRatio; |
| 93 | + ctx.clearRect(0, 0, canvas.width, canvas.height); |
| 94 | + ctx.drawImage(img, 0, 0, canvas.width, canvas.height); |
| 95 | + const pngUrl = canvas.toDataURL('image/png'); |
| 96 | + const a = document.createElement('a'); |
| 97 | + a.href = pngUrl; |
| 98 | + a.download = `${fileName}_${size}px.png`; |
| 99 | + document.body.appendChild(a); |
| 100 | + a.click(); |
| 101 | + document.body.removeChild(a); |
| 102 | + } else { |
| 103 | + console.error("Failed to process SVG: Invalid image dimensions (width or height is zero)."); |
| 104 | + } |
| 105 | + }; |
| 106 | + |
| 107 | + img.onerror = () => { |
| 108 | + console.error("Failed to load SVG image for canvas conversion."); |
| 109 | + }; |
| 110 | + |
| 111 | + img.src = url; |
| 112 | + } |
| 113 | + } catch (error) { |
| 114 | + console.error("An error occurred during the download process:", error); |
| 115 | + } finally { |
| 116 | + if (url) { |
| 117 | + URL.revokeObjectURL(url); |
| 118 | + } |
| 119 | + } |
| 120 | + }; |
| 121 | + |
| 122 | + return ( |
| 123 | + <div className="border border-slate-200 dark:border-slate-700 rounded-2xl overflow-hidden shadow-sm bg-white dark:bg-slate-800"> |
| 124 | + <div className={`p-8 sm:p-12 flex items-center justify-center ${isDark ? 'bg-slate-900' : 'bg-slate-50'}`}> |
| 125 | + <div className="w-48 h-24"> <LogoComponent theme={theme} /> </div> |
| 126 | + </div> |
| 127 | + <div className="p-4"> |
| 128 | + <h3 className="font-semibold text-slate-800 dark:text-slate-200">{title}</h3> |
| 129 | + <div className="mt-4 flex flex-wrap gap-2"> |
| 130 | + <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> |
| 131 | + <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> |
| 132 | + <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> |
| 133 | + <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> |
| 134 | + </div> |
| 135 | + </div> |
| 136 | + <canvas ref={canvasRef} style={{ display: 'none' }} /> |
| 137 | + </div> |
| 138 | + ); |
| 139 | +}; |
| 140 | + |
| 141 | + |
| 142 | +const ColorPalette = () => { |
| 143 | + const colors = [ |
| 144 | + { name: 'Polkadot Pink', hex: '#E6007A', text: 'white' }, { name: 'Slate Dark', hex: '#1e293b', text: 'white' }, |
| 145 | + { name: 'Slate Medium', hex: '#64748b', text: 'white' }, { name: 'Slate Light', hex: '#f8fafc', text: 'black' }, |
| 146 | + ]; |
| 147 | + const [copiedHex, setCopiedHex] = useState(''); |
| 148 | + const handleCopy = (hex: string) => { navigator.clipboard.writeText(hex); setCopiedHex(hex); setTimeout(() => setCopiedHex(''), 2000); }; |
| 149 | + return ( |
| 150 | + <div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-4 gap-6"> |
| 151 | + {colors.map(color => ( |
| 152 | + <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"> |
| 153 | + <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> |
| 154 | + <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> |
| 155 | + </div> |
| 156 | + ))} |
| 157 | + </div> |
| 158 | + ); |
| 159 | +}; |
| 160 | + |
| 161 | + |
| 162 | +export default function BrandKitPage() { |
| 163 | + const logoAssets: Omit<LogoCardProps, 'svgContent'>[] = [ |
| 164 | + { title: 'Primary Logo', LogoComponent: LogoCompleto, fileName: 'papi_simulator_logo_primary' }, |
| 165 | + { title: 'Primary Logo (White Version)', LogoComponent: LogoCompleto, fileName: 'papi_simulator_logo_primary_white', isDark: true }, |
| 166 | + { title: 'Symbol (Icon)', LogoComponent: Simbolo, fileName: 'papi_simulator_symbol' }, |
| 167 | + { title: 'Symbol (White Version)', LogoComponent: Simbolo, fileName: 'papi_simulator_symbol_white', isDark: true }, |
| 168 | + ]; |
| 169 | + |
| 170 | + return ( |
| 171 | + <main className="bg-slate-50 dark:bg-slate-900 min-h-screen text-slate-800 dark:text-slate-200 font-sans"> |
| 172 | + <div className="container mx-auto px-4 py-12 sm:py-16 md:py-24"> |
| 173 | + <header className="text-center max-w-3xl mx-auto mb-16"> |
| 174 | + <div className="w-48 mx-auto mb-6 text-slate-900 dark:text-white"> <Simbolo theme="light" /> </div> |
| 175 | + <h1 className="text-4xl sm:text-5xl font-extrabold text-slate-900 dark:text-slate-50 tracking-tight">PAPI Simulator Brand Kit</h1> |
| 176 | + <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> |
| 177 | + </header> |
| 178 | + |
| 179 | + <section className="mb-16"> |
| 180 | + <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> |
| 181 | + <div className="grid grid-cols-1 md:grid-cols-2 gap-8"> |
| 182 | + {logoAssets.map(asset => { |
| 183 | + const theme = asset.isDark ? 'dark' : 'light'; |
| 184 | + const type = asset.title.includes('Symbol') ? 'symbol' : 'complete'; |
| 185 | + return <LogoCard key={asset.fileName} {...asset} svgContent={generateSvgString(type, theme)} />; |
| 186 | + })} |
| 187 | + </div> |
| 188 | + </section> |
| 189 | + |
| 190 | + <section className="mb-16"> |
| 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">Color Palette</h2> |
| 192 | + <ColorPalette /> |
| 193 | + </section> |
| 194 | + |
| 195 | + <section className="mb-16"> |
| 196 | + <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> |
| 197 | + <div className="bg-white dark:bg-slate-800 p-8 rounded-2xl border border-slate-200 dark:border-slate-700 shadow-sm"> |
| 198 | + <h3 className="text-2xl font-bold text-slate-800 dark:text-slate-200">Inter</h3> |
| 199 | + <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> |
| 200 | + <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> |
| 201 | + <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> |
| 202 | + </div> |
| 203 | + </section> |
| 204 | + |
| 205 | + <section> |
| 206 | + <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> |
| 207 | + <div className="grid grid-cols-1 md:grid-cols-2 gap-8"> |
| 208 | + <div className="bg-white dark:bg-slate-800 p-8 rounded-2xl border border-slate-200 dark:border-slate-700 shadow-sm"> |
| 209 | + <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> |
| 210 | + <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> |
| 211 | + </div> |
| 212 | + <div className="bg-white dark:bg-slate-800 p-8 rounded-2xl border border-slate-200 dark:border-slate-700 shadow-sm"> |
| 213 | + <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> |
| 214 | + <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> |
| 215 | + </div> |
| 216 | + </div> |
| 217 | + </section> |
| 218 | + </div> |
| 219 | + <footer className="text-center py-8 border-t border-slate-200 dark:border-slate-700"> |
| 220 | + <p className="text-slate-500 dark:text-slate-400">© {new Date().getFullYear()} PAPI Simulator. All rights reserved.</p> |
| 221 | + </footer> |
| 222 | + </main> |
| 223 | + ); |
| 224 | +} |
0 commit comments