Split card design into front+back, add sound effects & background music

Card design:
- Separate cardFront + cardBack (each own/equip independently)
- Fronts: classic (free), ivory/rosegold (buy), parchment/mint (earned)
- Backs: classic (free), sapphire/emerald (buy), ruby/royal (earned)
- PlayingCard `front` prop; table applies front to all faces, back to opponents
- Profile has front + back pickers; shop has both sections

Audio:
- Web Audio synth engine (no asset files): SFX for card/deal/trump/trick,
  win/lose, message, notify, award, levelup, purchase, kot + ambient music
- Toggles in profile (Audio) + mute button in game HUD; prefs persisted
- Wired across game-store, rewards, daily, shop, chat

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
soroush.asadi
2026-06-04 11:49:19 +03:30
parent db4eade619
commit ae239f4c51
18 changed files with 579 additions and 72 deletions
+13 -6
View File
@@ -14,8 +14,9 @@ import {
} from "lucide-react";
import { useGameStore } from "@/lib/game-store";
import { useSessionStore } from "@/lib/session-store";
import { useUIStore } from "@/lib/ui-store";
import { useUIStore, type Screen } from "@/lib/ui-store";
import { useI18n } from "@/lib/i18n";
import { sound } from "@/lib/sound";
import { SUIT_SYMBOL } from "@/lib/hokm/types";
import { TopBar } from "./online/TopBar";
@@ -28,13 +29,19 @@ export function HomeScreen() {
const isAuthed = useSessionStore((s) => s.isAuthed);
const signOut = useSessionStore((s) => s.signOut);
const nav = (screen: Screen) => {
sound.init();
sound.play("click");
go(screen);
};
const playVsComputer = () => {
const you = profile?.displayName || t("seat.you");
newMatch({ names: [you, "آرش", "کیان", "نیلوفر"], targetScore: 7 });
goGame("home");
};
const playOnline = () => (isAuthed ? go("online") : go("auth"));
const playOnline = () => nav(isAuthed ? "online" : "auth");
return (
<main className="persian-pattern relative min-h-dvh w-full overflow-y-auto">
@@ -78,10 +85,10 @@ export function HomeScreen() {
{/* tiles */}
<div className="grid grid-cols-4 gap-2.5 mt-4">
<Tile icon={<User className="size-5" />} label={t("menu.profile")} onClick={() => go("profile")} />
<Tile icon={<Users className="size-5" />} label={t("menu.friends")} onClick={() => go(isAuthed ? "friends" : "auth")} />
<Tile icon={<Trophy className="size-5" />} label={t("menu.leaderboard")} onClick={() => go("leaderboard")} />
<Tile icon={<ShoppingBag className="size-5" />} label={t("menu.shop")} onClick={() => go("shop")} />
<Tile icon={<User className="size-5" />} label={t("menu.profile")} onClick={() => nav("profile")} />
<Tile icon={<Users className="size-5" />} label={t("menu.friends")} onClick={() => nav(isAuthed ? "friends" : "auth")} />
<Tile icon={<Trophy className="size-5" />} label={t("menu.leaderboard")} onClick={() => nav("leaderboard")} />
<Tile icon={<ShoppingBag className="size-5" />} label={t("menu.shop")} onClick={() => nav("shop")} />
</div>
<div className="flex-1" />