Lobby: leagues are play buttons w/ arrow; remove background music feature
CI/CD / CI - API (dotnet build + engine sim) (push) Successful in 35s
CI/CD / CI - Web (tsc + next build) (push) Successful in 1m14s
CI/CD / Deploy - local stack (db + server + web) (push) Successful in 58s

- OnlineLobbyScreen: each league row is now a tappable play button (queues a
  ranked match at that league's stake) with a forward arrow; the cheapest
  enterable league is highlighted gold. Drops the redundant separate "ranked
  random" CTA and the select-then-play step.
- Remove the background-music feature entirely: deleted the floating MusicToggle,
  the TopBar music button, and the Profile audio music toggle + style picker.
  sound.startMusic() is now an inert no-op so music never plays (sfx unchanged).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
soroush.asadi
2026-06-11 17:23:26 +03:30
parent deb83cf77c
commit efefbcec3d
5 changed files with 66 additions and 198 deletions
+61 -79
View File
@@ -1,11 +1,10 @@
"use client";
import { motion } from "framer-motion";
import { Coins, Lock, Trophy } from "lucide-react";
import { useState } from "react";
import { ChevronLeft, Coins, Lock, Trophy } from "lucide-react";
import { ScreenHeader, ScreenShell } from "@/components/online/ScreenHeader";
import { CoinsPill } from "@/components/online/CoinsPill";
import { MATCH_LEAGUES, leagueById } from "@/lib/online/gamification";
import { MATCH_LEAGUES } from "@/lib/online/gamification";
import { useOnlineStore } from "@/lib/online-store";
import { useSessionStore } from "@/lib/session-store";
import { useUIStore } from "@/lib/ui-store";
@@ -37,20 +36,19 @@ export function OnlineLobbyScreen() {
const profile = useSessionStore((s) => s.profile);
const coins = profile?.coins ?? 0;
const level = profile?.level ?? 1;
const [leagueId, setLeagueId] = useState(MATCH_LEAGUES[0].id);
const league = leagueById(leagueId);
const entry = league.entry;
const lockedLeague = level < league.minLevel;
// Ranked random always costs the entry (you stake it).
const onRandom = async () => {
// The cheapest league you can enter is highlighted as the default pick.
const featuredId = MATCH_LEAGUES.find((l) => level >= l.minLevel)?.id;
// Each league is its own play button — tap to queue a ranked match at its stake.
const playLeague = async (l: (typeof MATCH_LEAGUES)[number]) => {
if (guardActiveMatch()) return;
if (lockedLeague) return;
if (coins < entry) {
if (level < l.minLevel) return;
if (coins < l.entry) {
go("buycoins");
return;
}
await startMatchmaking({ ranked: true, stake: entry });
await startMatchmaking({ ranked: true, stake: l.entry });
go("matchmaking");
};
@@ -58,80 +56,64 @@ export function OnlineLobbyScreen() {
<ScreenShell>
<ScreenHeader title={t("lobby.title")} right={<CoinsPill />} />
{/* league pick (only for ranked) */}
<div className="panel rounded-2xl p-4 mb-4">
<div className="flex items-center gap-1.5 text-sm text-cream/70 mb-2.5">
<Trophy className="size-4 text-gold-400" />
{t("lobby.chooseLeague")}
</div>
<div className="grid gap-2 sm:grid-cols-2">
{MATCH_LEAGUES.map((l) => {
const locked = level < l.minLevel;
const active = l.id === leagueId;
return (
<button
key={l.id}
disabled={locked}
onClick={() => setLeagueId(l.id)}
className={cn(
"w-full rounded-2xl p-3 flex items-center gap-3 border text-start transition",
active
? "border-gold-500/70 bg-gold-500/10"
: "border-navy-700/60 bg-navy-900/50 hover:border-navy-600",
locked && "opacity-50 cursor-not-allowed"
)}
<div className="flex items-center gap-1.5 text-sm text-cream/70 mb-3">
<Trophy className="size-4 text-gold-400" />
{t("lobby.chooseLeague")}
</div>
<div className="grid gap-3">
{MATCH_LEAGUES.map((l) => {
const locked = level < l.minLevel;
const gold = !locked && l.id === featuredId;
const poor = !locked && coins < l.entry;
return (
<motion.button
key={l.id}
whileTap={locked ? undefined : { scale: 0.985 }}
disabled={locked}
onClick={() => playLeague(l)}
className={cn(
"w-full rounded-3xl p-4 flex items-center gap-3 text-start transition",
gold ? "btn-gold" : "press-3d panel hover:border-gold-500/40",
locked && "opacity-50 cursor-not-allowed"
)}
>
<span
className="grid size-12 place-items-center rounded-2xl text-2xl shrink-0"
style={{ background: gold ? "rgba(0,0,0,.12)" : l.color + "22" }}
>
<span
className="size-10 rounded-xl flex items-center justify-center text-xl shrink-0"
style={{ background: l.color + "22" }}
>
{l.icon}
{l.icon}
</span>
<span className="flex-1 min-w-0">
<span className={cn("block text-base font-black", gold ? "text-[#2a1f04]" : "text-cream")}>
{locale === "fa" ? l.nameFa : l.nameEn}
</span>
<span className="flex-1 min-w-0">
<span className="block text-sm font-black text-cream">
{locale === "fa" ? l.nameFa : l.nameEn}
</span>
<span className="block text-[11px] text-cream/55">
{locale === "fa" ? l.descFa : l.descEn}
</span>
<span className={cn("block text-[11px]", gold ? "text-[#2a1f04]/70" : "text-cream/55")}>
{locale === "fa" ? l.descFa : l.descEn}
</span>
{locked ? (
<span className="text-[11px] text-rose-300 flex items-center gap-1 shrink-0">
<Lock className="size-3.5" />
{t("lobby.lvl")} {l.minLevel}
</span>
) : (
<span className="flex items-center gap-1 text-gold-300 font-black text-sm shrink-0">
{poor && (
<span className="block text-[10px] text-rose-300 mt-0.5">{t("lobby.needCoins")}</span>
)}
</span>
{locked ? (
<span className="text-[11px] text-rose-300 flex items-center gap-1 shrink-0">
<Lock className="size-3.5" />
{t("lobby.lvl")} {l.minLevel}
</span>
) : (
<span className="flex items-center gap-2.5 shrink-0">
<span className={cn("flex items-center gap-1 font-black text-sm", gold ? "text-[#2a1f04]" : "text-gold-300")}>
{l.entry.toLocaleString()}
<Coins className="size-3.5" />
</span>
)}
</button>
);
})}
</div>
{!lockedLeague && coins < entry && (
<p className="text-rose-300 text-xs mt-2 text-center">{t("lobby.needCoins")}</p>
)}
<ChevronLeft className={cn("size-5 ltr:rotate-180", gold ? "text-[#2a1f04]" : "text-gold-400")} />
</span>
)}
</motion.button>
);
})}
</div>
<motion.button
whileTap={{ scale: 0.985 }}
onClick={onRandom}
className="btn-gold w-full rounded-3xl p-5 flex items-center gap-4 text-start"
>
<span className="grid size-12 place-items-center rounded-2xl bg-black/15 text-[#2a1f04]">
<Trophy className="size-6" />
</span>
<span className="flex-1">
<span className="block text-lg font-black text-[#2a1f04]">{t("lobby.random")}</span>
<span className="block text-xs text-[#2a1f04]/70">{t("lobby.randomDesc")}</span>
</span>
<span className="flex items-center gap-1 text-[#2a1f04] font-black">
{entry}
<Coins className="size-4" />
</span>
</motion.button>
</ScreenShell>
);
}
+2 -25
View File
@@ -1,7 +1,7 @@
"use client";
import { motion } from "framer-motion";
import { Check, ChevronLeft, Crown, Eye, EyeOff, Lock, LogOut, Music, Pencil, Star, Upload, Users, Volume2 } from "lucide-react";
import { Check, ChevronLeft, Crown, Eye, EyeOff, Lock, LogOut, Pencil, Star, Upload, Users, Volume2 } from "lucide-react";
import { useRef, useState } from "react";
import { ScreenHeader, ScreenShell } from "@/components/online/ScreenHeader";
import { RankBadge } from "@/components/online/RankBadge";
@@ -509,34 +509,11 @@ function SocialSettings() {
function SoundSettings() {
const { t } = useI18n();
const { sfx, music, musicTrack, toggleSfx, toggleMusic, setMusicTrack } = useSoundStore();
const tracks = [
{ id: "santoor" as const, label: t("settings.trackSantoor") },
{ id: "playful" as const, label: t("settings.trackPlayful") },
];
const { sfx, toggleSfx } = useSoundStore();
return (
<div className="panel rounded-2xl p-4">
<h3 className="text-sm font-bold text-cream/80 mb-2">{t("settings.audio")}</h3>
<ToggleRow icon={<Volume2 className="size-4 text-gold-400" />} label={t("settings.sound")} on={sfx} onClick={toggleSfx} />
<ToggleRow icon={<Music className="size-4 text-gold-400" />} label={t("settings.music")} on={music} onClick={toggleMusic} />
{/* music style picker */}
<div className="mt-3">
<div className="text-[11px] text-cream/55 mb-1.5">{t("settings.musicStyle")}</div>
<div className="grid grid-cols-2 gap-2">
{tracks.map((tr) => (
<button
key={tr.id}
onClick={() => setMusicTrack(tr.id)}
className={cn(
"press-3d rounded-xl py-2.5 text-sm font-bold",
musicTrack === tr.id ? "btn-gold" : "bg-navy-900/70 gold-border text-cream/70"
)}
>
{tr.label}
</button>
))}
</div>
</div>
</div>
);
}