diff --git a/docs/AI_USAGE.md b/docs/AI_USAGE.md
index 1f831c3..653e0e5 100644
--- a/docs/AI_USAGE.md
+++ b/docs/AI_USAGE.md
@@ -174,6 +174,19 @@
- **Review:** The team refined the layout, colors, difficulty display, and
accessibility semantics during review.
+## Element detail page (`src/components/elements/`, `src/lib/catalog.ts`)
+
+- **Tool:** GitHub Copilot
+- **What the team did:** Provided the detail-page design and specified modular
+ sections, reuse of the existing variant and difficulty components, existing
+ CSS colors only, and accessibility requirements.
+- **What GitHub Copilot did:** Added a route-backed page, a local catalog data
+ adapter, reusable header, metadata, diagram and action sections, and tests
+ for catalog lookup and variant navigation. The page composes the existing
+ `ElementVariants` and `DifficultyBadge` components.
+- **Review:** The team checked the page in the browser, corrected theme-token
+ use and image sizing, and reviewed keyboard navigation and accessible names.
+
## Missing fields in the GraphQL schema (`backend/src/schema.graphql`, `docs/API.md`)
- **Tool:** Claude Code, and Claude for the research.
diff --git a/package-lock.json b/package-lock.json
index 86b1715..2397b46 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -21,7 +21,8 @@
"lucide-react": "^1.58.0",
"pg": "^8.23.1",
"react": "^19.2.8",
- "react-dom": "^19.2.8"
+ "react-dom": "^19.2.8",
+ "react-router-dom": "^7.18.4"
},
"devDependencies": {
"@babel/core": "^7.29.7",
@@ -115,12 +116,6 @@
"apollo-pbts": "bin/pbts"
}
},
- "node_modules/@apollo/protobufjs/node_modules/long": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz",
- "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==",
- "license": "Apache-2.0"
- },
"node_modules/@apollo/server": {
"version": "5.5.1",
"resolved": "https://registry.npmjs.org/@apollo/server/-/server-5.5.1.tgz",
@@ -1543,12 +1538,25 @@
}
}
},
- "node_modules/@prisma/client/node_modules/@prisma/client-runtime-utils": {
+ "node_modules/@prisma/client-runtime-utils": {
"version": "7.10.1",
"resolved": "https://registry.npmjs.org/@prisma/client-runtime-utils/-/client-runtime-utils-7.10.1.tgz",
"integrity": "sha512-AzoATVIgmO3lcZfM443+9SI7+cyLMW2+Psw4op8WQHpHvHCX//CKxv8fwhiL0yGWF8DjdQrFewxrZo5McBdB8g==",
"license": "Apache-2.0"
},
+ "node_modules/@prisma/config": {
+ "version": "7.10.1",
+ "resolved": "https://registry.npmjs.org/@prisma/config/-/config-7.10.1.tgz",
+ "integrity": "sha512-+0swF5ELUa3P/9cIR26+gpYjjCbGE0qvEBZhV4zMDkYsrtHjZ884MQbOnWFWXj600N0sSJNnHbtBqe12yDngWg==",
+ "devOptional": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "c12": "3.3.4",
+ "deepmerge-ts": "8.0.2",
+ "effect": "3.20.0",
+ "empathic": "2.0.0"
+ }
+ },
"node_modules/@prisma/debug": {
"version": "7.10.1",
"resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-7.10.1.tgz",
@@ -1595,6 +1603,59 @@
"@prisma/debug": "7.10.1"
}
},
+ "node_modules/@prisma/engines": {
+ "version": "7.10.1",
+ "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-7.10.1.tgz",
+ "integrity": "sha512-R6/ghv1Bu36FhCdQLFof1pNtyoWoeDzJLYeTKjgqb4eGtOAJx7ZqJtcm4pzkahLfZ9rYz8AkmsMDwkyiYbXPCQ==",
+ "devOptional": true,
+ "hasInstallScript": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@prisma/debug": "7.10.1",
+ "@prisma/engines-version": "8.1.0-1.d1b3f377ca835d73fd50954a5e22128b9ebdd6b8",
+ "@prisma/fetch-engine": "7.10.1",
+ "@prisma/get-platform": "7.10.1"
+ }
+ },
+ "node_modules/@prisma/engines-version": {
+ "version": "8.1.0-1.d1b3f377ca835d73fd50954a5e22128b9ebdd6b8",
+ "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-8.1.0-1.d1b3f377ca835d73fd50954a5e22128b9ebdd6b8.tgz",
+ "integrity": "sha512-XjXmwjN2NE340RpXoXv8ZF8aNAvr0FE+7gNfkKeW6vyjXs7F8uxvEbWQoVrhioXDQoIsnNmv7r+wxvzfdEZaWA==",
+ "devOptional": true,
+ "license": "Apache-2.0"
+ },
+ "node_modules/@prisma/engines/node_modules/@prisma/get-platform": {
+ "version": "7.10.1",
+ "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.10.1.tgz",
+ "integrity": "sha512-iVnghHne5vcaobBjPVCMEjh5r/1UCj394EIGTUclasFRatpR/MQwcM2veLv5kgoFSyYP54nch/lNFCQRYcDxvg==",
+ "devOptional": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@prisma/debug": "7.10.1"
+ }
+ },
+ "node_modules/@prisma/fetch-engine": {
+ "version": "7.10.1",
+ "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-7.10.1.tgz",
+ "integrity": "sha512-qZezeaExLWG2ptbHOrMxdiWluKHgqEclTdJbvY6pk6+Wg7EiVkeq5U3cXz9ti8nd0bngNvsDgledLHVRg5LF1w==",
+ "devOptional": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@prisma/debug": "7.10.1",
+ "@prisma/engines-version": "8.1.0-1.d1b3f377ca835d73fd50954a5e22128b9ebdd6b8",
+ "@prisma/get-platform": "7.10.1"
+ }
+ },
+ "node_modules/@prisma/fetch-engine/node_modules/@prisma/get-platform": {
+ "version": "7.10.1",
+ "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.10.1.tgz",
+ "integrity": "sha512-iVnghHne5vcaobBjPVCMEjh5r/1UCj394EIGTUclasFRatpR/MQwcM2veLv5kgoFSyYP54nch/lNFCQRYcDxvg==",
+ "devOptional": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@prisma/debug": "7.10.1"
+ }
+ },
"node_modules/@prisma/get-platform": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.2.0.tgz",
@@ -4295,6 +4356,26 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/deepmerge-ts": {
+ "version": "8.0.2",
+ "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-8.0.2.tgz",
+ "integrity": "sha512-uqbvqLUMrc6p0MO+WBRtTxY55hmyh94WRwI5a++PZe54X+bfVh59FSN7uWCBCW1CCVjzjnrwzfI8zidE2obMMw==",
+ "devOptional": true,
+ "funding": [
+ {
+ "type": "ko-fi",
+ "url": "https://ko-fi.com/rebeccastevens"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/deepmerge-ts"
+ }
+ ],
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=16.9.0"
+ }
+ },
"node_modules/define-data-property": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
@@ -5896,10 +5977,9 @@
}
},
"node_modules/long": {
- "version": "5.3.2",
- "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz",
- "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==",
- "devOptional": true,
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz",
+ "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==",
"license": "Apache-2.0"
},
"node_modules/lru-cache": {
@@ -6034,6 +6114,35 @@
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"license": "MIT"
},
+ "node_modules/mysql2": {
+ "version": "3.24.4",
+ "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.24.4.tgz",
+ "integrity": "sha512-A2olluVlj0mvgyIRRISMEzXc51m+21mRtcMVjJyIpt2GG98+XrC9m9HzsqcMsX2LcnfccJvY5NB22g8fENBnOA==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "aws-ssl-profiles": "^1.1.2",
+ "generate-function": "^2.3.1",
+ "iconv-lite": "^0.7.3",
+ "long": "^5.3.2",
+ "lru.min": "^1.1.4",
+ "named-placeholders": "^1.1.6",
+ "sql-escaper": "^1.5.1"
+ },
+ "engines": {
+ "node": ">= 8.0"
+ },
+ "peerDependencies": {
+ "@types/node": ">= 8"
+ }
+ },
+ "node_modules/mysql2/node_modules/long": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz",
+ "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==",
+ "devOptional": true,
+ "license": "Apache-2.0"
+ },
"node_modules/named-placeholders": {
"version": "1.1.6",
"resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.6.tgz",
@@ -6397,14 +6506,14 @@
}
},
"node_modules/pkg-types": {
- "version": "2.3.3",
- "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.3.tgz",
- "integrity": "sha512-j/lCFdcppV0JxWpCEITdbDltBxPP6cHT+yNJ6Go2OgoSA9518X847X9z0p6LtA4Nc16+eQzCZjRrWanTGvHJ5w==",
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.4.0.tgz",
+ "integrity": "sha512-07Fys7iVx0X6TcviMOgogM1Lk33ZyqOezMnU79S+kffWqwolG/5W9V8EgQSeYCIfFTYVgssRbnH630Oa2+soBA==",
"devOptional": true,
"license": "MIT",
"dependencies": {
"confbox": "^0.3.1",
- "exsolve": "^1.1.1",
+ "exsolve": "^1.1.3",
"pathe": "^2.0.3"
}
},
@@ -6566,104 +6675,6 @@
}
}
},
- "node_modules/prisma/node_modules/@prisma/config": {
- "version": "7.10.1",
- "resolved": "https://registry.npmjs.org/@prisma/config/-/config-7.10.1.tgz",
- "integrity": "sha512-+0swF5ELUa3P/9cIR26+gpYjjCbGE0qvEBZhV4zMDkYsrtHjZ884MQbOnWFWXj600N0sSJNnHbtBqe12yDngWg==",
- "devOptional": true,
- "license": "Apache-2.0",
- "dependencies": {
- "c12": "3.3.4",
- "deepmerge-ts": "8.0.2",
- "effect": "3.20.0",
- "empathic": "2.0.0"
- }
- },
- "node_modules/prisma/node_modules/@prisma/engines": {
- "version": "7.10.1",
- "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-7.10.1.tgz",
- "integrity": "sha512-R6/ghv1Bu36FhCdQLFof1pNtyoWoeDzJLYeTKjgqb4eGtOAJx7ZqJtcm4pzkahLfZ9rYz8AkmsMDwkyiYbXPCQ==",
- "devOptional": true,
- "hasInstallScript": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@prisma/debug": "7.10.1",
- "@prisma/engines-version": "8.1.0-1.d1b3f377ca835d73fd50954a5e22128b9ebdd6b8",
- "@prisma/fetch-engine": "7.10.1",
- "@prisma/get-platform": "7.10.1"
- }
- },
- "node_modules/prisma/node_modules/@prisma/engines-version": {
- "version": "8.1.0-1.d1b3f377ca835d73fd50954a5e22128b9ebdd6b8",
- "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-8.1.0-1.d1b3f377ca835d73fd50954a5e22128b9ebdd6b8.tgz",
- "integrity": "sha512-XjXmwjN2NE340RpXoXv8ZF8aNAvr0FE+7gNfkKeW6vyjXs7F8uxvEbWQoVrhioXDQoIsnNmv7r+wxvzfdEZaWA==",
- "devOptional": true,
- "license": "Apache-2.0"
- },
- "node_modules/prisma/node_modules/@prisma/fetch-engine": {
- "version": "7.10.1",
- "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-7.10.1.tgz",
- "integrity": "sha512-qZezeaExLWG2ptbHOrMxdiWluKHgqEclTdJbvY6pk6+Wg7EiVkeq5U3cXz9ti8nd0bngNvsDgledLHVRg5LF1w==",
- "devOptional": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@prisma/debug": "7.10.1",
- "@prisma/engines-version": "8.1.0-1.d1b3f377ca835d73fd50954a5e22128b9ebdd6b8",
- "@prisma/get-platform": "7.10.1"
- }
- },
- "node_modules/prisma/node_modules/@prisma/get-platform": {
- "version": "7.10.1",
- "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.10.1.tgz",
- "integrity": "sha512-iVnghHne5vcaobBjPVCMEjh5r/1UCj394EIGTUclasFRatpR/MQwcM2veLv5kgoFSyYP54nch/lNFCQRYcDxvg==",
- "devOptional": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@prisma/debug": "7.10.1"
- }
- },
- "node_modules/prisma/node_modules/deepmerge-ts": {
- "version": "8.0.2",
- "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-8.0.2.tgz",
- "integrity": "sha512-uqbvqLUMrc6p0MO+WBRtTxY55hmyh94WRwI5a++PZe54X+bfVh59FSN7uWCBCW1CCVjzjnrwzfI8zidE2obMMw==",
- "devOptional": true,
- "funding": [
- {
- "type": "ko-fi",
- "url": "https://ko-fi.com/rebeccastevens"
- },
- {
- "type": "tidelift",
- "url": "https://tidelift.com/funding/github/npm/deepmerge-ts"
- }
- ],
- "license": "BSD-3-Clause",
- "engines": {
- "node": ">=16.9.0"
- }
- },
- "node_modules/prisma/node_modules/mysql2": {
- "version": "3.24.4",
- "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.24.4.tgz",
- "integrity": "sha512-A2olluVlj0mvgyIRRISMEzXc51m+21mRtcMVjJyIpt2GG98+XrC9m9HzsqcMsX2LcnfccJvY5NB22g8fENBnOA==",
- "devOptional": true,
- "license": "MIT",
- "dependencies": {
- "aws-ssl-profiles": "^1.1.2",
- "generate-function": "^2.3.1",
- "iconv-lite": "^0.7.3",
- "long": "^5.3.2",
- "lru.min": "^1.1.4",
- "named-placeholders": "^1.1.6",
- "sql-escaper": "^1.5.1"
- },
- "engines": {
- "node": ">= 8.0"
- },
- "peerDependencies": {
- "@types/node": ">= 8"
- }
- },
"node_modules/proper-lockfile": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz",
@@ -6823,6 +6834,57 @@
"react": "^19.3.0"
}
},
+ "node_modules/react-router": {
+ "version": "7.18.4",
+ "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.18.4.tgz",
+ "integrity": "sha512-PUPQcMhMGRAslLcvtlPz/kmzBEWPhLdgLFrL7pLNepBL6dX0lWj4WD2cUYVgYCuT3jxvghYFg81cDTj44DhetQ==",
+ "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.18.4",
+ "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.18.4.tgz",
+ "integrity": "sha512-yrfmJHIpDG7taCpqKjT1G5B6q3O2K+RN8/fgNf0lTjCwiPbQ0ei6vXX9ZjQR+7ld8Tr7Z5xmyMnZ8YJrphWQUw==",
+ "license": "MIT",
+ "dependencies": {
+ "react-router": "7.18.4"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=18",
+ "react-dom": ">=18"
+ }
+ },
+ "node_modules/react-router/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/readdirp": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.1.1.tgz",
@@ -7050,6 +7112,12 @@
"url": "https://opencollective.com/express"
}
},
+ "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/set-function-length": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
@@ -7209,9 +7277,9 @@
}
},
"node_modules/source-map-js": {
- "version": "1.2.2",
- "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.2.tgz",
- "integrity": "sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==",
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
"dev": true,
"license": "BSD-3-Clause",
"engines": {
diff --git a/package.json b/package.json
index 30329df..ae196eb 100644
--- a/package.json
+++ b/package.json
@@ -31,7 +31,8 @@
"lucide-react": "^1.58.0",
"pg": "^8.23.1",
"react": "^19.2.8",
- "react-dom": "^19.2.8"
+ "react-dom": "^19.2.8",
+ "react-router-dom": "^7.18.4"
},
"devDependencies": {
"@babel/core": "^7.29.7",
diff --git a/src/App.tsx b/src/App.tsx
index ef6af75..e85caf4 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1,14 +1,19 @@
-import './App.css'
+import { Navigate, Route, Routes } from 'react-router-dom'
+import './index.css'
+import './styles/variables.css'
+import './styles/global.css'
+import ElementDetailPage from './components/elements/ElementDetailPage'
function App() {
- const iconsUrl = `${import.meta.env.BASE_URL}icons.svg`
-
return (
-
+ {status} +
++ {element.description} +
+ )} ++ Loading element details +
++ No catalog item matches this address. +
+ + View a catalog element + ++ The catalog item could not be loaded. Please try again. +
+ ++ No illustration is available for this element. +
+ )} +Element description goes here.
-+ This element has no other variants. +
+ ) : ( ++ {variant.description} +
+