Shop redesign: tactile cards + product detail sheet
CI/CD / CI - API (dotnet build + engine sim) (push) Successful in 26s
CI/CD / CI - Web (tsc + next build) (push) Successful in 1m1s
CI/CD / Deploy - local stack (db + server + web) (push) Failing after 0s

Answers "what's in this pack / how much XP": shop items now carry contents
(sticker ids / emojis), xp amount, and a fa/en description. Cards are tactile
(press-3d), show the artwork + name + a quick hint (item count or +XP) + price,
with an owned check badge. Tapping a card opens a detail sheet that shows the
full contents (every sticker/emoji rendered), the XP granted, a description,
and a Buy button (gold when affordable, "need coins" otherwise).

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

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
soroush.asadi
2026-06-05 09:04:46 +03:30
parent 96c8abbeb3
commit be8c758425
4 changed files with 243 additions and 136 deletions
+2
View File
@@ -260,6 +260,7 @@ const fa: Dict = {
"shop.stickers": "بسته استیکرها",
"shop.xp": "امتیاز تجربه (XP)",
"shop.xpHint": "افزایش سریع سطح — XP گران است",
"shop.includes": "شامل",
"reward.newTitle": "عنوان جدید",
"reactions.title": "شکلک",
@@ -525,6 +526,7 @@ const en: Dict = {
"shop.stickers": "Sticker packs",
"shop.xp": "XP packs",
"shop.xpHint": "Level up faster — XP is expensive",
"shop.includes": "Includes",
"reward.newTitle": "New title",
"reactions.title": "Emoji",
+15
View File
@@ -827,6 +827,8 @@ export class MockOnlineService implements OnlineService {
nameEn: "Avatar",
price: a.price!,
preview: a.emoji,
descFa: "آواتار نمایه شما در بازی و جدول",
descEn: "Your profile avatar in games & leaderboard",
}));
const backItems: ShopItem[] = CARD_BACKS.filter((c) => c.price > 0).map((c) => ({
id: c.id,
@@ -835,6 +837,8 @@ export class MockOnlineService implements OnlineService {
nameEn: c.nameEn,
price: c.price,
preview: c.accent,
descFa: "طرح پشت کارت‌ها روی میز",
descEn: "The pattern on the back of your cards",
}));
const frontItems: ShopItem[] = CARD_FRONTS.filter((c) => c.price > 0).map((c) => ({
id: c.id,
@@ -843,6 +847,8 @@ export class MockOnlineService implements OnlineService {
nameEn: c.nameEn,
price: c.price,
preview: c.bg2,
descFa: "ظاهر روی کارت‌های شما",
descEn: "The face style of your cards",
}));
const reactionItems: ShopItem[] = REACTION_PACKS.filter((r) => r.price > 0).map((r) => ({
id: r.id,
@@ -851,6 +857,9 @@ export class MockOnlineService implements OnlineService {
nameEn: r.nameEn,
price: r.price,
preview: r.reactions[0],
contents: r.reactions,
descFa: `${faNum(r.reactions.length)} ایموجی برای استفاده در بازی`,
descEn: `${r.reactions.length} in-game emotes`,
}));
const stickerItems: ShopItem[] = STICKER_PACKS.filter((p) => p.price > 0).map((p) => ({
id: p.id,
@@ -859,6 +868,9 @@ export class MockOnlineService implements OnlineService {
nameEn: p.nameEn,
price: p.price,
preview: p.stickers[0], // sticker id; ShopScreen renders via <Sticker>
contents: p.stickers,
descFa: `${faNum(p.stickers.length)} استیکر برای استفاده در بازی`,
descEn: `${p.stickers.length} in-game stickers`,
}));
const xpItems: ShopItem[] = XP_PACKS.map((x) => ({
id: x.id,
@@ -867,6 +879,9 @@ export class MockOnlineService implements OnlineService {
nameEn: `${x.xp} XP`,
price: x.price,
preview: "⚡",
xp: x.xp,
descFa: `${faNum(x.xp)} امتیاز تجربه که بلافاصله به حساب اضافه می‌شود`,
descEn: `${x.xp} XP added to your account instantly`,
}));
return [...avatarItems, ...frontItems, ...backItems, ...reactionItems, ...stickerItems, ...xpItems];
}
+7
View File
@@ -389,6 +389,13 @@ export interface ShopItem {
nameEn: string;
price: number;
preview: string; // emoji/avatar id/color
/** what the pack contains: sticker ids (stickerpack) or emoji chars (reactionpack) */
contents?: string[];
/** XP granted by an xp pack */
xp?: number;
/** short fa/en description of what the item is/does */
descFa?: string;
descEn?: string;
}
/* ------------------------------ Coin packs --------------------------- */