Ship Steam login, D1 player sync, and cached「我」dashboard.

Players get a fast TTL-backed homepage (local profile / Cloudflare D1) with dense UI polish; login unlocks /home without blocking on every OpenDota refresh.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
voson
2026-08-01 01:24:30 +08:00
co-authored by Cursor
parent 4a61aeeb26
commit f5b7011c45
65 changed files with 7304 additions and 552 deletions
+13 -1
View File
@@ -4,7 +4,7 @@
* Path-based router for the Climperor web site (web/frontend).
*
* Synchronizes the browser URL with app state across these dimensions:
* - page: heroes | rankings | matches | streamers | trends | mechanics | items | patches | players
* - page: home | heroes | rankings | matches | streamers | trends | mechanics | items | patches | players
* - hero: selected hero key + detail sub-tab
* (skills|core|fears|trends|matchups|matches|streamers|patches; legacy stats → trends)
* - rankings: Immortal leaderboard region
@@ -12,6 +12,8 @@
* - matches: star-player recent matches (pro_matches)
* /matches[/account_id][?origin=pro|china][&page=N]
* (default origin all omitted; page=1 omitted)
* - home: logged-in Steam self homepage (players view + auth.account_id)
* /home[/{match_id}]
* - players: PC post-match player home + match detail (local/OSS JSON)
* /players/{account_id}[/{match_id}]
* - streamers: curated streamer directory
@@ -41,6 +43,7 @@
const ROUTE_DEFAULT = "/heroes";
const VALID_PAGES = [
"home",
"heroes",
"rankings",
"matches",
@@ -161,6 +164,11 @@ function parseHash(hashOrPath) {
if (segs[1] && /^\d+$/.test(segs[1])) {
out.matchesPlayerId = segs[1];
}
} else if (page === "home") {
// Logged-in self homepage; optional match detail: /home/{match_id}
if (segs[1] && /^\d+$/.test(segs[1])) {
out.playerMatchId = segs[1];
}
} else if (page === "players") {
if (segs[1] && /^\d+$/.test(segs[1])) {
out.playerAccountId = segs[1];
@@ -240,6 +248,10 @@ function serializeHash(state) {
if (state.matchesPlayerId) {
path += "/" + encodeURIComponent(String(state.matchesPlayerId));
}
} else if (state.page === "home") {
if (state.playerMatchId) {
path += "/" + encodeURIComponent(String(state.playerMatchId));
}
} else if (state.page === "players") {
if (state.playerAccountId) {
path += "/" + encodeURIComponent(String(state.playerAccountId));