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>
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>
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>
- 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>
- 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>
- 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>
- 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>
- 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>
- 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>