Commit Graph

129 Commits

Author SHA1 Message Date
soroush.asadi 7a18bc39e6 Achievements overhaul: 37 achievements, page with tabs, leagues, gating
CI/CD / CI - API (dotnet build + engine sim) (push) Failing after 1m40s
CI/CD / CI - Web (tsc + next build) (push) Failing after 1m21s
CI/CD / Deploy - local stack (db + server + web) (push) Has been skipped
Achievements (client + server mirror, metric-driven so the list is one source):
- 37 achievements across 6 categories (Victories, Kot, Streaks, Levels, Ranks,
  Veterancy) incl. 7–0 sweeps, kot milestones (1/5/10/25/50/100), win streaks
  (3/5/10/15), level milestones every 5 (5..50), rank floors, games/tricks.
- New AchievementsScreen with category tabs, progress bars, coin + sticker-unlock
  badges, and unlocked/locked states; summary header (unlocked count + coins).
- Some achievements unlock sticker packs: Seven–Zip→Hokm, 25 Kots→Taunts,
  100 Wins→Persian (ownedStickerPackIds now also honors profile.unlocked).
- Prestige titles added: Expert, Professional, Captain, Leader (+ existing).
- Tracks new stat shutoutWins; MatchSummary.shutout (7–0). Profile shows a
  6-item preview + "view all" link.

Leagues: 3 ranked entry tiers — Starter (100, lvl1), Pro (500, lvl10),
Expert (1000, lvl20). Higher league stakes more, so wins/losses swing bigger;
kot bonus now scales to the stake (40%). OnlineLobby shows league cards with
level gating.

Profile photo upload gated to level 25 (client button + server Update guard).

Win animation: PostMatchRewardsModal now shows an animated coins-won count-up
hero on a win.

Verified: dotnet build + tsc + next build clean; sim unlocks 26 achievements
over 500 matches; live server grants first_win/first_kot/shutout_1 and pays
2050 coins on an expert-league shutout+kot win. Images rebuilt on :1500/:1505.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 21:47:38 +03:30
soroush.asadi d66208e39e Resume: exiting a match keeps it alive instead of destroying it
CI/CD / CI - API (dotnet build + engine sim) (push) Failing after 1m40s
CI/CD / CI - Web (tsc + next build) (push) Failing after 1m19s
CI/CD / Deploy - local stack (db + server + web) (push) Has been skipped
Leaving the table (back button, browser/hardware back) no longer resets the
game — it minimizes it and stays resumable:
- game-store: add `paused` + minimize()/resume(). Single-player (AI) matches
  pause their local timers so nothing happens while away; live (server-run)
  matches keep streaming via the still-active subscription (the .NET GameRoom
  already runs the match to completion and re-broadcasts state on reconnect).
- GameScreen: an unmount effect minimizes any in-progress match no matter how
  you leave; only a finished match (reward dismissed) tears down.
- ResumeGameBar: floating "return to game" pill shown from any screen while a
  match is alive, or while a finished match still has an unseen reward.
- page.tsx: after a full reload, re-enter live mode (minimized) when the server
  re-broadcasts state, and notify when a match you left finishes while away.

Verified: tsc + next build clean; web image rebuilt and serving on :1500.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 20:58:05 +03:30
soroush.asadi a1c2cc0889 Fix: screens can't scroll (min-h-dvh under overflow-hidden body)
CI/CD / CI - API (dotnet build + engine sim) (push) Failing after 1m41s
CI/CD / CI - Web (tsc + next build) (push) Failing after 1m20s
CI/CD / Deploy - local stack (db + server + web) (push) Has been skipped
body is overflow:hidden (to lock the game table), so a min-h-dvh shell just
expands past the viewport and is clipped — its overflow-y-auto never engages.
Make ScreenShell + HomeScreen fixed-height h-dvh scroll containers (with
overscroll-contain + bottom padding). Fixes Profile/Friends/Shop/Leaderboard/
Lobby/BuyCoins/Notifications and the home menu on short viewports.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 20:30:47 +03:30
soroush.asadi fde8b93206 Fix Docker build/runtime: 0.0.0.0 bind, npm ci, HTTP-mirror fallback
CI/CD / CI - API (dotnet build + engine sim) (push) Failing after 1m40s
CI/CD / CI - Web (tsc + next build) (push) Failing after 1m31s
CI/CD / Deploy - local stack (db + server + web) (push) Has been skipped
Issues found bringing the stack up locally and fixed:
- Server was loopback-only inside the container (appsettings "Urls=localhost"
  wins over ASPNETCORE_URLS) → published port returned "empty reply". Force the
  bind with command-line args: ENTRYPOINT dotnet Hokm.Server.dll --urls 0.0.0.0:5005.
