mirror of
https://github.com/iamlukethedev/Claw3D.git
synced 2026-07-31 03:52:15 +00:00
feat: swap agent avatars to bunny-themed SVGs
Co-authored-by: Luke The Dev <iamlukethedev@users.noreply.github.com>
This commit is contained in:
co-authored by
Luke The Dev
parent
083c146aac
commit
aa6f540221
@@ -1,32 +1,32 @@
|
||||
const AVATAR_PALETTE = [
|
||||
"#1d4ed8",
|
||||
"#0f766e",
|
||||
"#7c3aed",
|
||||
"#c2410c",
|
||||
"#be123c",
|
||||
"#4f46e5",
|
||||
"#0f172a",
|
||||
"#1f2937",
|
||||
] as const;
|
||||
|
||||
const AVATAR_ACCENTS = [
|
||||
"#bfdbfe",
|
||||
"#99f6e4",
|
||||
"#ddd6fe",
|
||||
"#fed7aa",
|
||||
"#fecdd3",
|
||||
"#c7d2fe",
|
||||
"#cbd5e1",
|
||||
const AVATAR_BACKGROUNDS = [
|
||||
"#67e8f9",
|
||||
"#a7f3d0",
|
||||
"#fbcfe8",
|
||||
"#fde68a",
|
||||
"#c4b5fd",
|
||||
"#fdba74",
|
||||
"#93c5fd",
|
||||
"#f9a8d4",
|
||||
] as const;
|
||||
|
||||
const AVATAR_FOREGROUNDS = [
|
||||
"#eff6ff",
|
||||
const AVATAR_EAR_INNERS = [
|
||||
"#f472b6",
|
||||
"#fb7185",
|
||||
"#f9a8d4",
|
||||
"#fda4af",
|
||||
] as const;
|
||||
|
||||
const AVATAR_FUR = [
|
||||
"#ffffff",
|
||||
"#f8fafc",
|
||||
"#fefce8",
|
||||
"#fef2f2",
|
||||
"#fdf4ff",
|
||||
] as const;
|
||||
|
||||
const AVATAR_EYE_COLORS = ["#1f2937", "#0f172a", "#3f3f46"] as const;
|
||||
|
||||
const AVATAR_NOSE_COLORS = ["#f43f5e", "#ec4899", "#fb7185"] as const;
|
||||
|
||||
const hashSeed = (seed: string) => {
|
||||
let hash = 2166136261;
|
||||
for (let i = 0; i < seed.length; i += 1) {
|
||||
@@ -56,24 +56,39 @@ export const buildAvatarSvg = (seed: string): string => {
|
||||
}
|
||||
|
||||
const hash = hashSeed(trimmed);
|
||||
const background = pick(AVATAR_PALETTE, hash);
|
||||
const accent = pick(AVATAR_ACCENTS, hash >>> 3);
|
||||
const foreground = pick(AVATAR_FOREGROUNDS, hash >>> 5);
|
||||
const background = pick(AVATAR_BACKGROUNDS, hash);
|
||||
const earInner = pick(AVATAR_EAR_INNERS, hash >>> 2);
|
||||
const fur = pick(AVATAR_FUR, hash >>> 4);
|
||||
const eye = pick(AVATAR_EYE_COLORS, hash >>> 7);
|
||||
const nose = pick(AVATAR_NOSE_COLORS, hash >>> 9);
|
||||
const label = buildAvatarLabel(trimmed);
|
||||
const offsetA = 16 + (hash % 28);
|
||||
const offsetB = 68 - ((hash >>> 7) % 24);
|
||||
const radius = 12 + ((hash >>> 11) % 10);
|
||||
const tilt = ((hash >>> 13) % 20) - 10;
|
||||
const leftEarTilt = 10 + ((hash >>> 11) % 13);
|
||||
const rightEarTilt = 10 + ((hash >>> 14) % 13);
|
||||
const blinkHeight = 4 + ((hash >>> 17) % 3);
|
||||
const cheekOffset = 13 + ((hash >>> 19) % 4);
|
||||
const whiskerLift = (hash >>> 21) % 3;
|
||||
|
||||
return [
|
||||
`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 80 80" role="img" aria-label="${label} avatar">`,
|
||||
`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 80 80" role="img" aria-label="${label} bunny avatar" data-bunny="true">`,
|
||||
`<rect width="80" height="80" rx="18" fill="${background}"/>`,
|
||||
`<circle cx="${offsetA}" cy="18" r="${radius}" fill="${accent}" opacity="0.65"/>`,
|
||||
`<circle cx="${offsetB}" cy="66" r="${radius + 4}" fill="${accent}" opacity="0.42"/>`,
|
||||
`<path d="M10 60 Q40 ${44 + tilt} 70 22 L70 80 L10 80 Z" fill="${foreground}" opacity="0.14"/>`,
|
||||
`<circle cx="40" cy="32" r="16" fill="${foreground}" opacity="0.92"/>`,
|
||||
`<path d="M20 72c4-12 14-18 20-18s16 6 20 18" fill="${foreground}" opacity="0.92"/>`,
|
||||
`<text x="40" y="37" text-anchor="middle" font-family="system-ui, sans-serif" font-size="11" font-weight="700" fill="${background}">${label}</text>`,
|
||||
`<circle cx="16" cy="14" r="7" fill="#ffffff" opacity="0.22"/>`,
|
||||
`<circle cx="66" cy="64" r="9" fill="#ffffff" opacity="0.18"/>`,
|
||||
`<path d="M28 38 C${26 - leftEarTilt * 0.18} 22, ${26 - leftEarTilt * 0.08} 10, 32 6 C36 10, 35 23, 34 38 Z" fill="${fur}"/>`,
|
||||
`<path d="M28 34 C${27 - leftEarTilt * 0.12} 23, 28 15, 31.5 11 C34 14, 33 23, 32 34 Z" fill="${earInner}" opacity="0.95"/>`,
|
||||
`<path d="M52 38 C${54 + rightEarTilt * 0.18} 22, ${54 + rightEarTilt * 0.08} 10, 48 6 C44 10, 45 23, 46 38 Z" fill="${fur}"/>`,
|
||||
`<path d="M52 34 C${53 + rightEarTilt * 0.12} 23, 52 15, 48.5 11 C46 14, 47 23, 48 34 Z" fill="${earInner}" opacity="0.95"/>`,
|
||||
`<ellipse cx="40" cy="45" rx="21" ry="20" fill="${fur}"/>`,
|
||||
`<ellipse cx="40" cy="57" rx="15" ry="11" fill="#ffffff" opacity="0.56"/>`,
|
||||
`<ellipse cx="33" cy="44" rx="3" ry="${blinkHeight}" fill="${eye}"/>`,
|
||||
`<ellipse cx="47" cy="44" rx="3" ry="${blinkHeight}" fill="${eye}"/>`,
|
||||
`<circle cx="${40 - cheekOffset}" cy="52" r="3.2" fill="${earInner}" opacity="0.4"/>`,
|
||||
`<circle cx="${40 + cheekOffset}" cy="52" r="3.2" fill="${earInner}" opacity="0.4"/>`,
|
||||
`<path d="M40 49 l4 4 l-4 3.2 l-4 -3.2 Z" fill="${nose}"/>`,
|
||||
`<path d="M40 55 v4" stroke="#7f1d1d" stroke-width="1.6" stroke-linecap="round"/>`,
|
||||
`<path d="M40 59 c-3.4 0 -5.6 1.4 -6.7 3.6" stroke="#7f1d1d" stroke-width="1.6" fill="none" stroke-linecap="round"/>`,
|
||||
`<path d="M40 59 c3.4 0 5.6 1.4 6.7 3.6" stroke="#7f1d1d" stroke-width="1.6" fill="none" stroke-linecap="round"/>`,
|
||||
`<path d="M27 ${54 - whiskerLift} H20 M27 ${58 - whiskerLift} H19 M53 ${54 - whiskerLift} H60 M53 ${58 - whiskerLift} H61" stroke="#9f1239" stroke-width="1.2" stroke-linecap="round" opacity="0.8"/>`,
|
||||
`<text x="40" y="76" text-anchor="middle" font-family="system-ui, sans-serif" font-size="8.5" font-weight="700" fill="${eye}" opacity="0.9">${label}</text>`,
|
||||
`</svg>`,
|
||||
].join("");
|
||||
};
|
||||
|
||||
@@ -8,6 +8,8 @@ describe("multiavatar helpers", () => {
|
||||
expect(svg).toContain("<svg");
|
||||
expect(svg).toContain("</svg>");
|
||||
expect(svg).toContain("AA");
|
||||
expect(svg).toContain("bunny avatar");
|
||||
expect(svg).toContain('data-bunny="true"');
|
||||
});
|
||||
|
||||
it("buildAvatarDataUrl returns a data url", () => {
|
||||
|
||||
Reference in New Issue
Block a user