A mobile-friendly web app for browsing and downloading games via magnet links. Browse game libraries from sources like FitGirl Repacks and SteamRip, search by title, and grab magnet links — all from a clean, dark UI that works great on both desktop and mobile.
- 🔍 Search — Debounced client-side search across loaded games
- 🎨 Cover Art — Automatically fetches game artwork from Steam CDN and SteamGridDB
- 📱 Mobile-first — Responsive grid, touch-friendly (44px tap targets), works on 7" Android
- ⚡ Cached — Sources cached for 30 min, artwork cached for 24h (in-memory)
- 🧩 Multi-source — Supports FitGirl Repacks, SteamRip, or all sources at once
- 🪄 Graceful fallback — No artwork? Gets a colored gradient placeholder with title initial
- 📄 Paginated — 50 games per page with a load-more button
- 🖼️ Game detail modal — Click any card for full info + all download links
| Layer | Tech |
|---|---|
| Frontend | React + Vite + TypeScript |
| Backend | Node.js + Express + TypeScript |
| Styling | Plain CSS — dark theme, CSS grid |
| Artwork | Steam CDN, Hydra API, SteamGridDB |
- Node.js 18+
- npm 8+
git clone https://github.com/00cyre/gauntlet.git
cd gauntlet
npm install
npm run devThis starts both servers concurrently:
- Backend →
http://localhost:3001 - Frontend →
http://localhost:5173
Create a backend/.env file (see backend/.env.example):
PORT=3001
STEAMGRIDDB_API_KEY=your_key_here # Optional — enables SteamGridDB as artwork fallbackThe app works without a SteamGridDB key — it'll fall back to Steam CDN and Hydra API for artwork.
Returns the list of available game sources.
[
{ "id": "fitgirl", "name": "FitGirl Repacks", "url": "..." },
{ "id": "steamrip", "name": "SteamRip", "url": "..." }
]Fetches and filters games from a source. Proxies through the backend to bypass CORS/Cloudflare.
source— source id (fitgirl,steamrip). Omit or use empty string for all.q— optional search query (case-insensitive title match)
Returns an array of:
{ "title": "string", "uris": ["magnet:..."], "fileSize": "string", "uploadDate": "string" }Fetches cover art for a game title. Tries in order:
- Steam store search → Steam CDN cover
- Hydra public API
- SteamGridDB (if
STEAMGRIDDB_API_KEYis set)
Returns:
{ "cover": "url", "background": "url", "title": "string" }gauntlet/
├── backend/
│ ├── src/
│ │ ├── index.ts # Express app entry
│ │ └── routes/
│ │ ├── sources.ts # /api/sources
│ │ ├── games.ts # /api/games
│ │ └── artwork.ts # /api/artwork
│ ├── .env.example
│ ├── package.json
│ └── tsconfig.json
├── frontend/
│ ├── src/
│ │ ├── App.tsx # Main app component
│ │ ├── main.tsx
│ │ └── index.css # Dark theme styles
│ ├── index.html
│ ├── package.json
│ ├── tsconfig.json
│ └── vite.config.ts
├── package.json # Root workspace (runs both with concurrently)
└── SPEC.md
Sources are defined in backend/src/routes/sources.ts. Each source points to a JSON feed in the hydralinks.cloud format:
{ id: "mysource", name: "My Source", url: "https://hydralinks.cloud/sources/mysource.json" }MIT