- Web image: npm install crashed on alpine ("Exit handler never called"); root
  cause was UNABLE_TO_GET_ISSUER_CERT_LOCALLY — the Nexus mirror serves a partial
  chain that Node's CA bundle can't complete. Use npm ci + strict-ssl=false.
- .NET restore hit the same partial chain (NU1301 PartialChain). Both registries
  are now build ARGs (NUGET_INDEX / NPM_REGISTRY) defaulting to the HTTPS mirror
  (CI runner trusts it); local .env overrides to the plain-HTTP Nexus
  (http://171.22.25.73:8081) which has no TLS. NuGet feed is generated inline with
  allowInsecureConnections so .NET 10 accepts the HTTP source.

Verified on local Docker (Postgres-backed): db+server+web all healthy; API + web
reachable from host on 1505/1500; auth → profile (1000 coins) → friend add/accept
(bidirectional) → chat (unread) all 200; rows persisted in Postgres
(Profiles=2, Friends=2, Messages=1).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 19:59:27 +03:30
soroush.asadi 89d42184a1 Add Soroush CI/CD (Gitea + Nexus) + self-host fonts for offline build
CI/CD / CI - API (dotnet build + engine sim) (push) Failing after 1m40s
CI/CD / CI - Web (tsc + next build) (push) Failing after 1m20s
CI/CD / Deploy - local stack (db + server + web) (push) Has been skipped
Pipeline (.gitea/workflows/ci-cd.yml), all images/packages via Nexus mirror:
- CI api-build: dotnet restore/build server/Hokm.slnx + run Hokm.Sim (rules).
- CI web-check: npm install + tsc --noEmit + next build (static export).
- deploy (self-hosted): pre-deploy pg_dump backup, rollback image tag, build,
  bring up db -> server -> web with stop+rm+up --no-deps (no force-recreate,
  no bare compose down), health-wait each, prune.

Local stack (docker-compose.yml), ports in 1500-1600 so it coexists with manual
dev on 3000/5005:  web :1500 (nginx static) -> server :1505 (.NET) -> db :1510
(postgres, named volume + backups). Dockerfiles: server (.NET, NuGet via
nuget.docker.config, binds 0.0.0.0, busybox wget healthcheck) + web (Next static
export -> nginx, NEXT_PUBLIC_* baked as build args). nginx.conf SPA fallback.

Config: server CORS is now config-driven (Cors__Origins) so the deployed web
origin is allowed without code edits. deploy/ENV_FILE.example documents the
Gitea ENV_FILE secret; DEPLOY.md covers setup/run/LAN-IP/rollback/migrations.

Fonts: switch Vazirmatn + Plus Jakarta Sans from next/font/google (build-time
Google fetch -> fails on the Iran CI runner) to self-hosted @fontsource-variable
packages. Build is offline and ~3x faster; 7 woff2 emitted into out/.

Verified locally: dotnet build slnx + Hokm.Sim (300 matches, exit 0); tsc clean;
next build clean with self-hosted fonts.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 19:09:31 +03:30
soroush.asadi e778e8b5bd Server-backed friends, chat, IAB scaffold + EF migrations/Postgres
- Social: EF-backed friends graph + chat (SocialService/SocialModels);
  REST endpoints (friends add/accept/decline/remove/list/requests,
  chat conversations/messages/send) with real-time hub events
  (friendRequest/social/chat). GameManager tracks online users for presence.
- Client SignalrService: friends + chat now hit the server and react to
  hub events (refetch + emit); no longer delegated to the mock.
- IAB: /api/coins/iab/verify endpoint + IabVerifyReq for Cafe Bazaar/Myket
  (token verification is a documented TODO pending store accounts/SKUs).
- Persistence: EF Core Design package + DesignTimeDbContextFactory (Postgres),
  Program auto-migrate/EnsureCreated, appsettings.Production.json.example
  with Supabase connection + live ZarinPal template.

Verified end-to-end (two users, SQLite dev): request -> accept ->
bidirectional friends, chat send with per-user fromMe, unread count +
read-on-fetch. Server + client builds clean (dotnet build, tsc, next build).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 18:26:22 +03:30
soroush.asadi cfed2950b2 Add ZarinPal sandbox payments for buying coins (config-driven merchant)
- ZarinpalService (request/verify) + /api/coins/pay/request (JWT) and
  /api/coins/pay/callback (verify → credit via ProfileService.BuyCoins → redirect
  back with ?pay=success); merchant id from config (sandbox default)
- Client buyCoins (live) returns the StartPay redirect URL; BuyCoinsScreen
  redirects; page.tsx handles the ?pay return (notify + refresh)
- Verified: sandbox request returns a real StartPay URL
- Documented Cafe Bazaar (Poolakey) / Myket IAB as the required store payment path

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 17:59:30 +03:30
soroush.asadi 4f2e4e14ea Server-authoritative economy: wire client to server; entry + rewards on hub
Server:
- daily (/api/daily, /api/daily/claim) + shop (/api/shop/buy) + ChargeEntry
- GameRoom (via IServiceScopeFactory) deducts ranked entry at match start and
  applies match rewards at match-over, broadcasting profile + reward over the hub
- tested: daily, shop (owned-guard), ranked entry deduction pushed over hub

Client:
- SignalrService routes profile/coins/plan/daily/shop/match to the server (Bearer);
  onProfile/onReward hub events; guest/offline fall back to local
- session-store syncs profile from hub; game-store serverReward; GameScreen shows
  live ranked reward from hub (no double submit), submits client-run games
- single source of truth in live mode (no economy divergence)

Postgres-ready via config (Provider=postgres); EnsureCreated for now.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 17:32:47 +03:30
soroush.asadi d0b8976713 Server persistence: EF Core profiles + coin ledger + authoritative rewards
- EF Core (SQLite dev / Postgres prod via config); ProfileRow JSON blob +
  LedgerRow audit; EnsureCreated at startup
- C# Gamification port (ranks/elo/coins/xp/achievements/titles) → server
  computes match rewards; ProfileService (get/update/plan/buyCoins/applyMatch)
- JWT endpoints: profile GET/PUT, plan, coins packs/buy, match/result;
  auth upserts the profile
- Tested end-to-end (buy + ranked win+kot persisted & server-computed)
- Client still mock-backed for now (wiring is the next step)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 16:52:25 +03:30
soroush.asadi cdb8d522dd Economy: free vs paid games + buy-coins page; friends remove confirmation
- Coins only matter for ranked: free games (vs computer / private friend rooms)
  cost nothing; random ranked requires an entry (stake), gated by balance →
  routes to buy-coins when short
- Buy Coins page (CoinPack/getCoinPacks/buyCoins; mock credits now, real
  Zarinpal/IDPay TODO); TopBar coins → buy; lobby create-room is Free
- Friends: removed instant red ✕ delete; UserMinus → inline confirm before remove

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 16:28:59 +03:30
soroush.asadi fe136f7ee4 Auth: email/Google "coming soon"; mobile polish pass on the table
- AuthScreen: phone+OTP is the only active method; email & Google shown as
  disabled "coming soon" buttons (until SMTP/Gmail are configured)
- GameTable responsive: compact scoreboard, smaller seat avatars, and
  turn-indicator/timer positions tuned for small screens

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 16:11:30 +03:30
soroush.asadi 0b376aec16 Fix mobile: compress card fan to fit any screen; kill horizontal overflow
- PlayerHand measures viewport and compresses the fan (responsive card size +
  dynamic overlap) so all 13 cards are visible and tappable on phones — no
  off-screen cards, no horizontal scroll; added tap feedback
- globals: html/body overflow-x hidden + overscroll-behavior none

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 16:00:00 +03:30
soroush.asadi 2d2352dfe8 Add in-app + real-time notifications (SignalR/mock, Iran-friendly)
- AppNotification + OnlineService.onNotification (hub event + mock periodic) —
  no FCM/APNs (blocked in Iran); uses the existing realtime channel
- notification-store + pushNotification(); 🔔 bell with unread badge in TopBar,
  notifications screen, global toaster (plays notify sfx)
- Wired events: daily reward, post-match achievements, friend requests
- Closed-app push (Pushe/Najva/Chabok) noted as a later step (needs provider keys)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 15:52:06 +03:30
soroush.asadi e02d976dda Add Abrha as a Maven Central fallback mirror (Myket stays primary)
- Myket (root) proxies Central + Google/AGP; Abrha
  (mirror.abrha.net/repository/maven/) is Central-only → used as fallback
- build.gradle + gradle-mirror.init template list both; APK rebuilds clean

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 15:26:57 +03:30
soroush.asadi 84ccbea56a Build Android APK via Myket maven mirror (verified)
- Myket mirror serves maven2 layout at root https://maven.myket.ir (proxies
  Maven Central + Google/AGP); android/build.gradle uses it
