/* P.E.S.T Web App โ€” Dashboard (orbit: rotating PFP + pinned project nodes) */ function RotatingPFP({ member }) { const pfps = (member.pfps || []).filter((p) => p.img); const [i, setI] = useState(0); useEffect(() => { if (pfps.length < 2) return; const t = setInterval(() => setI((x) => (x + 1) % pfps.length), 4200); return () => clearInterval(t); }, [pfps.length]); return (
{pfps.length === 0 &&
{member.nick.slice(0, 2)}
} {pfps.map((p, k) => ( {p.name} ))}
{member.nick}
); } function orbitPositions(n) { // evenly distribute exactly n nodes on an ellipse, first at top const rx = 41, ry = 38; const out = []; for (let i = 0; i < n; i++) { const a = (-90 + (360 / n) * i) * (Math.PI / 180); out.push({ x: 50 + rx * Math.cos(a), y: 50 + ry * Math.sin(a) }); } return out; } function Dashboard({ onNavigate }) { const store = useStore(); const me = store.member; // full member object of logged-in user const pinned = store.user.pinned .map((id) => store.projects.find((p) => p.id === id)) .filter(Boolean); // Only render real pinned projects โ€” no empty/placeholder nodes. const pos = orbitPositions(pinned.length); const [qa, setQa] = useState(null); const [pickTokens, setPickTokens] = useState(false); const [clock, setClock] = useState(new Date()); useEffect(() => { const t = setInterval(() => setClock(new Date()), 30000); return () => clearInterval(t); }, []); const hh = String(clock.getHours()).padStart(2, "0"); const mm = String(clock.getMinutes()).padStart(2, "0"); const dateStr = clock.toLocaleDateString("en-GB", { weekday: "short", day: "2-digit", month: "short" }); return (
Dashboard

Welcome back, {me.nick}

{hh}:{mm}{dateStr} ยท {pinned.length}/{window.PEST_APP.MAX_PINS} pinned
{pinned.map((p, i) => (
setQa(p)}> {p.name}
))}
{pinned.length === 0 && (

Your hive is empty. Pin the projects you use most from the Projects page โ€” they'll orbit your node here for one-tap access.

)} setPickTokens(true)} /> {qa && setQa(null)} />} {pickTokens && setPickTokens(false)} />}
); } function QuickAccess({ project, onClose }) { const store = useStore(); useEffect(() => { const k = (e) => e.key === "Escape" && onClose(); window.addEventListener("keydown", k); return () => window.removeEventListener("keydown", k); }, []); const links = Object.keys(project.links || {}).filter((k) => project.links[k]); return (
e.stopPropagation()}>

{project.name}

{((project.categories && project.categories.length) ? project.categories : (project.category ? [project.category] : [])).map((c) => {c})}
{links.map((k) => { const meta = window.A_SOCIAL[k]; const I = meta.icon; return {meta.label}; })} {links.length === 0 && No links yet.}
{project.links.website && Open}
); } window.Dashboard = Dashboard; window.orbitPositions = orbitPositions; window.QuickAccess = QuickAccess;