diff --git a/packages/web/src/components/ui/Avatar.tsx b/packages/web/src/components/ui/Avatar.tsx index a28b0eec..026db272 100644 --- a/packages/web/src/components/ui/Avatar.tsx +++ b/packages/web/src/components/ui/Avatar.tsx @@ -21,6 +21,28 @@ const statusColors: Record = { offline: 'bg-status-offline', }; +/** + * Builds a CSS radial-gradient mask that punches a circular hole in the avatar + * where the status dot sits. The hole reveals the parent background, creating + * a true cutout effect on any surface — no border-color matching needed. + * + * Prototype reference (.m-dot): 12px box with 3px border (border-box) = 6px + * visible color, positioned at bottom:-2 right:-2 → center 4px from corner. + */ +function buildCutoutMask(size: number): string { + const { dot, gap, inset } = getDotMetrics(size); + const cx = size - inset; + const cy = size - inset; + const r = dot / 2 + gap; + return `radial-gradient(circle at ${cx}px ${cy}px, transparent ${r}px, black ${r + 0.5}px)`; +} + +/** Returns visible dot diameter, gap width, and center inset from avatar edge. */ +function getDotMetrics(size: number) { + if (size <= 24) return { dot: 5, gap: 2, inset: 3 }; + return { dot: 6, gap: 3, inset: 4 }; +} + export function Avatar({ src, name, size = 40, status, className = '', onClick, user, userId }: AvatarProps) { const openUserProfile = useUIStore((s) => s.openUserProfile); const initials = name.charAt(0).toUpperCase(); @@ -40,6 +62,14 @@ export function Avatar({ src, name, size = 40, status, className = '', onClick, } }; + // Only compute mask when status dot is visible + const cutoutMask = status ? buildCutoutMask(size) : undefined; + const maskStyle: React.CSSProperties | undefined = cutoutMask + ? { maskImage: cutoutMask, WebkitMaskImage: cutoutMask } + : undefined; + + const { dot: dotDiameter, inset: dotInset } = getDotMetrics(size); + return (
{ (e.target as HTMLImageElement).style.display = 'none'; const parent = (e.target as HTMLImageElement).parentElement; @@ -63,7 +94,7 @@ export function Avatar({ src, name, size = 40, status, className = '', onClick, ) : null}
{initials}
@@ -71,11 +102,10 @@ export function Avatar({ src, name, size = 40, status, className = '', onClick,
)}