diff --git a/frontend/.astro/types.d.ts b/frontend/.astro/types.d.ts index 03d7cc4..f964fe0 100644 --- a/frontend/.astro/types.d.ts +++ b/frontend/.astro/types.d.ts @@ -1,2 +1 @@ /// -/// \ No newline at end of file diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 2078c1e..c6a83f0 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -90,7 +90,8 @@ "version": "2.12.2", "resolved": "https://registry.npmjs.org/@astrojs/compiler/-/compiler-2.12.2.tgz", "integrity": "sha512-w2zfvhjNCkNMmMMOn5b0J8+OmUaBL1o40ipMvqcG6NRpdC+lKxmTi48DT8Xw0SzJ3AfmeFLB45zXZXtmbsjcgw==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@astrojs/internal-helpers": { "version": "0.7.2", @@ -299,6 +300,7 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.4.tgz", "integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==", "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.3", @@ -831,6 +833,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=18" }, @@ -854,6 +857,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=18" } @@ -3069,6 +3073,7 @@ "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.1.0.tgz", "integrity": "sha512-uKm5PU+MHTootlWEY+mZ4vvXoCn4fLQxT9dSc1sXVMSFkINTJVN8cAQROpwcKm8bJ/c7rgZVIBWzH5T78sNZZw==", "license": "MIT", + "peer": true, "dependencies": { "node-fetch": "^2.7.0" } @@ -3122,6 +3127,7 @@ "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-1.3.0.tgz", "integrity": "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==", "license": "MIT", + "peer": true, "engines": { "node": "^14.21.3 || >=16" }, @@ -4644,6 +4650,7 @@ "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", "license": "MIT", + "peer": true, "engines": { "node": ">=10.0.0" }, @@ -4999,6 +5006,7 @@ "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", "license": "MIT", + "peer": true, "engines": { "node": ">=10.0.0" }, @@ -5306,6 +5314,7 @@ "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", "license": "MIT", + "peer": true, "engines": { "node": ">=10.0.0" }, @@ -6261,6 +6270,7 @@ "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.87.4.tgz", "integrity": "sha512-T5GT/1ZaNsUXf5I3RhcYuT17I4CPlbZgyLxc/ZGv7ciS6esytlbjb3DgUFO6c8JWYMDpdjSWInyGZUErgzqhcA==", "license": "MIT", + "peer": true, "dependencies": { "@tanstack/query-core": "5.87.4" }, @@ -6442,8 +6452,7 @@ "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@types/babel__core": { "version": "7.20.5", @@ -6593,6 +6602,7 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-24.3.1.tgz", "integrity": "sha512-3vXmQDXy+woz+gnrTvuvNrPzekOi+Ds0ReMxw0LzBiK3a+1k0kQn9f2NWk+lgD4rJehFUmYy2gMhJ2ZI+7YP9g==", "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~7.10.0" } @@ -6611,6 +6621,7 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.12.tgz", "integrity": "sha512-cMoR+FoAf/Jyq6+Df2/Z41jISvGZZ2eTlnsaJRptmZ76Caldwy1odD4xTr/gNV9VLj0AWgg/nmkevIyUfIIq5w==", "license": "MIT", + "peer": true, "dependencies": { "csstype": "^3.0.2" } @@ -6620,6 +6631,7 @@ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.9.tgz", "integrity": "sha512-qXRuZaOsAdXKFyOhRBg6Lqqc0yay13vN7KrIg4L7N4aaHN68ma9OK3NE1BoDFgFOTfM7zg+3/8+2n8rLUH3OKQ==", "license": "MIT", + "peer": true, "peerDependencies": { "@types/react": "^19.0.0" } @@ -6702,6 +6714,7 @@ "integrity": "sha512-B7RIQiTsCBBmY+yW4+ILd6mF5h1FUwJsVvpqkrgpszYifetQ2Ke+Z4u6aZh0CblkUGIdR59iYVyXqqZGkZ3aBw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.43.0", "@typescript-eslint/types": "8.43.0", @@ -6912,6 +6925,7 @@ "resolved": "https://registry.npmjs.org/@vanilla-extract/css/-/css-1.17.3.tgz", "integrity": "sha512-jHivr1UPoJTX5Uel4AZSOwrCf4mO42LcdmnhJtUxZaRWhW4FviFbIfs0moAWWld7GOT+2XnuVZjjA/K32uUnMQ==", "license": "MIT", + "peer": true, "dependencies": { "@emotion/hash": "^0.9.0", "@vanilla-extract/private": "^1.0.8", @@ -7120,6 +7134,7 @@ "resolved": "https://registry.npmjs.org/@wagmi/core/-/core-2.20.3.tgz", "integrity": "sha512-gsbuHnWxf0AYZISvR8LvF/vUCIq6/ZwT5f5/FKd6wLA7Wq05NihCvmQpIgrcVbpSJPL67wb6S8fXm3eJGJA1vQ==", "license": "MIT", + "peer": true, "dependencies": { "eventemitter3": "5.0.1", "mipd": "0.0.7", @@ -7693,6 +7708,7 @@ "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", "license": "MIT", + "peer": true, "engines": { "node": ">=10.0.0" }, @@ -7766,6 +7782,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -7930,7 +7947,6 @@ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=10" }, @@ -8026,6 +8042,7 @@ "resolved": "https://registry.npmjs.org/astro/-/astro-5.14.1.tgz", "integrity": "sha512-gPa8NY7/lP8j8g81iy8UwANF3+aukKRWS68IlthZQNgykpg80ne6lbHOp6FErYycxQ1TUhgEfkXVDQZAoJx8Bg==", "license": "MIT", + "peer": true, "dependencies": { "@astrojs/compiler": "^2.12.2", "@astrojs/internal-helpers": "0.7.3", @@ -8504,6 +8521,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001737", "electron-to-chromium": "^1.5.211", @@ -8597,6 +8615,7 @@ "integrity": "sha512-WDtdLmJvAuNNPzByAYpRo2rF1Mmradw6gvWsQKf63476DDXmomT9zUiGypLcG4ibIM67vhAj8jJRdbmEws2Aqw==", "hasInstallScript": true, "license": "MIT", + "peer": true, "dependencies": { "node-gyp-build": "^4.3.0" }, @@ -9535,8 +9554,7 @@ "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/dotenv": { "version": "17.2.2", @@ -9590,6 +9608,7 @@ "resolved": "https://registry.npmjs.org/eciesjs/-/eciesjs-0.4.15.tgz", "integrity": "sha512-r6kEJXDKecVOCj2nLMuXK/FCPeurW33+3JRpfXVbjLja3XUYFfD9I/JBreH6sUyzcm3G/YQboBjMla6poKeSdA==", "license": "MIT", + "peer": true, "dependencies": { "@ecies/ciphers": "^0.2.3", "@noble/ciphers": "^1.3.0", @@ -9904,6 +9923,7 @@ "integrity": "sha512-QePbBFMJFjgmlE+cXAlbHZbHpdFVS2E/6vzCy7aKlebddvl1vadiC4JFV5u/wqTkNUwEV8WrQi257jf5f06hrg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -10605,7 +10625,8 @@ "version": "6.4.9", "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.9.tgz", "integrity": "sha512-JEPTiaOt9f04oa6NOkc4aH+nVp5I3wEjpHbIPqfgCdD5v5bUzy7xQqwcVO2aDQgOWhI28da57HksMrzK9HlRxg==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/eventemitter3": { "version": "5.0.1", @@ -12525,6 +12546,7 @@ "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.5.1.tgz", "integrity": "sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w==", "license": "MIT", + "peer": true, "bin": { "jiti": "lib/jiti-cli.mjs" } @@ -12565,6 +12587,7 @@ "integrity": "sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "cssstyle": "^4.2.1", "data-urls": "^5.0.0", @@ -12746,6 +12769,7 @@ "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.1.tgz", "integrity": "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==", "license": "MPL-2.0", + "peer": true, "dependencies": { "detect-libc": "^2.0.3" }, @@ -13126,7 +13150,6 @@ "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", "dev": true, "license": "MIT", - "peer": true, "bin": { "lz-string": "bin/bin.js" } @@ -15201,7 +15224,6 @@ "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", @@ -15418,6 +15440,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.1.1.tgz", "integrity": "sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -15427,6 +15450,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.1.tgz", "integrity": "sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw==", "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.26.0" }, @@ -15439,6 +15463,7 @@ "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.62.0.tgz", "integrity": "sha512-7KWFejc98xqG/F4bAxpL41NB3o1nnvQO1RWZT3TqRZYL8RryQETGfEdVnJN2fy1crCiBLLjkRBVK05j24FxJGA==", "license": "MIT", + "peer": true, "engines": { "node": ">=18.0.0" }, @@ -15464,8 +15489,7 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/react-refresh": { "version": "0.17.0", @@ -15588,6 +15612,7 @@ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "license": "MIT", + "peer": true, "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -16175,6 +16200,7 @@ "resolved": "https://registry.npmjs.org/seroval/-/seroval-1.3.2.tgz", "integrity": "sha512-RbcPH1n5cfwKrru7v7+zrZvjLurgHhGyso3HTyGtRivGWgYjbOmGuivCQaORNELjNONoK35nj28EoWul9sb1zQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=10" } @@ -16414,6 +16440,7 @@ "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.1.tgz", "integrity": "sha512-hJVXfu3E28NmzGk8o1sHhN3om52tRvwYeidbj7xKy2eIIse5IoKX3USlS6Tqt3BHAtflLIkCQBkzVrEEfWUyYQ==", "license": "MIT", + "peer": true, "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.3.2", @@ -17715,6 +17742,7 @@ "integrity": "sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==", "hasInstallScript": true, "license": "MIT", + "peer": true, "dependencies": { "node-gyp-build": "^4.3.0" }, @@ -17755,6 +17783,7 @@ "resolved": "https://registry.npmjs.org/valtio/-/valtio-1.13.2.tgz", "integrity": "sha512-Qik0o+DSy741TmkqmRfjq+0xpZBXi/Y6+fXZLn0xNF1z/waFMbE3rkivv5Zcf9RrMUp6zswf2J7sbh2KBlba5A==", "license": "MIT", + "peer": true, "dependencies": { "derive-valtio": "0.1.0", "proxy-compare": "2.6.0", @@ -17838,6 +17867,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "@noble/curves": "1.9.1", "@noble/hashes": "1.8.0", @@ -17862,6 +17892,7 @@ "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.5.tgz", "integrity": "sha512-4cKBO9wR75r0BeIWWWId9XK9Lj6La5X846Zw9dFfzMRw38IlTk2iCcUt6hsyiDRcPidc55ZParFYDXi0nXOeLQ==", "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", @@ -18064,6 +18095,7 @@ "resolved": "https://registry.npmjs.org/wagmi/-/wagmi-2.16.9.tgz", "integrity": "sha512-5NbjvuNNhT0t0lQsDD5otQqZ5RZBM1UhInHoBq/Lpnr6xLLa8AWxYqHg5oZtGCdiUNltys11iBOS6z4mLepIqw==", "license": "MIT", + "peer": true, "dependencies": { "@wagmi/connectors": "5.9.9", "@wagmi/core": "2.20.3", @@ -18296,6 +18328,7 @@ "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", "license": "MIT", + "peer": true, "engines": { "node": ">=10.0.0" }, @@ -18596,6 +18629,7 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/frontend/public/badge_bug_hunter.svg b/frontend/public/badge_bug_hunter.svg new file mode 100644 index 0000000..ad2173c --- /dev/null +++ b/frontend/public/badge_bug_hunter.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/public/badge_community.svg b/frontend/public/badge_community.svg new file mode 100644 index 0000000..bba3a56 --- /dev/null +++ b/frontend/public/badge_community.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/public/badge_default.svg b/frontend/public/badge_default.svg new file mode 100644 index 0000000..05cf760 --- /dev/null +++ b/frontend/public/badge_default.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/public/badge_documentation.svg b/frontend/public/badge_documentation.svg new file mode 100644 index 0000000..f1739c6 --- /dev/null +++ b/frontend/public/badge_documentation.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/public/badge_open_source.svg b/frontend/public/badge_open_source.svg new file mode 100644 index 0000000..1bcba58 --- /dev/null +++ b/frontend/public/badge_open_source.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/public/badge_smart_contract.svg b/frontend/public/badge_smart_contract.svg new file mode 100644 index 0000000..1cb5fea --- /dev/null +++ b/frontend/public/badge_smart_contract.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/components/AppWrapper.tsx b/frontend/src/components/AppWrapper.tsx index 80a1421..9aa3b3a 100644 --- a/frontend/src/components/AppWrapper.tsx +++ b/frontend/src/components/AppWrapper.tsx @@ -1,9 +1,8 @@ import "@rainbow-me/rainbowkit/styles.css"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { WagmiProvider } from "wagmi"; -import { RainbowKitProvider } from "@rainbow-me/rainbowkit"; +import { RainbowKitProvider, ConnectButton } from "@rainbow-me/rainbowkit"; import { config } from "../lib/wagmi"; -import { ConnectButton } from "@rainbow-me/rainbowkit"; import { SidebarProvider, SidebarTrigger, @@ -11,7 +10,7 @@ import { } from "@/components/ui/sidebar"; import { AppSidebar } from "@/components/AppSidebar"; import { ActivityTokenBalance } from "@/components/ActivityTokenBalance"; -import { AppBackground } from "@/components/AppBackground"; +import { Background } from "@/components/Background"; const queryClient = new QueryClient(); @@ -25,7 +24,7 @@ export function AppWrapper({ children }: AppWrapperProps) {
- + diff --git a/frontend/src/components/AppBackground.tsx b/frontend/src/components/Background.tsx similarity index 99% rename from frontend/src/components/AppBackground.tsx rename to frontend/src/components/Background.tsx index 6f7747f..c9e3bca 100644 --- a/frontend/src/components/AppBackground.tsx +++ b/frontend/src/components/Background.tsx @@ -1,4 +1,5 @@ -export function AppBackground() { +// components/Background.tsx +export function Background() { return (
= { + "Bug Hunter": "/badge_bug_hunter.svg", + "Community": "/badge_community.svg", + "Documentation": "/badge_documentation.svg", + "Open Source": "/badge_open_source.svg", + "Smart Contract": "/badge_smart_contract.svg", +}; + export function BadgesList(): React.ReactElement { const { data, isLoading, error } = useGetBadges(); const [searchQuery, setSearchQuery] = useState(""); @@ -59,18 +67,22 @@ export function BadgesList(): React.ReactElement {
{filtered.map((badge) => ( - + + } + > - -
- - {badge.name} -
-
+ {badge.name} {badge.description}
-
+ ))}
diff --git a/frontend/src/components/ui/BadgeCard.tsx b/frontend/src/components/ui/BadgeCard.tsx new file mode 100644 index 0000000..d8d878b --- /dev/null +++ b/frontend/src/components/ui/BadgeCard.tsx @@ -0,0 +1,106 @@ +import React, { useRef } from "react"; +import { cn } from "@/lib/utils"; +import { Card } from "@/components/ui/card"; + +interface BadgeCardProps { + children: React.ReactNode; + className?: string; + foregroundIcon?: React.ReactNode; // NEW optional prop +} + +export function BadgeCard({ + children, + className, + foregroundIcon, +}: BadgeCardProps) { + const tiltRef = useRef(null); + const contentRef = useRef(null); + const iconRef = useRef(null); + + const onEnter = () => { + if (!tiltRef.current || !contentRef.current) return; + tiltRef.current.style.transition = "transform 200ms ease-out"; + contentRef.current.style.transition = "transform 200ms ease-out"; + if (iconRef.current) + iconRef.current.style.transition = "transform 200ms ease-out"; + + tiltRef.current.style.transform = "rotateX(0deg) rotateY(0deg)"; + contentRef.current.style.transform = "translateZ(30px)"; + if (iconRef.current) iconRef.current.style.transform = "translateZ(180px)"; + }; + + const onMove = (e: React.MouseEvent) => { + if (!tiltRef.current || !contentRef.current) return; + const rect = tiltRef.current.getBoundingClientRect(); + const x = (e.clientX - rect.left) / rect.width - 0.5; + const y = (e.clientY - rect.top) / rect.height - 0.5; + const rotateX = y * -12; + const rotateY = x * 12; + + tiltRef.current.style.transition = "transform 120ms ease-out"; + contentRef.current.style.transition = "transform 120ms ease-out"; + if (iconRef.current) + iconRef.current.style.transition = "transform 120ms ease-out"; + + tiltRef.current.style.transform = `rotateX(${rotateX}deg) rotateY(${rotateY}deg)`; + contentRef.current.style.transform = `translateZ(30px) translate(${x * 8}px, ${y * 8}px)`; + if (iconRef.current) { + iconRef.current.style.transform = `translateZ(60px) translate(${x * 12}px, ${y * 12}px)`; + } + }; + + const onLeave = () => { + if (!tiltRef.current || !contentRef.current) return; + tiltRef.current.style.transition = "transform 350ms ease-out"; + contentRef.current.style.transition = "transform 350ms ease-out"; + if (iconRef.current) + iconRef.current.style.transition = "transform 350ms ease-out"; + + tiltRef.current.style.transform = "rotateX(0deg) rotateY(0deg)"; + contentRef.current.style.transform = "translateZ(0px)"; + if (iconRef.current) iconRef.current.style.transform = "translateZ(0px)"; + }; + + return ( +
+
+ + {" "} + {foregroundIcon && ( +
+ {foregroundIcon} +
+ )} +
+ {children} +
+
+
+
+ ); +} diff --git a/frontend/src/components/ui/card.tsx b/frontend/src/components/ui/card.tsx index 93a82d9..842314d 100644 --- a/frontend/src/components/ui/card.tsx +++ b/frontend/src/components/ui/card.tsx @@ -1,20 +1,65 @@ import * as React from "react"; - import { cn } from "@/lib/utils"; -function Card({ className, ...props }: React.ComponentProps<"div">) { +// Customized ShadCn component, don't overwrite it +interface CardProps extends React.ComponentProps<"div"> { + with3D?: boolean; + foregroundIcon?: React.ReactNode; +} + +function Card({ + className, + with3D = false, + foregroundIcon, + children, + ...props +}: CardProps) { + const ref = React.useRef(null); + + const handleMouseMove = (e: React.MouseEvent) => { + if (!with3D || !ref.current) return; + const rect = ref.current.getBoundingClientRect(); + const x = e.clientX - rect.left; + const y = e.clientY - rect.top; + const centerX = rect.width / 2; + const centerY = rect.height / 2; + const rotateX = ((y - centerY) / centerY) * -5; + const rotateY = ((x - centerX) / centerX) * 5; + + ref.current.style.transform = `rotateX(${rotateX}deg) rotateY(${rotateY}deg)`; + }; + + const handleMouseLeave = () => { + if (!with3D || !ref.current) return; + ref.current.style.transform = "rotateX(0deg) rotateY(0deg)"; + }; + return (
+ > +
+ {children} +
+ {with3D && foregroundIcon && ( +
+ {foregroundIcon} +
+ )} +
); } +// keep your existing slots the same function CardHeader({ className, ...props }: React.ComponentProps<"div">) { return (