import React, { useState, useEffect, useRef } from 'react'; import { AssetMeta, ColorSwatch } from './types'; import { Header } from './components/Header'; import { PreFlightInspector } from './components/PreFlightInspector'; import { ColorInspector, BRAND_DEFAULT_SWATCHES } from './components/ColorInspector'; import { Toast } from './components/Toast'; import { ExportAuditReportModal } from './components/ExportAuditReportModal'; import { WelcomeBanner } from './components/WelcomeBanner'; import { FileReviewModal } from './components/FileReviewModal'; import { extractColorsFromCanvas, extractColorsFromSvg, rgbToHex, rgbToCmyk, findClosestPantone } from './utils/colorUtils'; import { evaluateVerdict } from './utils/printCalculations'; import { renderPdfFile } from './utils/pdfRenderer'; // Preset generator helpers function createVectorSvgDataUrl(): { url: string; raw: string } { const svg = ` `; return { url: 'data:image/svg+xml;utf8,' + encodeURIComponent(svg), raw: svg, }; } function createRasterDataUrl( w: number, h: number, bg: string, accent: string, text: string, sub: string ): string { const canvas = document.createElement('canvas'); canvas.width = Math.min(1200, w); canvas.height = Math.round((h / w) * canvas.width); const ctx = canvas.getContext('2d'); if (!ctx) return ''; const isPoster = w >= 3000; const isWebWarning = w <= 800; // Background ctx.fillStyle = bg; ctx.fillRect(0, 0, canvas.width, canvas.height); if (isPoster) { // COMMERCIAL POSTER MASTER (3600 x 2400 Landscape) // Outer Border & bleed guide ctx.strokeStyle = accent; ctx.lineWidth = 8; ctx.strokeRect(14, 14, canvas.width - 28, canvas.height - 28); // Subtle poster grid ctx.strokeStyle = 'rgba(0, 194, 224, 0.12)'; ctx.lineWidth = 1; for (let x = 60; x < canvas.width; x += 120) { ctx.beginPath(); ctx.moveTo(x, 20); ctx.lineTo(x, canvas.height - 20); ctx.stroke(); } for (let y = 60; y < canvas.height; y += 120) { ctx.beginPath(); ctx.moveTo(20, y); ctx.lineTo(canvas.width - 20, y); ctx.stroke(); } // Corner crop marks ctx.strokeStyle = '#FFFFFF'; ctx.lineWidth = 2; const markLen = 24; // Top-left ctx.beginPath(); ctx.moveTo(8, 20); ctx.lineTo(8 + markLen, 20); ctx.stroke(); ctx.beginPath(); ctx.moveTo(20, 8); ctx.lineTo(20, 8 + markLen); ctx.stroke(); // Top-right ctx.beginPath(); ctx.moveTo(canvas.width - 8, 20); ctx.lineTo(canvas.width - 8 - markLen, 20); ctx.stroke(); ctx.beginPath(); ctx.moveTo(canvas.width - 20, 8); ctx.lineTo(canvas.width - 20, 8 + markLen); ctx.stroke(); // Bottom-left ctx.beginPath(); ctx.moveTo(8, canvas.height - 20); ctx.lineTo(8 + markLen, canvas.height - 20); ctx.stroke(); ctx.beginPath(); ctx.moveTo(20, canvas.height - 8); ctx.lineTo(20, canvas.height - 8 - markLen); ctx.stroke(); // Bottom-right ctx.beginPath(); ctx.moveTo(canvas.width - 8, canvas.height - 20); ctx.lineTo(canvas.width - 8 - markLen, canvas.height - 20); ctx.stroke(); ctx.beginPath(); ctx.moveTo(canvas.width - 20, canvas.height - 8); ctx.lineTo(canvas.width - 20, canvas.height - 8 - markLen); ctx.stroke(); // Central graphic medallion ctx.save(); ctx.translate(canvas.width / 2, canvas.height / 2 - 35); ctx.fillStyle = '#00C2E0'; ctx.beginPath(); ctx.arc(0, 0, 48, 0, Math.PI * 2); ctx.fill(); ctx.fillStyle = '#001C38'; ctx.beginPath(); ctx.arc(0, 0, 36, 0, Math.PI * 2); ctx.fill(); ctx.fillStyle = '#FFFFFF'; ctx.font = 'bold 28px sans-serif'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillText('★', 0, 2); ctx.restore(); // Typography ctx.fillStyle = '#FFFFFF'; ctx.font = `bold ${Math.round(canvas.width * 0.038)}px sans-serif`; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillText(text, canvas.width / 2, canvas.height / 2 + 35); ctx.fillStyle = accent; ctx.font = `bold ${Math.round(canvas.width * 0.019)}px monospace`; ctx.fillText(sub, canvas.width / 2, canvas.height / 2 + 75); // Color calibration test bar along bottom const barColors = ['#00FFFF', '#FF00FF', '#FFFF00', '#000000', '#001C38', '#00C2E0', '#FFFFFF']; const barW = 28; const barH = 12; const startX = (canvas.width - barColors.length * barW) / 2; barColors.forEach((c, idx) => { ctx.fillStyle = c; ctx.fillRect(startX + idx * barW, canvas.height - 36, barW, barH); ctx.strokeStyle = 'rgba(255,255,255,0.4)'; ctx.lineWidth = 1; ctx.strokeRect(startX + idx * barW, canvas.height - 36, barW, barH); }); } else if (isWebWarning) { // 72 DPI WEB SCREENSHOT WARNING // Mock browser title bar ctx.fillStyle = '#450a0a'; ctx.fillRect(0, 0, canvas.width, 32); ctx.fillStyle = '#ef4444'; ctx.beginPath(); ctx.arc(16, 16, 5, 0, Math.PI * 2); ctx.fill(); ctx.fillStyle = '#f59e0b'; ctx.beginPath(); ctx.arc(32, 16, 5, 0, Math.PI * 2); ctx.fill(); ctx.fillStyle = '#10b981'; ctx.beginPath(); ctx.arc(48, 16, 5, 0, Math.PI * 2); ctx.fill(); ctx.fillStyle = 'rgba(255,255,255,0.6)'; ctx.font = '11px monospace'; ctx.textAlign = 'left'; ctx.fillText('https://web-screenshot-preview.com/72dpi-grab.jpg', 64, 20); // Warning icon ctx.fillStyle = '#f87171'; ctx.font = 'bold 44px sans-serif'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillText('▲', canvas.width / 2, canvas.height / 2 - 35); // Typography ctx.fillStyle = '#FFFFFF'; ctx.font = `bold ${Math.round(canvas.width * 0.04)}px sans-serif`; ctx.textAlign = 'center'; ctx.fillText(text, canvas.width / 2, canvas.height / 2 + 15); ctx.fillStyle = '#fca5a5'; ctx.font = `bold ${Math.round(canvas.width * 0.024)}px monospace`; ctx.fillText(sub, canvas.width / 2, canvas.height / 2 + 50); // Pixelation grid illusion ctx.strokeStyle = 'rgba(255, 255, 255, 0.08)'; ctx.lineWidth = 2; for (let x = 0; x < canvas.width; x += 16) { ctx.beginPath(); ctx.moveTo(x, 32); ctx.lineTo(x, canvas.height); ctx.stroke(); } for (let y = 32; y < canvas.height; y += 16) { ctx.beginPath(); ctx.moveTo(0, y); ctx.lineTo(canvas.width, y); ctx.stroke(); } } else { // Standard graphic ctx.strokeStyle = accent; ctx.lineWidth = 8; ctx.strokeRect(16, 16, canvas.width - 32, canvas.height - 32); ctx.fillStyle = '#FFFFFF'; ctx.font = `bold ${Math.round(canvas.width * 0.045)}px sans-serif`; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillText(text, canvas.width / 2, canvas.height / 2 - 15); ctx.fillStyle = accent; ctx.font = `bold ${Math.round(canvas.width * 0.025)}px monospace`; ctx.fillText(sub, canvas.width / 2, canvas.height / 2 + 35); } return canvas.toDataURL('image/png'); } export default function App() { const [asset, setAsset] = useState(null); const [targetDpi, setTargetDpi] = useState(300); const [swatches, setSwatches] = useState(BRAND_DEFAULT_SWATCHES); const [isUserAssetActive, setIsUserAssetActive] = useState(false); const [isEyedropperActive, setIsEyedropperActive] = useState(false); const [toastMessage, setToastMessage] = useState(null); // Modals & Onboarding const [isAuditModalOpen, setIsAuditModalOpen] = useState(false); const [isFileReviewOpen, setIsFileReviewOpen] = useState(false); const [isWelcomeOpen, setIsWelcomeOpen] = useState(() => { try { return localStorage.getItem('sd_welcome_dismissed') !== 'true'; } catch { return true; } }); const handleDismissWelcome = () => { setIsWelcomeOpen(false); try { localStorage.setItem('sd_welcome_dismissed', 'true'); } catch {} }; const handleOpenWelcome = () => { setIsWelcomeOpen(true); }; // Hidden canvas for sampling const hiddenCanvasRef = useRef(null); const showToast = (msg: string) => { setToastMessage(msg); setTimeout(() => { setToastMessage((current) => (current === msg ? null : current)); }, 2500); }; const handleCopyValue = (value: string, label: string) => { navigator.clipboard.writeText(value); showToast(`${label} copied: ${value}`); }; // Preset switchers const loadPreset = (presetKey: 'vector' | 'press' | 'web' | 'apparel') => { setIsEyedropperActive(false); if (presetKey === 'vector') { const v = createVectorSvgDataUrl(); setAsset({ name: 'Space_Dragon_Designs_Master_Vector.svg', type: 'vector', fileSize: 18450, mimeType: 'image/svg+xml', naturalWidth: 5970, naturalHeight: 2245, previewUrl: v.url, svgRaw: v.raw, isSample: true, isNativeVectorColor: true, }); const vectorSwatches: ColorSwatch[] = [ { id: 'sd-black', name: 'Pure Black', role: 'Letterforms & Outer Monogram Arch', hex: '#000000', rgb: [0, 0, 0], cmyk: { c: 0, m: 0, y: 0, k: 100 }, pantoneMatch: 'Process Black C', isOutOfGamut: false, }, { id: 'sd-cerulean', name: 'Cerulean Blue', role: 'Dragon Head, Flowing Spine Curve & Star', hex: '#008FDE', rgb: [0, 143, 222], cmyk: { c: 85, m: 38, y: 0, k: 0 }, pantoneMatch: '299 C', isOutOfGamut: false, }, ]; setSwatches(vectorSwatches); setIsUserAssetActive(true); showToast('Loaded Space Dragon Designs Master Vector preset'); } else if (presetKey === 'press') { const url = createRasterDataUrl( 3600, 2400, '#001C38', '#00C2E0', '300 DPI COMMERCIAL POSTER MASTER', '3600 × 2400 PX • PREPRESS READY' ); setAsset({ name: 'SampleDemo_300DPI_Poster.png', type: 'raster', fileSize: 245000, mimeType: 'image/png', naturalWidth: 3600, naturalHeight: 2400, previewUrl: url, isSample: true, }); setSwatches(BRAND_DEFAULT_SWATCHES); setIsUserAssetActive(true); showToast('Loaded 300 DPI Commercial Master preset'); } else if (presetKey === 'web') { const url = createRasterDataUrl( 600, 400, '#7f1d1d', '#f87171', 'LOW-RES 72 DPI WEB SCREENSHOT', '600 × 400 PX • INSUFFICIENT FOR PRESS' ); setAsset({ name: 'SampleDemo_72DPI_WebGrab.jpg', type: 'raster', fileSize: 48000, mimeType: 'image/jpeg', naturalWidth: 600, naturalHeight: 400, previewUrl: url, isSample: true, }); setSwatches([ { id: 'web-darkred', name: 'Screen Dark Crimson', role: 'Web Background Ink', hex: '#7F1D1D', rgb: [127, 29, 29], cmyk: rgbToCmyk(127, 29, 29), pantoneMatch: '188 C', isOutOfGamut: false, }, { id: 'web-lightred', name: 'Coral Alert Red', role: 'Border Accent', hex: '#F87171', rgb: [248, 113, 113], cmyk: rgbToCmyk(248, 113, 113), pantoneMatch: '177 C', isOutOfGamut: false, }, ]); setIsUserAssetActive(true); showToast('Loaded 72 DPI Web Warning preset'); } else if (presetKey === 'apparel') { const url = createRasterDataUrl( 3000, 3500, '#071524', '#00C2E0', 'STREETWEAR T-SHIRT SEPARATION', '3000 × 3500 PX • 10" × 11.6" CHEST PRINT' ); setAsset({ name: 'SampleDemo_Streetwear_Graphic.png', type: 'raster', fileSize: 450000, mimeType: 'image/png', naturalWidth: 3000, naturalHeight: 3500, previewUrl: url, isSample: true, }); setSwatches(BRAND_DEFAULT_SWATCHES); setIsUserAssetActive(true); showToast('Loaded Apparel Graphic preset'); } }; // User file upload handler const handleFileUpload = (file: File) => { setIsEyedropperActive(false); const isSvg = file.type === 'image/svg+xml' || file.name.toLowerCase().endsWith('.svg'); const isPdf = file.type === 'application/pdf' || file.name.toLowerCase().endsWith('.pdf'); if (isSvg) { const reader = new FileReader(); reader.onload = (e) => { const svgContent = (e.target?.result as string) || ''; let dataUrl = ''; try { const utf8Bytes = new TextEncoder().encode(svgContent); let binary = ''; for (let i = 0; i < utf8Bytes.length; i++) { binary += String.fromCharCode(utf8Bytes[i]); } dataUrl = `data:image/svg+xml;base64,${btoa(binary)}`; } catch { dataUrl = `data:image/svg+xml;utf8,${encodeURIComponent(svgContent)}`; } const extracted = extractColorsFromSvg(svgContent); setAsset({ name: file.name, type: 'vector', fileSize: file.size, mimeType: 'image/svg+xml', naturalWidth: 1000, naturalHeight: 1000, previewUrl: dataUrl, svgRaw: svgContent, isSample: false, }); setSwatches(extracted.length > 0 ? extracted : BRAND_DEFAULT_SWATCHES); setIsUserAssetActive(true); showToast(`Auditing Vector Asset: ${file.name}`); }; reader.readAsText(file); } else if (isPdf) { showToast(`Inspecting PDF vector streams: ${file.name}...`); renderPdfFile(file) .then((result) => { setAsset({ name: file.name, type: 'pdf', fileSize: file.size, mimeType: 'application/pdf', naturalWidth: result.naturalWidth, naturalHeight: result.naturalHeight, previewUrl: result.previewUrl, pageCount: result.pageCount, isSample: false, isNativeVectorColor: result.isNativeVectorColor, colorSpaceInfo: result.colorSpaceSummary?.join(', '), }); setSwatches(result.extractedColors.length > 0 ? result.extractedColors : BRAND_DEFAULT_SWATCHES); setIsUserAssetActive(true); if (result.isNativeVectorColor) { showToast(`Extracted exact vector inks directly from PDF: ${result.extractedColors.length} designer colors`); } else { showToast(`Audited PDF: ${file.name} (${result.naturalWidth}×${result.naturalHeight}px @ 300 DPI)`); } }) .catch((err) => { console.error('PDF rendering failed:', err); // High-fidelity fallback SVG card if parsing fails const pdfSvgPlaceholder = ` COMMERCIAL PRESS PDF VECTOR DOCUMENT MASTER ${file.name} `; const blob = new Blob([pdfSvgPlaceholder], { type: 'image/svg+xml' }); setAsset({ name: file.name, type: 'pdf', fileSize: file.size, mimeType: 'application/pdf', naturalWidth: 2550, naturalHeight: 3300, previewUrl: URL.createObjectURL(blob), isSample: false, }); setSwatches(BRAND_DEFAULT_SWATCHES); setIsUserAssetActive(true); showToast(`Auditing Commercial PDF: ${file.name}`); }); } else { // Raster image (PNG, JPG, WEBP, etc.) const reader = new FileReader(); reader.onload = (e) => { const dataUrl = (e.target?.result as string) || ''; const img = new Image(); img.src = dataUrl; img.onload = () => { const w = img.naturalWidth || 800; const h = img.naturalHeight || 600; // High-resolution color analysis canvas (up to 1200px) // Preserves sharp logo vector edges and true designer pixel values without downsampling blur const maxDim = 1200; let canvasW = w; let canvasH = h; if (canvasW > maxDim || canvasH > maxDim) { if (canvasW >= canvasH) { canvasH = Math.max(1, Math.round((h / w) * maxDim)); canvasW = maxDim; } else { canvasW = Math.max(1, Math.round((w / h) * maxDim)); canvasH = maxDim; } } const canvas = hiddenCanvasRef.current || document.createElement('canvas'); canvas.width = canvasW; canvas.height = canvasH; const ctx = canvas.getContext('2d', { willReadFrequently: true }); let extractedSwatches: ColorSwatch[] = []; if (ctx) { ctx.imageSmoothingEnabled = true; ctx.imageSmoothingQuality = 'high'; ctx.drawImage(img, 0, 0, canvas.width, canvas.height); extractedSwatches = extractColorsFromCanvas(canvas, ctx, 6); } setAsset({ name: file.name, type: 'raster', fileSize: file.size, mimeType: file.type || 'image/png', naturalWidth: w, naturalHeight: h, previewUrl: dataUrl, isSample: false, }); setSwatches(extractedSwatches.length > 0 ? extractedSwatches : BRAND_DEFAULT_SWATCHES); setIsUserAssetActive(true); showToast(`Auditing Raster Artwork: ${w} × ${h} px`); }; }; reader.readAsDataURL(file); } }; // Eyedropper sampling directly from artwork const handleEyedropperPick = (rgb: [number, number, number], hex: string) => { const cmyk = rgbToCmyk(rgb[0], rgb[1], rgb[2]); const pantone = findClosestPantone(rgb); const newSwatch: ColorSwatch = { id: `eyedropper-${Date.now()}-${hex}`, name: `Sampled Ink (${hex})`, role: 'Eyedropper Picked Color', hex, rgb, cmyk, pantoneMatch: pantone.code, isOutOfGamut: false, }; setSwatches((prev) => [newSwatch, ...prev.slice(0, 5)]); showToast(`Sampled color ${hex} (Pantone ${pantone.code})`); }; // Reset entire auditor back to default baseline (upload screen) const handleReset = () => { setAsset(null); setTargetDpi(300); setSwatches(BRAND_DEFAULT_SWATCHES); setIsUserAssetActive(false); setIsEyedropperActive(false); showToast('Reset to upload screen'); }; // Export palette helper const handleExportPalette = (format: 'css' | 'json' | 'svg') => { let content = ''; let filename = `SpaceDragon_Palette_${Date.now()}.${format}`; let mimeType = 'text/plain'; if (format === 'css') { content = `:root {\n${swatches .map((s, i) => ` --color-ink-${i + 1}: ${s.hex}; /* ${s.name} | CMYK: ${s.cmyk.c}% ${s.cmyk.m}% ${s.cmyk.y}% ${s.cmyk.k}% */`) .join('\n')}\n}`; mimeType = 'text/css'; } else if (format === 'json') { content = JSON.stringify(swatches, null, 2); mimeType = 'application/json'; } else if (format === 'svg') { content = ` ${swatches .map( (s, i) => ` ${s.hex} PMS ${s.pantoneMatch || 'CMYK'} ${s.cmyk.c},${s.cmyk.m},${s.cmyk.y},${s.cmyk.k} ` ) .join('')} `; mimeType = 'image/svg+xml'; } const blob = new Blob([content], { type: mimeType }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = filename; a.click(); URL.revokeObjectURL(url); showToast(`Exported color palette as ${format.toUpperCase()}`); }; const verdict = asset ? evaluateVerdict(asset.type, asset.naturalWidth, asset.naturalHeight, targetDpi) : null; return (
{/* Hidden processing canvas */} {/* Main Container Container */}
{/* Brand Header with Live Badges and Actions */}
setIsAuditModalOpen(true)} onOpenWelcome={handleOpenWelcome} hasActiveAsset={!!asset} /> {/* Welcome Onboarding Banner for First-Time Users */} loadPreset(key)} /> {/* Module 01: Pre-Flight Resolution Inspector */} setIsEyedropperActive(!isEyedropperActive)} onOpenReviewModal={() => setIsFileReviewOpen(true)} onOpenAuditModal={() => setIsAuditModalOpen(true)} swatches={swatches} onToast={showToast} /> {/* Module 02: Production Color Codes & Swatches (Hidden until upload is complete) */} {asset && ( { setSwatches(BRAND_DEFAULT_SWATCHES); setIsUserAssetActive(false); showToast('Returned to default Space Dragon brand swatches'); }} onExportPalette={handleExportPalette} /> )} {/* Feature 3: "Fix It / Rebuild It" Lead Bridge (Below the report, only shown on Amber/Red) */} {asset && (verdict?.status === 'constrained' || verdict?.status === 'warning') && (
Production Art Department • Vector Reconstruction

Need this artwork scaled up or rebuilt? If you only have a low-res image, AI concept, or screenshot, we turn raster artwork into infinitely scalable master vector files.

)}
{/* Footer */} {/* Modals & Toasts */} {asset && verdict && ( <> setIsAuditModalOpen(false)} asset={asset} targetDpi={targetDpi} verdict={verdict} swatches={swatches} onCopied={showToast} /> setIsFileReviewOpen(false)} asset={asset} targetDpi={targetDpi} verdict={verdict} /> )}
); }