Something went wrong. Try again.
grain.social is a photo sharing platform built on atproto. grain.social
appview atproto photography
Something went wrong. Try again.
12345678910111213141516171819202122232425262728293031323334353637383940414243/** Build a blob URL for a profile image. Returns pre-resolved URLs as-is. */export function blobUrl(did: string, blobRef: unknown): string | null { if (!blobRef) return null; if (typeof blobRef === "string") { if (blobRef.startsWith("http")) return blobRef; try { blobRef = JSON.parse(blobRef); } catch { return null; } } const p = blobRef as any; if (!p?.ref?.$link) return null; return `https://cdn.bsky.app/img/avatar/plain/${did}/${p.ref.$link}@jpeg`;}
/** Truncate a DID for display. */export function truncDid(did: string): string { if (did.length <= 24) return did; return did.slice(0, 12) + "\u2026" + did.slice(-6);}
/** Compact relative time matching grain-native: now, 2m, 3h, 4d, 1w, then Mon DD. */export function relativeTime(iso: string): string { const date = new Date(iso); const diff = (Date.now() - date.getTime()) / 1000;
if (diff < 60) return "now"; if (diff < 3600) return `${Math.floor(diff / 60)}m`; if (diff < 86400) return `${Math.floor(diff / 3600)}h`; if (diff < 604800) return `${Math.floor(diff / 86400)}d`; if (diff < 2592000) return `${Math.floor(diff / 604800)}w`;
return date.toLocaleDateString("en-US", { month: "short", day: "numeric" });}
/** Compact number formatting: 1K, 1.2M, etc. */export function compactCount(n: number): string { if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1).replace(/\.0$/, "")}M`; if (n >= 1_000) return `${(n / 1_000).toFixed(1).replace(/\.0$/, "")}K`; return String(n);}