Files
Jyotisha/frontend/src/lib/beam-avatar.ts
T
Jesse 49f6edd7fb
Staging Backend Quality Gate / validate (push) Successful in 13m34s
Staging Backend Quality Gate / publish (push) Has been cancelled
feat(web): add persistent beam avatars
2026-08-07 12:07:57 +08:00

67 lines
2.4 KiB
TypeScript

import { z } from "zod";
export const BEAM_AVATAR_RENDERER_VERSION = 1 as const;
export const beamAvatarPalettes = [
{ name: "星砂", colors: ["#17324D", "#2D6073", "#D5A45D", "#F2E6CF", "#B9684A"] },
{ name: "靛夜", colors: ["#202842", "#495B8C", "#93A3D1", "#E4D9C7", "#C58D68"] },
{ name: "莲火", colors: ["#553640", "#A35D63", "#D99B7A", "#F2D8BA", "#5E7161"] },
{ name: "松雾", colors: ["#1F3D3A", "#4D746C", "#A7B59A", "#E8DEC8", "#C77B56"] },
{ name: "晨曦", colors: ["#34405F", "#7484A6", "#E2AF72", "#F4E5C9", "#B75D54"] },
{ name: "紫藤", colors: ["#3E3555", "#77638F", "#B9A3CA", "#E9DCCB", "#C47B6C"] },
{ name: "海盐", colors: ["#1D4554", "#4F8090", "#A8C0BD", "#EFE2C8", "#D18A5D"] },
{ name: "桂影", colors: ["#443B2D", "#7B6848", "#C6A25E", "#EFE0BE", "#8C5B4C"] },
] as const;
export const beamAvatarPaletteSchema = z.number().int().min(0).max(beamAvatarPalettes.length - 1);
export const beamAvatarSchema = z.object({
seed: z.string().uuid(),
palette: beamAvatarPaletteSchema,
rendererVersion: z.literal(BEAM_AVATAR_RENDERER_VERSION),
}).strict();
export const beamAvatarPatchSchema = z.discriminatedUnion("action", [
z.object({
action: z.literal("set_palette"),
palette: beamAvatarPaletteSchema,
}).strict(),
z.object({
action: z.literal("randomize"),
palette: beamAvatarPaletteSchema.optional(),
}).strict(),
]);
export type BeamAvatar = z.infer<typeof beamAvatarSchema>;
export type BeamAvatarPatch = z.infer<typeof beamAvatarPatchSchema>;
export function createBeamAvatarProfilePatch(
patch: BeamAvatarPatch,
createSeed: () => string = () => globalThis.crypto.randomUUID(),
) {
return {
...(patch.palette !== undefined ? { avatar_palette: patch.palette } : {}),
avatar_renderer_version: BEAM_AVATAR_RENDERER_VERSION,
...(patch.action === "randomize" ? { avatar_seed: createSeed() } : {}),
};
}
export function beamAvatarFromProfile(profile: Record<string, unknown>): BeamAvatar {
return beamAvatarSchema.parse({
seed: profile.avatar_seed,
palette: profile.avatar_palette,
rendererVersion: profile.avatar_renderer_version,
});
}
export function optionalBeamAvatarFromProfile(
profile: Record<string, unknown>,
): BeamAvatar | null {
const parsed = beamAvatarSchema.safeParse({
seed: profile.avatar_seed,
palette: profile.avatar_palette,
rendererVersion: profile.avatar_renderer_version,
});
return parsed.success ? parsed.data : null;
}