From 98fd8f625eb5c12a021993c4a809748bc057ce7a Mon Sep 17 00:00:00 2001 From: David Yen Date: Sat, 20 Sep 2025 22:42:38 -0700 Subject: [PATCH] Rewrite UI with React95 components --- README.md | 11 +- package-lock.json | 1125 ++--------------------- package.json | 10 - src/components/AimCursor.tsx | 61 +- src/components/BugArea.tsx | 18 +- src/components/BugCard.tsx | 256 ++++-- src/components/BugCrawler.tsx | 173 ++-- src/components/BugForecast.tsx | 17 +- src/components/BugTrendsChart.tsx | 17 +- src/components/Captcha.tsx | 22 +- src/components/Desktop.tsx | 54 +- src/components/DesktopIcon.tsx | 80 +- src/components/DesktopWindow.tsx | 76 +- src/components/FortuneCookie.tsx | 48 +- src/components/StartMenu.tsx | 140 ++- src/components/Taskbar.tsx | 271 +++--- src/components/WindowControlIcons.tsx | 12 +- src/components/components.test.ts | 4 +- src/components/ui/badge-variants.ts | 21 - src/components/ui/badge.tsx | 12 - src/components/ui/card.tsx | 78 -- src/components/ui/label.tsx | 17 - src/components/ui/progress.tsx | 25 - src/components/ui/select.tsx | 114 --- src/components/ui/tabs.tsx | 52 -- src/components/ui/textarea.tsx | 17 - src/components/ui/ui-components.test.ts | 89 -- src/index.css | 33 +- src/lib/utils.test.ts | 16 - src/lib/utils.ts | 6 - src/routes/Bugs.tsx | 48 +- src/routes/Dashboard.tsx | 895 +++++++----------- src/routes/EasterEgg.tsx | 52 +- src/routes/Fortune.tsx | 20 +- src/routes/JobDescription.tsx | 248 +---- src/routes/Leaderboard.tsx | 389 +++++--- src/routes/NewBug.tsx | 242 ++--- src/routes/NotFound.tsx | 60 +- src/routes/SignUp.tsx | 124 +-- src/routes/UserProfile.tsx | 178 ++-- src/routes/Weather.tsx | 86 +- src/types/badge-props.ts | 6 - tailwind.config.js | 76 -- vite.config.ts | 4 +- 44 files changed, 1945 insertions(+), 3358 deletions(-) delete mode 100644 src/components/ui/badge-variants.ts delete mode 100644 src/components/ui/badge.tsx delete mode 100644 src/components/ui/card.tsx delete mode 100644 src/components/ui/label.tsx delete mode 100644 src/components/ui/progress.tsx delete mode 100644 src/components/ui/select.tsx delete mode 100644 src/components/ui/tabs.tsx delete mode 100644 src/components/ui/textarea.tsx delete mode 100644 src/components/ui/ui-components.test.ts delete mode 100644 src/lib/utils.test.ts delete mode 100644 src/lib/utils.ts delete mode 100644 src/types/badge-props.ts delete mode 100644 tailwind.config.js diff --git a/README.md b/README.md index 5e6913c..947a74e 100644 --- a/README.md +++ b/README.md @@ -13,14 +13,10 @@ [![D3.js](https://img.shields.io/badge/D3.js-7.9.0-F7931E?style=flat-square&logo=d3.js&logoColor=white)](https://d3js.org) [![Zustand](https://img.shields.io/badge/Zustand-5.0.6-FF6B6B?style=flat-square&logo=react&logoColor=white)](https://github.com/pmndrs/zustand) [![TypeScript](https://img.shields.io/badge/TypeScript-5.8.3-3178C6?style=flat-square&logo=typescript&logoColor=white)](https://www.typescriptlang.org) -[![Tailwind CSS](https://img.shields.io/badge/Tailwind%20CSS-4.1.11-06B6D4?style=flat-square&logo=tailwindcss&logoColor=white)](https://tailwindcss.com) -[![Framer Motion](https://img.shields.io/badge/Framer%20Motion-12.19.2-0055FF?style=flat-square&logo=framer&logoColor=white)](https://www.framer.com/motion) [![ESLint](https://img.shields.io/badge/ESLint-9.29.0-4B32C3?style=flat-square&logo=eslint)](https://eslint.org) [![Node.js](https://img.shields.io/badge/Node.js-18+-339933?style=flat-square&logo=node.js&logoColor=white)](https://nodejs.org) [![Prettier](https://img.shields.io/badge/Prettier-3.6.2-F7B93E?style=flat-square&logo=prettier&logoColor=black)](https://prettier.io) -[![Lucide React](https://img.shields.io/badge/Lucide%20React-0.523.0-F56565?style=flat-square&logo=lucide&logoColor=white)](https://lucide.dev) -[![Radix UI](https://img.shields.io/badge/Radix%20UI-Multiple%20Versions-161618?style=flat-square&logo=radix-ui&logoColor=white)](https://www.radix-ui.com) [![Bundle Size](https://img.shields.io/badge/Bundle%20Size-Astronomical-purple?style=flat-square&logo=webpack)](vite.config.ts) [![Dependencies](https://img.shields.io/badge/Dependencies-18%20Too%20Many-orange?style=flat-square&logo=npm)](package.json) @@ -52,13 +48,12 @@ This is a "bug bounty hackathon" project that ironically has more bugs than it f - πŸ“Š **Dashboard with Fancy Charts**: D3.js charts now sport gradients, grids, and zoom - πŸ† **Leaderboard System**: Compete with yourself since you're probably the only user - 🎨 **Windows 95 UI**: Powered by React95 components so every window, button, and menu looks like it came straight from 1995 +- 🧱 **Tailwind Detox**: The entire interface now leans on React95 and styled-componentsβ€”no utility classes in sight - πŸͺŸ **Pixel-Perfect Window Controls**: Title bars now rock the classic `_`, `β–‘`, and `X` glyphs straight out of Windows 95 - πŸ–±οΈ **Authentic Desktop Shell**: Draggable, resizable windows with minimize/maximize controls, a living taskbar, and a Start menu that launches every feature - πŸ—ƒοΈ **Zustand State Management**: Because Redux wasn't complicated enough, we needed something "simpler" - ⚑ **Blazing Fast Vite**: The only thing fast about this project - 🎭 **TypeScript**: Added types everywhere except where they're actually needed -- 🎨 **Tailwind CSS**: 47KB of utility classes to style 3 buttons -- 🎬 **Framer Motion**: Animated everything because static websites are for quitters - πŸ–₯️ **Windows 95 Taskbar**: Pixel-perfect Start button, beveled task list, and system tray clock straight from 1995 - πŸ“‚ **Start Menu**: Rebuilt with the teal sidebar, beveled casing, and Start button alignment from classic Windows 95 for quick navigation - πŸ—‚οΈ **Desktop Shortcuts Everywhere**: Every page now shows up on the desktop for @@ -89,10 +84,7 @@ This project showcases the fine art of using 30 dependencies to accomplish what - **React 19**: For building a user interface that could've been static HTML - **TypeScript**: Because JavaScript wasn't confusing enough -- **Tailwind CSS**: Why write 10 lines of CSS when you can write 100 class names? -- **Radix UI**: Premium headless components for basic HTML elements - **D3.js**: Nuclear-powered charting library for displaying 5 data points -- **Framer Motion**: Hollywood-grade animations for hover effects - **Zustand**: State management for 3 variables that could've been `useState` - **UUID**: Industrial-strength ID generation for a client-side game - **Lucide Icons**: 1000+ icons imported to use exactly 7 @@ -183,7 +175,6 @@ npm run format # Rearrange deck chairs on the Titanic src/ β”œβ”€β”€ assets/ # 12 bug PNGs that total 2MB β”œβ”€β”€ components/ # 15 components that could be 3 -β”‚ └── ui/ # Radix UI wrappers because abstraction is life β”œβ”€β”€ lib/ # One utility function that could be inline β”œβ”€β”€ mock/ # Fake data more realistic than our deadlines β”œβ”€β”€ routes/ # 5 pages for a single-page concept diff --git a/package-lock.json b/package-lock.json index e048421..80d1239 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,23 +8,13 @@ "name": "bug-bounty-hackathon", "version": "0.0.0", "dependencies": { - "@radix-ui/react-progress": "^1.1.7", - "@radix-ui/react-tabs": "^1.1.12", - "@tailwindcss/vite": "^4.1.11", "@types/styled-components": "^5.1.34", - "class-variance-authority": "^0.7.1", - "clsx": "^2.1.1", "d3": "^7.9.0", - "framer-motion": "^12.19.2", - "lucide-react": "^0.523.0", "react": "^19.1.0", "react-dom": "^19.1.0", "react-rnd": "^10.5.2", "react95": "^4.0.0", "styled-components": "^6.1.19", - "tailwind-merge": "^3.3.1", - "tailwindcss": "^4.1.11", - "tailwindcss-animate": "^1.0.7", "uuid": "^11.1.0", "zustand": "^5.0.6" }, @@ -48,6 +38,7 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", @@ -367,6 +358,7 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -383,6 +375,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -399,6 +392,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -415,6 +409,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -431,6 +426,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -447,6 +443,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -463,6 +460,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -479,6 +477,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -495,6 +494,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -511,6 +511,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -527,6 +528,7 @@ "cpu": [ "ia32" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -543,6 +545,7 @@ "cpu": [ "loong64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -559,6 +562,7 @@ "cpu": [ "mips64el" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -575,6 +579,7 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -591,6 +596,7 @@ "cpu": [ "riscv64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -607,6 +613,7 @@ "cpu": [ "s390x" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -623,6 +630,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -639,6 +647,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -655,6 +664,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -671,6 +681,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -687,6 +698,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -703,6 +715,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -719,6 +732,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -735,6 +749,7 @@ "cpu": [ "ia32" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -751,6 +766,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -967,22 +983,11 @@ "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@isaacs/fs-minipass": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", - "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", - "license": "ISC", - "dependencies": { - "minipass": "^7.0.4" - }, - "engines": { - "node": ">=18.0.0" - } - }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.8", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", + "dev": true, "license": "MIT", "dependencies": { "@jridgewell/set-array": "^1.2.1", @@ -997,6 +1002,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, "license": "MIT", "engines": { "node": ">=6.0.0" @@ -1006,6 +1012,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true, "license": "MIT", "engines": { "node": ">=6.0.0" @@ -1015,12 +1022,14 @@ "version": "1.5.0", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true, "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.25", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", @@ -1065,318 +1074,6 @@ "node": ">= 8" } }, - "node_modules/@radix-ui/primitive": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.2.tgz", - "integrity": "sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA==", - "license": "MIT" - }, - "node_modules/@radix-ui/react-collection": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.7.tgz", - "integrity": "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-slot": "1.2.3" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-compose-refs": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", - "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-context": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz", - "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-direction": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.1.tgz", - "integrity": "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-id": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz", - "integrity": "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-use-layout-effect": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-presence": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.4.tgz", - "integrity": "sha512-ueDqRbdc4/bkaQT3GIpLQssRlFgWaL/U2z/S31qRwwLWoxHLgry3SIfCwhxeQNbirEUXFa+lq3RL3oBYXtcmIA==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-use-layout-effect": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-primitive": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", - "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-slot": "1.2.3" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-progress": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/@radix-ui/react-progress/-/react-progress-1.1.7.tgz", - "integrity": "sha512-vPdg/tF6YC/ynuBIJlk1mm7Le0VgW6ub6J2UWnTQ7/D23KXcPI1qy+0vBkgKgd38RCMJavBXpB83HPNFMTb0Fg==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-primitive": "2.1.3" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-roving-focus": { - "version": "1.1.10", - "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.10.tgz", - "integrity": "sha512-dT9aOXUen9JSsxnMPv/0VqySQf5eDQ6LCk5Sw28kamz8wSOW2bJdlX2Bg5VUIIcV+6XlHpWTIuTPCf/UNIyq8Q==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.2", - "@radix-ui/react-collection": "1.1.7", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-direction": "1.1.1", - "@radix-ui/react-id": "1.1.1", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-use-callback-ref": "1.1.1", - "@radix-ui/react-use-controllable-state": "1.2.2" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-slot": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", - "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.2" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-tabs": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.12.tgz", - "integrity": "sha512-GTVAlRVrQrSw3cEARM0nAx73ixrWDPNZAruETn3oHCNP6SbZ/hNxdxp+u7VkIEv3/sFoLq1PfcHrl7Pnp0CDpw==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-direction": "1.1.1", - "@radix-ui/react-id": "1.1.1", - "@radix-ui/react-presence": "1.1.4", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-roving-focus": "1.1.10", - "@radix-ui/react-use-controllable-state": "1.2.2" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-callback-ref": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz", - "integrity": "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-controllable-state": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz", - "integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-use-effect-event": "0.0.2", - "@radix-ui/react-use-layout-effect": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-effect-event": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.2.tgz", - "integrity": "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-use-layout-effect": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-layout-effect": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz", - "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, "node_modules/@rolldown/pluginutils": { "version": "1.0.0-beta.19", "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.19.tgz", @@ -1391,6 +1088,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1404,6 +1102,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1417,6 +1116,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1430,6 +1130,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1443,6 +1144,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1456,6 +1158,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1469,6 +1172,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1482,6 +1186,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1495,6 +1200,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1508,6 +1214,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1521,6 +1228,7 @@ "cpu": [ "loong64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1534,6 +1242,7 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1547,6 +1256,7 @@ "cpu": [ "riscv64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1560,6 +1270,7 @@ "cpu": [ "riscv64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1573,6 +1284,7 @@ "cpu": [ "s390x" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1586,6 +1298,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1599,6 +1312,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1612,6 +1326,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1625,6 +1340,7 @@ "cpu": [ "ia32" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1638,274 +1354,13 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ "win32" ] }, - "node_modules/@tailwindcss/node": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.11.tgz", - "integrity": "sha512-yzhzuGRmv5QyU9qLNg4GTlYI6STedBWRE7NjxP45CsFYYq9taI0zJXZBMqIC/c8fViNLhmrbpSFS57EoxUmD6Q==", - "license": "MIT", - "dependencies": { - "@ampproject/remapping": "^2.3.0", - "enhanced-resolve": "^5.18.1", - "jiti": "^2.4.2", - "lightningcss": "1.30.1", - "magic-string": "^0.30.17", - "source-map-js": "^1.2.1", - "tailwindcss": "4.1.11" - } - }, - "node_modules/@tailwindcss/oxide": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.11.tgz", - "integrity": "sha512-Q69XzrtAhuyfHo+5/HMgr1lAiPP/G40OMFAnws7xcFEYqcypZmdW8eGXaOUIeOl1dzPJBPENXgbjsOyhg2nkrg==", - "hasInstallScript": true, - "license": "MIT", - "dependencies": { - "detect-libc": "^2.0.4", - "tar": "^7.4.3" - }, - "engines": { - "node": ">= 10" - }, - "optionalDependencies": { - "@tailwindcss/oxide-android-arm64": "4.1.11", - "@tailwindcss/oxide-darwin-arm64": "4.1.11", - "@tailwindcss/oxide-darwin-x64": "4.1.11", - "@tailwindcss/oxide-freebsd-x64": "4.1.11", - "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.11", - "@tailwindcss/oxide-linux-arm64-gnu": "4.1.11", - "@tailwindcss/oxide-linux-arm64-musl": "4.1.11", - "@tailwindcss/oxide-linux-x64-gnu": "4.1.11", - "@tailwindcss/oxide-linux-x64-musl": "4.1.11", - "@tailwindcss/oxide-wasm32-wasi": "4.1.11", - "@tailwindcss/oxide-win32-arm64-msvc": "4.1.11", - "@tailwindcss/oxide-win32-x64-msvc": "4.1.11" - } - }, - "node_modules/@tailwindcss/oxide-android-arm64": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.11.tgz", - "integrity": "sha512-3IfFuATVRUMZZprEIx9OGDjG3Ou3jG4xQzNTvjDoKmU9JdmoCohQJ83MYd0GPnQIu89YoJqvMM0G3uqLRFtetg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-darwin-arm64": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.11.tgz", - "integrity": "sha512-ESgStEOEsyg8J5YcMb1xl8WFOXfeBmrhAwGsFxxB2CxY9evy63+AtpbDLAyRkJnxLy2WsD1qF13E97uQyP1lfQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-darwin-x64": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.11.tgz", - "integrity": "sha512-EgnK8kRchgmgzG6jE10UQNaH9Mwi2n+yw1jWmof9Vyg2lpKNX2ioe7CJdf9M5f8V9uaQxInenZkOxnTVL3fhAw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-freebsd-x64": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.11.tgz", - "integrity": "sha512-xdqKtbpHs7pQhIKmqVpxStnY1skuNh4CtbcyOHeX1YBE0hArj2romsFGb6yUmzkq/6M24nkxDqU8GYrKrz+UcA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.11.tgz", - "integrity": "sha512-ryHQK2eyDYYMwB5wZL46uoxz2zzDZsFBwfjssgB7pzytAeCCa6glsiJGjhTEddq/4OsIjsLNMAiMlHNYnkEEeg==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.11.tgz", - "integrity": "sha512-mYwqheq4BXF83j/w75ewkPJmPZIqqP1nhoghS9D57CLjsh3Nfq0m4ftTotRYtGnZd3eCztgbSPJ9QhfC91gDZQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm64-musl": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.11.tgz", - "integrity": "sha512-m/NVRFNGlEHJrNVk3O6I9ggVuNjXHIPoD6bqay/pubtYC9QIdAMpS+cswZQPBLvVvEF6GtSNONbDkZrjWZXYNQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-x64-gnu": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.11.tgz", - "integrity": "sha512-YW6sblI7xukSD2TdbbaeQVDysIm/UPJtObHJHKxDEcW2exAtY47j52f8jZXkqE1krdnkhCMGqP3dbniu1Te2Fg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-x64-musl": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.11.tgz", - "integrity": "sha512-e3C/RRhGunWYNC3aSF7exsQkdXzQ/M+aYuZHKnw4U7KQwTJotnWsGOIVih0s2qQzmEzOFIJ3+xt7iq67K/p56Q==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-wasm32-wasi": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.11.tgz", - "integrity": "sha512-Xo1+/GU0JEN/C/dvcammKHzeM6NqKovG+6921MR6oadee5XPBaKOumrJCXvopJ/Qb5TH7LX/UAywbqrP4lax0g==", - "bundleDependencies": [ - "@napi-rs/wasm-runtime", - "@emnapi/core", - "@emnapi/runtime", - "@tybys/wasm-util", - "@emnapi/wasi-threads", - "tslib" - ], - "cpu": [ - "wasm32" - ], - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "^1.4.3", - "@emnapi/runtime": "^1.4.3", - "@emnapi/wasi-threads": "^1.0.2", - "@napi-rs/wasm-runtime": "^0.2.11", - "@tybys/wasm-util": "^0.9.0", - "tslib": "^2.8.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.11.tgz", - "integrity": "sha512-UgKYx5PwEKrac3GPNPf6HVMNhUIGuUh4wlDFR2jYYdkX6pL/rn73zTq/4pzUm8fOjAn5L8zDeHp9iXmUGOXZ+w==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-win32-x64-msvc": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.11.tgz", - "integrity": "sha512-YfHoggn1j0LK7wR82TOucWc5LDCguHnoS879idHekmmiR7g9HUtMw9MI0NHatS28u/Xlkfi9w5RJWgz2Dl+5Qg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/vite": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.1.11.tgz", - "integrity": "sha512-RHYhrR3hku0MJFRV+fN2gNbDNEh3dwKvY8XJvTxCSXeMOsCRSr+uKvDWQcbizrHgjML6ZmTE5OwMrl5wKcujCw==", - "license": "MIT", - "dependencies": { - "@tailwindcss/node": "4.1.11", - "@tailwindcss/oxide": "4.1.11", - "tailwindcss": "4.1.11" - }, - "peerDependencies": { - "vite": "^5.2.0 || ^6 || ^7" - } - }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -2239,6 +1694,7 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", + "dev": true, "license": "MIT" }, "node_modules/@types/geojson": { @@ -2280,7 +1736,7 @@ "version": "19.1.6", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.6.tgz", "integrity": "sha512-4hOiT/dwO8Ko0gV1m/TJZYk3y0KBnY9vzDh7W+DH17b2HFSOGgdj33dhihPeuy3l0q23+4e+hoXHV6hCC4dCXw==", - "devOptional": true, + "dev": true, "license": "MIT", "peerDependencies": { "@types/react": "^19.0.0" @@ -2785,40 +2241,10 @@ "supports-color": "^7.1.0" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/chownr": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", - "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, - "node_modules/class-variance-authority": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", - "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==", - "license": "Apache-2.0", - "dependencies": { - "clsx": "^2.1.1" - }, - "funding": { - "url": "https://polar.sh/cva" - } - }, - "node_modules/clsx": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", - "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", - "license": "MIT", - "engines": { - "node": ">=6" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, "node_modules/color-convert": { @@ -3340,15 +2766,6 @@ "robust-predicates": "^3.0.2" } }, - "node_modules/detect-libc": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", - "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==", - "license": "Apache-2.0", - "engines": { - "node": ">=8" - } - }, "node_modules/electron-to-chromium": { "version": "1.5.155", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.155.tgz", @@ -3356,23 +2773,11 @@ "dev": true, "license": "ISC" }, - "node_modules/enhanced-resolve": { - "version": "5.18.2", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.2.tgz", - "integrity": "sha512-6Jw4sE1maoRJo3q8MsSIn2onJFbLTOjY9hlx4DZXmOKvLRd1Ok2kXmAGXaafL2+ijsJZ1ClYbl/pmqr9+k4iUQ==", - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" - }, - "engines": { - "node": ">=10.13.0" - } - }, "node_modules/esbuild": { "version": "0.25.4", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.4.tgz", "integrity": "sha512-8pgjLUcUjcgDg+2Q4NYXnPbo/vncAY4UmyaCm0jZevERqCHZIaWwdJHkf8XQtu4AxSKCdvrUbT0XUr1IdZzI8Q==", + "dev": true, "hasInstallScript": true, "license": "MIT", "bin": { @@ -3761,37 +3166,11 @@ "dev": true, "license": "ISC" }, - "node_modules/framer-motion": { - "version": "12.19.2", - "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.19.2.tgz", - "integrity": "sha512-0cWMLkYr+i0emeXC4hkLF+5aYpzo32nRdQ0D/5DI460B3O7biQ3l2BpDzIGsAHYuZ0fpBP0DC8XBkVf6RPAlZw==", - "license": "MIT", - "dependencies": { - "motion-dom": "^12.19.0", - "motion-utils": "^12.19.0", - "tslib": "^2.4.0" - }, - "peerDependencies": { - "@emotion/is-prop-valid": "*", - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" - }, - "peerDependenciesMeta": { - "@emotion/is-prop-valid": { - "optional": true - }, - "react": { - "optional": true - }, - "react-dom": { - "optional": true - } - } - }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, "hasInstallScript": true, "license": "MIT", "optional": true, @@ -3838,12 +3217,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "license": "ISC" - }, "node_modules/graphemer": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", @@ -3968,15 +3341,6 @@ "dev": true, "license": "ISC" }, - "node_modules/jiti": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz", - "integrity": "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==", - "license": "MIT", - "bin": { - "jiti": "lib/jiti-cli.mjs" - } - }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -4067,234 +3431,6 @@ "node": ">= 0.8.0" } }, - "node_modules/lightningcss": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.1.tgz", - "integrity": "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==", - "license": "MPL-2.0", - "dependencies": { - "detect-libc": "^2.0.3" - }, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - }, - "optionalDependencies": { - "lightningcss-darwin-arm64": "1.30.1", - "lightningcss-darwin-x64": "1.30.1", - "lightningcss-freebsd-x64": "1.30.1", - "lightningcss-linux-arm-gnueabihf": "1.30.1", - "lightningcss-linux-arm64-gnu": "1.30.1", - "lightningcss-linux-arm64-musl": "1.30.1", - "lightningcss-linux-x64-gnu": "1.30.1", - "lightningcss-linux-x64-musl": "1.30.1", - "lightningcss-win32-arm64-msvc": "1.30.1", - "lightningcss-win32-x64-msvc": "1.30.1" - } - }, - "node_modules/lightningcss-darwin-arm64": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.1.tgz", - "integrity": "sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-x64": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.1.tgz", - "integrity": "sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-freebsd-x64": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.1.tgz", - "integrity": "sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.1.tgz", - "integrity": "sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q==", - "cpu": [ - "arm" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.1.tgz", - "integrity": "sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.1.tgz", - "integrity": "sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.1.tgz", - "integrity": "sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-musl": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.1.tgz", - "integrity": "sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.1.tgz", - "integrity": "sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.1.tgz", - "integrity": "sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -4340,24 +3476,6 @@ "yallist": "^3.0.2" } }, - "node_modules/lucide-react": { - "version": "0.523.0", - "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.523.0.tgz", - "integrity": "sha512-rUjQoy7egZT9XYVXBK1je9ckBnNp7qzRZOhLQx5RcEp2dCGlXo+mv6vf7Am4LimEcFBJIIZzSGfgTqc9QCrPSw==", - "license": "ISC", - "peerDependencies": { - "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" - } - }, - "node_modules/magic-string": { - "version": "0.30.17", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", - "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0" - } - }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -4395,57 +3513,6 @@ "node": "*" } }, - "node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "license": "ISC", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/minizlib": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.0.2.tgz", - "integrity": "sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==", - "license": "MIT", - "dependencies": { - "minipass": "^7.1.2" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/mkdirp": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", - "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", - "license": "MIT", - "bin": { - "mkdirp": "dist/cjs/src/bin.js" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/motion-dom": { - "version": "12.19.0", - "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.19.0.tgz", - "integrity": "sha512-m96uqq8VbwxFLU0mtmlsIVe8NGGSdpBvBSHbnnOJQxniPaabvVdGgxSamhuDwBsRhwX7xPxdICgVJlOpzn/5bw==", - "license": "MIT", - "dependencies": { - "motion-utils": "^12.19.0" - } - }, - "node_modules/motion-utils": { - "version": "12.19.0", - "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.19.0.tgz", - "integrity": "sha512-BuFTHINYmV07pdWs6lj6aI63vr2N4dg0vR+td0rtrdpWOhBzIkEklZyLcvKBoEtwSqx8Jg06vUB5RS0xDiUybw==", - "license": "MIT" - }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -4600,6 +3667,7 @@ "version": "8.5.6", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, "funding": [ { "type": "opencollective", @@ -4841,6 +3909,7 @@ "version": "4.41.0", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.41.0.tgz", "integrity": "sha512-HqMFpUbWlf/tvcxBFNKnJyzc7Lk+XO3FGc3pbNBLqEbOz0gPLRgcrlS3UF4MfUrVlstOaP/q0kM6GVvi+LrLRg==", + "dev": true, "license": "MIT", "dependencies": { "@types/estree": "1.0.7" @@ -5060,70 +4129,11 @@ "node": ">=8" } }, - "node_modules/tailwind-merge": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.3.1.tgz", - "integrity": "sha512-gBXpgUm/3rp1lMZZrM/w7D8GKqshif0zAymAhbCyIt8KMe+0v9DQ7cdYLR4FHH/cKpdTXb+A/tKKU3eolfsI+g==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/dcastil" - } - }, - "node_modules/tailwindcss": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz", - "integrity": "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==", - "license": "MIT" - }, - "node_modules/tailwindcss-animate": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/tailwindcss-animate/-/tailwindcss-animate-1.0.7.tgz", - "integrity": "sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==", - "license": "MIT", - "peerDependencies": { - "tailwindcss": ">=3.0.0 || insiders" - } - }, - "node_modules/tapable": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.2.tgz", - "integrity": "sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/tar": { - "version": "7.4.3", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.4.3.tgz", - "integrity": "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==", - "license": "ISC", - "dependencies": { - "@isaacs/fs-minipass": "^4.0.0", - "chownr": "^3.0.0", - "minipass": "^7.1.2", - "minizlib": "^3.0.1", - "mkdirp": "^3.0.1", - "yallist": "^5.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/tar/node_modules/yallist": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", - "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, "node_modules/tinyglobby": { "version": "0.2.14", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", + "dev": true, "license": "MIT", "dependencies": { "fdir": "^6.4.4", @@ -5140,6 +4150,7 @@ "version": "6.4.6", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz", "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==", + "dev": true, "license": "MIT", "peerDependencies": { "picomatch": "^3 || ^4" @@ -5154,6 +4165,7 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -5188,12 +4200,6 @@ "typescript": ">=4.8.4" } }, - "node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "license": "0BSD" - }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -5302,6 +4308,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/vite/-/vite-7.0.0.tgz", "integrity": "sha512-ixXJB1YRgDIw2OszKQS9WxGHKwLdCsbQNkpJN171udl6szi/rIySHL6/Os3s2+oE4P/FLD4dxg4mD7Wust+u5g==", + "dev": true, "license": "MIT", "dependencies": { "esbuild": "^0.25.0", @@ -5376,6 +4383,7 @@ "version": "6.4.6", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz", "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==", + "dev": true, "license": "MIT", "peerDependencies": { "picomatch": "^3 || ^4" @@ -5390,6 +4398,7 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, "license": "MIT", "engines": { "node": ">=12" diff --git a/package.json b/package.json index a31bb03..fd67ef4 100644 --- a/package.json +++ b/package.json @@ -14,23 +14,13 @@ "test": "node --experimental-transform-types --loader ./png-loader.js --experimental-test-coverage --test-coverage-include=src/components/** --test-coverage-include=src/routes/** --test" }, "dependencies": { - "@radix-ui/react-progress": "^1.1.7", - "@radix-ui/react-tabs": "^1.1.12", - "@tailwindcss/vite": "^4.1.11", "@types/styled-components": "^5.1.34", - "class-variance-authority": "^0.7.1", - "clsx": "^2.1.1", "d3": "^7.9.0", - "framer-motion": "^12.19.2", - "lucide-react": "^0.523.0", "react": "^19.1.0", "react-dom": "^19.1.0", "react-rnd": "^10.5.2", "react95": "^4.0.0", "styled-components": "^6.1.19", - "tailwind-merge": "^3.3.1", - "tailwindcss": "^4.1.11", - "tailwindcss-animate": "^1.0.7", "uuid": "^11.1.0", "zustand": "^5.0.6" }, diff --git a/src/components/AimCursor.tsx b/src/components/AimCursor.tsx index 68f0b64..57318e8 100644 --- a/src/components/AimCursor.tsx +++ b/src/components/AimCursor.tsx @@ -1,34 +1,53 @@ import React from 'react' +import { styled } from 'styled-components' import type { AimCursorProps } from '../types/aim-cursor-props' +const CursorWrapper = styled.div` + position: absolute; + z-index: 100; + pointer-events: none; +` + +const CursorCircle = styled.div` + position: absolute; + inset: 0; + border-radius: 50%; + border: 2px solid #cc0000; +` + +const CursorLineVertical = styled.div` + position: absolute; + top: 0; + bottom: 0; + left: 50%; + width: 1px; + background: #cc0000; + transform: translateX(-0.5px); +` + +const CursorLineHorizontal = styled.div` + position: absolute; + left: 0; + right: 0; + top: 50%; + height: 1px; + background: #cc0000; + transform: translateY(-0.5px); +` + /** A simple red cross-hair that never intercepts pointer events. */ const AimCursor: React.FC = ({ x, y }) => { const size = 24 const half = size / 2 return ( -
- {/* outer ring */} -
- {/* vertical line */} -
- {/* horizontal line */} -
-
+ + + + ) } diff --git a/src/components/BugArea.tsx b/src/components/BugArea.tsx index fd16566..c6a96b2 100644 --- a/src/components/BugArea.tsx +++ b/src/components/BugArea.tsx @@ -1,4 +1,5 @@ import { useEffect, useMemo, useRef, useState, useCallback } from 'react' +import { styled } from 'styled-components' import { useElementSize } from '../hooks/use-element-size' import type { BugAreaProps } from '../types/bug-area-props' import BugCrawler from './BugCrawler' @@ -10,6 +11,15 @@ import { useBugStore } from '../store' const SPEED = 320 // px per second the aim moves when an input is held/tilted const DEAD_ZONE = 0.15 // ignore tiny stick deflections +const AreaWrapper = styled.div` + position: relative; + width: 100%; + height: 100%; + overflow: hidden; + user-select: none; + cursor: none; +` + const BugArea: React.FC = ({ bugs }) => { /* ---------- container size tracking ---------- */ const containerRef = useRef(null) @@ -313,11 +323,7 @@ const BugArea: React.FC = ({ bugs }) => { /* ---------- render ---------- */ return ( -
+ {/* Aim cross-hair */} @@ -337,7 +343,7 @@ const BugArea: React.FC = ({ bugs }) => { /> ) })} -
+ ) } diff --git a/src/components/BugCard.tsx b/src/components/BugCard.tsx index 315cf7b..2c8e81e 100644 --- a/src/components/BugCard.tsx +++ b/src/components/BugCard.tsx @@ -1,10 +1,139 @@ -import { motion } from 'framer-motion' +import { styled } from 'styled-components' +import { Frame } from 'react95' +import type { + KeyboardEvent as ReactKeyboardEvent, + MouseEvent as ReactMouseEvent, +} from 'react' import type { BugCardProps } from '../types/bug-card-props' +import type { Bug } from '../types/bug' import { useBugStore, priorityModel } from '../store' -import clsx from 'clsx' import { getBugImage } from '../utils/utils' import { predictPriorityProbability } from '../lib/bug-priority-ml.ts' +type CardShellProps = { + $preview: boolean + $modal: boolean + $inactive: boolean +} + +type PriorityProps = { $priority?: Bug['priority'] } + +type ImageProps = { $modal: boolean } + +const cardWidth = ({ $preview, $modal }: CardShellProps) => + $preview ? '200px' : $modal ? 'min(90vw, 420px)' : '320px' + +const cardCursor = ({ $inactive }: CardShellProps) => + $inactive ? 'not-allowed' : 'pointer' + +const cardOpacity = ({ $inactive }: CardShellProps) => ($inactive ? 0.6 : 1) + +const cardFilter = ({ $inactive }: CardShellProps) => + $inactive ? 'grayscale(0.4)' : 'none' + +const cardHoverTransform = ({ $inactive }: CardShellProps) => + $inactive ? 'none' : 'translateY(-4px)' + +const imageHeight = ({ $modal }: ImageProps) => ($modal ? '200px' : '180px') + +const priorityBorder = ({ $priority }: PriorityProps) => + $priority === 'high' + ? '#b22222' + : $priority === 'medium' + ? '#b8860b' + : '#1f4f8f' + +const priorityBackground = ({ $priority }: PriorityProps) => + $priority === 'high' + ? '#f5d6d6' + : $priority === 'medium' + ? '#f4e5c0' + : '#d5e1f7' + +const CardShell = styled(Frame)` + position: relative; + width: ${cardWidth}; + background: ${({ theme }) => theme.material}; + display: flex; + flex-direction: column; + padding: 0; + overflow: hidden; + transition: + transform 0.2s ease, + box-shadow 0.2s ease; + cursor: ${cardCursor}; + opacity: ${cardOpacity}; + filter: ${cardFilter}; + + &:hover { + transform: ${cardHoverTransform}; + } +` + +const BugImage = styled.img` + width: 100%; + height: ${imageHeight}; + object-fit: cover; +` + +const Content = styled.div` + display: flex; + flex-direction: column; + gap: 12px; + padding: 12px 16px 16px; +` + +const HeaderRow = styled.div` + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 8px; +` + +const Title = styled.h3` + flex: 1; + margin: 0; + font-size: 18px; + line-height: 1.2; +` + +const Badge = styled.span` + display: inline-flex; + align-items: center; + padding: 2px 6px; + font-size: 11px; + border: 1px solid ${({ theme }) => theme.borderDark}; + background: ${({ theme }) => theme.canvas}; + font-family: 'ms_sans_serif', 'Microsoft Sans Serif', sans-serif; +` + +const PriorityBadge = styled(Badge)` + border-color: ${priorityBorder}; + background: ${priorityBackground}; + text-transform: uppercase; + letter-spacing: 0.05em; +` + +const Description = styled.p` + margin: 0; + font-size: 13px; + line-height: 1.4; +` + +const SquashedOverlay = styled.div` + position: absolute; + inset: 0; + background: rgba(178, 34, 34, 0.35); + display: flex; + align-items: center; + justify-content: center; + font-size: 22px; + font-weight: 700; + color: #ffffff; + text-shadow: 0 0 6px rgba(0, 0, 0, 0.6); + pointer-events: none; +` + export const BugCard: React.FC = ({ bug, preview = false, @@ -17,91 +146,56 @@ export const BugCard: React.FC = ({ ? predictPriorityProbability(bug.bounty, priorityModel) : null + const handleSquash = () => { + if (!bug.active) return + squashBug(bug.id) + } + + const handleClick = (event: ReactMouseEvent) => { + event.stopPropagation() + handleSquash() + } + + const handleKeyDown = (event: ReactKeyboardEvent) => { + if (event.key === 'Enter' || event.key === ' ') { + event.preventDefault() + handleSquash() + } + } + return ( - { - e.stopPropagation() - if (bug.active) { - squashBug(bug.id) - } - }} - initial={{ opacity: 0, scale: 0.9 }} - animate={{ opacity: 1, scale: 1 }} - transition={{ duration: 0.3 }} + - {/* Overlay for squashed bugs */} - {!bug.active && ( - - - SQUASHED - - - )} - -
- {!preview && ( - {bug.title} - )} - -
-

- {bug.title} -

- - +{bug.bounty} - - - {bug.pto}h PTO - + {!bug.active && SQUASHED} + + {!preview && } + + + + {bug.title} + +{bug.bounty} + {bug.pto}h PTO {bug.priority && ( - + {bug.priority} - + )} {highProb !== null && ( - - {Math.round(highProb * 100)}% - + {Math.round(highProb * 100)}% high risk )} -
- -

{bug.description}

-
-
+ + {bug.description} + + ) } diff --git a/src/components/BugCrawler.tsx b/src/components/BugCrawler.tsx index 3d4c889..6753957 100644 --- a/src/components/BugCrawler.tsx +++ b/src/components/BugCrawler.tsx @@ -1,16 +1,58 @@ import { useState, useEffect, useRef } from 'react' -import { motion } from 'framer-motion' +import { styled } from 'styled-components' +import { createPortal } from 'react-dom' +import { Button } from 'react95' import type { BugCrawlerProps } from '../types/bug-crawler-props' import { getBugImage } from '../utils/utils' import { BugCard } from './BugCard' -import ReactDOM from 'react-dom' -import clsx from 'clsx' import { useBugStore } from '../store' -import { Button } from 'react95' -/** ----------------------------------------------------------------------- - * BugCrawler β€” makes a bug sprite wander with a lightweight 3-D effect - * ---------------------------------------------------------------------- */ +const SPRITE_SIZE = 40 + +const BugSprite = styled.div` + position: absolute; + width: ${SPRITE_SIZE}px; + height: ${SPRITE_SIZE}px; + transform-style: preserve-3d; +` + +const BugImage = styled.img<{ $inactive: boolean }>` + width: 100%; + height: 100%; + transform-style: preserve-3d; + filter: ${({ $inactive }) => + $inactive + ? 'grayscale(0.6) opacity(0.5) drop-shadow(0 2px 4px rgba(0,0,0,0.2))' + : 'drop-shadow(0 4px 6px rgba(0,0,0,0.35))'}; + transition: transform 0.1s linear; +` + +const PreviewContainer = styled.div` + position: fixed; + z-index: 50; + pointer-events: none; +` + +const ModalBackdrop = styled.div` + position: fixed; + inset: 0; + z-index: 50; + display: flex; + align-items: center; + justify-content: center; + padding: 16px; + background: rgba(0, 0, 0, 0.2); +` + +const ModalContent = styled.div` + position: relative; +` + +const CloseButton = styled(Button).attrs({ square: true, size: 'sm' as const })` + position: absolute; + top: 8px; + right: 8px; +` const BugCrawler: React.FC = ({ x, @@ -19,22 +61,18 @@ const BugCrawler: React.FC = ({ containerWidth, containerHeight, }) => { - /* ---------------- UI state ---------------- */ const [showPreview, setShowPreview] = useState(false) const [hoverPos, setHoverPos] = useState({ x: 0, y: 0 }) - const inspectBug = useBugStore(s => s.inspectBug) const inspectedId = useBugStore(s => s.inspectedId) const showModal = inspectedId === bug.id - // Refs for focus management const bugRef = useRef(null) const modalRef = useRef(null) const wasOpenRef = useRef(showModal) const isAlive = bug.active - /* -------------- position & heading -------------- */ const [position, setPosition] = useState({ x, y }) const positionRef = useRef({ x, y }) @@ -45,38 +83,30 @@ const BugCrawler: React.FC = ({ }) const directionRef = useRef(direction) - /* -------------- timing refs -------------- */ const lastTurn = useRef(Date.now()) const rafRef = useRef() - /* keep refs fresh */ useEffect(() => { directionRef.current = direction }, [direction]) - // Manage focus when modal opens/closes useEffect(() => { if (showModal && isAlive) { wasOpenRef.current = true - requestAnimationFrame(() => { - modalRef.current?.focus() - }) - } else { - if (wasOpenRef.current) { - bugRef.current?.focus() - } + requestAnimationFrame(() => modalRef.current?.focus()) + } else if (wasOpenRef.current) { + bugRef.current?.focus() wasOpenRef.current = false } }, [showModal, isAlive]) - /* -------------- main rAF loop -------------- */ useEffect(() => { let last = performance.now() const step = (now: number) => { if (!isAlive) return - const dt = (now - last) / 1000 // seconds since last frame + const dt = (now - last) / 1000 last = now const elapsed = now - lastTurn.current @@ -90,12 +120,12 @@ const BugCrawler: React.FC = ({ lastTurn.current = now } - const speed = 36 // px per second - const bugSize = 40 + const speed = 36 + const maxX = (containerWidth || window.innerWidth) - SPRITE_SIZE + const maxY = (containerHeight || window.innerHeight) - SPRITE_SIZE + let newX = positionRef.current.x + directionRef.current.x * speed * dt let newY = positionRef.current.y + directionRef.current.y * speed * dt - const maxX = (containerWidth || window.innerWidth) - bugSize - const maxY = (containerHeight || window.innerHeight) - bugSize if (newX <= 0 || newX >= maxX) { directionRef.current.x *= -1 @@ -118,102 +148,71 @@ const BugCrawler: React.FC = ({ } }, [isAlive, containerWidth, containerHeight]) - /* -------------- transforms -------------- */ const heading2D = (Math.atan2(direction.y, direction.x) * 180) / Math.PI - 90 const tiltY = direction.x * 25 const tiltX = 18 const bugImage = getBugImage(bug.id) - /* ---------------- render ---------------- */ return ( <> - {/* perspective wrapper gives our sprite depth */} - inspectBug(bug.id)} - className="absolute cursor-pointer" - style={{ perspective: 600, width: 40, height: 40, zIndex: 5 }} + onMouseEnter={event => { + const rect = event.currentTarget.getBoundingClientRect() + setHoverPos({ x: rect.left, y: rect.top }) + setShowPreview(true) + }} + onMouseLeave={() => setShowPreview(false)} + style={{ + transform: `translate3d(${position.x}px, ${position.y}px, 0)`, + perspective: 600, + cursor: isAlive ? 'pointer' : 'default', + zIndex: isAlive ? 5 : 3, + }} > - { - const rect = e.currentTarget.getBoundingClientRect() - setHoverPos({ x: rect.left, y: rect.top }) - setShowPreview(true) - }} - onMouseLeave={() => setShowPreview(false)} - className={clsx( - 'w-full h-full will-change-transform', - !bug.active && 'grayscale opacity-50' - )} - animate={{ - rotate: heading2D + 180, - rotateY: tiltY, - rotateX: tiltX, - transition: { duration: 0.016, ease: 'linear' }, - }} + $inactive={!isAlive} style={{ - transformStyle: 'preserve-3d', - filter: 'drop-shadow(0 4px 6px rgba(0,0,0,0.35))', + transform: `rotate(${heading2D + 180}deg) rotateY(${tiltY}deg) rotateX(${tiltX}deg)`, }} /> - {/* hover card preview */} {showPreview && isAlive && - ReactDOM.createPortal( -
-
, + , document.body )} -
+ - {/* modal on click */} {showModal && isAlive && ( -
{ - e.stopPropagation() + onClick={event => { + event.stopPropagation() inspectBug(null) }} > -
{ - e.stopPropagation() + { + event.stopPropagation() }} > - + inspectBug(null)}>βœ• -
-
+ + )} ) diff --git a/src/components/BugForecast.tsx b/src/components/BugForecast.tsx index 3763f59..69e776f 100644 --- a/src/components/BugForecast.tsx +++ b/src/components/BugForecast.tsx @@ -1,9 +1,20 @@ import { useEffect, useRef } from 'react' import * as d3 from 'd3' +import { styled } from 'styled-components' import { Bug } from '../types/bug' import { forecastBugCounts } from '../lib/bug-forecast' import { useElementSize } from '../hooks/use-element-size' +const ForecastWrapper = styled.div` + width: 100%; +` + +const ForecastSvg = styled.svg` + width: 100%; + height: 160px; + user-select: none; +` + const BugForecast = ({ bugs }: { bugs: Bug[] }) => { const wrapperRef = useRef(null) const svgRef = useRef(null) @@ -69,9 +80,9 @@ const BugForecast = ({ bugs }: { bugs: Bug[] }) => { }, [bugs, size.width]) return ( -
- -
+ + + ) } diff --git a/src/components/BugTrendsChart.tsx b/src/components/BugTrendsChart.tsx index 0a300a4..18f649f 100644 --- a/src/components/BugTrendsChart.tsx +++ b/src/components/BugTrendsChart.tsx @@ -1,6 +1,7 @@ import { useEffect, useRef } from 'react' import { useElementSize } from '../hooks/use-element-size' import * as d3 from 'd3' +import { styled } from 'styled-components' import { Bug } from '../types/bug' import type { DataPoint } from '../types/bug-trends-chart' @@ -11,6 +12,16 @@ import type { DataPoint } from '../types/bug-trends-chart' * β€’ Resizes automatically with its container (ResizeObserver). * β€’ Displays a friendly "No data" placeholder if nothing to plot. */ +const ChartWrapper = styled.div` + width: 100%; +` + +const ChartSvg = styled.svg` + width: 100%; + height: 320px; + user-select: none; +` + const BugTrendsChart = ({ bugs }: { bugs: Bug[] }) => { const wrapperRef = useRef(null) const svgRef = useRef(null) @@ -411,9 +422,9 @@ const BugTrendsChart = ({ bugs }: { bugs: Bug[] }) => { }, [bugs, size.width]) return ( -
- -
+ + + ) } diff --git a/src/components/Captcha.tsx b/src/components/Captcha.tsx index ae0f022..81db274 100644 --- a/src/components/Captcha.tsx +++ b/src/components/Captcha.tsx @@ -1,8 +1,18 @@ import { useState, useEffect } from 'react' -import { Label } from './ui/label' +import { styled } from 'styled-components' import type { CaptchaProps } from '../types/captcha-props' import { TextInput } from 'react95' +const CaptchaContainer = styled.div` + display: flex; + flex-direction: column; + gap: 4px; +` + +const QuestionLabel = styled.label` + font-size: 13px; +` + export default function Captcha({ onChange }: CaptchaProps) { const [a, setA] = useState(0) const [b, setB] = useState(0) @@ -19,16 +29,16 @@ export default function Captcha({ onChange }: CaptchaProps) { }, [answer, a, b, onChange]) return ( -
- + setAnswer(e.target.value)} + onChange={event => setAnswer(event.target.value)} fullWidth /> -
+ ) } diff --git a/src/components/Desktop.tsx b/src/components/Desktop.tsx index ee6007a..454000a 100644 --- a/src/components/Desktop.tsx +++ b/src/components/Desktop.tsx @@ -1,4 +1,5 @@ import { useEffect, useMemo, useRef, useState } from 'react' +import { styled } from 'styled-components' import DesktopIcon from './DesktopIcon' import DesktopWindow from './DesktopWindow' import Taskbar from './Taskbar' @@ -7,10 +8,39 @@ import { DESKTOP_SHORTCUTS } from '../utils/window-apps' import type { WindowSize } from '../types/window' const DEFAULT_SIZE: WindowSize = { width: 1024, height: 720 } -const CONTAINER_PADDING = 32 // Tailwind p-4 -const ICON_GAP = 24 // Tailwind gap-6 +const ICON_GAP = 24 +const CONTAINER_PADDING = 32 const DEFAULT_ICON_SIZE = { width: 96, height: 112 } +const DesktopRoot = styled.div` + min-height: 100vh; + background: ${({ theme }) => theme.desktopBackground}; + color: ${({ theme }) => theme.canvasText}; + font-family: 'ms_sans_serif', 'Microsoft Sans Serif', sans-serif; + display: flex; + flex-direction: column; +` + +const DesktopSurface = styled.div` + flex: 1; + position: relative; + overflow: hidden; + padding: ${CONTAINER_PADDING}px; + display: flex; +` + +const IconColumns = styled.div` + display: flex; + gap: ${ICON_GAP}px; + align-items: flex-start; +` + +const IconColumn = styled.div` + display: flex; + flex-direction: column; + gap: ${ICON_GAP}px; +` + export default function Desktop() { const containerRef = useRef(null) const [containerSize, setContainerSize] = useState(DEFAULT_SIZE) @@ -124,15 +154,11 @@ export default function Desktop() { ]) return ( -
-
-
+ + + {shortcutsByColumn.map((column, columnIndex) => ( -
+ {column.map((app, itemIndex) => ( ))} -
+ ))} -
+ {sortedWindows.map(window => ( ))} -
+ -
+ ) } diff --git a/src/components/DesktopIcon.tsx b/src/components/DesktopIcon.tsx index 9b127b6..b739e56 100644 --- a/src/components/DesktopIcon.tsx +++ b/src/components/DesktopIcon.tsx @@ -1,9 +1,56 @@ import { forwardRef, useState } from 'react' +import { styled } from 'styled-components' import { useWindowManager } from '../contexts/WindowManagerContext' import type { WindowAppDefinition } from '../utils/window-apps' -const baseClasses = - 'flex flex-col items-center justify-center w-24 gap-2 text-center text-white cursor-default select-none bg-transparent border-none !px-0 !py-3 focus:outline-none focus-visible:outline focus-visible:outline-offset-1 focus-visible:outline-white' +const IconButton = styled.button<{ $selected: boolean }>` + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 10px; + width: 96px; + background: transparent; + border: none; + padding: 12px 4px; + color: ${({ theme }) => theme.canvasTextInvert}; + cursor: default; + user-select: none; + font-family: 'ms_sans_serif', 'Microsoft Sans Serif', sans-serif; + + &:focus-visible { + outline: 1px dotted ${({ theme }) => theme.canvasTextInvert}; + outline-offset: 4px; + } +` + +const IconGlyph = styled.div<{ $selected: boolean }>` + display: flex; + align-items: center; + justify-content: center; + width: 48px; + height: 48px; + font-size: 28px; + border: ${({ $selected, theme }) => + $selected + ? `1px solid ${theme.canvasTextInvert}` + : '1px solid transparent'}; + background: ${({ $selected }) => ($selected ? '#000080' : 'transparent')}; +` + +const IconLabel = styled.span<{ $selected: boolean }>` + padding: 2px 6px; + text-align: center; + font-size: 13px; + line-height: 1.2; + color: ${({ $selected, theme }) => + $selected ? theme.canvasTextInvert : theme.canvasTextInvert}; + background: ${({ $selected }) => ($selected ? '#000080' : 'transparent')}; + border: ${({ $selected, theme }) => + $selected + ? `1px solid ${theme.canvasTextInvert}` + : '1px solid transparent'}; +` const DesktopIcon = forwardRef( ({ app }, ref) => { @@ -11,33 +58,22 @@ const DesktopIcon = forwardRef( const [selected, setSelected] = useState(false) return ( - + + {app.icon} + + {app.title} + ) } ) diff --git a/src/components/DesktopWindow.tsx b/src/components/DesktopWindow.tsx index 5a2be8d..06ccb18 100644 --- a/src/components/DesktopWindow.tsx +++ b/src/components/DesktopWindow.tsx @@ -1,5 +1,6 @@ import { Suspense } from 'react' import { Rnd } from 'react-rnd' +import { styled } from 'styled-components' import { Button, Window, WindowContent, WindowHeader } from 'react95' import { Win95CloseIcon, @@ -16,6 +17,46 @@ type Props = { containerSize: WindowSize } +const WindowShell = styled(Window)` + display: flex; + flex-direction: column; + height: 100%; + width: 100%; +` + +const TitleBar = styled(WindowHeader).attrs({ className: 'win95-title-bar' })` + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; +` + +const TitleText = styled.span` + flex: 1; + overflow: hidden; + text-overflow: ellipsis; + padding-right: 8px; +` + +const ControlGroup = styled.div` + display: flex; + gap: 4px; +` + +const Content = styled(WindowContent)` + flex: 1; + width: 100%; + overflow: hidden; +` + +const LoadingFallback = styled.div` + display: flex; + align-items: center; + justify-content: center; + height: 100%; + padding: 16px; +` + export default function DesktopWindow({ windowState, containerSize }: Props) { const { closeWindow, @@ -61,16 +102,15 @@ export default function DesktopWindow({ windowState, containerSize }: Props) { enableResizing={!windowState.maximized} minWidth={360} minHeight={240} - className={`absolute ${windowState.minimized ? 'hidden' : ''}`} - style={{ zIndex: windowState.zIndex }} + style={{ + display: windowState.minimized ? 'none' : undefined, + zIndex: windowState.zIndex, + }} > - - - {windowState.title} -
+ + + {windowState.title} + -
-
- - - Loading... -
- } - > + + + + Loading...}> setWindowTitle(windowState.id, title)} /> - - + + ) } diff --git a/src/components/FortuneCookie.tsx b/src/components/FortuneCookie.tsx index f1b8f9a..10275ff 100644 --- a/src/components/FortuneCookie.tsx +++ b/src/components/FortuneCookie.tsx @@ -1,5 +1,5 @@ import { useEffect, useState } from 'react' -import { motion } from 'framer-motion' +import { styled } from 'styled-components' import { Button, Frame } from 'react95' const FORTUNES = [ @@ -11,6 +11,32 @@ const FORTUNES = [ 'Every bug you find hides two more.', ] +const FortuneFrame = styled(Frame).attrs({ + variant: 'window' as const, + shadow: true, +})` + margin-top: 8px; + background: ${({ theme }) => theme.material}; + padding: 16px; + text-align: center; + display: flex; + flex-direction: column; + gap: 12px; +` + +const FortuneText = styled(Frame).attrs({ variant: 'well' as const })` + display: inline-block; + background: ${({ theme }) => theme.canvas}; + padding: 6px 12px; + font-size: 13px; +` + +const ButtonRow = styled.div` + display: flex; + justify-content: center; + gap: 12px; +` + export default function FortuneCookie() { const [fortune, setFortune] = useState('') @@ -31,22 +57,12 @@ export default function FortuneCookie() { }, []) return ( - - - - πŸ₯  {fortune} - - -
+ + πŸ₯  {fortune} + -
- + + ) } diff --git a/src/components/StartMenu.tsx b/src/components/StartMenu.tsx index a441964..0c36ba3 100644 --- a/src/components/StartMenu.tsx +++ b/src/components/StartMenu.tsx @@ -1,4 +1,6 @@ import { useEffect } from 'react' +import { styled } from 'styled-components' +import { Frame, MenuList, MenuListItem } from 'react95' import { START_MENU_APPS } from '../utils/window-apps' import { useWindowManager } from '../contexts/WindowManagerContext' @@ -9,6 +11,74 @@ type StartMenuProps = { const MENU_WIDTH = 260 +const StartMenuPositioner = styled.div` + position: fixed; + z-index: 50; + width: ${MENU_WIDTH}px; +` + +const StartMenuFrame = styled(Frame).attrs({ + variant: 'window' as const, + shadow: true, +})` + display: flex; + overflow: hidden; +` + +const BrandColumn = styled.div` + width: 40px; + background: linear-gradient(180deg, #00007b 0%, #008080 60%, #4ea0c6 100%); + display: flex; + align-items: flex-end; + justify-content: center; +` + +const BrandText = styled.span` + writing-mode: vertical-rl; + transform: rotate(180deg); + font-family: 'ms_sans_serif', 'Microsoft Sans Serif', sans-serif; + font-size: 11px; + font-weight: 700; + letter-spacing: 0.12em; + text-transform: uppercase; + color: ${({ theme }) => theme.canvasTextInvert}; + padding: 6px 0; +` + +const MenuBody = styled.div` + flex: 1; + background: ${({ theme }) => theme.material}; + border-left: 1px solid ${({ theme }) => theme.borderDark}; +` + +const StyledMenuList = styled(MenuList)` + margin: 0; + padding: 6px; + display: flex; + flex-direction: column; + gap: 4px; +` + +const StyledMenuItem = styled(MenuListItem)` + display: flex; + align-items: center; + gap: 8px; + padding: 6px 10px; + font-weight: 600; + cursor: pointer; +` + +const ItemIcon = styled.span` + font-size: 18px; +` + +const ItemLabel = styled.span` + flex: 1; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +` + export default function StartMenu({ anchorRect, onClose }: StartMenuProps) { const { openWindow } = useWindowManager() @@ -34,56 +104,32 @@ export default function StartMenu({ anchorRect, onClose }: StartMenuProps) { const bottom = Math.max(viewportHeight - anchorTop + 2, 8) return ( -
-
-
-
- Windows 95 -
-
-
    - {START_MENU_APPS.map(item => ( -
  • - { - openWindow(item.id) - onClose() - }} - /> -
  • - ))} -
-
-
-
-
- ) -} - -type StartMenuItemProps = { - icon: string - title: string - onSelect: () => void -} - -function StartMenuItem({ icon, title, onSelect }: StartMenuItemProps) { - return ( - + + + Windows 95 + + + + {START_MENU_APPS.map(item => ( + { + openWindow(item.id) + onClose() + }} + > + {item.icon} + {item.title} + + ))} + + + + ) } diff --git a/src/components/Taskbar.tsx b/src/components/Taskbar.tsx index d7c2a54..54f9a3b 100644 --- a/src/components/Taskbar.tsx +++ b/src/components/Taskbar.tsx @@ -1,13 +1,95 @@ -import clsx from 'clsx' -import { forwardRef, useEffect, useLayoutEffect, useRef, useState } from 'react' +import { + forwardRef, + useEffect, + useLayoutEffect, + useMemo, + useRef, + useState, +} from 'react' +import { styled } from 'styled-components' +import { AppBar, Toolbar, Button, Frame, Separator } from 'react95' import StartMenu from './StartMenu' import { useWindowManager } from '../contexts/WindowManagerContext' const getTime = () => new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }) -const taskbarFrame = - 'shadow-[inset_0_1px_0_0_#ffffff,inset_0_2px_0_0_#dfdfdf,inset_0_-1px_0_0_#808080,inset_0_-2px_0_0_#404040]' +const TaskbarAppBar = styled(AppBar)` + position: relative; + top: auto; + bottom: 0; + left: 0; + right: auto; +` + +const TaskbarToolbar = styled(Toolbar)` + display: flex; + align-items: center; + gap: 8px; + padding: 4px 8px; +` + +const StyledStartButton = styled(Button)` + display: inline-flex; + align-items: center; + gap: 8px; + padding: 4px 14px; + font-weight: 700; + font-size: 13px; +` + +const WindowButtonsFrame = styled(Frame).attrs({ variant: 'status' as const })` + flex: 1; + display: flex; + align-items: center; + gap: 6px; + padding: 4px; + overflow-x: auto; + overflow-y: hidden; + min-height: 36px; +` + +const WindowButton = styled(Button).attrs({ + variant: 'thin' as const, + size: 'sm' as const, +})` + min-width: 140px; + display: inline-flex; + align-items: center; + justify-content: flex-start; + gap: 8px; + font-weight: 600; + text-align: left; + white-space: nowrap; +` + +const StatusGroup = styled.div` + display: flex; + align-items: stretch; + gap: 8px; +` + +const StatusIconsFrame = styled(Frame).attrs({ variant: 'status' as const })` + display: flex; + align-items: center; + gap: 8px; + padding: 4px 10px; +` + +const ClockFrame = styled(Frame).attrs({ variant: 'status' as const })` + min-width: 88px; + display: flex; + align-items: center; + justify-content: center; + padding: 4px 12px; + font-variant-numeric: tabular-nums; +` + +const StartMenuBackdrop = styled.div` + position: fixed; + inset: 0; + z-index: 40; +` type StartButtonProps = { open: boolean @@ -15,39 +97,24 @@ type StartButtonProps = { } const StartButton = forwardRef( - ({ open, onClick }, ref) => { - return ( - - ) - } + ({ open, onClick }, ref) => ( + + + Start + + ) ) StartButton.displayName = 'StartButton' -function Windows95Logo({ className = '' }: { className?: string }) { +function Windows95Logo() { return ( -