diff --git a/server/src/Hokm.Server/Profiles/ProfileModels.cs b/server/src/Hokm.Server/Profiles/ProfileModels.cs index 68a5049..8668354 100644 --- a/server/src/Hokm.Server/Profiles/ProfileModels.cs +++ b/server/src/Hokm.Server/Profiles/ProfileModels.cs @@ -41,7 +41,7 @@ public class ProfileDto public long? PlanUntil { get; set; } public int Level { get; set; } = 1; public int Xp { get; set; } - public int Coins { get; set; } = 1000; + public int Coins { get; set; } = 2000; public int Rating { get; set; } = 1000; public StatsDto Stats { get; set; } = new(); public List OwnedAvatars { get; set; } = new() { "a-fox", "a-lion" }; diff --git a/src/components/screens/ProfileScreen.tsx b/src/components/screens/ProfileScreen.tsx index 249d361..ddc73ba 100644 --- a/src/components/screens/ProfileScreen.tsx +++ b/src/components/screens/ProfileScreen.tsx @@ -1,7 +1,7 @@ "use client"; import { motion } from "framer-motion"; -import { Check, ChevronLeft, Crown, Eye, EyeOff, Gift, Lock, LogOut, MapPin, Pencil, Search, Star, Upload, Users, Volume2 } from "lucide-react"; +import { Check, ChevronLeft, Crown, Eye, EyeOff, Gift, Lock, LogOut, MapPin, Music, Pencil, Search, 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"; @@ -667,11 +667,12 @@ function CityRewardCard() { function SoundSettings() { const { t } = useI18n(); - const { sfx, toggleSfx } = useSoundStore(); + const { sfx, toggleSfx, music, toggleMusic } = useSoundStore(); return (

{t("settings.audio")}

} label={t("settings.sound")} on={sfx} onClick={toggleSfx} /> + } label={t("settings.music")} on={music} onClick={toggleMusic} />
); } diff --git a/src/lib/online/mock-service.ts b/src/lib/online/mock-service.ts index 675c4d9..1914bc1 100644 --- a/src/lib/online/mock-service.ts +++ b/src/lib/online/mock-service.ts @@ -128,7 +128,7 @@ function defaultProfile(session: AuthSession): UserProfile { plan: "free", level: 1, xp: 0, - coins: 1000, + coins: 2000, rating: 1000, stats: { games: 0, diff --git a/src/lib/sound.ts b/src/lib/sound.ts index 1d51369..3ff84be 100644 --- a/src/lib/sound.ts +++ b/src/lib/sound.ts @@ -38,7 +38,7 @@ class SoundManager { sfxEnabled = loadBool(LS_SFX); musicEnabled = loadBool(LS_MUSIC); musicTrack: MusicTrack = - (typeof window !== "undefined" && (localStorage.getItem(LS_TRACK) as MusicTrack)) || "santoor"; + (typeof window !== "undefined" && (localStorage.getItem(LS_TRACK) as MusicTrack)) || "playful"; /** Must be called from a user gesture to unlock audio. */ init() { @@ -210,8 +210,35 @@ class SoundManager { }, }; - /** Background music was removed from the game — these are inert no-ops. */ - startMusic() {} + /** Start the ambient loop for the current track (plays through musicGain so + * the in-game mute / music volume apply). Idempotent + needs an unlocked ctx. */ + startMusic() { + if (!this.musicEnabled || this.musicTimer || !this.ctx || !this.musicGain) return; + const tick = () => { + if (!this.ctx || !this.musicGain) return; + const cfg = this.TRACKS[this.musicTrack]; + const now = this.ctx.currentTime; + const freq = cfg.notes[this.step % cfg.notes.length]; + this.step++; + const voice = (f: number, scale = 1) => { + const osc = this.ctx!.createOscillator(); + const g = this.ctx!.createGain(); + osc.type = cfg.type; + osc.frequency.value = f; + g.gain.setValueAtTime(0.0001, now); + g.gain.linearRampToValueAtTime(cfg.peak * scale, now + cfg.attack); + g.gain.exponentialRampToValueAtTime(0.0001, now + cfg.dur); + osc.connect(g); + g.connect(this.musicGain!); + osc.start(now); + osc.stop(now + cfg.dur + 0.05); + }; + voice(freq); + if (cfg.fifth) voice(freq * 1.5, 0.45); // soft fifth above + }; + tick(); + this.musicTimer = setInterval(tick, this.TRACKS[this.musicTrack].gap); + } stopMusic() { if (this.musicTimer) {