Files
healer-man/src/ui/CharacterPreview.tsx
T
2026-08-30 14:16:56 -04:00

64 lines
2.7 KiB
TypeScript

import { lazy, Suspense, type CSSProperties } from "react";
import { classById, raceById } from "../app/characterCatalog";
import type { CharacterDraft, CharacterProfile } from "../app/types";
import { normalizePreviewFrameRateLimit } from "../game/frameRateSettings";
const CharacterPreview3D = lazy(() => import("./CharacterPreview3D"));
function AvatarSilhouette({ gender, raceSigil }: {
gender: CharacterDraft["gender"];
raceSigil: string;
}) {
return (
<div className={`character-avatar character-avatar--${gender}`} aria-hidden="true">
<span className="character-avatar__cloak" />
<span className="character-avatar__body" />
<span className="character-avatar__shoulder character-avatar__shoulder--left" />
<span className="character-avatar__shoulder character-avatar__shoulder--right" />
<span className="character-avatar__head"><b>{raceSigil}</b></span>
<span className="character-avatar__staff"><i /></span>
</div>
);
}
export function CharacterPreview({ character, draft, compact = false }: {
character?: CharacterProfile | null;
draft?: CharacterDraft;
compact?: boolean;
}) {
const source = character ?? draft;
if (!source) {
return <div className="character-preview character-preview--empty"><span>+</span><p>Create a character to answer the call.</p></div>;
}
const race = raceById(source.raceId);
const characterClass = classById(source.classId);
const name = "name" in source && source.name ? source.name : "New adventurer";
const rotation = "rotation" in source ? source.rotation : -8;
const previewFrameRate = normalizePreviewFrameRateLimit(
"settings" in source ? source.settings?.previewFrameRate : undefined,
);
const style = {
"--race-accent": race.accent,
"--class-color": characterClass.color,
"--avatar-turn": `${rotation}deg`,
} as CSSProperties;
return (
<figure className={`character-preview ${compact ? "character-preview--compact" : ""}`} style={style}>
<div className="character-preview__aura" aria-hidden="true"><i /><i /><i /></div>
{compact ? (
<AvatarSilhouette gender={source.gender} raceSigil={race.sigil} />
) : (
<Suspense fallback={<AvatarSilhouette gender={source.gender} raceSigil={race.sigil} />}>
<CharacterPreview3D source={source} frameRateLimit={previewFrameRate} />
</Suspense>
)}
<span className="sr-only">{source.gender} {race.name} {characterClass.name} preview</span>
<figcaption>
<small>{race.name} / {source.gender}</small>
<strong>{name}</strong>
<span style={{ color: characterClass.color }}>{characterClass.name}</span>
</figcaption>
</figure>
);
}