import React, { useState, useRef, useEffect, useCallback } from 'react'; import { Link } from 'react-router-dom'; import { ArrowLeftIcon, TrashIcon, SelectionIcon, DownloadSimpleIcon, PaintBrushIcon, WindIcon, WavesIcon, CodeIcon, CircleDashedIcon, } from '@phosphor-icons/react'; import Seo from '../../components/Seo'; import { useToast } from '../../hooks/useToast'; import BreadcrumbTitle from '../../components/BreadcrumbTitle'; const PROTOCOLS = [ { id: 'binary', label: 'BINARY_STREAM', symbols: ['0', '1'] }, { id: 'geometric', label: 'GEOMETRIC_ENTITIES', symbols: ['+', '×', '□', '○', '△', '⬡'], }, { id: 'alpha', label: 'ALPHA_CodeIcon', symbols: ['A', 'B', 'X', 'Y', 'Z', 'Σ', 'Φ', 'Ω'], }, { id: 'brutalist', label: 'VOID_BLOCKS', symbols: ['█', '▓', '▒', '░', '▖', '▗', '▘', '▙'], }, ]; const COLORS = [ { name: 'Pure Void', hex: '#050505', text: '#FFFFFF' }, { name: 'Emerald Flux', hex: '#10b981', text: '#000000' }, { name: 'Salmon Signal', hex: '#FA8072', text: '#000000' }, { name: 'Cyber Cyan', hex: '#00FFFF', text: '#000000' }, { name: 'Amber Warning', hex: '#f59e0b', text: '#000000' }, ]; const SymbolFlowPage = () => { const { addToast } = useToast(); const canvasRef = useRef(null); const containerRef = useRef(null); const requestRef = useRef(); const mouseRef = useRef({ x: -1000, y: -1000 }); // State const [protocol, setProtocol] = useState(PROTOCOLS[0]); const [accentColor, setAccentColor] = useState(COLORS[1]); const [density, setDensity] = useState(50); const [chaos, setChaos] = useState(0.002); const [velocity, setVelocity] = useState(2); const [trail, setTrail] = useState(0.15); const [isPaused, setIsPaused] = useState(false); const particles = useRef([]); const initParticles = useCallback( (width, height) => { const baseCount = Math.floor((width * height) / 1500); // Increased density const count = Math.max(100, Math.floor(baseCount * (density / 50))); particles.current = Array.from({ length: count }, () => ({ x: Math.random() * width, y: Math.random() * height, vx: 0, vy: 0, symbol: protocol.symbols[Math.floor(Math.random() * protocol.symbols.length)], size: Math.random() * 14 + 10, // Slightly larger life: Math.random() * 200 + 100, // Lifespan to prevent permanent clumping maxLife: 0, // Will be set in loop })); particles.current.forEach((p) => (p.maxLife = p.life)); }, [density, protocol], ); const animate = useCallback( (time) => { if (isPaused) return; const canvas = canvasRef.current; if (!canvas) return; const ctx = canvas.getContext('2d'); const { width, height } = canvas; // Fade background for trails ctx.fillStyle = `rgba(5, 5, 5, ${trail})`; ctx.fillRect(0, 0, width, height); ctx.fillStyle = accentColor.hex; particles.current.forEach((p) => { // 1. Life Cycle p.life -= 0.5; if (p.life <= 0) { p.x = Math.random() * width; p.y = Math.random() * height; p.life = Math.random() * 200 + 100; p.symbol = protocol.symbols[ Math.floor(Math.random() * protocol.symbols.length) ]; } // 2. Procedural Movement (Refined Flow Field) // Use multiple layers of sin/cos for more complexity const noise = p.x * chaos + p.y * chaos + time * 0.0005; const angle = Math.sin(noise * 10) * Math.PI + Math.cos(noise * 5) * Math.PI; // Target velocity based on field const tx = Math.cos(angle) * velocity; const ty = Math.sin(angle) * velocity; // Smooth acceleration p.vx += (tx - p.vx) * 0.1; p.vy += (ty - p.vy) * 0.1; // 3. Mouse Interaction (Repel) const dx = mouseRef.current.x - p.x; const dy = mouseRef.current.y - p.y; const dist = Math.sqrt(dx * dx + dy * dy); if (dist < 200) { const force = (200 - dist) / 200; p.vx -= (dx / dist) * force * 15 * (velocity / 2); p.vy -= (dy / dist) * force * 15 * (velocity / 2); } // 4. Update Position p.x += p.vx; p.y += p.vy; // 5. Edge Wrap if (p.x < -20) p.x = width + 20; if (p.x > width + 20) p.x = -20; if (p.y < -20) p.y = height + 20; if (p.y > height + 20) p.y = -20; // 6. Draw with Fade In/Out based on life const alpha = Math.min(1, p.life / 20, (p.maxLife - p.life) / 20); ctx.globalAlpha = alpha; ctx.font = `bold ${p.size}px "JetBrains Mono", monospace`; ctx.fillText(p.symbol, p.x, p.y); ctx.globalAlpha = 1.0; }); requestRef.current = requestAnimationFrame(animate); }, [accentColor, chaos, velocity, trail, isPaused, protocol], ); useEffect(() => { const handleResize = () => { if (!containerRef.current || !canvasRef.current) return; const { width, height } = containerRef.current.getBoundingClientRect(); const dpr = window.devicePixelRatio || 1; canvasRef.current.width = width * dpr; canvasRef.current.height = height * dpr; const ctx = canvasRef.current.getContext('2d'); ctx.scale(dpr, dpr); initParticles(width, height); }; handleResize(); window.addEventListener('resize', handleResize); requestRef.current = requestAnimationFrame(animate); return () => { window.removeEventListener('resize', handleResize); cancelAnimationFrame(requestRef.current); }; }, [animate, initParticles]); const handleMouseMove = (e) => { const rect = canvasRef.current.getBoundingClientRect(); mouseRef.current = { x: e.clientX - rect.left, y: e.clientY - rect.top, }; }; const handleDownload = () => { const canvas = canvasRef.current; if (!canvas) return; const link = document.createElement('a'); link.download = `symbol-flow-${Date.now()}.png`; link.href = canvas.toDataURL('image/png', 1.0); link.click(); addToast({ title: 'FLOW_CAPTURED', message: 'Current stream state exported to local storage.', }); }; return (
Generative stream protocol. Map technical symbol sets across procedural force fields to visualize digital kinetic energy.
Dynamic flow state utilized localized Canvas animation loops. Each entity responds to a composite vector field calculated from procedural noise and real-time cursor coordinates. Optimal systemic performance verified.