cast-avatar
Version:
Dependency-free deterministic SVG avatar generator for browsers.
48 lines (41 loc) • 2.71 kB
JavaScript
import { createRandom, pick } from '../hash.js';
import { resolvePalette } from '../palettes.js';
import { colorAt, svgFrame } from './common.js';
// Geometric robot face composed from primitives: a body color and an accent
// "screen" color, with seeded variation in the eye and mouth hardware.
export function renderBotAvatar(config) {
const random = createRandom(`${config.seed}:bot`);
const colors = resolvePalette(config.palette).shapeColors;
const body = colorAt(colors, random);
const accent = colorAt(colors, random, 4);
const antennaType = pick(['single', 'double', 'none'], random);
let antenna = '';
if (antennaType === 'single') {
antenna = `<path d="M64 34V20" stroke="${body}" stroke-width="4" stroke-linecap="round"/><circle cx="64" cy="16" r="5" fill="${accent}"/>`;
} else if (antennaType === 'double') {
antenna = `<path d="M50 34V22" stroke="${body}" stroke-width="3" stroke-linecap="round"/><circle cx="50" cy="19" r="4" fill="${accent}"/><path d="M78 34V22" stroke="${body}" stroke-width="3" stroke-linecap="round"/><circle cx="78" cy="19" r="4" fill="${accent}"/>`;
}
const ears = `<rect x="20" y="54" width="8" height="22" rx="3" fill="${body}"/><rect x="100" y="54" width="8" height="22" rx="3" fill="${body}"/>`;
const head = `<rect x="28" y="34" width="72" height="62" rx="14" fill="${body}"/>`;
const bolts = ['35 41', '93 41', '35 89', '93 89'].map((p) => `<circle cx="${p.split(' ')[0]}" cy="${p.split(' ')[1]}" r="2.5" fill="${accent}" opacity="0.7"/>`).join('');
const panel = `<rect x="38" y="44" width="52" height="30" rx="8" fill="#0f172a" opacity="0.85"/>`;
const eyeType = pick(['round', 'square', 'visor'], random);
let eyes;
if (eyeType === 'round') {
eyes = `<circle cx="52" cy="59" r="6" fill="${accent}"/><circle cx="76" cy="59" r="6" fill="${accent}"/>`;
} else if (eyeType === 'square') {
eyes = `<rect x="46" y="53" width="12" height="12" rx="2" fill="${accent}"/><rect x="70" y="53" width="12" height="12" rx="2" fill="${accent}"/>`;
} else {
eyes = `<rect x="44" y="55" width="40" height="8" rx="4" fill="${accent}"/>`;
}
const mouthType = pick(['grille', 'line', 'smile'], random);
let mouth;
if (mouthType === 'grille') {
mouth = [50, 57, 64, 71, 78].map((x) => `<rect x="${x}" y="82" width="3" height="9" rx="1" fill="${accent}"/>`).join('');
} else if (mouthType === 'line') {
mouth = `<rect x="50" y="85" width="28" height="4" rx="2" fill="${accent}"/>`;
} else {
mouth = `<path d="M50 84q14 10 28 0" fill="none" stroke="${accent}" stroke-width="4" stroke-linecap="round"/>`;
}
return svgFrame(config, [antenna, ears, head, bolts, panel, eyes, mouth].join(''));
}