import React, { useState, useRef, useEffect, useCallback } from 'react'; import { Link } from 'react-router-dom'; import { motion, AnimatePresence } from 'framer-motion'; import { ArrowLeftIcon, PaletteIcon, PlusIcon, TrashIcon, ArrowsClockwiseIcon, TextAaIcon, SelectionIcon, InfoIcon, DownloadSimpleIcon, ArrowsOutCardinalIcon, } from '@phosphor-icons/react'; import Seo from '../../components/Seo'; import { useToast } from '../../hooks/useToast'; const DEFAULT_COLORS = [ '#ef4444', '#10b981', '#3b82f6', '#f59e0b', '#8b5cf6', '#ec4899', ]; const BlendLabPage = () => { const appName = 'Blend Lab'; const { addToast } = useToast(); const canvasRef = useRef(null); const [blobs, setBlobs] = useState([ { id: 1, color: '#ef4444', x: 20, y: 20, size: 40 }, { id: 2, color: '#10b981', x: 70, y: 30, size: 50 }, { id: 3, color: '#3b82f6', x: 40, y: 70, size: 45 }, ]); const [blurAmount, setBlurAmount] = useState(60); const [noiseOpacity, setNoiseOpacity] = useState(0.15); // Text Layer 1 const [topText, setTopText] = useState('DESIGN'); const [topFontSize, setTopFontSize] = useState(12); const [topFontColor, setTopFontColor] = useState('#FFFFFF'); const [topFontWeight, setTopFontWeight] = useState(900); const [topX, setTopX] = useState(50); const [topY, setTopY] = useState(45); // Text Layer 2 const [bottomText, setBottomText] = useState('STUDIO'); const [bottomFontSize, setBottomFontSize] = useState(8); const [bottomFontColor, setBottomFontColor] = useState('#FFFFFF'); const [bottomFontWeight, setBottomFontWeight] = useState(400); const [bottomX, setBottomX] = useState(50); const [bottomY, setBottomY] = useState(55); const drawComposition = useCallback( (ctx, width, height) => { const scale = width / 1000; // 1. Strict Black Background ctx.fillStyle = '#050505'; ctx.fillRect(0, 0, width, height); // 2. Draw Blobs ctx.save(); ctx.filter = `blur(${blurAmount * scale}px)`; blobs.forEach((blob) => { ctx.save(); ctx.globalAlpha = 0.8; ctx.globalCompositeOperation = 'screen'; ctx.fillStyle = blob.color; ctx.beginPath(); const radius = (blob.size / 100) * width; ctx.arc( (blob.x / 100) * width, (blob.y / 100) * height, radius, 0, Math.PI * 2, ); ctx.fill(); ctx.restore(); }); ctx.restore(); // 3. Tiled Noise Grain const noiseSize = 256; const noiseCanvas = document.createElement('canvas'); noiseCanvas.width = noiseSize; noiseCanvas.height = noiseSize; const nCtx = noiseCanvas.getContext('2d'); const nData = nCtx.createImageData(noiseSize, noiseSize); for (let i = 0; i < nData.data.length; i += 4) { const val = Math.random() * 255; nData.data[i] = nData.data[i + 1] = nData.data[i + 2] = val; nData.data[i + 3] = 255; } nCtx.putImageData(nData, 0, 0); ctx.save(); ctx.globalAlpha = noiseOpacity; ctx.globalCompositeOperation = 'overlay'; const pattern = ctx.createPattern(noiseCanvas, 'repeat'); ctx.fillStyle = pattern; ctx.fillRect(0, 0, width, height); ctx.restore(); // 4. Typography ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; // Top Text ctx.save(); ctx.fillStyle = topFontColor; const scaledTopSize = topFontSize * 16 * scale; ctx.font = `${topFontWeight} ${scaledTopSize}px "Playfair Display", serif`; ctx.shadowColor = 'rgba(0,0,0,0.5)'; ctx.shadowBlur = 30 * scale; ctx.fillText(topText, (topX / 100) * width, (topY / 100) * height); ctx.restore(); // Bottom Text if (bottomText) { ctx.save(); ctx.fillStyle = bottomFontColor; const scaledBottomSize = bottomFontSize * 16 * scale; ctx.font = `${bottomFontWeight} ${scaledBottomSize}px "Playfair Display", serif`; ctx.shadowColor = 'rgba(0,0,0,0.5)'; ctx.shadowBlur = 30 * scale; ctx.fillText( bottomText, (bottomX / 100) * width, (bottomY / 100) * height, ); ctx.restore(); } }, [ blobs, blurAmount, noiseOpacity, topText, topFontSize, topFontColor, topFontWeight, topX, topY, bottomText, bottomFontSize, bottomFontColor, bottomFontWeight, bottomX, bottomY, ], ); useEffect(() => { const canvas = canvasRef.current; if (!canvas) return; const ctx = canvas.getContext('2d'); const dpr = window.devicePixelRatio || 1; const rect = canvas.getBoundingClientRect(); canvas.width = rect.width * dpr; canvas.height = rect.height * dpr; ctx.scale(dpr, dpr); document.fonts.ready.then(() => { drawComposition(ctx, rect.width, rect.height); }); }, [drawComposition]); const handleDownload = () => { const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); const W = 3840; const H = 2160; canvas.width = W; canvas.height = H; drawComposition(ctx, W, H); const link = document.createElement('a'); link.download = `fezcodex-master-${Date.now()}.png`; link.href = canvas.toDataURL('image/png', 1.0); link.click(); addToast({ title: 'Export Complete', message: '4K master sequence generated.', }); }; const addBlob = () => { if (blobs.length >= 12) { addToast({ title: 'Limit Reached', message: 'Maximum color layers active.', }); return; } const newBlob = { id: Date.now(), color: DEFAULT_COLORS[Math.floor(Math.random() * DEFAULT_COLORS.length)], x: Math.random() * 80 + 10, y: Math.random() * 80 + 10, size: Math.random() * 30 + 30, }; setBlobs([...blobs, newBlob]); }; const updateBlob = (id, field, value) => { setBlobs(blobs.map((b) => (b.id === id ? { ...b, [field]: value } : b))); }; const removeBlob = (id) => { setBlobs(blobs.filter((b) => b.id !== id)); }; return (
Applications

{appName}

Chromatic synthesis lab. Map entities across the coordinate matrix and apply diffusion filters to create high-impact compositions.

{/* LEFT: Scene Controls */}

Color_Entities

{blobs.map((blob) => (
updateBlob(blob.id, 'color', e.target.value) } className="w-6 h-6 rounded-full border-none cursor-pointer bg-transparent" /> {blob.color}
Magnitude {blob.size}%
updateBlob( blob.id, 'size', parseInt(e.target.value), ) } className="w-full accent-white h-1 bg-gray-800 rounded-lg appearance-none cursor-pointer" />
updateBlob( blob.id, 'x', parseInt(e.target.value), ) } className="w-full accent-white h-1 bg-gray-800 rounded-lg appearance-none cursor-pointer" />
updateBlob( blob.id, 'y', parseInt(e.target.value), ) } className="w-full accent-white h-1 bg-gray-800 rounded-lg appearance-none cursor-pointer" />
))}

