diff --git a/README.md b/README.md
index e806706..ef507d3 100644
--- a/README.md
+++ b/README.md
@@ -1,18 +1,50 @@
-# React + Vite
+# YuGiOh Collection Manager — Frontend
-This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
+React frontend for browsing and managing a personal YuGiOh card collection.
-Currently, two official plugins are available:
+## Tech Stack
-- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Oxc](https://oxc.rs)
-- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/)
+- **React 19** + Vite
+- **react-virtuoso** — virtualized card list (handles 13k+ cards)
+- **react-router-dom** — client-side routing
-## React Compiler
+## Pages
-The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation).
+| Route | Description |
+|---|---|
+| `/` | Card list with search, filters, owned count tracking |
+| `/sets` | Browse sets, view cards per set with owned counts |
-## Expanding the ESLint configuration
+## Features
-If you are developing a production application, we recommend using TypeScript with type-aware lint rules enabled. Check out the [TS template](https://github.com/vitejs/vite/tree/main/packages/create-vite/template-react-ts) for information on how to integrate TypeScript and [`typescript-eslint`](https://typescript-eslint.io) in your project.
+- Search by name with punctuation normalization and typo tolerance
+- Filter by type (Monster / Spell / Trap), owned-only toggle, sort by name or owned count
+- Per-printing owned count with +/− controls
+- Card detail panel with artwork switcher (multiple artworks per card)
+- Collection export as JSON backup
+- Mobile layout with swipeable bottom sheet for card/set detail
+- Full DB import via YGOPRODeck API
-## Start Frontend: npm run dev
+## Development
+
+```bash
+npm install
+npm run dev
+```
+
+Requires the API running at `http://localhost:3000` (proxied via `/api`).
+
+## Deployment
+
+Built as a Docker image served by Nginx. CI/CD via Woodpecker on push to `main`.
+
+```bash
+docker build -t yugioh-frontend .
+docker run -d --name yugioh-frontend --network yugioh -p 8041:80 yugioh-frontend
+```
+
+## Environment
+
+| Variable | Description |
+|---|---|
+| `VITE_API_URL` | API base URL for dev proxy (default: `http://localhost:3000`) |
diff --git a/package-lock.json b/package-lock.json
index 8ef650b..0034e8b 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -10,8 +10,8 @@
"dependencies": {
"react": "^19.2.4",
"react-dom": "^19.2.4",
- "react-virtuoso": "^4.18.3",
- "react-window": "^2.2.7"
+ "react-router-dom": "^7.15.1",
+ "react-virtuoso": "^4.18.3"
},
"devDependencies": {
"@eslint/js": "^9.39.4",
@@ -1138,6 +1138,19 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/cookie": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz",
+ "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
"node_modules/cross-spawn": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
@@ -2255,6 +2268,44 @@
"react": "^19.2.4"
}
},
+ "node_modules/react-router": {
+ "version": "7.15.1",
+ "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.15.1.tgz",
+ "integrity": "sha512-R8rl9HhgikFYoPJymnUtPXWbnDb3oget6lQnfIoupbt61aT9aOhRkDsY2XRhZRyX1Z/8a5sL74fXmFNm3NRK5A==",
+ "license": "MIT",
+ "dependencies": {
+ "cookie": "^1.0.1",
+ "set-cookie-parser": "^2.6.0"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=18",
+ "react-dom": ">=18"
+ },
+ "peerDependenciesMeta": {
+ "react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/react-router-dom": {
+ "version": "7.15.1",
+ "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.15.1.tgz",
+ "integrity": "sha512-AzF62gjY6U9rkMq4RfP/r2EVtQ7DMfNMjyOp/flLTCrtRylLiK4wT4pSq6O8rOXZ2eXdZYJPEYe+ifomiv+Igg==",
+ "license": "MIT",
+ "dependencies": {
+ "react-router": "7.15.1"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=18",
+ "react-dom": ">=18"
+ }
+ },
"node_modules/react-virtuoso": {
"version": "4.18.3",
"resolved": "https://registry.npmjs.org/react-virtuoso/-/react-virtuoso-4.18.3.tgz",
@@ -2265,16 +2316,6 @@
"react-dom": ">=16 || >=17 || >= 18 || >=19"
}
},
- "node_modules/react-window": {
- "version": "2.2.7",
- "resolved": "https://registry.npmjs.org/react-window/-/react-window-2.2.7.tgz",
- "integrity": "sha512-SH5nvfUQwGHYyriDUAOt7wfPsfG9Qxd6OdzQxl5oQ4dsSsUicqQvjV7dR+NqZ4coY0fUn3w1jnC5PwzIUWEg5w==",
- "license": "MIT",
- "peerDependencies": {
- "react": "^18.0.0 || ^19.0.0",
- "react-dom": "^18.0.0 || ^19.0.0"
- }
- },
"node_modules/resolve-from": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
@@ -2342,6 +2383,12 @@
"semver": "bin/semver.js"
}
},
+ "node_modules/set-cookie-parser": {
+ "version": "2.7.2",
+ "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz",
+ "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==",
+ "license": "MIT"
+ },
"node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
diff --git a/package.json b/package.json
index 9be697a..28553ab 100644
--- a/package.json
+++ b/package.json
@@ -12,6 +12,7 @@
"dependencies": {
"react": "^19.2.4",
"react-dom": "^19.2.4",
+ "react-router-dom": "^7.15.1",
"react-virtuoso": "^4.18.3"
},
"devDependencies": {
diff --git a/src/App.jsx b/src/App.jsx
index 5e68296..6169d0d 100644
--- a/src/App.jsx
+++ b/src/App.jsx
@@ -1,8 +1,21 @@
import React from 'react';
+import { BrowserRouter, Routes, Route } from 'react-router-dom';
+import NavBar from './components/NavBar/NavBar';
import HomePage from './pages/HomePage';
+import SetsPage from './pages/SetsPage';
function App() {
- return
Loading…
+ : ( +Loading sets…
; + + return ( +