Files
HokmPlay/src/components/online/avatarArt.tsx
T
soroush.asadi ccfc9b0536
CI/CD / CI - API (dotnet build + engine sim) (push) Successful in 3m7s
CI/CD / CI - Web (tsc + next build) (push) Successful in 1m9s
CI/CD / Deploy - local stack (db + server + web) (push) Failing after 2m24s
Redesign avatars as a gods/legends pantheon (custom SVG medallions)
Replaced the childish animal emoji avatars with custom inline-SVG "deity
medallions" (gradient disc + gold ring + heraldic emblem) — Athena, Zeus,
Poseidon, Horus, Odin, Thor, Cyrus, Simorgh, Ishtar, Nike, etc. IDs unchanged
so owned avatars keep working; Avatar renders the art (emoji fallback for legacy
ids). Shop now shows the art + the god name (was generic "Avatar").

Files: components/online/avatarArt.tsx (new art + pantheon map), Avatar.tsx
(render art), ShopScreen Preview (avatar → <Avatar/>), mock-service avatar shop
names from AVATAR_ART.

Verified: tsc + next build clean; web rebuilt on :1500.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 18:16:17 +03:30

219 lines
11 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
/**
* Custom inline-SVG avatar art — a "pantheon" of gods & legends rendered as
* heraldic medallions (gradient disc + gold ring + a bold mythic emblem).
* Keyed by the same avatar ids used in types.ts AVATARS, so owned avatars keep
* working; the Avatar component renders this when art exists, else falls back to
* the emoji. No external assets.
*/
type Emblem =
| "crown" | "trident" | "bolt" | "sun" | "eye" | "ankh" | "lion" | "eagle"
| "owl" | "helmet" | "phoenix" | "laurel" | "star8" | "flame" | "moon"
| "dragon" | "wings" | "skull" | "gem" | "lyre";
interface AvatarArt {
nameFa: string;
nameEn: string;
c1: string; // disc gradient (top)
c2: string; // disc gradient (bottom)
ring: string; // ring / accent
em: Emblem;
emColor: string;
}
/* ----------------------------- emblems ------------------------------- */
// Each emblem is drawn for a 100×100 viewBox, roughly centred in 28..72,
// using currentColor so the wrapper sets the tint.
const EMBLEMS: Record<Emblem, React.ReactNode> = {
crown: (
<path d="M26 64 L26 40 L38 50 L50 32 L62 50 L74 40 L74 64 Z M24 66 h52 v6 h-52 z" />
),
trident: (
<>
<rect x="47" y="34" width="6" height="40" rx="2" />
<path d="M30 40 q0 -14 10 -16 q-4 6 -2 14 M70 40 q0 -14 -10 -16 q4 6 2 14" />
<path d="M30 30 v12 M50 26 v12 M70 30 v12" stroke="currentColor" strokeWidth="5" fill="none" strokeLinecap="round" />
<path d="M30 42 h40" stroke="currentColor" strokeWidth="5" />
</>
),
bolt: <path d="M54 24 L34 56 H48 L44 78 L68 44 H52 Z" />,
sun: (
<>
<circle cx="50" cy="50" r="16" />
<g stroke="currentColor" strokeWidth="4" strokeLinecap="round">
<path d="M50 22 v8 M50 70 v8 M22 50 h8 M70 50 h8 M31 31 l6 6 M63 63 l6 6 M69 31 l-6 6 M37 63 l-6 6" />
</g>
</>
),
eye: (
<>
<path d="M26 50 Q50 32 74 50 Q50 68 26 50 Z" fill="none" stroke="currentColor" strokeWidth="5" />
<circle cx="50" cy="50" r="8" />
<path d="M50 60 q-2 10 -12 12 M58 58 l10 12" stroke="currentColor" strokeWidth="4" fill="none" strokeLinecap="round" />
</>
),
ankh: (
<>
<ellipse cx="50" cy="38" rx="11" ry="13" fill="none" stroke="currentColor" strokeWidth="6" />
<rect x="46" y="50" width="8" height="28" rx="2" />
<rect x="34" y="56" width="32" height="7" rx="2" />
</>
),
lion: (
<>
<g stroke="currentColor" strokeWidth="3">
<path d="M50 26 l8 8 10 -4 -3 11 9 7 -9 5 3 11 -10 -3 -8 8 -8 -8 -10 3 3 -11 -9 -5 9 -7 -3 -11 10 4 z" />
</g>
<circle cx="50" cy="52" r="15" fill="currentColor" />
<circle cx="44" cy="50" r="2.4" fill="#1a1206" />
<circle cx="56" cy="50" r="2.4" fill="#1a1206" />
<path d="M46 58 q4 4 8 0" stroke="#1a1206" strokeWidth="2.4" fill="none" strokeLinecap="round" />
</>
),
eagle: (
<>
<path d="M50 32 q-22 2 -30 16 q16 -6 24 0 q-4 8 -2 18 l8 6 8 -6 q2 -10 -2 -18 q8 -6 24 0 q-8 -14 -30 -16 z" />
<path d="M50 56 l-4 14 h8 z" />
</>
),
owl: (
<>
<circle cx="40" cy="46" r="12" fill="none" stroke="currentColor" strokeWidth="5" />
<circle cx="60" cy="46" r="12" fill="none" stroke="currentColor" strokeWidth="5" />
<circle cx="40" cy="46" r="4" /><circle cx="60" cy="46" r="4" />
<path d="M50 52 l-5 8 h10 z" />
<path d="M30 34 l6 8 M70 34 l-6 8" stroke="currentColor" strokeWidth="4" strokeLinecap="round" />
</>
),
helmet: (
<>
<path d="M30 46 a20 20 0 0 1 40 0 v18 h-12 v-10 h-4 v10 h-8 v-10 h-4 v10 h-12 z" />
<rect x="48" y="22" width="4" height="14" />
<path d="M50 22 q10 -2 14 6 q-8 -2 -14 0 z" />
</>
),
phoenix: (
<>
<path d="M50 28 q-6 10 -2 18 q-14 -8 -24 -2 q12 2 16 10 q-10 0 -16 8 q14 -4 22 2 q-2 8 4 14 q6 -6 4 -14 q8 -6 22 -2 q-6 -8 -16 -8 q4 -8 16 -10 q-10 -6 -24 2 q4 -8 -2 -18 z" />
</>
),
laurel: (
<>
<path d="M34 70 Q22 50 34 30 M66 70 Q78 50 66 30" fill="none" stroke="currentColor" strokeWidth="5" strokeLinecap="round" />
<g fill="currentColor">
<ellipse cx="29" cy="40" rx="4" ry="2.6" transform="rotate(-40 29 40)" />
<ellipse cx="30" cy="52" rx="4" ry="2.6" transform="rotate(-20 30 52)" />
<ellipse cx="71" cy="40" rx="4" ry="2.6" transform="rotate(40 71 40)" />
<ellipse cx="70" cy="52" rx="4" ry="2.6" transform="rotate(20 70 52)" />
</g>
<path d="M44 38 l5 5 11 -13" fill="none" stroke="currentColor" strokeWidth="5" strokeLinecap="round" strokeLinejoin="round" />
</>
),
star8: (
<path d="M50 24 l6 16 16 -6 -10 14 14 8 -16 4 4 16 -14 -10 -10 14 -2 -16 -16 4 12 -12 -12 -12 16 4 2 -16 10 14 10 -14 -4 16 16 -4 -14 12 z" />
),
flame: <path d="M50 24 q10 14 6 24 q-2 6 -8 8 q4 -8 -2 -14 q-2 8 -8 10 q-8 4 -8 14 q0 14 22 14 q22 0 22 -16 q0 -12 -10 -22 q2 8 -4 10 q4 -12 -2 -22 z" />,
moon: (
<>
<path d="M58 26 a24 24 0 1 0 0 48 a18 18 0 0 1 0 -48 z" />
<path d="M70 34 l2 5 5 2 -5 2 -2 5 -2 -5 -5 -2 5 -2 z" />
</>
),
dragon: (
<>
<path d="M30 60 q-6 -18 10 -24 q-2 -8 6 -10 q-2 6 2 8 q10 -2 16 6 q8 0 10 8 q-6 -2 -10 2 q4 6 0 12 q-6 -6 -12 -4 q-8 6 -18 2 q-2 6 -8 6 q2 -6 -2 -8 z" />
<circle cx="44" cy="44" r="2.4" fill="#1a1206" />
</>
),
wings: (
<>
<circle cx="50" cy="48" r="9" />
<path d="M41 48 q-18 -10 -26 -2 q12 0 14 6 q-12 -2 -16 6 q14 -4 20 0 M59 48 q18 -10 26 -2 q-12 0 -14 6 q12 -2 16 6 q-14 -4 -20 0" fill="none" stroke="currentColor" strokeWidth="4" strokeLinecap="round" />
</>
),
skull: (
<>
<path d="M32 46 a18 18 0 0 1 36 0 v8 q0 6 -6 8 v6 h-24 v-6 q-6 -2 -6 -8 z" />
<circle cx="42" cy="48" r="4.5" fill="#160a0a" />
<circle cx="58" cy="48" r="4.5" fill="#160a0a" />
<path d="M50 54 l-3 7 h6 z" fill="#160a0a" />
</>
),
gem: (
<>
<path d="M50 26 L70 42 L50 76 L30 42 Z" />
<path d="M30 42 H70 M50 26 L42 42 L50 76 M50 26 L58 42 L50 76" fill="none" stroke="#0b1020" strokeWidth="2" opacity="0.5" />
</>
),
lyre: (
<>
<path d="M36 30 q-8 6 -6 26 M64 30 q8 6 6 26" fill="none" stroke="currentColor" strokeWidth="5" strokeLinecap="round" />
<path d="M36 30 q14 -6 28 0" fill="none" stroke="currentColor" strokeWidth="5" strokeLinecap="round" />
<g stroke="currentColor" strokeWidth="2.4"><path d="M44 36 v30 M50 35 v32 M56 36 v30" /></g>
<path d="M32 64 h36 l-6 8 h-24 z" />
</>
),
};
/* --------------- pantheon: id → god/legend + palette ----------------- */
const PANTHEON: [string, AvatarArt][] = [
["a-fox", { nameFa: "آتنا", nameEn: "Athena", c1: "#1f7a6e", c2: "#0c2e2a", ring: "#f4d27a", em: "owl", emColor: "#f6e7b6" }],
["a-lion", { nameFa: "شیردل", nameEn: "Leonidas", c1: "#b5701c", c2: "#3a2207", ring: "#ffd98a", em: "lion", emColor: "#ffe9b0" }],
["a-owl", { nameFa: "هرمس", nameEn: "Hermes", c1: "#2f6fb0", c2: "#0e2240", ring: "#cfe2ff", em: "wings", emColor: "#eaf3ff" }],
["a-cat", { nameFa: "باستت", nameEn: "Bastet", c1: "#1d8f86", c2: "#0a2b29", ring: "#ffd98a", em: "ankh", emColor: "#ffe9b0" }],
["a-tiger", { nameFa: "آرش", nameEn: "Arash", c1: "#a8431f", c2: "#34110a", ring: "#ffb27a", em: "eagle", emColor: "#ffe0c2" }],
["a-panda", { nameFa: "تور", nameEn: "Thor", c1: "#566b86", c2: "#161f2e", ring: "#bcd0ff", em: "bolt", emColor: "#eef4ff" }],
["a-bear", { nameFa: "اودین", nameEn: "Odin", c1: "#475569", c2: "#13151b", ring: "#cbd5e1", em: "helmet", emColor: "#eef2f7" }],
["a-eagle", { nameFa: "هوروس", nameEn: "Horus", c1: "#2f7d7a", c2: "#0b2a2c", ring: "#ffd98a", em: "eagle", emColor: "#ffe9b0" }],
["a-wolf", { nameFa: "فِنریر", nameEn: "Fenrir", c1: "#3b4a63", c2: "#11151f", ring: "#9fb4d6", em: "lion", emColor: "#cfd9ec" }],
["a-shark", { nameFa: "پوزایدون", nameEn: "Poseidon", c1: "#1565a8", c2: "#08203a", ring: "#7fd4ff", em: "trident", emColor: "#e6f6ff" }],
["a-dragon", { nameFa: "اژدها", nameEn: "Drakon", c1: "#157a3e", c2: "#082416", ring: "#9af0b0", em: "dragon", emColor: "#dcffe6" }],
["a-unicorn", { nameFa: "سیمرغ", nameEn: "Simorgh", c1: "#b23a6a", c2: "#34101f", ring: "#ffc6dd", em: "phoenix", emColor: "#ffe3ee" }],
["a-peacock", { nameFa: "هرا", nameEn: "Hera", c1: "#136f86", c2: "#07232b", ring: "#7fe0d6", em: "eye", emColor: "#e6fffb" }],
["a-swan", { nameFa: "آفرودیت", nameEn: "Aphrodite", c1: "#9c5bd6", c2: "#2a1340", ring: "#e9c8ff", em: "wings", emColor: "#f6ecff" }],
["a-tophat", { nameFa: "مرلین", nameEn: "Merlin", c1: "#3b3170", c2: "#140e2c", ring: "#c9b6ff", em: "moon", emColor: "#ece2ff" }],
["a-diamond", { nameFa: "ایشتار", nameEn: "Ishtar", c1: "#1f6fae", c2: "#0a1f3a", ring: "#bfe6ff", em: "star8", emColor: "#eaf6ff" }],
["a-moneybag", { nameFa: "پلوتوس", nameEn: "Plutus", c1: "#b58a16", c2: "#3a2a06", ring: "#ffe28a", em: "gem", emColor: "#fff2c2" }],
["a-trophy", { nameFa: "نیکه", nameEn: "Nike", c1: "#b5901c", c2: "#3a2c07", ring: "#ffe9a0", em: "laurel", emColor: "#fff4c8" }],
["a-robot", { nameFa: "تالوس", nameEn: "Talos", c1: "#4a7c8c", c2: "#11242a", ring: "#a8e0ee", em: "gem", emColor: "#e6fbff" }],
["a-wizard", { nameFa: "زئوس", nameEn: "Zeus", c1: "#9a7b2a", c2: "#2e2208", ring: "#ffe79a", em: "bolt", emColor: "#fff6cf" }],
["a-ninja", { nameFa: "شینوبی", nameEn: "Shinobi", c1: "#2b3340", c2: "#0c0f14", ring: "#8f9bb0", em: "skull", emColor: "#dfe5ee" }],
["a-king", { nameFa: "کوروش", nameEn: "Cyrus", c1: "#9a6b1f", c2: "#2f2008", ring: "#ffd98a", em: "wings", emColor: "#ffefc2" }],
["a-genie", { nameFa: "دیو", nameEn: "Djinn", c1: "#7a2ea8", c2: "#260a3a", ring: "#d6a8ff", em: "flame", emColor: "#f1ddff" }],
["a-crown", { nameFa: "شاهنشاه", nameEn: "Shahanshah", c1: "#b58a16", c2: "#3a2a06", ring: "#ffe28a", em: "crown", emColor: "#fff4c8" }],
["a-gem", { nameFa: "اهورا", nameEn: "Ahura", c1: "#1f8fae", c2: "#082a3a", ring: "#9fe8ff", em: "sun", emColor: "#eafbff" }],
];
export const AVATAR_ART: Record<string, AvatarArt> = Object.fromEntries(PANTHEON);
export function hasAvatarArt(id: string): boolean {
return !!AVATAR_ART[id];
}
export function avatarArtName(id: string, locale: "fa" | "en"): string | null {
const a = AVATAR_ART[id];
return a ? (locale === "fa" ? a.nameFa : a.nameEn) : null;
}
/** Render a god/legend medallion for the given avatar id (returns null if none). */
export function AvatarArtSvg({ id, size }: { id: string; size: number }) {
const a = AVATAR_ART[id];
if (!a) return null;
const gid = `avg-${id}`;
return (
<svg width={size} height={size} viewBox="0 0 100 100" aria-hidden>
<defs>
<radialGradient id={gid} cx="42%" cy="34%" r="80%">
<stop offset="0" stopColor={a.c1} />
<stop offset="1" stopColor={a.c2} />
</radialGradient>
</defs>
<circle cx="50" cy="50" r="48" fill={`url(#${gid})`} />
<circle cx="50" cy="50" r="46.5" fill="none" stroke={a.ring} strokeWidth="2.5" opacity="0.85" />
<g style={{ color: a.emColor }} fill="currentColor" stroke="none">
{EMBLEMS[a.em]}
</g>
</svg>
);
}