1234567891011export function faviconUrl(domain: string): string | null {12 if (!domain) return null;13 if (typeof chrome === "undefined" || !chrome.runtime?.getURL) return null;1415 try {16 const url = new URL(chrome.runtime.getURL("/_favicon/"));17 url.searchParams.set("pageUrl", `https://${domain}`);18 url.searchParams.set("size", "32");19 return url.toString();20 } catch {21 return null;22 }23}242526272829export function titleInitials(source: string): string {30 const cleaned = source.replace(/^https?:\/\//, "").replace(/^www\./, "").trim();31 if (!cleaned) return "·";32 const words = cleaned.split(/[\s.\-_/]+/).filter(Boolean);33 if (words.length >= 2 && words[0]![0] && words[1]![0]) {34 return (words[0]![0]! + words[1]![0]!).toUpperCase();35 }36 const single = words[0] ?? cleaned;37 return single.slice(0, 2).toUpperCase();38}39404142434445export function titleBackground(source: string): string {46 let hash = 0;47 for (let i = 0; i < source.length; i++) {48 hash = ((hash << 5) - hash + source.charCodeAt(i)) | 0;49 }50 const hue = ((hash % 360) + 360) % 360;51 return `hsl(${hue}, 32%, 26%)`;52}53545556function imgSig(img: HTMLImageElement): string {57 const c = document.createElement("canvas");58 c.width = c.height = 8;59 const ctx = c.getContext("2d")!;60 ctx.drawImage(img, 0, 0, 8, 8);61 const d = ctx.getImageData(0, 0, 8, 8).data;62 let s = "";63 for (let i = 0; i < d.length; i += 4) s += `${d[i]},${d[i + 1]},${d[i + 2]};`;64 return s;65}666768let _globeSig: Promise<string | null> | undefined;6970717273747576export function globeFaviconSig(): Promise<string | null> {77 if (_globeSig) return _globeSig;78 _globeSig = new Promise<string | null>((resolve) => {79 const src = faviconUrl("zz-peach-no-favicon-sentinel.nonexistent");80 if (!src) { resolve(null); return; }81 const img = new Image();82 img.onload = () => { try { resolve(imgSig(img)); } catch { resolve(null); } };83 img.onerror = () => resolve(null);84 img.src = src;85 });86 return _globeSig;87}888990919293export function faviconImgSig(img: HTMLImageElement): string | null {94 try { return imgSig(img); } catch { return null; }95}