- gradle-mirror.init.gradle template injects the mirror into all modules and
  pins build-tools 36 + Java 17 (this env lacks build-tools 35 / JDK 21)
- ANDROID.md updated with the exact working build command
- Produces app-debug.apk (~4.5 MB)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 15:22:34 +03:30
soroush.asadi 38691154c8 Add Capacitor Android packaging (APK) for Cafe Bazaar / Myket
- Next static export (output: export) wrapped by Capacitor; appId
  com.bargevasat.app, appName «برگ وسط»
- android/ native project + @capacitor/app; hardware back handled by
  CapacitorBack (back a screen, exit at home)
- npm scripts (cap:sync, android:open, android:apk), ANDROID.md
- Gradle Maven-mirror init-script template (dl.google.com/Maven Central are
  blocked in Iran — same reason NuGet is mirrored)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 14:31:14 +03:30
soroush.asadi d04605d118 Brand the app as «برگ وسط» / Barg-e Vasat
- Name + tagline («بازی حکم آنلاین») across i18n (app.title/subtitle),
  layout metadata, PWA manifest, app icon, package name, server health
- Gameplay term «حکم» unchanged; repo/folder stay hokm/HokmPlay

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 14:01:14 +03:30
soroush.asadi 292dedd843 Show live online-players count on the home screen
- OnlineService.getOnlineCount(); mock random-walks a believable number,
  SignalrService reads GET /api/stats/online (server tracks hub connections)
