45 lines
1.6 KiB
JavaScript
45 lines
1.6 KiB
JavaScript
// Deterministic, local-only avatars. No network calls.
|
|
export function avatarDataURL(seed, size = 40) {
|
|
// Hash seed → bytes
|
|
const h = sha256(seed);
|
|
// Colors from bytes
|
|
const hue = h[0] % 360;
|
|
const bg = `hsl(${(h[1]*3)%360} 25% 14%)`;
|
|
const fg = `hsl(${hue} 70% 60%)`;
|
|
|
|
// 5x5 grid mirrored; draw squares where bits set
|
|
const cells = 5, scale = Math.floor(size / cells);
|
|
let rects = "";
|
|
for (let y = 0; y < cells; y++) {
|
|
for (let x = 0; x < Math.ceil(cells/2); x++) {
|
|
const bit = (h[(y*3 + x) % h.length] >> (y % 5)) & 1;
|
|
if (bit) {
|
|
const xL = x*scale, xR = (cells-1-x)*scale, yP = y*scale;
|
|
rects += `<rect x="${xL}" y="${yP}" width="${scale}" height="${scale}" rx="2" ry="2" fill="${fg}"/>`;
|
|
if (x !== cells-1-x) {
|
|
rects += `<rect x="${xR}" y="${yP}" width="${scale}" height="${scale}" rx="2" ry="2" fill="${fg}"/>`;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
const svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${cells*scale} ${cells*scale}">
|
|
<rect width="100%" height="100%" fill="${bg}"/>${rects}
|
|
</svg>`;
|
|
return "data:image/svg+xml;base64," + btoa(unescape(encodeURIComponent(svg)));
|
|
}
|
|
|
|
function sha256(s) {
|
|
// Simple synchronous hash-ish bytes from string (non-cryptographic; fine for visuals)
|
|
let h1 = 0x6a09e667, h2 = 0xbb67ae85;
|
|
for (let i=0;i<s.length;i++) {
|
|
const c = s.charCodeAt(i);
|
|
h1 = (h1 ^ c) * 0x45d9f3b + ((h1<<7) | (h1>>>25));
|
|
h2 = (h2 ^ (c<<1)) * 0x27d4eb2d + ((h2<<9) | (h2>>>23));
|
|
}
|
|
const out = new Uint8Array(32);
|
|
for (let i=0;i<32;i++){
|
|
out[i] = (h1 >> (i%24)) ^ (h2 >> ((i*3)%24)) ^ (i*31);
|
|
}
|
|
return out;
|
|
}
|