@@ -12,6 +12,7 @@ import type {
|
||||
WorldAttributeSchema,
|
||||
} from '../types';
|
||||
import {
|
||||
buildCharacterSkillRenderId,
|
||||
type ContributionRow,
|
||||
formatAttributeMetricValue,
|
||||
getAttributeBonusPillClassName,
|
||||
@@ -56,6 +57,88 @@ export function StatusRow({
|
||||
);
|
||||
}
|
||||
|
||||
export function CharacterIdentityBadges({
|
||||
roleLabel,
|
||||
levelText = null,
|
||||
roleTone = 'sky',
|
||||
className = '',
|
||||
}: {
|
||||
roleLabel: string;
|
||||
levelText?: string | null;
|
||||
roleTone?: 'amber' | 'sky' | 'rose' | 'emerald' | 'zinc';
|
||||
className?: string;
|
||||
}) {
|
||||
const roleClass =
|
||||
roleTone === 'amber'
|
||||
? 'border-amber-300/20 bg-amber-500/10 text-amber-100'
|
||||
: roleTone === 'rose'
|
||||
? 'border-rose-300/20 bg-rose-500/10 text-rose-100'
|
||||
: roleTone === 'emerald'
|
||||
? 'border-emerald-300/20 bg-emerald-500/10 text-emerald-100'
|
||||
: roleTone === 'zinc'
|
||||
? 'border-white/10 bg-black/20 text-zinc-200'
|
||||
: 'border-sky-300/20 bg-sky-500/10 text-sky-100';
|
||||
|
||||
return (
|
||||
<div className={`flex flex-wrap items-center gap-2 ${className}`.trim()}>
|
||||
<span
|
||||
className={`rounded-full border px-2.5 py-1 text-[10px] tracking-[0.16em] ${roleClass}`}
|
||||
>
|
||||
{roleLabel}
|
||||
</span>
|
||||
{levelText ? (
|
||||
<span className="rounded-full border border-white/10 bg-black/20 px-2.5 py-1 text-[10px] tracking-[0.16em] text-zinc-200">
|
||||
{levelText}
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function PlayerLevelProgress({
|
||||
level,
|
||||
currentLevelXp,
|
||||
xpToNextLevel,
|
||||
className = '',
|
||||
}: {
|
||||
level: number;
|
||||
currentLevelXp: number;
|
||||
xpToNextLevel: number;
|
||||
className?: string;
|
||||
}) {
|
||||
const safeLevel = Math.max(1, Math.round(level));
|
||||
const safeCurrentLevelXp = Math.max(0, Math.round(currentLevelXp));
|
||||
const safeXpToNextLevel = Math.max(0, Math.round(xpToNextLevel));
|
||||
const ratio =
|
||||
safeXpToNextLevel <= 0
|
||||
? 1
|
||||
: Math.max(
|
||||
0,
|
||||
Math.min(1, safeCurrentLevelXp / safeXpToNextLevel),
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<div className="flex items-center justify-between gap-3 text-[11px]">
|
||||
<div className="font-semibold text-amber-50">Lv.{safeLevel}</div>
|
||||
<div className="text-zinc-400">
|
||||
{safeXpToNextLevel > 0
|
||||
? `${safeCurrentLevelXp}/${safeXpToNextLevel}`
|
||||
: 'MAX'}
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-2 h-1.5 overflow-hidden rounded-full border border-white/10 bg-black/30">
|
||||
<div
|
||||
className="h-full rounded-full bg-[linear-gradient(90deg,rgba(251,191,36,0.78),rgba(253,224,71,0.96))]"
|
||||
style={{
|
||||
width: ratio <= 0 ? '0%' : `${Math.max(6, ratio * 100)}%`,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function CharacterSkillsList({
|
||||
skills,
|
||||
onSelectSkill,
|
||||
@@ -75,7 +158,8 @@ export function CharacterSkillsList({
|
||||
|
||||
return (
|
||||
<div className="grid gap-2 sm:grid-cols-2">
|
||||
{skills.map((skill) => {
|
||||
{skills.map((skill, index) => {
|
||||
const skillRenderId = buildCharacterSkillRenderId(skill, index);
|
||||
const content = (
|
||||
<>
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
@@ -99,9 +183,9 @@ export function CharacterSkillsList({
|
||||
if (onSelectSkill) {
|
||||
return (
|
||||
<button
|
||||
key={skill.id}
|
||||
key={skillRenderId}
|
||||
type="button"
|
||||
onClick={() => onSelectSkill(skill.id)}
|
||||
onClick={() => onSelectSkill(skillRenderId)}
|
||||
className="rounded-xl border border-white/8 bg-black/20 px-3 py-3 text-left text-sm text-zinc-300 transition-colors hover:border-sky-300/25 hover:bg-sky-500/8"
|
||||
>
|
||||
{content}
|
||||
@@ -111,7 +195,7 @@ export function CharacterSkillsList({
|
||||
|
||||
return (
|
||||
<div
|
||||
key={skill.id}
|
||||
key={skillRenderId}
|
||||
className="rounded-lg border border-white/5 bg-black/20 px-3 py-3 text-sm text-zinc-300"
|
||||
>
|
||||
{content}
|
||||
|
||||
Reference in New Issue
Block a user