- Home screen badge with pulsing dot, polls every 8s, localized digits

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 13:20:51 +03:30
soroush.asadi ceccf70de7 Wire client SignalrService to the live .NET backend
- @microsoft/signalr client implementing OnlineService: REST auth, hub
  matchmaking, server-driven game state (onState), play/trump, reactions;
  delegates not-yet-server-backed features (profile/friends/shop/chat/rooms)
  to the mock. Selected via NEXT_PUBLIC_USE_SERVER=1 (NEXT_PUBLIC_SERVER_URL)
- game-store live mode: enterServerMatch + applyServerState (maps server DTO,
  hides opponent hands, tally + SFX), inputs route to the hub; no local engine
- MatchmakingScreen auto-enters the live match when the server signals ready
- Verified end-to-end via scripts/live-test.mjs (auth -> hub -> match -> state)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 13:13:48 +03:30
soroush.asadi a3b797c8a3 Add History API routing so browser/Android back navigates screens
- ui-store syncs screens to history (push on go/goGame, hash URLs like #/profile),
  back() = history.back(), initHistory anchors a home base + restores deep links
- page.tsx listens to popstate; resolveScreen() guards transient screens
  (game/room/chat/matchmaking fall back to home when their state is gone)
- ScreenHeader + ChatScreen back buttons use history back; chat cleans up on unmount

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 12:56:14 +03:30
soroush.asadi 0a3ffa6314 Fix: in-game mute button now mutes background music too
HUD mute is a master toggle (sound effects + music) via sound-store.toggleAll;
icon reflects fully-muted state.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 12:46:51 +03:30
soroush.asadi aaf66b921f Phase G: scaffold .NET 10 + SignalR backend (engine port + hub + auth)
- server/ monorepo: Hokm.Engine (C# port of TS engine+AI, validated by sim),
  Hokm.Server (SignalR GameHub, in-memory matchmaking/rooms, server-side turn
  timers + bot fill + disconnect handling, per-seat state broadcast), Hokm.Sim
- JWT dev auth (OTP 1234 + email); CORS for the Next client; /hub/game
- NuGet restored from mirrors (Soroush Nexus + Liara); NuGetAudit off
- README + .NET .gitignore; static class Engine renamed Rules (namespace clash)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 12:42:15 +03:30
soroush.asadi ae239f4c51 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>
2026-06-04 11:49:19 +03:30
soroush.asadi db4eade619 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>
2026-06-04 11:15:28 +03:30
soroush.asadi f9425dea01 Add reactions (Sheklak), fix hakem card visibility during trump choice
- Reactions/emotes in-game: tray of owned emojis + animated per-seat bubbles
  (feature named "شکلک / Sheklak"). Packs: starter (free), champion/legend
  (earned by rating/wins), emotions/taunt (purchasable in shop)
- OnlineService.sendReaction/onReaction; mock echoes you + random opponents
- Fix: human hakem's 5 cards were blurred behind the trump-chooser overlay —
  raise hand to z-50 during choosing-trump so cards stay readable

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 11:02:25 +03:30
soroush.asadi 13ec0d4300 Turn timer + auto-play, disconnect/reconnect, cosmetics, queue & paid plan
- Turn timer (20s) for play/trump; system auto-plays a smart move on timeout
- Disconnect handling (mock): wait-for-return countdown, system covers turns
- Cosmetics: titles, card-back styles, custom profile-image upload, badges;
  pickers in Profile; shop sells card styles; reward modal shows new titles
- Paid plan (pro): free players queue when server busy, pro skips; upgrade flow
- OnlineService extended (upgradePlan, richer profile patch); mock implements
  queue + plans; gamification adds TITLES + CARD_STYLES

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 10:49:54 +03:30
soroush.asadi 5776036d78 Add friend chat; replace betting wording (شرط) with entry coins
- Friend-to-friend chat outside the game (ChatScreen) with mock replies,
  per-friend history, unread tracking; chat button on each friend row
- OnlineService + mock + online-store extended with chat (list/get/send/markRead)
- Reframe gambling term: "شرط"/"Stake" -> "سکه ورودی"/"Entry coins";
  free entry labeled رایگان/Free

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 10:21:44 +03:30
soroush.asadi e2d0a602b6 Build Hokm card game: offline vs-AI + online social/gamification (mock backend)
- Pure-TS Hokm engine (deal, hakem, trump, tricks, scoring, Kot) + AI bots
- Persian-luxury RTL UI (Next 16 / React 19 / Tailwind v4 / Framer Motion / Zustand)
- Online platform behind OnlineService seam (mock now, .NET SignalR later):
  auth (phone OTP + email/Google), profiles, friends, private rooms with
  partner pick, ranked matchmaking, leaderboard, shop
- Gamification: ranks/leagues, coins, XP/levels, daily rewards, achievements
- i18n fa/en, PWA manifest, engine + gamification sims

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 10:11:00 +03:30
soroush.asadi dff1a34f95 Initial commit from Create Next App 2026-06-04 06:09:11 +03:30