← All primitives
SeedEmblem
GenerativeA unique animated SVG 'system' generated deterministically from any string. Same seed, same emblem. Server-renderable, no canvas.
Requires the @keyframes ge-spin / ge-pulse and .ge-spin / .ge-pulse rules from globals.css — copy them in, or the emblem renders static.
$
View raw manifest →npx shadcn@latest add https://prism.icglabs.co/r/seed-emblem.jsoncomponents/prism/SeedEmblem.tsx
import { rngFrom, hashStr } from "@/lib/seed";
/**
* SeedEmblem — a deterministic, animated SVG "system" (orbiting rings, nodes,
* a glowing core) generated entirely from a seed string. Same string ⇒ same
* emblem, every time. Pure/server-renderable. Ported from Sky's GenEmblem.
*
* Use it for unique-but-consistent avatars, project marks, or entity glyphs.
*/
export function SeedEmblem({
seed,
hue = "#8b5cff",
label,
size = 56,
}: {
seed: string;
hue?: string;
label?: string;
size?: number;
}) {
const rng = rngFrom(seed);
const id = hashStr(seed).toString(36);
// Round derived values so server- and client-rendered SVG attributes serialize
// identically (trig last-ULP differences would otherwise trip hydration).
const r3 = (n: number) => Math.round(n * 1000) / 1000;
const rings = Array.from({ length: 3 }, (_, i) => {
const rx = r3(30 + i * 8 + rng() * 6);
const ry = r3(rx * (0.48 + rng() * 0.36));
const rot = Math.floor(rng() * 360);
const dur = r3(20 + i * 9 + rng() * 12);
return { rx, ry, rot, dur, rev: i % 2 === 1, op: 0.42 - i * 0.1 };
});
const nodeCount = 4 + Math.floor(rng() * 4);
const nodes = Array.from({ length: nodeCount }, () => {
const a = rng() * Math.PI * 2;
const r = 22 + rng() * 24;
return {
x: r3(60 + Math.cos(a) * r),
y: r3(60 + Math.sin(a) * r * 0.72),
rr: r3(1.3 + rng() * 1.8),
};
});
const coreR = r3(7 + rng() * 5);
return (
<svg viewBox="0 0 120 120" width={size} height={size} role="img" aria-hidden>
<defs>
<radialGradient id={`glow-${id}`}>
<stop offset="0%" stopColor={hue} stopOpacity="0.95" />
<stop offset="45%" stopColor={hue} stopOpacity="0.45" />
<stop offset="100%" stopColor={hue} stopOpacity="0" />
</radialGradient>
</defs>
{nodes.map((n, i) => (
<line key={`l${i}`} x1="60" y1="60" x2={n.x} y2={n.y} stroke={hue} strokeOpacity="0.12" strokeWidth="0.6" />
))}
{rings.map((r, i) => (
<ellipse
key={`r${i}`}
className="ge-spin"
style={{
animationDuration: `${r.dur}s`,
animationDirection: r.rev ? "reverse" : "normal",
animationDelay: `${-(r.rot / 360) * r.dur}s`,
}}
cx="60"
cy="60"
rx={r.rx}
ry={r.ry}
fill="none"
stroke={hue}
strokeOpacity={r.op}
strokeWidth="0.8"
/>
))}
{nodes.map((n, i) => (
<circle key={`n${i}`} cx={n.x} cy={n.y} r={n.rr} fill={hue} fillOpacity="0.85" />
))}
<circle className="ge-pulse" cx="60" cy="60" r={coreR * 1.9} fill={`url(#glow-${id})`} />
<circle cx="60" cy="60" r={coreR} fill={hue} fillOpacity="0.92" />
{label ? (
<text x="60" y="67" textAnchor="middle" fontSize="20" fontWeight="600" fill="#08080c">
{label}
</text>
) : (
<circle cx="60" cy="60" r={coreR * 0.42} fill="#ffffff" fillOpacity="0.85" />
)}
</svg>
);
}
lib/seed.ts
// Tiny deterministic PRNG so generative visuals are stable per seed string.
// Ported from Sky — the seed→visual pipeline is one of the most reusable tools.
export function hashStr(s: string): number {
let h = 1779033703 ^ s.length;
for (let i = 0; i < s.length; i++) {
h = Math.imul(h ^ s.charCodeAt(i), 3432918353);
h = (h << 13) | (h >>> 19);
}
h ^= h >>> 16;
return h >>> 0;
}
export function mulberry32(seed: number): () => number {
let a = seed >>> 0;
return function () {
a |= 0;
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;
};
}
export function rngFrom(s: string): () => number {
return mulberry32(hashStr(s));
}