// ============ 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 (
TEMP
HUM
);
}
function ProjectCover({ p }) {
if (p.id === 'dht11') return ;
return ;
}
window.ProjectCover = ProjectCover;