import type { Component } from "solid-js"; import { Show, createMemo } from "solid-js"; // deterministic hash ported from facehash function stringHash(str: string): number { let hash = 0; for (let i = 0; i < str.length; i++) { const char = str.charCodeAt(i); hash = (hash << 5) - hash + char; hash &= hash; } return Math.abs(hash); } interface AvatarProps { src?: string; name: string; size?: "sm" | "md" | "lg" | "xl"; status?: | "Online" | "Idle" | "Offline" | "online" | "idle" | "dnd" | "invisible"; showStatus?: boolean; } const sizeMap = { sm: 32, md: 40, lg: 48, xl: 64, }; const statusColorMap: Record = { Online: "bg-success", online: "bg-success", Idle: "bg-warning", idle: "bg-warning", dnd: "bg-error", invisible: "bg-gray-500", Offline: "bg-gray-300", offline: "bg-gray-300", }; // deterministic color palette that facehash uses internally const COLORS = [ "#f43f5e", "#ec4899", "#d946ef", "#a855f7", "#8b5cf6", "#6366f1", "#3b82f6", "#0ea5e9", "#06b6d4", "#14b8a6", "#10b981", "#22c55e", "#84cc16", "#eab308", "#f59e0b", "#f97316", "#ef4444", ]; // sphere positions for 3d rotation (ported from facehash) const SPHERE_POSITIONS = [ { x: -1, y: 1 }, { x: 1, y: 1 }, { x: 1, y: 0 }, { x: 0, y: 1 }, { x: -1, y: 0 }, { x: 0, y: 0 }, { x: 0, y: -1 }, { x: -1, y: -1 }, { x: 1, y: -1 }, ]; const ROTATE_RANGE = 15; const TRANSLATE_Z = 12; // face svgs ported from facehash - each is a pure svg path set function RoundEyes() { return ( ); } function CrossEyes() { return ( ); } function LineEyes() { return ( ); } function CurvedEyes() { return ( ); } const FACES: Component[] = [RoundEyes, CrossEyes, LineEyes, CurvedEyes]; const Avatar: Component = (props) => { const size = () => props.size ?? "md"; const px = () => sizeMap[size()]; // deterministic face properties derived from name const faceData = createMemo(() => { const hash = stringHash(props.name); const faceIndex = hash % FACES.length; const colorIndex = hash % COLORS.length; const position = SPHERE_POSITIONS[hash % SPHERE_POSITIONS.length]; const rotation = position ?? { x: 0, y: 0 }; return { FaceComponent: FACES[faceIndex], bgColor: COLORS[colorIndex], transform: `rotateX(${rotation.x * ROTATE_RANGE}deg) rotateY(${rotation.y * ROTATE_RANGE}deg) translateZ(${TRANSLATE_Z}px)`, initial: props.name.charAt(0).toUpperCase(), }; }); return (
{/* gradient overlay */}
{/* face content */}
{(() => { const Face = faceData().FaceComponent; return ; })()} {faceData().initial}
} > {props.name}
); }; export default Avatar;