Something went wrong. Try again.
Monorepo for Tangled tangled.org
Something went wrong. Try again.
JavaScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113// Deterministic, vibrant gradients for placeholder avatars.//// Everything is derived from the actor string alone, so the same actor always// gets the same gradient and nothing needs to be stored anywhere. The colors// are the seven chromatic entries of the base16 material darker palette, so// every stop is a saturated, high-contrast color instead of a washed-out tint.
// 32-bit FNV-1a. The old placeholder used a rolling hash to pick a flat color;// this keeps the same "same actor, same result" property while giving the PRNG// below a well-distributed seed.export const hashString = (str) => { let hash = 2166136261; for (let i = 0; i < str.length; i++) { hash ^= str.charCodeAt(i); hash = Math.imul(hash, 16777619); } return hash >>> 0;};
// mulberry32: tiny, fast, and more than good enough for picking pretty colors.const mulberry32 = (seed) => { let a = seed >>> 0; return () => { a = (a + 0x6d2b79f5) | 0; let t = Math.imul(a ^ (a >>> 15), 1 | a); t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t; return ((t ^ (t >>> 14)) >>> 0) / 4294967296; };};
const round = (n, places = 1) => Number(n.toFixed(places));
// The seven chromatic colors of base16 material darker, ordered around the// wheel so a run of them stays harmonious: red, orange, yellow, green, cyan,// blue, purple.const PALETTE = [ "#f07178", // red "#f78c6c", // orange "#ffcb6b", // yellow "#c3e88d", // green "#89ddff", // cyan "#82aaff", // blue "#c792ea" // purple];
/** * Pick all the parameters for an actor's gradient. * @returns {{ angle: number, stops: {offset: number, color: string}[], * glow: {x: number, y: number, r: number, opacity: number, color: string} }} */export const gradientFor = (actor) => { const rand = mulberry32(hashString(actor));
const angle = Math.round(rand() * 360); const stopCount = 3 + Math.floor(rand() * 3); // 3..5 stops const direction = rand() < 0.5 ? -1 : 1; const start = Math.floor(rand() * PALETTE.length);
// walk the palette from a random entry, wrapping, so the stops are a run of // neighbouring hues rather than a random collage const stops = []; for (let i = 0; i < stopCount; i++) { const index = (((start + direction * i) % PALETTE.length) + PALETTE.length) % PALETTE.length; stops.push({ offset: stopCount === 1 ? 0 : i / (stopCount - 1), color: PALETTE[index] }); }
// A soft light source for atmosphere, drawn from another palette color. const glow = { x: round(0.2 + rand() * 0.6, 2), y: round(0.15 + rand() * 0.5, 2), r: round(0.55 + rand() * 0.35, 2), opacity: round(0.25 + rand() * 0.2, 2), color: PALETTE[Math.floor(rand() * PALETTE.length)] };
return { angle, stops, glow };};
/** * Render an actor's placeholder as a self-contained SVG string. */export const gradientSvg = (actor, size = 128) => { const { angle, stops, glow } = gradientFor(actor); const id = hashString(actor).toString(36); const stopMarkup = stops .map( (s) => `<stop offset="${round(s.offset * 100)}%" stop-color="${s.color}"/>` ) .join("");
return ( `<svg width="${size}" height="${size}" viewBox="0 0 100 100" ` + `preserveAspectRatio="xMidYMid slice" xmlns="http://www.w3.org/2000/svg">` + `<defs>` + `<linearGradient id="g${id}" x1="0" y1="0" x2="1" y2="1" ` + `gradientTransform="rotate(${angle} 0.5 0.5)">${stopMarkup}</linearGradient>` + `<radialGradient id="l${id}" cx="${glow.x}" cy="${glow.y}" r="${glow.r}">` + `<stop offset="0%" stop-color="${glow.color}" stop-opacity="${glow.opacity}"/>` + `<stop offset="100%" stop-color="${glow.color}" stop-opacity="0"/>` + `</radialGradient>` + `</defs>` + `<rect width="100" height="100" fill="url(#g${id})"/>` + `<rect width="100" height="100" fill="url(#l${id})"/>` + `</svg>` );};