Lab_Matrix

Blur_Diffusion {blurAmount}PX
setBlurAmount(parseInt(e.target.value))} className="w-full accent-emerald-500 h-1 bg-gray-800 rounded-lg appearance-none cursor-pointer" />
Noise_Grain {Math.round(noiseOpacity * 100)}%
setNoiseOpacity(parseFloat(e.target.value)) } className="w-full accent-emerald-500 h-1 bg-gray-800 rounded-lg appearance-none cursor-pointer" />
{/* RIGHT: Preview & Typography (Typography moved below) */}

Synthesis engine utilizes a unified Canvas rendering protocol to ensure perfect parity between live calibration and high-resolution export sequences.

{/* Typography Section (Full Width below preview) */}
{/* Text 1 Config */}

Text_Layer_01

setTopText(e.target.value)} className="w-full bg-black/40 border border-white/10 p-4 font-black uppercase tracking-widest text-sm focus:border-emerald-500/50 outline-none transition-all" />
Scale {topFontSize}
setTopFontSize(parseFloat(e.target.value)) } className="w-full accent-white h-1 bg-gray-800 rounded-lg appearance-none cursor-pointer" />
Color {topFontColor}
setTopFontColor(e.target.value)} className="w-full h-1 cursor-pointer bg-transparent border-none appearance-none" />
Weight {topFontWeight}
setTopFontWeight(parseInt(e.target.value)) } className="w-full accent-white h-1 bg-gray-800 rounded-lg appearance-none cursor-pointer" />
Position_Matrix {topX} : {topY}
setTopX(parseInt(e.target.value))} className="w-full accent-white h-1 bg-gray-800 rounded-lg appearance-none cursor-pointer" /> setTopY(parseInt(e.target.value))} className="w-full accent-white h-1 bg-gray-800 rounded-lg appearance-none cursor-pointer" />
{/* Text 2 Config */}

Text_Layer_02

setBottomText(e.target.value)} className="w-full bg-black/40 border border-white/10 p-4 font-black uppercase tracking-widest text-sm focus:border-emerald-500/50 outline-none transition-all" />
Scale {bottomFontSize}
setBottomFontSize(parseFloat(e.target.value)) } className="w-full accent-white h-1 bg-gray-800 rounded-lg appearance-none cursor-pointer" />
Color {bottomFontColor}
setBottomFontColor(e.target.value)} className="w-full h-1 cursor-pointer bg-transparent border-none appearance-none" />
Weight {bottomFontWeight}
setBottomFontWeight(parseInt(e.target.value)) } className="w-full accent-white h-1 bg-gray-800 rounded-lg appearance-none cursor-pointer" />
Position_Matrix {bottomX} : {bottomY}
setBottomX(parseInt(e.target.value))} className="w-full accent-white h-1 bg-gray-800 rounded-lg appearance-none cursor-pointer" /> setBottomY(parseInt(e.target.value))} className="w-full accent-white h-1 bg-gray-800 rounded-lg appearance-none cursor-pointer" />
); }; export default BlendLabPage;