/* ============================================ Retratos: foto real (img/[id].jpg|png|webp) si existe, si no, silueta SVG editorial con iniciales. SUBIR FOTOS: carpeta img/ con el nombre del personaje en minúsculas: lucia.jpg, mateo.jpg, carmen.jpg... ============================================ */ // Cache global: probamos cada foto UNA sola vez por sesión const PHOTO_CACHE = {}; function probePhoto(id) { if (PHOTO_CACHE[id]) return Promise.resolve(PHOTO_CACHE[id]); const exts = ['jpg', 'png', 'webp']; return new Promise(resolve => { let i = 0; const tryNext = () => { if (i >= exts.length) { PHOTO_CACHE[id] = null; return resolve(null); } const url = 'img/' + id + '.' + exts[i++]; const im = new Image(); im.onload = () => { PHOTO_CACHE[id] = url; resolve(url); }; im.onerror = tryNext; im.src = url; }; tryNext(); }); } function usePhoto(id) { const [url, setUrl] = useState(PHOTO_CACHE[id] !== undefined ? PHOTO_CACHE[id] : undefined); useEffect(() => { let alive = true; if (PHOTO_CACHE[id] !== undefined) { setUrl(PHOTO_CACHE[id]); return; } probePhoto(id).then(u => { if (alive) setUrl(u); }); return () => { alive = false; }; }, [id]); return url; } const PORTRAIT_PALETTES = { lucia: { bg1: '#E7D9C6', bg2: '#C9A97A', ink: '#3D2E1E', init: 'L' }, mateo: { bg1: '#C4826B', bg2: '#8F4A3A', ink: '#2A1610', init: 'M' }, carmen: { bg1: '#D69C7A', bg2: '#B15839', ink: '#3D1E10', init: 'C' }, pablo: { bg1: '#B5A88A', bg2: '#7A6E52', ink: '#26221A', init: 'P' }, nerea: { bg1: '#A8B5B8', bg2: '#5F7378', ink: '#1F2A2D', init: 'N' }, diego: { bg1: '#9CAF8B', bg2: '#4A5D3A', ink: '#1E2617', init: 'D' }, alba: { bg1: '#E4DBCB', bg2: '#B8A98F', ink: '#3A342A', init: 'A' }, javier: { bg1: '#C7A97C', bg2: '#8F6E3D', ink: '#2E2416', init: 'J' }, sara: { bg1: '#D8B4B4', bg2: '#A66565', ink: '#3D1F1F', init: 'S' }, raul: { bg1: '#9FA8B5', bg2: '#5A6373', ink: '#1E2530', init: 'R' }, elena: { bg1: '#D4C4A8', bg2: '#9A7F52', ink: '#332815', init: 'E' }, ivan: { bg1: '#BFA890', bg2: '#7D6549', ink: '#2A2015', init: 'I' }, }; // IDs conocidos (personajes demo). IDs reales de usuario ('u3', …) usan // paleta derivada por hash + inicial de su nombre (prop `label`). const FALLBACK_IDS = Object.keys(PORTRAIT_PALETTES); function hashId(s) { let h = 0; s = String(s); for (let i = 0; i < s.length; i++) h = (h * 31 + s.charCodeAt(i)) | 0; return Math.abs(h); } // Silueta abstracta editorial (cabeza + hombros) function SilhouetteSvg({ ink, opacity = 0.28 }) { return ( ); } function Portrait({ id, tag, label, size = 'md', className = '', style = {}, showTag = true, children }) { const known = PORTRAIT_PALETTES[id]; const p = known || PORTRAIT_PALETTES[FALLBACK_IDS[hashId(id) % FALLBACK_IDS.length]]; const initSize = { xs: 40, sm: 56, md: 88, lg: 120, xl: 160 }[size] || 88; const photo = usePhoto(id); return (