Add designed sticker packs (SVG art) to the reactions system

- 15 hand-designed inline-SVG stickers (faces, Hokm: حکم/کُت/crown/ace,
  Persian: chai/آفرین/rose, taunts: clown/zzz/ضعیف) in components/online/Sticker.tsx
- Sticker packs: faces (free), hokm (earned @rating 1300), persian & taunt (buy)
- In-game tray now tabbed Emoji | Stickers; stickers broadcast as "sticker:<id>"
  and render as large animated bubbles per seat
- Shop sells sticker packs; profile.ownedStickerPacks; gamification helpers
  ownedStickers/ownedStickerPackIds; mock opponents send stickers too

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
soroush.asadi
2026-06-04 11:15:28 +03:30
parent f9425dea01
commit db4eade619
8 changed files with 359 additions and 20 deletions
+202
View File
@@ -0,0 +1,202 @@
"use client";
/**
* Hand-designed sticker artwork as inline SVG — no external assets.
* Each sticker is keyed by id; packs (see gamification.ts) reference these ids.
*/
type SvgProps = { className?: string };
function Face({
bg1,
bg2,
children,
}: {
bg1: string;
bg2: string;
children: React.ReactNode;
}) {
return (
<>
<defs>
<radialGradient id="fg" cx="40%" cy="35%" r="75%">
<stop offset="0" stopColor={bg1} />
<stop offset="1" stopColor={bg2} />
</radialGradient>
</defs>
<circle cx="50" cy="50" r="44" fill="url(#fg)" stroke="rgba(0,0,0,0.18)" strokeWidth="2" />
{children}
</>
);
}
const STICKERS: Record<string, React.ReactNode> = {
/* ----------------------------- faces ----------------------------- */
happy: (
<Face bg1="#ffe680" bg2="#f5b301">
<circle cx="36" cy="44" r="5" fill="#3a2a00" />
<circle cx="64" cy="44" r="5" fill="#3a2a00" />
<path d="M32 62 Q50 80 68 62" fill="none" stroke="#3a2a00" strokeWidth="5" strokeLinecap="round" />
</Face>
),
sad: (
<Face bg1="#bfe3ff" bg2="#5aa6e0">
<circle cx="36" cy="44" r="5" fill="#13314d" />
<circle cx="64" cy="44" r="5" fill="#13314d" />
<path d="M32 70 Q50 56 68 70" fill="none" stroke="#13314d" strokeWidth="5" strokeLinecap="round" />
<path d="M64 50 q4 10 0 16 q-4 -6 0 -16" fill="#2f8fd6" />
</Face>
),
cool: (
<Face bg1="#ffe680" bg2="#f5b301">
<rect x="24" y="38" width="22" height="13" rx="5" fill="#1b1b1b" />
<rect x="54" y="38" width="22" height="13" rx="5" fill="#1b1b1b" />
<rect x="46" y="42" width="8" height="3" fill="#1b1b1b" />
<path d="M36 64 Q52 74 66 60" fill="none" stroke="#3a2a00" strokeWidth="5" strokeLinecap="round" />
</Face>
),
love: (
<Face bg1="#ffc1e3" bg2="#ff5fa2">
<path d="M30 40 l6 -6 6 6 -6 8 z" fill="#c1124e" />
<path d="M58 40 l6 -6 6 6 -6 8 z" fill="#c1124e" />
<path d="M34 62 Q50 78 66 62" fill="none" stroke="#7a0b30" strokeWidth="5" strokeLinecap="round" />
</Face>
),
angry: (
<Face bg1="#ff9d7a" bg2="#e23b1e">
<path d="M28 38 l16 6" stroke="#3a0a00" strokeWidth="5" strokeLinecap="round" />
<path d="M72 38 l-16 6" stroke="#3a0a00" strokeWidth="5" strokeLinecap="round" />
<circle cx="37" cy="50" r="4.5" fill="#3a0a00" />
<circle cx="63" cy="50" r="4.5" fill="#3a0a00" />
<path d="M34 72 Q50 60 66 72" fill="none" stroke="#3a0a00" strokeWidth="5" strokeLinecap="round" />
</Face>
),
/* ------------------------------ hokm ----------------------------- */
"hokm-badge": (
<>
<defs>
<linearGradient id="gold" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stopColor="#f1da8a" />
<stop offset="0.55" stopColor="#d4af37" />
<stop offset="1" stopColor="#b8860b" />
</linearGradient>
</defs>
<circle cx="50" cy="50" r="42" fill="url(#gold)" stroke="#7a5a00" strokeWidth="3" />
<circle cx="50" cy="50" r="34" fill="none" stroke="#7a5a00" strokeWidth="1.5" opacity="0.5" />
<text x="50" y="46" textAnchor="middle" fontFamily="Vazirmatn, sans-serif" fontWeight="900" fontSize="26" fill="#3a2a04">
حکم
</text>
<text x="50" y="72" textAnchor="middle" fontSize="20" fill="#3a2a04">
</text>
</>
),
"kot-stamp": (
<g transform="rotate(-14 50 50)">
<circle cx="50" cy="50" r="40" fill="none" stroke="#d11a2a" strokeWidth="5" />
<circle cx="50" cy="50" r="33" fill="none" stroke="#d11a2a" strokeWidth="2" />
<text x="50" y="60" textAnchor="middle" fontFamily="Vazirmatn, sans-serif" fontWeight="900" fontSize="34" fill="#d11a2a">
کُت!
</text>
</g>
),
crown: (
<>
<defs>
<linearGradient id="cg" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stopColor="#ffe89a" />
<stop offset="1" stopColor="#d4af37" />
</linearGradient>
</defs>
<path d="M18 70 L24 34 L40 54 L50 26 L60 54 L76 34 L82 70 Z" fill="url(#cg)" stroke="#7a5a00" strokeWidth="2.5" strokeLinejoin="round" />
<rect x="18" y="70" width="64" height="10" rx="3" fill="url(#cg)" stroke="#7a5a00" strokeWidth="2.5" />
<circle cx="50" cy="24" r="5" fill="#ff5d73" />
<circle cx="24" cy="32" r="4" fill="#6aa6ff" />
<circle cx="76" cy="32" r="4" fill="#6aa6ff" />
</>
),
"ace-spade": (
<>
<rect x="24" y="14" width="52" height="72" rx="8" fill="#fffdf7" stroke="#caa84a" strokeWidth="2.5" />
<text x="34" y="32" textAnchor="middle" fontSize="14" fontWeight="800" fill="#1b1b1b">A</text>
<text x="50" y="62" textAnchor="middle" fontSize="34" fill="#1b1b1b"></text>
<text x="66" y="80" textAnchor="middle" fontSize="14" fontWeight="800" fill="#1b1b1b" transform="rotate(180 66 75)">A</text>
</>
),
/* ----------------------------- persian --------------------------- */
chai: (
<>
<path d="M40 24 q4 -8 0 -14 M52 24 q5 -9 0 -16" fill="none" stroke="#cbd5e1" strokeWidth="3" strokeLinecap="round" opacity="0.8" />
<path d="M34 30 H66 L61 78 Q60 84 54 84 H46 Q40 84 39 78 Z" fill="#fff" stroke="#caa84a" strokeWidth="2.5" />
<path d="M37 40 H63 L59 72 Q58 76 53 76 H47 Q42 76 41 72 Z" fill="#a8431a" />
<ellipse cx="50" cy="88" rx="26" ry="6" fill="#e9d9a8" stroke="#caa84a" strokeWidth="2" />
<path d="M66 44 q14 2 12 16 q-2 10 -14 8" fill="none" stroke="#caa84a" strokeWidth="3" />
</>
),
afarin: (
<>
<defs>
<linearGradient id="rib" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stopColor="#f1da8a" />
<stop offset="1" stopColor="#c9a227" />
</linearGradient>
</defs>
<path d="M14 36 H86 L74 50 L86 64 H14 L26 50 Z" fill="url(#rib)" stroke="#7a5a00" strokeWidth="2" />
<text x="50" y="56" textAnchor="middle" fontFamily="Vazirmatn, sans-serif" fontWeight="900" fontSize="20" fill="#3a2a04">
آفرین
</text>
</>
),
rose: (
<>
<path d="M50 60 C40 80 30 84 26 92 M50 60 C60 80 70 84 74 92" stroke="#2f7d32" strokeWidth="4" fill="none" />
<path d="M30 74 q-12 -2 -14 -12 q12 0 16 8 M70 74 q12 -2 14 -12 q-12 0 -16 8" fill="#2f7d32" opacity="0.85" />
<circle cx="50" cy="42" r="22" fill="#d11a2a" />
<path d="M50 24 a18 18 0 0 1 0 36 a12 12 0 0 0 0 -24 a8 8 0 0 1 0 -12" fill="#a30f1f" opacity="0.7" />
<circle cx="50" cy="42" r="7" fill="#7a0b16" />
</>
),
/* ------------------------------ taunt ---------------------------- */
clown: (
<Face bg1="#fff3d6" bg2="#ffd98a">
<circle cx="36" cy="44" r="4.5" fill="#1b1b1b" />
<circle cx="64" cy="44" r="4.5" fill="#1b1b1b" />
<circle cx="50" cy="56" r="7" fill="#e23b1e" />
<path d="M30 66 Q50 84 70 66" fill="none" stroke="#e23b1e" strokeWidth="5" strokeLinecap="round" />
<circle cx="22" cy="40" r="6" fill="#ff5fa2" opacity="0.8" />
<circle cx="78" cy="40" r="6" fill="#ff5fa2" opacity="0.8" />
</Face>
),
sleep: (
<Face bg1="#ffe680" bg2="#f5b301">
<path d="M30 44 q6 -5 12 0 M58 44 q6 -5 12 0" fill="none" stroke="#3a2a00" strokeWidth="4" strokeLinecap="round" />
<circle cx="50" cy="64" r="5" fill="none" stroke="#3a2a00" strokeWidth="4" />
<text x="74" y="34" fontFamily="Vazirmatn, sans-serif" fontSize="16" fontWeight="900" fill="#13314d">z</text>
<text x="82" y="24" fontFamily="Vazirmatn, sans-serif" fontSize="11" fontWeight="900" fill="#13314d">z</text>
</Face>
),
weak: (
<>
<circle cx="50" cy="50" r="42" fill="#1f2b4d" stroke="#d11a2a" strokeWidth="3" />
<path d="M50 30 v26 M50 56 l-9 -9 M50 56 l9 -9" stroke="#ff6b81" strokeWidth="5" fill="none" strokeLinecap="round" />
<text x="50" y="80" textAnchor="middle" fontFamily="Vazirmatn, sans-serif" fontWeight="900" fontSize="16" fill="#ff6b81">
ضعیف!
</text>
</>
),
};
export const STICKER_IDS = Object.keys(STICKERS);
export function Sticker({ id, size = 64, className }: { id: string; size?: number } & SvgProps) {
const art = STICKERS[id];
if (!art) return null;
return (
<svg viewBox="0 0 100 100" width={size} height={size} className={className} role="img">
{art}
</svg>
);
}