// ============ GENERATIVE PROJECT COVERS ============ // Picks a glyph based on project category, draws gradient-dark cover with chips. // DHT11 gets a special animated mini-dashboard. const GLYPHS = { ia: ( ), robot: ( ), iot: ( ), web: ( ), academic: ( ) }; function pickGlyph(p) { if (p.id === 'dht11') return 'iot'; const cats = p.cats || []; if (cats.includes('Robótica')) return 'robot'; if (cats.includes('IA')) return 'ia'; if (cats.includes('Académico')) return 'academic'; if (cats.includes('Web')) return 'web'; return 'web'; } function GenericCover({ p }) { const key = pickGlyph(p); return (
{p.id}.angelo.dev
{GLYPHS[key]}
{p.stack.slice(0, 4).map((s, i) => {s})}
); } // DHT11 mini-dashboard cover ---------------------------------------------------- function DHTCover({ p }) { const [temp, setTemp] = React.useState(24.5); const [hum, setHum] = React.useState(58); const reduce = window.useReducedMotion(); const [pathTemp, setPathTemp] = React.useState(''); const [pathHum, setPathHum] = React.useState(''); const [areaTemp, setAreaTemp] = React.useState(''); const [areaHum, setAreaHum] = React.useState(''); React.useEffect(() => { let raf; const W = 200, H = 80; const N = 20; const tempSeries = Array.from({length: N}, (_, i) => 24 + Math.sin(i * 0.4) * 1.5 + Math.random() * 0.4); const humSeries = Array.from({length: N}, (_, i) => 58 + Math.cos(i * 0.35) * 4 + Math.random() * 1); let t = 0; const norm = (v, min, max) => H - ((v - min) / (max - min)) * (H - 10) - 5; const tick = () => { t += 0.06; // shift series each frame tempSeries.shift(); tempSeries.push(24 + Math.sin(t * 0.6) * 1.8 + Math.random() * 0.3); humSeries.shift(); humSeries.push(58 + Math.cos(t * 0.5) * 4 + Math.random() * 0.8); const dx = W / (N - 1); const tPts = tempSeries.map((v, i) => [i * dx, norm(v, 20, 30)]); const hPts = humSeries.map((v, i) => [i * dx, norm(v, 40, 70)]); const toPath = (pts) => 'M ' + pts.map(p => p.join(' ')).join(' L '); const toArea = (pts) => toPath(pts) + ` L ${W} ${H} L 0 ${H} Z`; setPathTemp(toPath(tPts)); setPathHum(toPath(hPts)); setAreaTemp(toArea(tPts)); setAreaHum(toArea(hPts)); const lastTemp = tempSeries[tempSeries.length - 1]; const lastHum = humSeries[humSeries.length - 1]; setTemp(lastTemp.toFixed(1)); setHum(Math.round(lastHum)); if (!reduce) raf = requestAnimationFrame(() => setTimeout(tick, 220)); }; tick(); return () => { if (raf) cancelAnimationFrame(raf); }; }, [reduce]); return (
Temperatura
{temp}°C
Humedad
{hum}% HR
TEMP HUM
{[0,1,2,3].map(i => ( ))}
); } function ProjectCover({ p }) { if (p.id === 'dht11') return ; return ; } window.ProjectCover = ProjectCover;