UI: unified design system, light/dark themes, redesigned pages and player UI

Tokens and components in styles.css, shared Ui (dialogs, toasts), icons,
bottom tab bar on mobile, keyboard/gamepad navigation, library shelf with
filters and conversion badges, hero game page, restyled player screens.
Games list now returns per-game version counts. Adds docs/UI_DESIGN.md.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
mrleo1nidandClaude Sonnet 5.5 committed 2026-10-25 03:43:53 +03:00
1 parent 51a7e77e56
commit 9ee3e1ff6e
27 files changed
+2399 -508

No files matched your search

+23 -3
View File
@@ -3,7 +3,7 @@ from typing import Annotated
from fastapi import APIRouter, Depends, HTTPException, Request, status
from PIL import Image, UnidentifiedImageError
from sqlalchemy import exists, or_, select, true
from sqlalchemy import exists, func, or_, select, true
from starlette.concurrency import run_in_threadpool
from app.blobs import put_bytes
@@ -40,7 +40,7 @@ def can_edit_game(user: User, game: Game) -> bool:
@router.get("", response_model=list[GameOut])
async def list_games(user: Player, db: DB, q: str = "", tag: str = "") -> list[Game]:
async def list_games(user: Player, db: DB, q: str = "", tag: str = "") -> list[dict]:
stmt = select(Game).order_by(Game.title)
if q.strip():
pattern = f"%{q.strip()}%"
@@ -57,7 +57,27 @@ async def list_games(user: Player, db: DB, q: str = "", tag: str = "") -> list[G
exists().where(GameVersion.game_id == Game.id, visible_versions_filter(user))
| (Game.created_by_id == user.id)
)
return list((await db.scalars(stmt.limit(500))).all())
games = list((await db.scalars(stmt.limit(500))).all())
counts: dict[int, dict[str, int]] = {}
if games:
rows = await db.execute(
select(GameVersion.game_id, GameVersion.status, func.count())
.where(GameVersion.game_id.in_([g.id for g in games]), visible_versions_filter(user))
.group_by(GameVersion.game_id, GameVersion.status)
)
for game_id, st, n in rows:
key = "ready" if st == "ready" else "failed" if st == "failed" else "pending"
counts.setdefault(game_id, {}).setdefault(key, 0)
counts[game_id][key] += n
return [
{
**GameOut.model_validate(g).model_dump(),
"ready_versions": counts.get(g.id, {}).get("ready", 0),
"pending_versions": counts.get(g.id, {}).get("pending", 0),
"failed_versions": counts.get(g.id, {}).get("failed", 0),
}
for g in games
]
@router.post("", response_model=GameOut, status_code=201)