rizwarrior commited on
Commit
5de5922
·
0 Parent(s):

Initial commit - Demucs Docker app

Browse files
.dockerignore ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Node modules and build artifacts
2
+ node_modules/
3
+ dist/
4
+ build/
5
+
6
+ # Python cache
7
+ __pycache__/
8
+ *.pyc
9
+ *.pyo
10
+ *.pyd
11
+ .Python
12
+
13
+ # Development files
14
+ .git/
15
+ .gitignore
16
+ .vscode/
17
+ .idea/
18
+
19
+ # OS files
20
+ .DS_Store
21
+ Thumbs.db
22
+
23
+ # Logs
24
+ *.log
25
+ npm-debug.log*
26
+
27
+ # Runtime data
28
+ pids/
29
+ *.pid
30
+ *.seed
31
+ *.pid.lock
32
+
33
+ # Coverage directory used by tools like istanbul
34
+ coverage/
35
+
36
+ # Environment variables
37
+ .env
38
+ .env.local
39
+ .env.development.local
40
+ .env.test.local
41
+ .env.production.local
42
+
43
+ # Temporary files and directories
44
+ tmp/
45
+ temp/
46
+ separated/
47
+ songs/
48
+
49
+ # Package lock files (we'll use npm ci instead)
50
+ package-lock.json
51
+
52
+ # README (we'll create a new one)
53
+ README.md
.gitignore ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ # Dependencies
3
+ node_modules/
4
+ npm-debug.log*
5
+ yarn-debug.log*
6
+ yarn-error.log*
7
+
8
+ # Production builds
9
+ build/
10
+ dist/
11
+
12
+ # Python
13
+ __pycache__/
14
+ *.py[cod]
15
+ *$py.class
16
+ *.so
17
+ .Python
18
+ env/
19
+ venv/
20
+ ENV/
21
+ env.bak/
22
+ venv.bak/
23
+
24
+ # Exclude embedded repositories
25
+ demucs/
26
+
27
+ # Exclude large FFmpeg build
28
+ ffmpeg-7.1.1-essentials_build/
29
+
30
+ # Environment variables
31
+ .env
32
+ .env.local
33
+ .env.development.local
34
+ .env.test.local
35
+ .env.production.local
36
+
37
+ # Logs
38
+ *.log
39
+ logs/
40
+
41
+ # Runtime data
42
+ pids/
43
+ *.pid
44
+ *.seed
45
+ *.pid.lock
46
+
47
+ # OS generated files
48
+ .DS_Store
49
+ .DS_Store?
50
+ ._*
51
+ .Spotlight-V100
52
+ .Trashes
53
+ ehthumbs.db
54
+ Thumbs.db
55
+ desktop.ini
56
+
57
+ # IDE files
58
+ .vscode/
59
+ .idea/
60
+ *.swp
61
+ *.swo
62
+ *~
63
+
64
+ # Temporary files
65
+ *.tmp
66
+ *.temp
67
+ .cache/
68
+
69
+ # Audio processing directories
70
+ uploads/
71
+ separated/
72
+
73
+ # Coverage directory used by tools like istanbul
74
+ coverage/
75
+
76
+ # Optional npm cache directory
77
+ .npm
78
+
79
+ # Optional eslint cache
80
+ .eslintcache
Dockerfile ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Multi-stage build for React frontend + Flask backend
2
+ FROM node:18-alpine AS frontend-build
3
+
4
+ # Set working directory
5
+ WORKDIR /app
6
+
7
+ # Copy package files
8
+ COPY package*.json ./
9
+
10
+ # Install dependencies
11
+ RUN npm install
12
+
13
+ # Copy source code
14
+ COPY src/ ./src/
15
+ COPY index.html ./
16
+ COPY vite.config.js ./
17
+
18
+ # Build the React app
19
+ RUN npm run build
20
+
21
+ # Python stage
22
+ FROM python:3.10-slim
23
+
24
+ # Set working directory
25
+ WORKDIR /app
26
+
27
+ # Install system dependencies
28
+ RUN apt-get update && apt-get install -y \
29
+ ffmpeg \
30
+ && rm -rf /var/lib/apt/lists/*
31
+
32
+ # Copy requirements and install Python dependencies
33
+ COPY requirements.txt .
34
+ RUN pip install --no-cache-dir -r requirements.txt
35
+
36
+ # Copy the Flask server
37
+ COPY server.py .
38
+
39
+ # Copy the built React app from the frontend stage
40
+ COPY --from=frontend-build /app/dist ./dist
41
+
42
+ # Create a non-root user
43
+ RUN useradd -m -u 1000 user
44
+ USER user
45
+
46
+ # Expose port 7860 (Hugging Face Spaces default)
47
+ EXPOSE 7860
48
+
49
+ # Start the Flask server
50
+ CMD ["python", "server.py"]
README.md ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Demucs Music Source Separation
3
+ emoji: 🎵
4
+ colorFrom: purple
5
+ colorTo: pink
6
+ sdk: docker
7
+ pinned: false
8
+ license: mit
9
+ ---
10
+
11
+ # Demucs Music Source Separation
12
+
13
+ An AI-powered web application for separating music into individual stems (vocals, drums, bass, other) using Meta's Demucs model.
14
+
15
+ ## Features
16
+
17
+ - 🎵 **High-Quality Separation**: Uses the latest Demucs htdemucs model for superior audio separation
18
+ - 🎨 **Beautiful UI**: Modern React interface with real-time progress tracking
19
+ - 📱 **Responsive Design**: Works on desktop and mobile devices
20
+ - ⚡ **Fast Processing**: Optimized for quick audio processing
21
+ - 💾 **Easy Download**: Download individual stems as WAV files
22
+
23
+ ## Supported Audio Formats
24
+
25
+ - MP3
26
+ - WAV
27
+ - FLAC
28
+ - OGG
29
+ - M4A
30
+ - AAC
31
+
32
+ ## How to Use
33
+
34
+ 1. **Upload** your audio file using the drag-and-drop interface
35
+ 2. **Wait** for the AI to process and separate the audio (progress bar shows status)
36
+ 3. **Listen** to individual stems using the built-in audio players
37
+ 4. **Download** the separated tracks you want to keep
38
+
39
+ ## Technology Stack
40
+
41
+ - **Frontend**: React + Vite
42
+ - **Backend**: Flask + Python
43
+ - **AI Model**: Meta's Demucs (Hybrid Transformer)
44
+ - **Audio Processing**: PyTorch + TorchAudio
45
+ - **Deployment**: Docker + Hugging Face Spaces
46
+
47
+ ## Local Development
48
+
49
+ ```bash
50
+ # Install dependencies
51
+ npm install
52
+ pip install -r requirements.txt
53
+
54
+ # Start development servers
55
+ npm run dev # Frontend (port 3001)
56
+ python server.py # Backend (port 5000)
57
+ ```
58
+
59
+ ## Credits
60
+
61
+ - [Demucs](https://github.com/facebookresearch/demucs) by Meta Research
62
+ - Built with ❤️ for the music community
63
+
64
+ ## License
65
+
66
+ MIT License - feel free to use and modify!
index.html ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <link rel="icon" type="image/svg+xml" href="/vite.svg" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <meta name="theme-color" content="#000000" />
8
+ <meta
9
+ name="description"
10
+ content="AI-powered music source separation with Demucs"
11
+ />
12
+ <title>Demucs - Music Source Separation</title>
13
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
14
+ </head>
15
+ <body>
16
+ <noscript>You need to enable JavaScript to run this app.</noscript>
17
+ <div id="root"></div>
18
+ <script type="module" src="/src/index.js"></script>
19
+ </body>
20
+ </html>
package-lock.json ADDED
@@ -0,0 +1,1586 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "demucs-frontend",
3
+ "version": "0.1.0",
4
+ "lockfileVersion": 3,
5
+ "requires": true,
6
+ "packages": {
7
+ "": {
8
+ "name": "demucs-frontend",
9
+ "version": "0.1.0",
10
+ "dependencies": {
11
+ "axios": "^1.4.0",
12
+ "lucide-react": "^0.263.1",
13
+ "react": "^18.2.0",
14
+ "react-dom": "^18.2.0"
15
+ },
16
+ "devDependencies": {
17
+ "@types/react": "^18.2.15",
18
+ "@types/react-dom": "^18.2.7",
19
+ "@vitejs/plugin-react": "^4.0.3",
20
+ "vite": "^4.4.5"
21
+ }
22
+ },
23
+ "node_modules/@ampproject/remapping": {
24
+ "version": "2.3.0",
25
+ "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",
26
+ "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==",
27
+ "dev": true,
28
+ "license": "Apache-2.0",
29
+ "dependencies": {
30
+ "@jridgewell/gen-mapping": "^0.3.5",
31
+ "@jridgewell/trace-mapping": "^0.3.24"
32
+ },
33
+ "engines": {
34
+ "node": ">=6.0.0"
35
+ }
36
+ },
37
+ "node_modules/@babel/code-frame": {
38
+ "version": "7.27.1",
39
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
40
+ "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==",
41
+ "dev": true,
42
+ "license": "MIT",
43
+ "dependencies": {
44
+ "@babel/helper-validator-identifier": "^7.27.1",
45
+ "js-tokens": "^4.0.0",
46
+ "picocolors": "^1.1.1"
47
+ },
48
+ "engines": {
49
+ "node": ">=6.9.0"
50
+ }
51
+ },
52
+ "node_modules/@babel/compat-data": {
53
+ "version": "7.28.0",
54
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.0.tgz",
55
+ "integrity": "sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw==",
56
+ "dev": true,
57
+ "license": "MIT",
58
+ "engines": {
59
+ "node": ">=6.9.0"
60
+ }
61
+ },
62
+ "node_modules/@babel/core": {
63
+ "version": "7.28.3",
64
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.3.tgz",
65
+ "integrity": "sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ==",
66
+ "dev": true,
67
+ "license": "MIT",
68
+ "dependencies": {
69
+ "@ampproject/remapping": "^2.2.0",
70
+ "@babel/code-frame": "^7.27.1",
71
+ "@babel/generator": "^7.28.3",
72
+ "@babel/helper-compilation-targets": "^7.27.2",
73
+ "@babel/helper-module-transforms": "^7.28.3",
74
+ "@babel/helpers": "^7.28.3",
75
+ "@babel/parser": "^7.28.3",
76
+ "@babel/template": "^7.27.2",
77
+ "@babel/traverse": "^7.28.3",
78
+ "@babel/types": "^7.28.2",
79
+ "convert-source-map": "^2.0.0",
80
+ "debug": "^4.1.0",
81
+ "gensync": "^1.0.0-beta.2",
82
+ "json5": "^2.2.3",
83
+ "semver": "^6.3.1"
84
+ },
85
+ "engines": {
86
+ "node": ">=6.9.0"
87
+ },
88
+ "funding": {
89
+ "type": "opencollective",
90
+ "url": "https://opencollective.com/babel"
91
+ }
92
+ },
93
+ "node_modules/@babel/generator": {
94
+ "version": "7.28.3",
95
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.3.tgz",
96
+ "integrity": "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==",
97
+ "dev": true,
98
+ "license": "MIT",
99
+ "dependencies": {
100
+ "@babel/parser": "^7.28.3",
101
+ "@babel/types": "^7.28.2",
102
+ "@jridgewell/gen-mapping": "^0.3.12",
103
+ "@jridgewell/trace-mapping": "^0.3.28",
104
+ "jsesc": "^3.0.2"
105
+ },
106
+ "engines": {
107
+ "node": ">=6.9.0"
108
+ }
109
+ },
110
+ "node_modules/@babel/helper-compilation-targets": {
111
+ "version": "7.27.2",
112
+ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz",
113
+ "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==",
114
+ "dev": true,
115
+ "license": "MIT",
116
+ "dependencies": {
117
+ "@babel/compat-data": "^7.27.2",
118
+ "@babel/helper-validator-option": "^7.27.1",
119
+ "browserslist": "^4.24.0",
120
+ "lru-cache": "^5.1.1",
121
+ "semver": "^6.3.1"
122
+ },
123
+ "engines": {
124
+ "node": ">=6.9.0"
125
+ }
126
+ },
127
+ "node_modules/@babel/helper-globals": {
128
+ "version": "7.28.0",
129
+ "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz",
130
+ "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==",
131
+ "dev": true,
132
+ "license": "MIT",
133
+ "engines": {
134
+ "node": ">=6.9.0"
135
+ }
136
+ },
137
+ "node_modules/@babel/helper-module-imports": {
138
+ "version": "7.27.1",
139
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz",
140
+ "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==",
141
+ "dev": true,
142
+ "license": "MIT",
143
+ "dependencies": {
144
+ "@babel/traverse": "^7.27.1",
145
+ "@babel/types": "^7.27.1"
146
+ },
147
+ "engines": {
148
+ "node": ">=6.9.0"
149
+ }
150
+ },
151
+ "node_modules/@babel/helper-module-transforms": {
152
+ "version": "7.28.3",
153
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz",
154
+ "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==",
155
+ "dev": true,
156
+ "license": "MIT",
157
+ "dependencies": {
158
+ "@babel/helper-module-imports": "^7.27.1",
159
+ "@babel/helper-validator-identifier": "^7.27.1",
160
+ "@babel/traverse": "^7.28.3"
161
+ },
162
+ "engines": {
163
+ "node": ">=6.9.0"
164
+ },
165
+ "peerDependencies": {
166
+ "@babel/core": "^7.0.0"
167
+ }
168
+ },
169
+ "node_modules/@babel/helper-plugin-utils": {
170
+ "version": "7.27.1",
171
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz",
172
+ "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==",
173
+ "dev": true,
174
+ "license": "MIT",
175
+ "engines": {
176
+ "node": ">=6.9.0"
177
+ }
178
+ },
179
+ "node_modules/@babel/helper-string-parser": {
180
+ "version": "7.27.1",
181
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
182
+ "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
183
+ "dev": true,
184
+ "license": "MIT",
185
+ "engines": {
186
+ "node": ">=6.9.0"
187
+ }
188
+ },
189
+ "node_modules/@babel/helper-validator-identifier": {
190
+ "version": "7.27.1",
191
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz",
192
+ "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==",
193
+ "dev": true,
194
+ "license": "MIT",
195
+ "engines": {
196
+ "node": ">=6.9.0"
197
+ }
198
+ },
199
+ "node_modules/@babel/helper-validator-option": {
200
+ "version": "7.27.1",
201
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz",
202
+ "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==",
203
+ "dev": true,
204
+ "license": "MIT",
205
+ "engines": {
206
+ "node": ">=6.9.0"
207
+ }
208
+ },
209
+ "node_modules/@babel/helpers": {
210
+ "version": "7.28.3",
211
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.3.tgz",
212
+ "integrity": "sha512-PTNtvUQihsAsDHMOP5pfobP8C6CM4JWXmP8DrEIt46c3r2bf87Ua1zoqevsMo9g+tWDwgWrFP5EIxuBx5RudAw==",
213
+ "dev": true,
214
+ "license": "MIT",
215
+ "dependencies": {
216
+ "@babel/template": "^7.27.2",
217
+ "@babel/types": "^7.28.2"
218
+ },
219
+ "engines": {
220
+ "node": ">=6.9.0"
221
+ }
222
+ },
223
+ "node_modules/@babel/parser": {
224
+ "version": "7.28.3",
225
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.3.tgz",
226
+ "integrity": "sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA==",
227
+ "dev": true,
228
+ "license": "MIT",
229
+ "dependencies": {
230
+ "@babel/types": "^7.28.2"
231
+ },
232
+ "bin": {
233
+ "parser": "bin/babel-parser.js"
234
+ },
235
+ "engines": {
236
+ "node": ">=6.0.0"
237
+ }
238
+ },
239
+ "node_modules/@babel/plugin-transform-react-jsx-self": {
240
+ "version": "7.27.1",
241
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz",
242
+ "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==",
243
+ "dev": true,
244
+ "license": "MIT",
245
+ "dependencies": {
246
+ "@babel/helper-plugin-utils": "^7.27.1"
247
+ },
248
+ "engines": {
249
+ "node": ">=6.9.0"
250
+ },
251
+ "peerDependencies": {
252
+ "@babel/core": "^7.0.0-0"
253
+ }
254
+ },
255
+ "node_modules/@babel/plugin-transform-react-jsx-source": {
256
+ "version": "7.27.1",
257
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz",
258
+ "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==",
259
+ "dev": true,
260
+ "license": "MIT",
261
+ "dependencies": {
262
+ "@babel/helper-plugin-utils": "^7.27.1"
263
+ },
264
+ "engines": {
265
+ "node": ">=6.9.0"
266
+ },
267
+ "peerDependencies": {
268
+ "@babel/core": "^7.0.0-0"
269
+ }
270
+ },
271
+ "node_modules/@babel/template": {
272
+ "version": "7.27.2",
273
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz",
274
+ "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==",
275
+ "dev": true,
276
+ "license": "MIT",
277
+ "dependencies": {
278
+ "@babel/code-frame": "^7.27.1",
279
+ "@babel/parser": "^7.27.2",
280
+ "@babel/types": "^7.27.1"
281
+ },
282
+ "engines": {
283
+ "node": ">=6.9.0"
284
+ }
285
+ },
286
+ "node_modules/@babel/traverse": {
287
+ "version": "7.28.3",
288
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.3.tgz",
289
+ "integrity": "sha512-7w4kZYHneL3A6NP2nxzHvT3HCZ7puDZZjFMqDpBPECub79sTtSO5CGXDkKrTQq8ksAwfD/XI2MRFX23njdDaIQ==",
290
+ "dev": true,
291
+ "license": "MIT",
292
+ "dependencies": {
293
+ "@babel/code-frame": "^7.27.1",
294
+ "@babel/generator": "^7.28.3",
295
+ "@babel/helper-globals": "^7.28.0",
296
+ "@babel/parser": "^7.28.3",
297
+ "@babel/template": "^7.27.2",
298
+ "@babel/types": "^7.28.2",
299
+ "debug": "^4.3.1"
300
+ },
301
+ "engines": {
302
+ "node": ">=6.9.0"
303
+ }
304
+ },
305
+ "node_modules/@babel/types": {
306
+ "version": "7.28.2",
307
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.2.tgz",
308
+ "integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==",
309
+ "dev": true,
310
+ "license": "MIT",
311
+ "dependencies": {
312
+ "@babel/helper-string-parser": "^7.27.1",
313
+ "@babel/helper-validator-identifier": "^7.27.1"
314
+ },
315
+ "engines": {
316
+ "node": ">=6.9.0"
317
+ }
318
+ },
319
+ "node_modules/@esbuild/android-arm": {
320
+ "version": "0.18.20",
321
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz",
322
+ "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==",
323
+ "cpu": [
324
+ "arm"
325
+ ],
326
+ "dev": true,
327
+ "license": "MIT",
328
+ "optional": true,
329
+ "os": [
330
+ "android"
331
+ ],
332
+ "engines": {
333
+ "node": ">=12"
334
+ }
335
+ },
336
+ "node_modules/@esbuild/android-arm64": {
337
+ "version": "0.18.20",
338
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz",
339
+ "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==",
340
+ "cpu": [
341
+ "arm64"
342
+ ],
343
+ "dev": true,
344
+ "license": "MIT",
345
+ "optional": true,
346
+ "os": [
347
+ "android"
348
+ ],
349
+ "engines": {
350
+ "node": ">=12"
351
+ }
352
+ },
353
+ "node_modules/@esbuild/android-x64": {
354
+ "version": "0.18.20",
355
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz",
356
+ "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==",
357
+ "cpu": [
358
+ "x64"
359
+ ],
360
+ "dev": true,
361
+ "license": "MIT",
362
+ "optional": true,
363
+ "os": [
364
+ "android"
365
+ ],
366
+ "engines": {
367
+ "node": ">=12"
368
+ }
369
+ },
370
+ "node_modules/@esbuild/darwin-arm64": {
371
+ "version": "0.18.20",
372
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz",
373
+ "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==",
374
+ "cpu": [
375
+ "arm64"
376
+ ],
377
+ "dev": true,
378
+ "license": "MIT",
379
+ "optional": true,
380
+ "os": [
381
+ "darwin"
382
+ ],
383
+ "engines": {
384
+ "node": ">=12"
385
+ }
386
+ },
387
+ "node_modules/@esbuild/darwin-x64": {
388
+ "version": "0.18.20",
389
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz",
390
+ "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==",
391
+ "cpu": [
392
+ "x64"
393
+ ],
394
+ "dev": true,
395
+ "license": "MIT",
396
+ "optional": true,
397
+ "os": [
398
+ "darwin"
399
+ ],
400
+ "engines": {
401
+ "node": ">=12"
402
+ }
403
+ },
404
+ "node_modules/@esbuild/freebsd-arm64": {
405
+ "version": "0.18.20",
406
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz",
407
+ "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==",
408
+ "cpu": [
409
+ "arm64"
410
+ ],
411
+ "dev": true,
412
+ "license": "MIT",
413
+ "optional": true,
414
+ "os": [
415
+ "freebsd"
416
+ ],
417
+ "engines": {
418
+ "node": ">=12"
419
+ }
420
+ },
421
+ "node_modules/@esbuild/freebsd-x64": {
422
+ "version": "0.18.20",
423
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz",
424
+ "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==",
425
+ "cpu": [
426
+ "x64"
427
+ ],
428
+ "dev": true,
429
+ "license": "MIT",
430
+ "optional": true,
431
+ "os": [
432
+ "freebsd"
433
+ ],
434
+ "engines": {
435
+ "node": ">=12"
436
+ }
437
+ },
438
+ "node_modules/@esbuild/linux-arm": {
439
+ "version": "0.18.20",
440
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz",
441
+ "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==",
442
+ "cpu": [
443
+ "arm"
444
+ ],
445
+ "dev": true,
446
+ "license": "MIT",
447
+ "optional": true,
448
+ "os": [
449
+ "linux"
450
+ ],
451
+ "engines": {
452
+ "node": ">=12"
453
+ }
454
+ },
455
+ "node_modules/@esbuild/linux-arm64": {
456
+ "version": "0.18.20",
457
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz",
458
+ "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==",
459
+ "cpu": [
460
+ "arm64"
461
+ ],
462
+ "dev": true,
463
+ "license": "MIT",
464
+ "optional": true,
465
+ "os": [
466
+ "linux"
467
+ ],
468
+ "engines": {
469
+ "node": ">=12"
470
+ }
471
+ },
472
+ "node_modules/@esbuild/linux-ia32": {
473
+ "version": "0.18.20",
474
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz",
475
+ "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==",
476
+ "cpu": [
477
+ "ia32"
478
+ ],
479
+ "dev": true,
480
+ "license": "MIT",
481
+ "optional": true,
482
+ "os": [
483
+ "linux"
484
+ ],
485
+ "engines": {
486
+ "node": ">=12"
487
+ }
488
+ },
489
+ "node_modules/@esbuild/linux-loong64": {
490
+ "version": "0.18.20",
491
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz",
492
+ "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==",
493
+ "cpu": [
494
+ "loong64"
495
+ ],
496
+ "dev": true,
497
+ "license": "MIT",
498
+ "optional": true,
499
+ "os": [
500
+ "linux"
501
+ ],
502
+ "engines": {
503
+ "node": ">=12"
504
+ }
505
+ },
506
+ "node_modules/@esbuild/linux-mips64el": {
507
+ "version": "0.18.20",
508
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz",
509
+ "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==",
510
+ "cpu": [
511
+ "mips64el"
512
+ ],
513
+ "dev": true,
514
+ "license": "MIT",
515
+ "optional": true,
516
+ "os": [
517
+ "linux"
518
+ ],
519
+ "engines": {
520
+ "node": ">=12"
521
+ }
522
+ },
523
+ "node_modules/@esbuild/linux-ppc64": {
524
+ "version": "0.18.20",
525
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz",
526
+ "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==",
527
+ "cpu": [
528
+ "ppc64"
529
+ ],
530
+ "dev": true,
531
+ "license": "MIT",
532
+ "optional": true,
533
+ "os": [
534
+ "linux"
535
+ ],
536
+ "engines": {
537
+ "node": ">=12"
538
+ }
539
+ },
540
+ "node_modules/@esbuild/linux-riscv64": {
541
+ "version": "0.18.20",
542
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz",
543
+ "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==",
544
+ "cpu": [
545
+ "riscv64"
546
+ ],
547
+ "dev": true,
548
+ "license": "MIT",
549
+ "optional": true,
550
+ "os": [
551
+ "linux"
552
+ ],
553
+ "engines": {
554
+ "node": ">=12"
555
+ }
556
+ },
557
+ "node_modules/@esbuild/linux-s390x": {
558
+ "version": "0.18.20",
559
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz",
560
+ "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==",
561
+ "cpu": [
562
+ "s390x"
563
+ ],
564
+ "dev": true,
565
+ "license": "MIT",
566
+ "optional": true,
567
+ "os": [
568
+ "linux"
569
+ ],
570
+ "engines": {
571
+ "node": ">=12"
572
+ }
573
+ },
574
+ "node_modules/@esbuild/linux-x64": {
575
+ "version": "0.18.20",
576
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz",
577
+ "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==",
578
+ "cpu": [
579
+ "x64"
580
+ ],
581
+ "dev": true,
582
+ "license": "MIT",
583
+ "optional": true,
584
+ "os": [
585
+ "linux"
586
+ ],
587
+ "engines": {
588
+ "node": ">=12"
589
+ }
590
+ },
591
+ "node_modules/@esbuild/netbsd-x64": {
592
+ "version": "0.18.20",
593
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz",
594
+ "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==",
595
+ "cpu": [
596
+ "x64"
597
+ ],
598
+ "dev": true,
599
+ "license": "MIT",
600
+ "optional": true,
601
+ "os": [
602
+ "netbsd"
603
+ ],
604
+ "engines": {
605
+ "node": ">=12"
606
+ }
607
+ },
608
+ "node_modules/@esbuild/openbsd-x64": {
609
+ "version": "0.18.20",
610
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz",
611
+ "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==",
612
+ "cpu": [
613
+ "x64"
614
+ ],
615
+ "dev": true,
616
+ "license": "MIT",
617
+ "optional": true,
618
+ "os": [
619
+ "openbsd"
620
+ ],
621
+ "engines": {
622
+ "node": ">=12"
623
+ }
624
+ },
625
+ "node_modules/@esbuild/sunos-x64": {
626
+ "version": "0.18.20",
627
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz",
628
+ "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==",
629
+ "cpu": [
630
+ "x64"
631
+ ],
632
+ "dev": true,
633
+ "license": "MIT",
634
+ "optional": true,
635
+ "os": [
636
+ "sunos"
637
+ ],
638
+ "engines": {
639
+ "node": ">=12"
640
+ }
641
+ },
642
+ "node_modules/@esbuild/win32-arm64": {
643
+ "version": "0.18.20",
644
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz",
645
+ "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==",
646
+ "cpu": [
647
+ "arm64"
648
+ ],
649
+ "dev": true,
650
+ "license": "MIT",
651
+ "optional": true,
652
+ "os": [
653
+ "win32"
654
+ ],
655
+ "engines": {
656
+ "node": ">=12"
657
+ }
658
+ },
659
+ "node_modules/@esbuild/win32-ia32": {
660
+ "version": "0.18.20",
661
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz",
662
+ "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==",
663
+ "cpu": [
664
+ "ia32"
665
+ ],
666
+ "dev": true,
667
+ "license": "MIT",
668
+ "optional": true,
669
+ "os": [
670
+ "win32"
671
+ ],
672
+ "engines": {
673
+ "node": ">=12"
674
+ }
675
+ },
676
+ "node_modules/@esbuild/win32-x64": {
677
+ "version": "0.18.20",
678
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz",
679
+ "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==",
680
+ "cpu": [
681
+ "x64"
682
+ ],
683
+ "dev": true,
684
+ "license": "MIT",
685
+ "optional": true,
686
+ "os": [
687
+ "win32"
688
+ ],
689
+ "engines": {
690
+ "node": ">=12"
691
+ }
692
+ },
693
+ "node_modules/@jridgewell/gen-mapping": {
694
+ "version": "0.3.13",
695
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
696
+ "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
697
+ "dev": true,
698
+ "license": "MIT",
699
+ "dependencies": {
700
+ "@jridgewell/sourcemap-codec": "^1.5.0",
701
+ "@jridgewell/trace-mapping": "^0.3.24"
702
+ }
703
+ },
704
+ "node_modules/@jridgewell/resolve-uri": {
705
+ "version": "3.1.2",
706
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
707
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
708
+ "dev": true,
709
+ "license": "MIT",
710
+ "engines": {
711
+ "node": ">=6.0.0"
712
+ }
713
+ },
714
+ "node_modules/@jridgewell/sourcemap-codec": {
715
+ "version": "1.5.5",
716
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
717
+ "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
718
+ "dev": true,
719
+ "license": "MIT"
720
+ },
721
+ "node_modules/@jridgewell/trace-mapping": {
722
+ "version": "0.3.30",
723
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.30.tgz",
724
+ "integrity": "sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==",
725
+ "dev": true,
726
+ "license": "MIT",
727
+ "dependencies": {
728
+ "@jridgewell/resolve-uri": "^3.1.0",
729
+ "@jridgewell/sourcemap-codec": "^1.4.14"
730
+ }
731
+ },
732
+ "node_modules/@rolldown/pluginutils": {
733
+ "version": "1.0.0-beta.27",
734
+ "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz",
735
+ "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==",
736
+ "dev": true,
737
+ "license": "MIT"
738
+ },
739
+ "node_modules/@types/babel__core": {
740
+ "version": "7.20.5",
741
+ "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
742
+ "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
743
+ "dev": true,
744
+ "license": "MIT",
745
+ "dependencies": {
746
+ "@babel/parser": "^7.20.7",
747
+ "@babel/types": "^7.20.7",
748
+ "@types/babel__generator": "*",
749
+ "@types/babel__template": "*",
750
+ "@types/babel__traverse": "*"
751
+ }
752
+ },
753
+ "node_modules/@types/babel__generator": {
754
+ "version": "7.27.0",
755
+ "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz",
756
+ "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==",
757
+ "dev": true,
758
+ "license": "MIT",
759
+ "dependencies": {
760
+ "@babel/types": "^7.0.0"
761
+ }
762
+ },
763
+ "node_modules/@types/babel__template": {
764
+ "version": "7.4.4",
765
+ "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz",
766
+ "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
767
+ "dev": true,
768
+ "license": "MIT",
769
+ "dependencies": {
770
+ "@babel/parser": "^7.1.0",
771
+ "@babel/types": "^7.0.0"
772
+ }
773
+ },
774
+ "node_modules/@types/babel__traverse": {
775
+ "version": "7.28.0",
776
+ "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz",
777
+ "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==",
778
+ "dev": true,
779
+ "license": "MIT",
780
+ "dependencies": {
781
+ "@babel/types": "^7.28.2"
782
+ }
783
+ },
784
+ "node_modules/@types/prop-types": {
785
+ "version": "15.7.15",
786
+ "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz",
787
+ "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==",
788
+ "dev": true,
789
+ "license": "MIT"
790
+ },
791
+ "node_modules/@types/react": {
792
+ "version": "18.3.23",
793
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.23.tgz",
794
+ "integrity": "sha512-/LDXMQh55EzZQ0uVAZmKKhfENivEvWz6E+EYzh+/MCjMhNsotd+ZHhBGIjFDTi6+fz0OhQQQLbTgdQIxxCsC0w==",
795
+ "dev": true,
796
+ "license": "MIT",
797
+ "dependencies": {
798
+ "@types/prop-types": "*",
799
+ "csstype": "^3.0.2"
800
+ }
801
+ },
802
+ "node_modules/@types/react-dom": {
803
+ "version": "18.3.7",
804
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz",
805
+ "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==",
806
+ "dev": true,
807
+ "license": "MIT",
808
+ "peerDependencies": {
809
+ "@types/react": "^18.0.0"
810
+ }
811
+ },
812
+ "node_modules/@vitejs/plugin-react": {
813
+ "version": "4.7.0",
814
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz",
815
+ "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==",
816
+ "dev": true,
817
+ "license": "MIT",
818
+ "dependencies": {
819
+ "@babel/core": "^7.28.0",
820
+ "@babel/plugin-transform-react-jsx-self": "^7.27.1",
821
+ "@babel/plugin-transform-react-jsx-source": "^7.27.1",
822
+ "@rolldown/pluginutils": "1.0.0-beta.27",
823
+ "@types/babel__core": "^7.20.5",
824
+ "react-refresh": "^0.17.0"
825
+ },
826
+ "engines": {
827
+ "node": "^14.18.0 || >=16.0.0"
828
+ },
829
+ "peerDependencies": {
830
+ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
831
+ }
832
+ },
833
+ "node_modules/asynckit": {
834
+ "version": "0.4.0",
835
+ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
836
+ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
837
+ "license": "MIT"
838
+ },
839
+ "node_modules/axios": {
840
+ "version": "1.11.0",
841
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.11.0.tgz",
842
+ "integrity": "sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==",
843
+ "license": "MIT",
844
+ "dependencies": {
845
+ "follow-redirects": "^1.15.6",
846
+ "form-data": "^4.0.4",
847
+ "proxy-from-env": "^1.1.0"
848
+ }
849
+ },
850
+ "node_modules/browserslist": {
851
+ "version": "4.25.3",
852
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.3.tgz",
853
+ "integrity": "sha512-cDGv1kkDI4/0e5yON9yM5G/0A5u8sf5TnmdX5C9qHzI9PPu++sQ9zjm1k9NiOrf3riY4OkK0zSGqfvJyJsgCBQ==",
854
+ "dev": true,
855
+ "funding": [
856
+ {
857
+ "type": "opencollective",
858
+ "url": "https://opencollective.com/browserslist"
859
+ },
860
+ {
861
+ "type": "tidelift",
862
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
863
+ },
864
+ {
865
+ "type": "github",
866
+ "url": "https://github.com/sponsors/ai"
867
+ }
868
+ ],
869
+ "license": "MIT",
870
+ "dependencies": {
871
+ "caniuse-lite": "^1.0.30001735",
872
+ "electron-to-chromium": "^1.5.204",
873
+ "node-releases": "^2.0.19",
874
+ "update-browserslist-db": "^1.1.3"
875
+ },
876
+ "bin": {
877
+ "browserslist": "cli.js"
878
+ },
879
+ "engines": {
880
+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
881
+ }
882
+ },
883
+ "node_modules/call-bind-apply-helpers": {
884
+ "version": "1.0.2",
885
+ "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
886
+ "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
887
+ "license": "MIT",
888
+ "dependencies": {
889
+ "es-errors": "^1.3.0",
890
+ "function-bind": "^1.1.2"
891
+ },
892
+ "engines": {
893
+ "node": ">= 0.4"
894
+ }
895
+ },
896
+ "node_modules/caniuse-lite": {
897
+ "version": "1.0.30001735",
898
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001735.tgz",
899
+ "integrity": "sha512-EV/laoX7Wq2J9TQlyIXRxTJqIw4sxfXS4OYgudGxBYRuTv0q7AM6yMEpU/Vo1I94thg9U6EZ2NfZx9GJq83u7w==",
900
+ "dev": true,
901
+ "funding": [
902
+ {
903
+ "type": "opencollective",
904
+ "url": "https://opencollective.com/browserslist"
905
+ },
906
+ {
907
+ "type": "tidelift",
908
+ "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
909
+ },
910
+ {
911
+ "type": "github",
912
+ "url": "https://github.com/sponsors/ai"
913
+ }
914
+ ],
915
+ "license": "CC-BY-4.0"
916
+ },
917
+ "node_modules/combined-stream": {
918
+ "version": "1.0.8",
919
+ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
920
+ "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
921
+ "license": "MIT",
922
+ "dependencies": {
923
+ "delayed-stream": "~1.0.0"
924
+ },
925
+ "engines": {
926
+ "node": ">= 0.8"
927
+ }
928
+ },
929
+ "node_modules/convert-source-map": {
930
+ "version": "2.0.0",
931
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
932
+ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
933
+ "dev": true,
934
+ "license": "MIT"
935
+ },
936
+ "node_modules/csstype": {
937
+ "version": "3.1.3",
938
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
939
+ "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
940
+ "dev": true,
941
+ "license": "MIT"
942
+ },
943
+ "node_modules/debug": {
944
+ "version": "4.4.1",
945
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
946
+ "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==",
947
+ "dev": true,
948
+ "license": "MIT",
949
+ "dependencies": {
950
+ "ms": "^2.1.3"
951
+ },
952
+ "engines": {
953
+ "node": ">=6.0"
954
+ },
955
+ "peerDependenciesMeta": {
956
+ "supports-color": {
957
+ "optional": true
958
+ }
959
+ }
960
+ },
961
+ "node_modules/delayed-stream": {
962
+ "version": "1.0.0",
963
+ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
964
+ "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
965
+ "license": "MIT",
966
+ "engines": {
967
+ "node": ">=0.4.0"
968
+ }
969
+ },
970
+ "node_modules/dunder-proto": {
971
+ "version": "1.0.1",
972
+ "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
973
+ "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
974
+ "license": "MIT",
975
+ "dependencies": {
976
+ "call-bind-apply-helpers": "^1.0.1",
977
+ "es-errors": "^1.3.0",
978
+ "gopd": "^1.2.0"
979
+ },
980
+ "engines": {
981
+ "node": ">= 0.4"
982
+ }
983
+ },
984
+ "node_modules/electron-to-chromium": {
985
+ "version": "1.5.207",
986
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.207.tgz",
987
+ "integrity": "sha512-mryFrrL/GXDTmAtIVMVf+eIXM09BBPlO5IQ7lUyKmK8d+A4VpRGG+M3ofoVef6qyF8s60rJei8ymlJxjUA8Faw==",
988
+ "dev": true,
989
+ "license": "ISC"
990
+ },
991
+ "node_modules/es-define-property": {
992
+ "version": "1.0.1",
993
+ "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
994
+ "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
995
+ "license": "MIT",
996
+ "engines": {
997
+ "node": ">= 0.4"
998
+ }
999
+ },
1000
+ "node_modules/es-errors": {
1001
+ "version": "1.3.0",
1002
+ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
1003
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
1004
+ "license": "MIT",
1005
+ "engines": {
1006
+ "node": ">= 0.4"
1007
+ }
1008
+ },
1009
+ "node_modules/es-object-atoms": {
1010
+ "version": "1.1.1",
1011
+ "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
1012
+ "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
1013
+ "license": "MIT",
1014
+ "dependencies": {
1015
+ "es-errors": "^1.3.0"
1016
+ },
1017
+ "engines": {
1018
+ "node": ">= 0.4"
1019
+ }
1020
+ },
1021
+ "node_modules/es-set-tostringtag": {
1022
+ "version": "2.1.0",
1023
+ "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
1024
+ "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
1025
+ "license": "MIT",
1026
+ "dependencies": {
1027
+ "es-errors": "^1.3.0",
1028
+ "get-intrinsic": "^1.2.6",
1029
+ "has-tostringtag": "^1.0.2",
1030
+ "hasown": "^2.0.2"
1031
+ },
1032
+ "engines": {
1033
+ "node": ">= 0.4"
1034
+ }
1035
+ },
1036
+ "node_modules/esbuild": {
1037
+ "version": "0.18.20",
1038
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz",
1039
+ "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==",
1040
+ "dev": true,
1041
+ "hasInstallScript": true,
1042
+ "license": "MIT",
1043
+ "bin": {
1044
+ "esbuild": "bin/esbuild"
1045
+ },
1046
+ "engines": {
1047
+ "node": ">=12"
1048
+ },
1049
+ "optionalDependencies": {
1050
+ "@esbuild/android-arm": "0.18.20",
1051
+ "@esbuild/android-arm64": "0.18.20",
1052
+ "@esbuild/android-x64": "0.18.20",
1053
+ "@esbuild/darwin-arm64": "0.18.20",
1054
+ "@esbuild/darwin-x64": "0.18.20",
1055
+ "@esbuild/freebsd-arm64": "0.18.20",
1056
+ "@esbuild/freebsd-x64": "0.18.20",
1057
+ "@esbuild/linux-arm": "0.18.20",
1058
+ "@esbuild/linux-arm64": "0.18.20",
1059
+ "@esbuild/linux-ia32": "0.18.20",
1060
+ "@esbuild/linux-loong64": "0.18.20",
1061
+ "@esbuild/linux-mips64el": "0.18.20",
1062
+ "@esbuild/linux-ppc64": "0.18.20",
1063
+ "@esbuild/linux-riscv64": "0.18.20",
1064
+ "@esbuild/linux-s390x": "0.18.20",
1065
+ "@esbuild/linux-x64": "0.18.20",
1066
+ "@esbuild/netbsd-x64": "0.18.20",
1067
+ "@esbuild/openbsd-x64": "0.18.20",
1068
+ "@esbuild/sunos-x64": "0.18.20",
1069
+ "@esbuild/win32-arm64": "0.18.20",
1070
+ "@esbuild/win32-ia32": "0.18.20",
1071
+ "@esbuild/win32-x64": "0.18.20"
1072
+ }
1073
+ },
1074
+ "node_modules/escalade": {
1075
+ "version": "3.2.0",
1076
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
1077
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
1078
+ "dev": true,
1079
+ "license": "MIT",
1080
+ "engines": {
1081
+ "node": ">=6"
1082
+ }
1083
+ },
1084
+ "node_modules/follow-redirects": {
1085
+ "version": "1.15.11",
1086
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
1087
+ "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
1088
+ "funding": [
1089
+ {
1090
+ "type": "individual",
1091
+ "url": "https://github.com/sponsors/RubenVerborgh"
1092
+ }
1093
+ ],
1094
+ "license": "MIT",
1095
+ "engines": {
1096
+ "node": ">=4.0"
1097
+ },
1098
+ "peerDependenciesMeta": {
1099
+ "debug": {
1100
+ "optional": true
1101
+ }
1102
+ }
1103
+ },
1104
+ "node_modules/form-data": {
1105
+ "version": "4.0.4",
1106
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
1107
+ "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
1108
+ "license": "MIT",
1109
+ "dependencies": {
1110
+ "asynckit": "^0.4.0",
1111
+ "combined-stream": "^1.0.8",
1112
+ "es-set-tostringtag": "^2.1.0",
1113
+ "hasown": "^2.0.2",
1114
+ "mime-types": "^2.1.12"
1115
+ },
1116
+ "engines": {
1117
+ "node": ">= 6"
1118
+ }
1119
+ },
1120
+ "node_modules/fsevents": {
1121
+ "version": "2.3.3",
1122
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
1123
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
1124
+ "dev": true,
1125
+ "hasInstallScript": true,
1126
+ "license": "MIT",
1127
+ "optional": true,
1128
+ "os": [
1129
+ "darwin"
1130
+ ],
1131
+ "engines": {
1132
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
1133
+ }
1134
+ },
1135
+ "node_modules/function-bind": {
1136
+ "version": "1.1.2",
1137
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
1138
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
1139
+ "license": "MIT",
1140
+ "funding": {
1141
+ "url": "https://github.com/sponsors/ljharb"
1142
+ }
1143
+ },
1144
+ "node_modules/gensync": {
1145
+ "version": "1.0.0-beta.2",
1146
+ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
1147
+ "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
1148
+ "dev": true,
1149
+ "license": "MIT",
1150
+ "engines": {
1151
+ "node": ">=6.9.0"
1152
+ }
1153
+ },
1154
+ "node_modules/get-intrinsic": {
1155
+ "version": "1.3.0",
1156
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
1157
+ "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
1158
+ "license": "MIT",
1159
+ "dependencies": {
1160
+ "call-bind-apply-helpers": "^1.0.2",
1161
+ "es-define-property": "^1.0.1",
1162
+ "es-errors": "^1.3.0",
1163
+ "es-object-atoms": "^1.1.1",
1164
+ "function-bind": "^1.1.2",
1165
+ "get-proto": "^1.0.1",
1166
+ "gopd": "^1.2.0",
1167
+ "has-symbols": "^1.1.0",
1168
+ "hasown": "^2.0.2",
1169
+ "math-intrinsics": "^1.1.0"
1170
+ },
1171
+ "engines": {
1172
+ "node": ">= 0.4"
1173
+ },
1174
+ "funding": {
1175
+ "url": "https://github.com/sponsors/ljharb"
1176
+ }
1177
+ },
1178
+ "node_modules/get-proto": {
1179
+ "version": "1.0.1",
1180
+ "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
1181
+ "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
1182
+ "license": "MIT",
1183
+ "dependencies": {
1184
+ "dunder-proto": "^1.0.1",
1185
+ "es-object-atoms": "^1.0.0"
1186
+ },
1187
+ "engines": {
1188
+ "node": ">= 0.4"
1189
+ }
1190
+ },
1191
+ "node_modules/gopd": {
1192
+ "version": "1.2.0",
1193
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
1194
+ "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
1195
+ "license": "MIT",
1196
+ "engines": {
1197
+ "node": ">= 0.4"
1198
+ },
1199
+ "funding": {
1200
+ "url": "https://github.com/sponsors/ljharb"
1201
+ }
1202
+ },
1203
+ "node_modules/has-symbols": {
1204
+ "version": "1.1.0",
1205
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
1206
+ "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
1207
+ "license": "MIT",
1208
+ "engines": {
1209
+ "node": ">= 0.4"
1210
+ },
1211
+ "funding": {
1212
+ "url": "https://github.com/sponsors/ljharb"
1213
+ }
1214
+ },
1215
+ "node_modules/has-tostringtag": {
1216
+ "version": "1.0.2",
1217
+ "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
1218
+ "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
1219
+ "license": "MIT",
1220
+ "dependencies": {
1221
+ "has-symbols": "^1.0.3"
1222
+ },
1223
+ "engines": {
1224
+ "node": ">= 0.4"
1225
+ },
1226
+ "funding": {
1227
+ "url": "https://github.com/sponsors/ljharb"
1228
+ }
1229
+ },
1230
+ "node_modules/hasown": {
1231
+ "version": "2.0.2",
1232
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
1233
+ "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
1234
+ "license": "MIT",
1235
+ "dependencies": {
1236
+ "function-bind": "^1.1.2"
1237
+ },
1238
+ "engines": {
1239
+ "node": ">= 0.4"
1240
+ }
1241
+ },
1242
+ "node_modules/js-tokens": {
1243
+ "version": "4.0.0",
1244
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
1245
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
1246
+ "license": "MIT"
1247
+ },
1248
+ "node_modules/jsesc": {
1249
+ "version": "3.1.0",
1250
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
1251
+ "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
1252
+ "dev": true,
1253
+ "license": "MIT",
1254
+ "bin": {
1255
+ "jsesc": "bin/jsesc"
1256
+ },
1257
+ "engines": {
1258
+ "node": ">=6"
1259
+ }
1260
+ },
1261
+ "node_modules/json5": {
1262
+ "version": "2.2.3",
1263
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
1264
+ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
1265
+ "dev": true,
1266
+ "license": "MIT",
1267
+ "bin": {
1268
+ "json5": "lib/cli.js"
1269
+ },
1270
+ "engines": {
1271
+ "node": ">=6"
1272
+ }
1273
+ },
1274
+ "node_modules/loose-envify": {
1275
+ "version": "1.4.0",
1276
+ "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
1277
+ "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
1278
+ "license": "MIT",
1279
+ "dependencies": {
1280
+ "js-tokens": "^3.0.0 || ^4.0.0"
1281
+ },
1282
+ "bin": {
1283
+ "loose-envify": "cli.js"
1284
+ }
1285
+ },
1286
+ "node_modules/lru-cache": {
1287
+ "version": "5.1.1",
1288
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
1289
+ "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
1290
+ "dev": true,
1291
+ "license": "ISC",
1292
+ "dependencies": {
1293
+ "yallist": "^3.0.2"
1294
+ }
1295
+ },
1296
+ "node_modules/lucide-react": {
1297
+ "version": "0.263.1",
1298
+ "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.263.1.tgz",
1299
+ "integrity": "sha512-keqxAx97PlaEN89PXZ6ki1N8nRjGWtDa4021GFYLNj0RgruM5odbpl8GHTExj0hhPq3sF6Up0gnxt6TSHu+ovw==",
1300
+ "license": "ISC",
1301
+ "peerDependencies": {
1302
+ "react": "^16.5.1 || ^17.0.0 || ^18.0.0"
1303
+ }
1304
+ },
1305
+ "node_modules/math-intrinsics": {
1306
+ "version": "1.1.0",
1307
+ "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
1308
+ "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
1309
+ "license": "MIT",
1310
+ "engines": {
1311
+ "node": ">= 0.4"
1312
+ }
1313
+ },
1314
+ "node_modules/mime-db": {
1315
+ "version": "1.52.0",
1316
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
1317
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
1318
+ "license": "MIT",
1319
+ "engines": {
1320
+ "node": ">= 0.6"
1321
+ }
1322
+ },
1323
+ "node_modules/mime-types": {
1324
+ "version": "2.1.35",
1325
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
1326
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
1327
+ "license": "MIT",
1328
+ "dependencies": {
1329
+ "mime-db": "1.52.0"
1330
+ },
1331
+ "engines": {
1332
+ "node": ">= 0.6"
1333
+ }
1334
+ },
1335
+ "node_modules/ms": {
1336
+ "version": "2.1.3",
1337
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
1338
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
1339
+ "dev": true,
1340
+ "license": "MIT"
1341
+ },
1342
+ "node_modules/nanoid": {
1343
+ "version": "3.3.11",
1344
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
1345
+ "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
1346
+ "dev": true,
1347
+ "funding": [
1348
+ {
1349
+ "type": "github",
1350
+ "url": "https://github.com/sponsors/ai"
1351
+ }
1352
+ ],
1353
+ "license": "MIT",
1354
+ "bin": {
1355
+ "nanoid": "bin/nanoid.cjs"
1356
+ },
1357
+ "engines": {
1358
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
1359
+ }
1360
+ },
1361
+ "node_modules/node-releases": {
1362
+ "version": "2.0.19",
1363
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz",
1364
+ "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==",
1365
+ "dev": true,
1366
+ "license": "MIT"
1367
+ },
1368
+ "node_modules/picocolors": {
1369
+ "version": "1.1.1",
1370
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
1371
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
1372
+ "dev": true,
1373
+ "license": "ISC"
1374
+ },
1375
+ "node_modules/postcss": {
1376
+ "version": "8.5.6",
1377
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
1378
+ "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
1379
+ "dev": true,
1380
+ "funding": [
1381
+ {
1382
+ "type": "opencollective",
1383
+ "url": "https://opencollective.com/postcss/"
1384
+ },
1385
+ {
1386
+ "type": "tidelift",
1387
+ "url": "https://tidelift.com/funding/github/npm/postcss"
1388
+ },
1389
+ {
1390
+ "type": "github",
1391
+ "url": "https://github.com/sponsors/ai"
1392
+ }
1393
+ ],
1394
+ "license": "MIT",
1395
+ "dependencies": {
1396
+ "nanoid": "^3.3.11",
1397
+ "picocolors": "^1.1.1",
1398
+ "source-map-js": "^1.2.1"
1399
+ },
1400
+ "engines": {
1401
+ "node": "^10 || ^12 || >=14"
1402
+ }
1403
+ },
1404
+ "node_modules/proxy-from-env": {
1405
+ "version": "1.1.0",
1406
+ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
1407
+ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
1408
+ "license": "MIT"
1409
+ },
1410
+ "node_modules/react": {
1411
+ "version": "18.3.1",
1412
+ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
1413
+ "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
1414
+ "license": "MIT",
1415
+ "dependencies": {
1416
+ "loose-envify": "^1.1.0"
1417
+ },
1418
+ "engines": {
1419
+ "node": ">=0.10.0"
1420
+ }
1421
+ },
1422
+ "node_modules/react-dom": {
1423
+ "version": "18.3.1",
1424
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
1425
+ "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
1426
+ "license": "MIT",
1427
+ "dependencies": {
1428
+ "loose-envify": "^1.1.0",
1429
+ "scheduler": "^0.23.2"
1430
+ },
1431
+ "peerDependencies": {
1432
+ "react": "^18.3.1"
1433
+ }
1434
+ },
1435
+ "node_modules/react-refresh": {
1436
+ "version": "0.17.0",
1437
+ "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz",
1438
+ "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==",
1439
+ "dev": true,
1440
+ "license": "MIT",
1441
+ "engines": {
1442
+ "node": ">=0.10.0"
1443
+ }
1444
+ },
1445
+ "node_modules/rollup": {
1446
+ "version": "3.29.5",
1447
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.5.tgz",
1448
+ "integrity": "sha512-GVsDdsbJzzy4S/v3dqWPJ7EfvZJfCHiDqe80IyrF59LYuP+e6U1LJoUqeuqRbwAWoMNoXivMNeNAOf5E22VA1w==",
1449
+ "dev": true,
1450
+ "license": "MIT",
1451
+ "bin": {
1452
+ "rollup": "dist/bin/rollup"
1453
+ },
1454
+ "engines": {
1455
+ "node": ">=14.18.0",
1456
+ "npm": ">=8.0.0"
1457
+ },
1458
+ "optionalDependencies": {
1459
+ "fsevents": "~2.3.2"
1460
+ }
1461
+ },
1462
+ "node_modules/scheduler": {
1463
+ "version": "0.23.2",
1464
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
1465
+ "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
1466
+ "license": "MIT",
1467
+ "dependencies": {
1468
+ "loose-envify": "^1.1.0"
1469
+ }
1470
+ },
1471
+ "node_modules/semver": {
1472
+ "version": "6.3.1",
1473
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
1474
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
1475
+ "dev": true,
1476
+ "license": "ISC",
1477
+ "bin": {
1478
+ "semver": "bin/semver.js"
1479
+ }
1480
+ },
1481
+ "node_modules/source-map-js": {
1482
+ "version": "1.2.1",
1483
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
1484
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
1485
+ "dev": true,
1486
+ "license": "BSD-3-Clause",
1487
+ "engines": {
1488
+ "node": ">=0.10.0"
1489
+ }
1490
+ },
1491
+ "node_modules/update-browserslist-db": {
1492
+ "version": "1.1.3",
1493
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz",
1494
+ "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==",
1495
+ "dev": true,
1496
+ "funding": [
1497
+ {
1498
+ "type": "opencollective",
1499
+ "url": "https://opencollective.com/browserslist"
1500
+ },
1501
+ {
1502
+ "type": "tidelift",
1503
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
1504
+ },
1505
+ {
1506
+ "type": "github",
1507
+ "url": "https://github.com/sponsors/ai"
1508
+ }
1509
+ ],
1510
+ "license": "MIT",
1511
+ "dependencies": {
1512
+ "escalade": "^3.2.0",
1513
+ "picocolors": "^1.1.1"
1514
+ },
1515
+ "bin": {
1516
+ "update-browserslist-db": "cli.js"
1517
+ },
1518
+ "peerDependencies": {
1519
+ "browserslist": ">= 4.21.0"
1520
+ }
1521
+ },
1522
+ "node_modules/vite": {
1523
+ "version": "4.5.14",
1524
+ "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.14.tgz",
1525
+ "integrity": "sha512-+v57oAaoYNnO3hIu5Z/tJRZjq5aHM2zDve9YZ8HngVHbhk66RStobhb1sqPMIPEleV6cNKYK4eGrAbE9Ulbl2g==",
1526
+ "dev": true,
1527
+ "license": "MIT",
1528
+ "dependencies": {
1529
+ "esbuild": "^0.18.10",
1530
+ "postcss": "^8.4.27",
1531
+ "rollup": "^3.27.1"
1532
+ },
1533
+ "bin": {
1534
+ "vite": "bin/vite.js"
1535
+ },
1536
+ "engines": {
1537
+ "node": "^14.18.0 || >=16.0.0"
1538
+ },
1539
+ "funding": {
1540
+ "url": "https://github.com/vitejs/vite?sponsor=1"
1541
+ },
1542
+ "optionalDependencies": {
1543
+ "fsevents": "~2.3.2"
1544
+ },
1545
+ "peerDependencies": {
1546
+ "@types/node": ">= 14",
1547
+ "less": "*",
1548
+ "lightningcss": "^1.21.0",
1549
+ "sass": "*",
1550
+ "stylus": "*",
1551
+ "sugarss": "*",
1552
+ "terser": "^5.4.0"
1553
+ },
1554
+ "peerDependenciesMeta": {
1555
+ "@types/node": {
1556
+ "optional": true
1557
+ },
1558
+ "less": {
1559
+ "optional": true
1560
+ },
1561
+ "lightningcss": {
1562
+ "optional": true
1563
+ },
1564
+ "sass": {
1565
+ "optional": true
1566
+ },
1567
+ "stylus": {
1568
+ "optional": true
1569
+ },
1570
+ "sugarss": {
1571
+ "optional": true
1572
+ },
1573
+ "terser": {
1574
+ "optional": true
1575
+ }
1576
+ }
1577
+ },
1578
+ "node_modules/yallist": {
1579
+ "version": "3.1.1",
1580
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
1581
+ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
1582
+ "dev": true,
1583
+ "license": "ISC"
1584
+ }
1585
+ }
1586
+ }
package.json ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "demucs-frontend",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "dependencies": {
7
+ "react": "^18.2.0",
8
+ "react-dom": "^18.2.0",
9
+ "axios": "^1.4.0",
10
+ "lucide-react": "^0.263.1"
11
+ },
12
+ "devDependencies": {
13
+ "@types/react": "^18.2.15",
14
+ "@types/react-dom": "^18.2.7",
15
+ "@vitejs/plugin-react": "^4.0.3",
16
+ "vite": "^4.4.5"
17
+ },
18
+ "scripts": {
19
+ "dev": "vite",
20
+ "build": "vite build",
21
+ "preview": "vite preview"
22
+ }
23
+ }
requirements.txt ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ Flask==2.3.2
2
+ Flask-CORS==4.0.0
3
+ demucs==4.0.1
4
+ Werkzeug==2.3.6
5
+ gunicorn==21.2.0
server.py ADDED
@@ -0,0 +1,230 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Flask, request, jsonify, send_file, Response, send_from_directory
2
+ from flask_cors import CORS
3
+ import os
4
+ import subprocess
5
+ import tempfile
6
+ import shutil
7
+ from werkzeug.utils import secure_filename
8
+ import uuid
9
+ from pathlib import Path
10
+ import time
11
+ import io
12
+
13
+ app = Flask(__name__, static_folder='dist', static_url_path='')
14
+ CORS(app)
15
+
16
+ # Configuration
17
+ ALLOWED_EXTENSIONS = {'mp3', 'wav', 'flac', 'ogg', 'm4a', 'aac'}
18
+
19
+ # Store separated tracks in memory temporarily
20
+ active_sessions = {}
21
+
22
+ def allowed_file(filename):
23
+ return '.' in filename and \
24
+ filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
25
+
26
+ def cleanup_old_sessions():
27
+ """Clean up sessions older than 1 hour"""
28
+ current_time = time.time()
29
+ expired_sessions = []
30
+
31
+ for session_id, session_data in active_sessions.items():
32
+ if session_data.get('created_at', 0) < current_time - 3600: # 1 hour
33
+ expired_sessions.append(session_id)
34
+
35
+ for session_id in expired_sessions:
36
+ if session_id in active_sessions:
37
+ # Clean up temporary directory if it exists
38
+ temp_dir = active_sessions[session_id].get('temp_dir')
39
+ if temp_dir and os.path.exists(temp_dir):
40
+ try:
41
+ shutil.rmtree(temp_dir)
42
+ except:
43
+ pass
44
+ del active_sessions[session_id]
45
+
46
+ @app.route('/api/separate', methods=['POST'])
47
+ def separate_audio():
48
+ try:
49
+ # Clean up old sessions
50
+ cleanup_old_sessions()
51
+
52
+ if 'audio' not in request.files:
53
+ return jsonify({'error': 'No audio file provided'}), 400
54
+
55
+ file = request.files['audio']
56
+ if file.filename == '':
57
+ return jsonify({'error': 'No file selected'}), 400
58
+
59
+ if not allowed_file(file.filename):
60
+ return jsonify({'error': 'Invalid file format'}), 400
61
+
62
+ # Generate unique ID for this processing session
63
+ session_id = str(uuid.uuid4())
64
+
65
+ # Create temporary directory for this session
66
+ temp_dir = tempfile.mkdtemp(prefix=f"demucs_{session_id}_")
67
+
68
+ try:
69
+ # Save uploaded file to temp directory
70
+ filename = secure_filename(file.filename)
71
+ file_path = os.path.join(temp_dir, filename)
72
+ file.save(file_path)
73
+
74
+ # Add FFmpeg to PATH if it exists in the project directory
75
+ env = os.environ.copy()
76
+ ffmpeg_path = os.path.join(os.getcwd(), 'ffmpeg-7.1.1-essentials_build', 'bin')
77
+ if os.path.exists(ffmpeg_path):
78
+ env['PATH'] = ffmpeg_path + os.pathsep + env.get('PATH', '')
79
+
80
+ # Run demucs command with output to temp directory
81
+ output_dir = os.path.join(temp_dir, 'separated')
82
+ cmd = ['demucs', '--out', output_dir, file_path]
83
+
84
+ result = subprocess.run(cmd, capture_output=True, text=True, env=env)
85
+
86
+ if result.returncode != 0:
87
+ return jsonify({'error': f'Demucs processing failed with return code {result.returncode}: {result.stderr}'}), 500
88
+
89
+ # Find the output directory (Demucs creates subdirectories)
90
+ base_name = os.path.splitext(filename)[0]
91
+ track_dir = None
92
+
93
+ # Look for the output directory
94
+ for model_dir in os.listdir(output_dir):
95
+ model_path = os.path.join(output_dir, model_dir)
96
+ if os.path.isdir(model_path):
97
+ for potential_track_dir in os.listdir(model_path):
98
+ if potential_track_dir == base_name:
99
+ track_dir = os.path.join(model_path, potential_track_dir)
100
+ break
101
+ if track_dir:
102
+ break
103
+
104
+ if not track_dir or not os.path.exists(track_dir):
105
+ return jsonify({'error': 'Output files not found'}), 500
106
+
107
+ # Read separated tracks into memory
108
+ tracks = {}
109
+ track_files = ['vocals.wav', 'drums.wav', 'bass.wav', 'other.wav']
110
+ track_data = {}
111
+
112
+ for track_file in track_files:
113
+ track_path = os.path.join(track_dir, track_file)
114
+ if os.path.exists(track_path):
115
+ track_name = track_file.replace('.wav', '')
116
+
117
+ # Read file into memory
118
+ with open(track_path, 'rb') as f:
119
+ track_data[track_name] = f.read()
120
+
121
+ # Create a URL that the frontend can use to download the file
122
+ tracks[track_name] = f'/api/download/{session_id}/{track_name}'
123
+
124
+ if not tracks:
125
+ return jsonify({'error': 'No separated tracks found'}), 500
126
+
127
+ # Store session data in memory
128
+ active_sessions[session_id] = {
129
+ 'tracks': track_data,
130
+ 'original_filename': filename,
131
+ 'created_at': time.time(),
132
+ 'temp_dir': temp_dir
133
+ }
134
+
135
+ return jsonify({
136
+ 'success': True,
137
+ 'tracks': tracks,
138
+ 'session_id': session_id
139
+ })
140
+
141
+ except Exception as e:
142
+ # Clean up temp directory on error
143
+ if os.path.exists(temp_dir):
144
+ shutil.rmtree(temp_dir)
145
+ return jsonify({'error': f'Processing error: {str(e)}'}), 500
146
+
147
+ except Exception as e:
148
+ return jsonify({'error': f'Server error: {str(e)}'}), 500
149
+
150
+ @app.route('/api/download/<session_id>/<track_name>')
151
+ def download_track(session_id, track_name):
152
+ try:
153
+ # Check if session exists
154
+ if session_id not in active_sessions:
155
+ return jsonify({'error': 'Session not found or expired'}), 404
156
+
157
+ session_data = active_sessions[session_id]
158
+
159
+ # Check if track exists
160
+ if track_name not in session_data['tracks']:
161
+ return jsonify({'error': 'Track not found'}), 404
162
+
163
+ # Get track data from memory
164
+ track_data = session_data['tracks'][track_name]
165
+ original_filename = session_data['original_filename']
166
+
167
+ # Create a file-like object from the binary data
168
+ track_io = io.BytesIO(track_data)
169
+
170
+ # Generate download filename
171
+ base_name = os.path.splitext(original_filename)[0]
172
+ download_filename = f"{base_name}_{track_name}.wav"
173
+
174
+ return send_file(
175
+ track_io,
176
+ as_attachment=True,
177
+ download_name=download_filename,
178
+ mimetype='audio/wav'
179
+ )
180
+
181
+ except Exception as e:
182
+ return jsonify({'error': f'Download error: {str(e)}'}), 500
183
+
184
+ @app.route('/api/cleanup/<session_id>', methods=['POST'])
185
+ def cleanup_session(session_id):
186
+ """Manually clean up a session when user is done"""
187
+ try:
188
+ if session_id in active_sessions:
189
+ # Clean up temporary directory
190
+ temp_dir = active_sessions[session_id].get('temp_dir')
191
+ if temp_dir and os.path.exists(temp_dir):
192
+ shutil.rmtree(temp_dir)
193
+
194
+ # Remove from active sessions
195
+ del active_sessions[session_id]
196
+
197
+ return jsonify({'success': True, 'message': 'Session cleaned up'})
198
+ else:
199
+ return jsonify({'error': 'Session not found'}), 404
200
+
201
+ except Exception as e:
202
+ return jsonify({'error': f'Cleanup error: {str(e)}'}), 500
203
+
204
+ @app.route('/api/health')
205
+ def health_check():
206
+ active_count = len(active_sessions)
207
+ return jsonify({
208
+ 'status': 'healthy',
209
+ 'message': 'Demucs API is running',
210
+ 'active_sessions': active_count
211
+ })
212
+
213
+ # Serve React App
214
+ @app.route('/')
215
+ def serve_react_app():
216
+ return send_from_directory(app.static_folder, 'index.html')
217
+
218
+ @app.route('/<path:path>')
219
+ def serve_static_files(path):
220
+ if path != "" and os.path.exists(os.path.join(app.static_folder, path)):
221
+ return send_from_directory(app.static_folder, path)
222
+ else:
223
+ return send_from_directory(app.static_folder, 'index.html')
224
+
225
+ if __name__ == '__main__':
226
+ import os
227
+ port = int(os.environ.get('PORT', 7860)) # Hugging Face Spaces uses port 7860
228
+ print(f"Starting Demucs API server on port {port}...")
229
+ print("Make sure you have Demucs installed: pip install demucs")
230
+ app.run(debug=False, host='0.0.0.0', port=port)
src/App.css ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .App {
2
+ min-height: 100vh;
3
+ display: flex;
4
+ flex-direction: column;
5
+ }
6
+
7
+ .main-content {
8
+ flex: 1;
9
+ display: flex;
10
+ align-items: center;
11
+ justify-content: center;
12
+ padding: 2rem;
13
+ }
14
+
15
+ .error-container {
16
+ display: flex;
17
+ align-items: center;
18
+ justify-content: center;
19
+ width: 100%;
20
+ }
21
+
22
+ .error-message {
23
+ background: rgba(255, 255, 255, 0.95);
24
+ backdrop-filter: blur(10px);
25
+ border-radius: 20px;
26
+ padding: 2rem;
27
+ text-align: center;
28
+ box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
29
+ border: 1px solid rgba(255, 255, 255, 0.2);
30
+ max-width: 400px;
31
+ width: 100%;
32
+ }
33
+
34
+ .error-message h3 {
35
+ color: #e74c3c;
36
+ margin-bottom: 1rem;
37
+ font-weight: 600;
38
+ }
39
+
40
+ .error-message p {
41
+ color: #666;
42
+ margin-bottom: 1.5rem;
43
+ line-height: 1.5;
44
+ }
45
+
46
+ .retry-button {
47
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
48
+ color: white;
49
+ border: none;
50
+ padding: 0.75rem 1.5rem;
51
+ border-radius: 10px;
52
+ font-weight: 500;
53
+ cursor: pointer;
54
+ transition: all 0.3s ease;
55
+ }
56
+
57
+ .retry-button:hover {
58
+ transform: translateY(-2px);
59
+ box-shadow: 0 10px 20px rgba(0, 0, 0, 0.2);
60
+ }
src/App.js ADDED
@@ -0,0 +1,122 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useState } from 'react';
2
+ import './App.css';
3
+ import FileUpload from './components/FileUpload';
4
+ import AudioPlayer from './components/AudioPlayer';
5
+ import ProcessingStatus from './components/ProcessingStatus';
6
+ import Header from './components/Header';
7
+
8
+ function App() {
9
+ const [uploadedFile, setUploadedFile] = useState(null);
10
+ const [separatedTracks, setSeparatedTracks] = useState(null);
11
+ const [isProcessing, setIsProcessing] = useState(false);
12
+ const [processingProgress, setProcessingProgress] = useState(0);
13
+ const [error, setError] = useState(null);
14
+
15
+ const handleFileUpload = async (file) => {
16
+ setUploadedFile(file);
17
+ setIsProcessing(true);
18
+ setError(null);
19
+ setProcessingProgress(0);
20
+
21
+ const formData = new FormData();
22
+ formData.append('audio', file);
23
+
24
+ try {
25
+ // Simulate progress updates
26
+ const progressInterval = setInterval(() => {
27
+ setProcessingProgress(prev => {
28
+ if (prev >= 90) {
29
+ clearInterval(progressInterval);
30
+ return prev;
31
+ }
32
+ return prev + Math.random() * 10;
33
+ });
34
+ }, 1000);
35
+
36
+ const response = await fetch('/api/separate', {
37
+ method: 'POST',
38
+ body: formData,
39
+ });
40
+
41
+ clearInterval(progressInterval);
42
+
43
+ if (!response.ok) {
44
+ const errorData = await response.json().catch(() => ({ error: 'Unknown server error' }));
45
+ throw new Error(errorData.error || `Server error: ${response.status} ${response.statusText}`);
46
+ }
47
+
48
+ const result = await response.json();
49
+ setSeparatedTracks(result.tracks);
50
+ setProcessingProgress(100);
51
+
52
+ // Store session ID for cleanup
53
+ window.currentSessionId = result.session_id;
54
+ } catch (err) {
55
+ setError(err.message);
56
+ } finally {
57
+ setIsProcessing(false);
58
+ }
59
+ };
60
+
61
+ const handleReset = async () => {
62
+ // Clean up the current session if it exists
63
+ if (separatedTracks && window.currentSessionId) {
64
+ try {
65
+ await fetch(`/api/cleanup/${window.currentSessionId}`, {
66
+ method: 'POST'
67
+ });
68
+ } catch (error) {
69
+ console.warn('Failed to cleanup session:', error);
70
+ }
71
+ }
72
+
73
+ setUploadedFile(null);
74
+ setSeparatedTracks(null);
75
+ setIsProcessing(false);
76
+ setProcessingProgress(0);
77
+ setError(null);
78
+ window.currentSessionId = null;
79
+ };
80
+
81
+ return (
82
+ <div className="App">
83
+ <Header />
84
+
85
+ <main className="main-content">
86
+ {!uploadedFile && !separatedTracks && (
87
+ <FileUpload onFileUpload={handleFileUpload} />
88
+ )}
89
+
90
+ {isProcessing && (
91
+ <ProcessingStatus
92
+ progress={processingProgress}
93
+ fileName={uploadedFile?.name}
94
+ />
95
+ )}
96
+
97
+ {error && (
98
+ <div className="error-container">
99
+ <div className="error-message">
100
+ <h3>Processing Error</h3>
101
+ <p>{error}</p>
102
+ <button onClick={handleReset} className="retry-button">
103
+ Try Again
104
+ </button>
105
+ </div>
106
+ </div>
107
+ )}
108
+
109
+ {separatedTracks && !isProcessing && (
110
+ <AudioPlayer
111
+ key={uploadedFile?.name || 'default'}
112
+ tracks={separatedTracks}
113
+ originalFileName={uploadedFile?.name}
114
+ onReset={handleReset}
115
+ />
116
+ )}
117
+ </main>
118
+ </div>
119
+ );
120
+ }
121
+
122
+ export default App;
src/components/AudioPlayer.css ADDED
@@ -0,0 +1,298 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .player-container {
2
+ background: rgba(255, 255, 255, 0.95);
3
+ backdrop-filter: blur(10px);
4
+ border-radius: 20px;
5
+ padding: 2rem;
6
+ box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
7
+ border: 1px solid rgba(255, 255, 255, 0.2);
8
+ max-width: 900px;
9
+ width: 100%;
10
+ }
11
+
12
+ .player-header {
13
+ text-align: center;
14
+ margin-bottom: 2rem;
15
+ padding-bottom: 1.5rem;
16
+ border-bottom: 1px solid rgba(0, 0, 0, 0.1);
17
+ }
18
+
19
+ .player-header h2 {
20
+ color: #333;
21
+ font-size: 1.75rem;
22
+ font-weight: 600;
23
+ margin-bottom: 0.5rem;
24
+ }
25
+
26
+ .player-header p {
27
+ color: #666;
28
+ font-size: 1rem;
29
+ margin-bottom: 1.5rem;
30
+ }
31
+
32
+ .reset-button {
33
+ background: rgba(102, 126, 234, 0.1);
34
+ color: #667eea;
35
+ border: 1px solid rgba(102, 126, 234, 0.3);
36
+ padding: 0.75rem 1.5rem;
37
+ border-radius: 10px;
38
+ font-weight: 500;
39
+ cursor: pointer;
40
+ transition: all 0.3s ease;
41
+ display: inline-flex;
42
+ align-items: center;
43
+ gap: 0.5rem;
44
+ }
45
+
46
+ .reset-button:hover {
47
+ background: rgba(102, 126, 234, 0.2);
48
+ transform: translateY(-2px);
49
+ }
50
+
51
+ .main-controls {
52
+ display: flex;
53
+ align-items: center;
54
+ gap: 1.5rem;
55
+ margin-bottom: 2rem;
56
+ padding: 1.5rem;
57
+ background: rgba(0, 0, 0, 0.05);
58
+ border-radius: 15px;
59
+ }
60
+
61
+ .play-button {
62
+ width: 60px;
63
+ height: 60px;
64
+ border-radius: 50%;
65
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
66
+ color: white;
67
+ border: none;
68
+ cursor: pointer;
69
+ display: flex;
70
+ align-items: center;
71
+ justify-content: center;
72
+ transition: all 0.3s ease;
73
+ box-shadow: 0 10px 20px rgba(102, 126, 234, 0.3);
74
+ flex-shrink: 0;
75
+ }
76
+
77
+ .play-button:hover {
78
+ transform: scale(1.05);
79
+ box-shadow: 0 15px 30px rgba(102, 126, 234, 0.4);
80
+ }
81
+
82
+ .progress-container {
83
+ display: flex;
84
+ align-items: center;
85
+ gap: 1rem;
86
+ flex: 1;
87
+ }
88
+
89
+ .progress-bar {
90
+ flex: 1;
91
+ height: 8px;
92
+ background: rgba(0, 0, 0, 0.1);
93
+ border-radius: 4px;
94
+ cursor: pointer;
95
+ overflow: hidden;
96
+ }
97
+
98
+ .progress-fill {
99
+ height: 100%;
100
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
101
+ border-radius: 4px;
102
+ transition: width 0.1s ease;
103
+ }
104
+
105
+ .time-display {
106
+ color: #666;
107
+ font-size: 0.9rem;
108
+ font-weight: 500;
109
+ min-width: 40px;
110
+ }
111
+
112
+ .tracks-grid {
113
+ display: grid;
114
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
115
+ gap: 1.5rem;
116
+ margin-bottom: 2rem;
117
+ }
118
+
119
+ .track-card {
120
+ background: rgba(0, 0, 0, 0.03);
121
+ border-radius: 15px;
122
+ padding: 1.5rem;
123
+ transition: all 0.3s ease;
124
+ border: 1px solid rgba(0, 0, 0, 0.05);
125
+ }
126
+
127
+ .track-card:hover {
128
+ background: rgba(0, 0, 0, 0.05);
129
+ transform: translateY(-2px);
130
+ }
131
+
132
+ .track-header {
133
+ display: flex;
134
+ justify-content: space-between;
135
+ align-items: center;
136
+ margin-bottom: 1rem;
137
+ }
138
+
139
+ .track-info {
140
+ display: flex;
141
+ align-items: center;
142
+ gap: 0.75rem;
143
+ }
144
+
145
+ .track-icon {
146
+ font-size: 1.5rem;
147
+ }
148
+
149
+ .track-card h3 {
150
+ font-size: 1.1rem;
151
+ font-weight: 600;
152
+ margin: 0;
153
+ }
154
+
155
+ .track-actions {
156
+ display: flex;
157
+ gap: 0.5rem;
158
+ }
159
+
160
+ .mute-button,
161
+ .download-button {
162
+ width: 36px;
163
+ height: 36px;
164
+ border-radius: 8px;
165
+ border: none;
166
+ cursor: pointer;
167
+ display: flex;
168
+ align-items: center;
169
+ justify-content: center;
170
+ transition: all 0.3s ease;
171
+ background: rgba(0, 0, 0, 0.1);
172
+ color: #666;
173
+ }
174
+
175
+ .mute-button:hover,
176
+ .download-button:hover {
177
+ background: rgba(0, 0, 0, 0.15);
178
+ transform: scale(1.05);
179
+ }
180
+
181
+ .mute-button.muted {
182
+ background: rgba(231, 76, 60, 0.1);
183
+ color: #e74c3c;
184
+ }
185
+
186
+ .volume-control {
187
+ display: flex;
188
+ align-items: center;
189
+ gap: 1rem;
190
+ }
191
+
192
+ .volume-slider {
193
+ flex: 1;
194
+ height: 6px;
195
+ border-radius: 3px;
196
+ outline: none;
197
+ cursor: pointer;
198
+ -webkit-appearance: none;
199
+ appearance: none;
200
+ }
201
+
202
+ .volume-slider::-webkit-slider-thumb {
203
+ -webkit-appearance: none;
204
+ appearance: none;
205
+ width: 18px;
206
+ height: 18px;
207
+ border-radius: 50%;
208
+ background: white;
209
+ cursor: pointer;
210
+ box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2);
211
+ border: 2px solid currentColor;
212
+ }
213
+
214
+ .volume-slider::-moz-range-thumb {
215
+ width: 18px;
216
+ height: 18px;
217
+ border-radius: 50%;
218
+ background: white;
219
+ cursor: pointer;
220
+ box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2);
221
+ border: 2px solid currentColor;
222
+ }
223
+
224
+ .volume-label {
225
+ font-size: 0.8rem;
226
+ color: #666;
227
+ font-weight: 500;
228
+ min-width: 35px;
229
+ text-align: right;
230
+ }
231
+
232
+ .download-all {
233
+ text-align: center;
234
+ padding-top: 1.5rem;
235
+ border-top: 1px solid rgba(0, 0, 0, 0.1);
236
+ }
237
+
238
+ .download-all-button {
239
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
240
+ color: white;
241
+ border: none;
242
+ padding: 1rem 2rem;
243
+ border-radius: 12px;
244
+ font-size: 1rem;
245
+ font-weight: 600;
246
+ cursor: pointer;
247
+ transition: all 0.3s ease;
248
+ display: inline-flex;
249
+ align-items: center;
250
+ gap: 0.75rem;
251
+ box-shadow: 0 10px 20px rgba(102, 126, 234, 0.3);
252
+ }
253
+
254
+ .download-all-button:hover {
255
+ transform: translateY(-2px);
256
+ box-shadow: 0 15px 30px rgba(102, 126, 234, 0.4);
257
+ }
258
+
259
+ @media (max-width: 768px) {
260
+ .player-container {
261
+ padding: 1.5rem;
262
+ margin: 1rem;
263
+ }
264
+
265
+ .main-controls {
266
+ flex-direction: column;
267
+ gap: 1rem;
268
+ }
269
+
270
+ .progress-container {
271
+ width: 100%;
272
+ }
273
+
274
+ .tracks-grid {
275
+ grid-template-columns: 1fr;
276
+ gap: 1rem;
277
+ }
278
+
279
+ .track-card {
280
+ padding: 1rem;
281
+ }
282
+
283
+ .player-header h2 {
284
+ font-size: 1.5rem;
285
+ }
286
+ }
287
+
288
+ @media (max-width: 480px) {
289
+ .track-header {
290
+ flex-direction: column;
291
+ align-items: flex-start;
292
+ gap: 1rem;
293
+ }
294
+
295
+ .track-actions {
296
+ align-self: flex-end;
297
+ }
298
+ }
src/components/AudioPlayer.js ADDED
@@ -0,0 +1,263 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useState, useRef, useEffect } from 'react';
2
+ import { Play, Pause, Download, RotateCcw, Volume2, VolumeX } from 'lucide-react';
3
+ import './AudioPlayer.css';
4
+
5
+ const AudioPlayer = ({ tracks, originalFileName, onReset }) => {
6
+ const [isPlaying, setIsPlaying] = useState(false);
7
+ const [currentTime, setCurrentTime] = useState(0);
8
+ const [duration, setDuration] = useState(0);
9
+ const [volumes, setVolumes] = useState({
10
+ vocals: 1,
11
+ drums: 1,
12
+ bass: 1,
13
+ other: 1
14
+ });
15
+ const [mutedTracks, setMutedTracks] = useState({
16
+ vocals: false,
17
+ drums: false,
18
+ bass: false,
19
+ other: false
20
+ });
21
+
22
+ const audioRefs = useRef({});
23
+ const progressRef = useRef(null);
24
+
25
+ const trackInfo = {
26
+ vocals: { name: 'Vocals', color: '#e74c3c', icon: '🎤' },
27
+ drums: { name: 'Drums', color: '#f39c12', icon: '🥁' },
28
+ bass: { name: 'Bass', color: '#9b59b6', icon: '🎸' },
29
+ other: { name: 'Other', color: '#2ecc71', icon: '🎹' }
30
+ };
31
+
32
+ useEffect(() => {
33
+ const eventListeners = [];
34
+
35
+ // Use a small delay to ensure audio elements are rendered
36
+ const setupAudio = () => {
37
+ Object.keys(tracks).forEach(trackType => {
38
+ const audio = audioRefs.current[trackType];
39
+ if (audio) {
40
+ const handleLoadedMetadata = () => {
41
+ if (audioRefs.current[trackType]) {
42
+ setDuration(audioRefs.current[trackType].duration);
43
+ }
44
+ };
45
+
46
+ const handleTimeUpdate = () => {
47
+ if (audioRefs.current[trackType]) {
48
+ setCurrentTime(audioRefs.current[trackType].currentTime);
49
+ }
50
+ };
51
+
52
+ // Remove any existing listeners first
53
+ audio.removeEventListener('loadedmetadata', handleLoadedMetadata);
54
+ audio.removeEventListener('timeupdate', handleTimeUpdate);
55
+
56
+ // Add new listeners
57
+ audio.addEventListener('loadedmetadata', handleLoadedMetadata);
58
+ audio.addEventListener('timeupdate', handleTimeUpdate);
59
+
60
+ // Store references for cleanup
61
+ eventListeners.push({
62
+ audio,
63
+ events: [
64
+ { type: 'loadedmetadata', handler: handleLoadedMetadata },
65
+ { type: 'timeupdate', handler: handleTimeUpdate }
66
+ ]
67
+ });
68
+
69
+ // If metadata is already loaded, set duration immediately
70
+ if (audio.duration && !isNaN(audio.duration)) {
71
+ setDuration(audio.duration);
72
+ }
73
+ }
74
+ });
75
+ };
76
+
77
+ // Setup audio with a small delay to ensure DOM is ready
78
+ const timeoutId = setTimeout(setupAudio, 100);
79
+
80
+ return () => {
81
+ clearTimeout(timeoutId);
82
+
83
+ // Clean up event listeners
84
+ eventListeners.forEach(({ audio, events }) => {
85
+ if (audio) {
86
+ events.forEach(({ type, handler }) => {
87
+ audio.removeEventListener(type, handler);
88
+ });
89
+ }
90
+ });
91
+
92
+ // Pause and clean up audio elements
93
+ Object.values(audioRefs.current).forEach(audio => {
94
+ if (audio) {
95
+ audio.pause();
96
+ }
97
+ });
98
+ };
99
+ }, [tracks]);
100
+
101
+ const togglePlayPause = () => {
102
+ const newIsPlaying = !isPlaying;
103
+ setIsPlaying(newIsPlaying);
104
+
105
+ Object.values(audioRefs.current).forEach(audio => {
106
+ if (audio) {
107
+ if (newIsPlaying) {
108
+ audio.play().catch(console.error);
109
+ } else {
110
+ audio.pause();
111
+ }
112
+ }
113
+ });
114
+ };
115
+
116
+ const handleProgressClick = (e) => {
117
+ if (progressRef.current && duration > 0) {
118
+ const rect = progressRef.current.getBoundingClientRect();
119
+ const clickX = e.clientX - rect.left;
120
+ const newTime = (clickX / rect.width) * duration;
121
+
122
+ Object.values(audioRefs.current).forEach(audio => {
123
+ if (audio) {
124
+ try {
125
+ audio.currentTime = newTime;
126
+ } catch (error) {
127
+ console.warn('Could not set currentTime:', error);
128
+ }
129
+ }
130
+ });
131
+ }
132
+ };
133
+
134
+ const handleVolumeChange = (trackType, volume) => {
135
+ setVolumes(prev => ({ ...prev, [trackType]: volume }));
136
+ const audio = audioRefs.current[trackType];
137
+ if (audio) {
138
+ audio.volume = mutedTracks[trackType] ? 0 : volume;
139
+ }
140
+ };
141
+
142
+ const toggleMute = (trackType) => {
143
+ const newMuted = !mutedTracks[trackType];
144
+ setMutedTracks(prev => ({ ...prev, [trackType]: newMuted }));
145
+
146
+ const audio = audioRefs.current[trackType];
147
+ if (audio) {
148
+ audio.volume = newMuted ? 0 : volumes[trackType];
149
+ }
150
+ };
151
+
152
+ const formatTime = (time) => {
153
+ const minutes = Math.floor(time / 60);
154
+ const seconds = Math.floor(time % 60);
155
+ return `${minutes}:${seconds.toString().padStart(2, '0')}`;
156
+ };
157
+
158
+ const downloadTrack = (trackType) => {
159
+ const link = document.createElement('a');
160
+ link.href = tracks[trackType];
161
+ link.download = `${originalFileName}_${trackType}.wav`;
162
+ link.click();
163
+ };
164
+
165
+ return (
166
+ <div className="player-container">
167
+ <div className="player-header">
168
+ <h2>Separated Tracks</h2>
169
+ <p>Your music has been successfully separated into individual stems</p>
170
+ <button onClick={onReset} className="reset-button">
171
+ <RotateCcw size={16} />
172
+ Process Another Song
173
+ </button>
174
+ </div>
175
+
176
+ <div className="main-controls">
177
+ <button onClick={togglePlayPause} className="play-button">
178
+ {isPlaying ? <Pause size={24} /> : <Play size={24} />}
179
+ </button>
180
+
181
+ <div className="progress-container">
182
+ <span className="time-display">{formatTime(currentTime)}</span>
183
+ <div
184
+ ref={progressRef}
185
+ className="progress-bar"
186
+ onClick={handleProgressClick}
187
+ >
188
+ <div
189
+ className="progress-fill"
190
+ style={{ width: duration > 0 ? `${(currentTime / duration) * 100}%` : '0%' }}
191
+ ></div>
192
+ </div>
193
+ <span className="time-display">{formatTime(duration)}</span>
194
+ </div>
195
+ </div>
196
+
197
+ <div className="tracks-grid">
198
+ {Object.entries(tracks).map(([trackType, trackUrl]) => (
199
+ <div key={trackType} className="track-card">
200
+ <audio
201
+ ref={el => audioRefs.current[trackType] = el}
202
+ src={trackUrl}
203
+ preload="metadata"
204
+ />
205
+
206
+ <div className="track-header">
207
+ <div className="track-info">
208
+ <span className="track-icon">{trackInfo[trackType].icon}</span>
209
+ <h3 style={{ color: trackInfo[trackType].color }}>
210
+ {trackInfo[trackType].name}
211
+ </h3>
212
+ </div>
213
+
214
+ <div className="track-actions">
215
+ <button
216
+ onClick={() => toggleMute(trackType)}
217
+ className={`mute-button ${mutedTracks[trackType] ? 'muted' : ''}`}
218
+ >
219
+ {mutedTracks[trackType] ? <VolumeX size={16} /> : <Volume2 size={16} />}
220
+ </button>
221
+
222
+ <button
223
+ onClick={() => downloadTrack(trackType)}
224
+ className="download-button"
225
+ >
226
+ <Download size={16} />
227
+ </button>
228
+ </div>
229
+ </div>
230
+
231
+ <div className="volume-control">
232
+ <input
233
+ type="range"
234
+ min="0"
235
+ max="1"
236
+ step="0.1"
237
+ value={volumes[trackType]}
238
+ onChange={(e) => handleVolumeChange(trackType, parseFloat(e.target.value))}
239
+ className="volume-slider"
240
+ style={{
241
+ background: `linear-gradient(to right, ${trackInfo[trackType].color} 0%, ${trackInfo[trackType].color} ${volumes[trackType] * 100}%, #ddd ${volumes[trackType] * 100}%, #ddd 100%)`
242
+ }}
243
+ />
244
+ <span className="volume-label">{Math.round(volumes[trackType] * 100)}%</span>
245
+ </div>
246
+ </div>
247
+ ))}
248
+ </div>
249
+
250
+ <div className="download-all">
251
+ <button
252
+ onClick={() => Object.keys(tracks).forEach(downloadTrack)}
253
+ className="download-all-button"
254
+ >
255
+ <Download size={20} />
256
+ Download All Tracks
257
+ </button>
258
+ </div>
259
+ </div>
260
+ );
261
+ };
262
+
263
+ export default AudioPlayer;
src/components/FileUpload.css ADDED
@@ -0,0 +1,178 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .upload-container {
2
+ display: flex;
3
+ flex-direction: column;
4
+ align-items: center;
5
+ gap: 3rem;
6
+ max-width: 600px;
7
+ width: 100%;
8
+ }
9
+
10
+ .upload-area {
11
+ background: rgba(255, 255, 255, 0.95);
12
+ backdrop-filter: blur(10px);
13
+ border: 2px dashed #ddd;
14
+ border-radius: 20px;
15
+ padding: 3rem 2rem;
16
+ text-align: center;
17
+ cursor: pointer;
18
+ transition: all 0.3s ease;
19
+ width: 100%;
20
+ box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
21
+ }
22
+
23
+ .upload-area:hover,
24
+ .upload-area.drag-over {
25
+ border-color: #667eea;
26
+ background: rgba(255, 255, 255, 0.98);
27
+ transform: translateY(-5px);
28
+ box-shadow: 0 25px 50px rgba(0, 0, 0, 0.15);
29
+ }
30
+
31
+ .upload-content {
32
+ display: flex;
33
+ flex-direction: column;
34
+ align-items: center;
35
+ gap: 1.5rem;
36
+ }
37
+
38
+ .upload-icon {
39
+ color: #667eea;
40
+ opacity: 0.8;
41
+ }
42
+
43
+ .upload-area h2 {
44
+ color: #333;
45
+ font-size: 1.5rem;
46
+ font-weight: 600;
47
+ margin: 0;
48
+ }
49
+
50
+ .upload-area p {
51
+ color: #666;
52
+ font-size: 1rem;
53
+ margin: 0;
54
+ line-height: 1.5;
55
+ }
56
+
57
+ .supported-formats {
58
+ display: flex;
59
+ gap: 1rem;
60
+ flex-wrap: wrap;
61
+ justify-content: center;
62
+ }
63
+
64
+ .format-item {
65
+ display: flex;
66
+ align-items: center;
67
+ gap: 0.5rem;
68
+ background: rgba(102, 126, 234, 0.1);
69
+ padding: 0.5rem 1rem;
70
+ border-radius: 20px;
71
+ color: #667eea;
72
+ font-size: 0.9rem;
73
+ font-weight: 500;
74
+ }
75
+
76
+ .upload-button {
77
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
78
+ color: white;
79
+ border: none;
80
+ padding: 1rem 2rem;
81
+ border-radius: 12px;
82
+ font-size: 1rem;
83
+ font-weight: 600;
84
+ cursor: pointer;
85
+ transition: all 0.3s ease;
86
+ box-shadow: 0 10px 20px rgba(102, 126, 234, 0.3);
87
+ }
88
+
89
+ .upload-button:hover {
90
+ transform: translateY(-2px);
91
+ box-shadow: 0 15px 30px rgba(102, 126, 234, 0.4);
92
+ }
93
+
94
+ .info-section {
95
+ background: rgba(255, 255, 255, 0.9);
96
+ backdrop-filter: blur(10px);
97
+ border-radius: 20px;
98
+ padding: 2rem;
99
+ width: 100%;
100
+ text-align: center;
101
+ box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
102
+ }
103
+
104
+ .info-section h3 {
105
+ color: #333;
106
+ font-size: 1.25rem;
107
+ font-weight: 600;
108
+ margin-bottom: 1.5rem;
109
+ }
110
+
111
+ .steps {
112
+ display: flex;
113
+ justify-content: space-around;
114
+ gap: 1rem;
115
+ flex-wrap: wrap;
116
+ }
117
+
118
+ .step {
119
+ display: flex;
120
+ flex-direction: column;
121
+ align-items: center;
122
+ gap: 0.75rem;
123
+ flex: 1;
124
+ min-width: 120px;
125
+ }
126
+
127
+ .step-number {
128
+ width: 40px;
129
+ height: 40px;
130
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
131
+ color: white;
132
+ border-radius: 50%;
133
+ display: flex;
134
+ align-items: center;
135
+ justify-content: center;
136
+ font-weight: 600;
137
+ font-size: 1.1rem;
138
+ }
139
+
140
+ .step p {
141
+ color: #666;
142
+ font-size: 0.9rem;
143
+ margin: 0;
144
+ text-align: center;
145
+ }
146
+
147
+ @media (max-width: 768px) {
148
+ .upload-container {
149
+ gap: 2rem;
150
+ }
151
+
152
+ .upload-area {
153
+ padding: 2rem 1.5rem;
154
+ }
155
+
156
+ .upload-area h2 {
157
+ font-size: 1.25rem;
158
+ }
159
+
160
+ .supported-formats {
161
+ gap: 0.5rem;
162
+ }
163
+
164
+ .format-item {
165
+ padding: 0.4rem 0.8rem;
166
+ font-size: 0.8rem;
167
+ }
168
+
169
+ .steps {
170
+ flex-direction: column;
171
+ gap: 1.5rem;
172
+ }
173
+
174
+ .step {
175
+ flex-direction: row;
176
+ text-align: left;
177
+ }
178
+ }
src/components/FileUpload.js ADDED
@@ -0,0 +1,119 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useState, useRef } from 'react';
2
+ import { Upload, Music, FileAudio } from 'lucide-react';
3
+ import './FileUpload.css';
4
+
5
+ const FileUpload = ({ onFileUpload }) => {
6
+ const [isDragOver, setIsDragOver] = useState(false);
7
+ const fileInputRef = useRef(null);
8
+
9
+ const handleDragOver = (e) => {
10
+ e.preventDefault();
11
+ setIsDragOver(true);
12
+ };
13
+
14
+ const handleDragLeave = (e) => {
15
+ e.preventDefault();
16
+ setIsDragOver(false);
17
+ };
18
+
19
+ const handleDrop = (e) => {
20
+ e.preventDefault();
21
+ setIsDragOver(false);
22
+
23
+ const files = e.dataTransfer.files;
24
+ if (files.length > 0) {
25
+ handleFileSelect(files[0]);
26
+ }
27
+ };
28
+
29
+ const handleFileSelect = (file) => {
30
+ if (file && file.type.startsWith('audio/')) {
31
+ onFileUpload(file);
32
+ } else {
33
+ alert('Please select a valid audio file (MP3, WAV, FLAC, etc.)');
34
+ }
35
+ };
36
+
37
+ const handleClick = () => {
38
+ fileInputRef.current?.click();
39
+ };
40
+
41
+ const handleFileInputChange = (e) => {
42
+ const file = e.target.files[0];
43
+ if (file) {
44
+ handleFileSelect(file);
45
+ }
46
+ };
47
+
48
+ return (
49
+ <div className="upload-container">
50
+ <div
51
+ className={`upload-area ${isDragOver ? 'drag-over' : ''}`}
52
+ onDragOver={handleDragOver}
53
+ onDragLeave={handleDragLeave}
54
+ onDrop={handleDrop}
55
+ onClick={handleClick}
56
+ >
57
+ <input
58
+ ref={fileInputRef}
59
+ type="file"
60
+ accept="audio/*"
61
+ onChange={handleFileInputChange}
62
+ style={{ display: 'none' }}
63
+ />
64
+
65
+ <div className="upload-content">
66
+ <div className="upload-icon">
67
+ <Upload size={48} />
68
+ </div>
69
+
70
+ <h2>Upload Your Music</h2>
71
+ <p>Drag and drop an audio file here, or click to browse</p>
72
+
73
+ <div className="supported-formats">
74
+ <div className="format-item">
75
+ <FileAudio size={20} />
76
+ <span>MP3</span>
77
+ </div>
78
+ <div className="format-item">
79
+ <FileAudio size={20} />
80
+ <span>WAV</span>
81
+ </div>
82
+ <div className="format-item">
83
+ <FileAudio size={20} />
84
+ <span>FLAC</span>
85
+ </div>
86
+ <div className="format-item">
87
+ <Music size={20} />
88
+ <span>More</span>
89
+ </div>
90
+ </div>
91
+
92
+ <button className="upload-button">
93
+ Choose File
94
+ </button>
95
+ </div>
96
+ </div>
97
+
98
+ <div className="info-section">
99
+ <h3>How it works</h3>
100
+ <div className="steps">
101
+ <div className="step">
102
+ <div className="step-number">1</div>
103
+ <p>Upload your audio file</p>
104
+ </div>
105
+ <div className="step">
106
+ <div className="step-number">2</div>
107
+ <p>AI separates the tracks</p>
108
+ </div>
109
+ <div className="step">
110
+ <div className="step-number">3</div>
111
+ <p>Download isolated stems</p>
112
+ </div>
113
+ </div>
114
+ </div>
115
+ </div>
116
+ );
117
+ };
118
+
119
+ export default FileUpload;
src/components/Header.css ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .header {
2
+ background: rgba(255, 255, 255, 0.1);
3
+ backdrop-filter: blur(10px);
4
+ border-bottom: 1px solid rgba(255, 255, 255, 0.2);
5
+ padding: 1.5rem 2rem;
6
+ }
7
+
8
+ .header-content {
9
+ max-width: 1200px;
10
+ margin: 0 auto;
11
+ display: flex;
12
+ align-items: center;
13
+ justify-content: space-between;
14
+ flex-wrap: wrap;
15
+ gap: 1rem;
16
+ }
17
+
18
+ .logo {
19
+ display: flex;
20
+ align-items: center;
21
+ gap: 0.75rem;
22
+ color: white;
23
+ }
24
+
25
+ .logo h1 {
26
+ font-size: 1.75rem;
27
+ font-weight: 700;
28
+ margin: 0;
29
+ }
30
+
31
+ .tagline {
32
+ color: rgba(255, 255, 255, 0.8);
33
+ font-size: 0.9rem;
34
+ font-weight: 400;
35
+ margin: 0;
36
+ }
37
+
38
+ @media (max-width: 768px) {
39
+ .header-content {
40
+ flex-direction: column;
41
+ text-align: center;
42
+ }
43
+
44
+ .logo h1 {
45
+ font-size: 1.5rem;
46
+ }
47
+ }
src/components/Header.js ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from 'react';
2
+ import { Music } from 'lucide-react';
3
+ import './Header.css';
4
+
5
+ const Header = () => {
6
+ return (
7
+ <header className="header">
8
+ <div className="header-content">
9
+ <div className="logo">
10
+ <Music size={32} />
11
+ <h1>Thiiia</h1>
12
+ </div>
13
+ <p className="tagline">AI-Powered Music Source Separation</p>
14
+ </div>
15
+ </header>
16
+ );
17
+ };
18
+
19
+ export default Header;
src/components/ProcessingStatus.css ADDED
@@ -0,0 +1,177 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .processing-container {
2
+ display: flex;
3
+ align-items: center;
4
+ justify-content: center;
5
+ width: 100%;
6
+ }
7
+
8
+ .processing-card {
9
+ background: rgba(255, 255, 255, 0.95);
10
+ backdrop-filter: blur(10px);
11
+ border-radius: 20px;
12
+ padding: 3rem 2rem;
13
+ text-align: center;
14
+ box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
15
+ border: 1px solid rgba(255, 255, 255, 0.2);
16
+ max-width: 500px;
17
+ width: 100%;
18
+ }
19
+
20
+ .processing-header {
21
+ margin-bottom: 2rem;
22
+ }
23
+
24
+ .processing-icon {
25
+ color: #667eea;
26
+ margin-bottom: 1rem;
27
+ }
28
+
29
+ .spinner {
30
+ animation: spin 1s linear infinite;
31
+ }
32
+
33
+ @keyframes spin {
34
+ from { transform: rotate(0deg); }
35
+ to { transform: rotate(360deg); }
36
+ }
37
+
38
+ .processing-card h2 {
39
+ color: #333;
40
+ font-size: 1.5rem;
41
+ font-weight: 600;
42
+ margin-bottom: 0.5rem;
43
+ }
44
+
45
+ .processing-card p {
46
+ color: #666;
47
+ font-size: 1rem;
48
+ line-height: 1.5;
49
+ margin: 0;
50
+ }
51
+
52
+ .file-info {
53
+ display: flex;
54
+ align-items: center;
55
+ justify-content: center;
56
+ gap: 0.75rem;
57
+ background: rgba(102, 126, 234, 0.1);
58
+ padding: 1rem;
59
+ border-radius: 12px;
60
+ margin-bottom: 2rem;
61
+ color: #667eea;
62
+ }
63
+
64
+ .file-name {
65
+ font-weight: 500;
66
+ font-size: 0.9rem;
67
+ word-break: break-all;
68
+ }
69
+
70
+ .progress-section {
71
+ margin-bottom: 2rem;
72
+ }
73
+
74
+ .progress-bar {
75
+ width: 100%;
76
+ height: 8px;
77
+ background: rgba(102, 126, 234, 0.1);
78
+ border-radius: 4px;
79
+ overflow: hidden;
80
+ margin-bottom: 0.75rem;
81
+ }
82
+
83
+ .progress-fill {
84
+ height: 100%;
85
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
86
+ border-radius: 4px;
87
+ transition: width 0.3s ease;
88
+ }
89
+
90
+ .progress-text {
91
+ color: #667eea;
92
+ font-weight: 600;
93
+ font-size: 1rem;
94
+ }
95
+
96
+ .processing-steps {
97
+ display: flex;
98
+ flex-direction: column;
99
+ gap: 1rem;
100
+ margin-bottom: 2rem;
101
+ text-align: left;
102
+ }
103
+
104
+ .step {
105
+ display: flex;
106
+ align-items: center;
107
+ gap: 1rem;
108
+ padding: 0.75rem;
109
+ border-radius: 8px;
110
+ transition: all 0.3s ease;
111
+ }
112
+
113
+ .step.active {
114
+ background: rgba(102, 126, 234, 0.1);
115
+ color: #667eea;
116
+ }
117
+
118
+ .step.completed {
119
+ background: rgba(46, 204, 113, 0.1);
120
+ color: #2ecc71;
121
+ }
122
+
123
+ .step-dot {
124
+ width: 12px;
125
+ height: 12px;
126
+ border-radius: 50%;
127
+ background: #ddd;
128
+ transition: all 0.3s ease;
129
+ }
130
+
131
+ .step.active .step-dot {
132
+ background: #667eea;
133
+ animation: pulse 1.5s infinite;
134
+ }
135
+
136
+ .step.completed .step-dot {
137
+ background: #2ecc71;
138
+ }
139
+
140
+ @keyframes pulse {
141
+ 0%, 100% { opacity: 1; }
142
+ 50% { opacity: 0.5; }
143
+ }
144
+
145
+ .step span {
146
+ font-size: 0.9rem;
147
+ font-weight: 500;
148
+ }
149
+
150
+ .estimated-time {
151
+ color: #999;
152
+ font-size: 0.85rem;
153
+ font-style: italic;
154
+ }
155
+
156
+ .estimated-time p {
157
+ margin: 0;
158
+ }
159
+
160
+ @media (max-width: 768px) {
161
+ .processing-card {
162
+ padding: 2rem 1.5rem;
163
+ margin: 1rem;
164
+ }
165
+
166
+ .processing-card h2 {
167
+ font-size: 1.25rem;
168
+ }
169
+
170
+ .file-info {
171
+ padding: 0.75rem;
172
+ }
173
+
174
+ .file-name {
175
+ font-size: 0.8rem;
176
+ }
177
+ }
src/components/ProcessingStatus.js ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from 'react';
2
+ import { Loader2, Music } from 'lucide-react';
3
+ import './ProcessingStatus.css';
4
+
5
+ const ProcessingStatus = ({ progress, fileName }) => {
6
+ return (
7
+ <div className="processing-container">
8
+ <div className="processing-card">
9
+ <div className="processing-header">
10
+ <div className="processing-icon">
11
+ <Loader2 size={32} className="spinner" />
12
+ </div>
13
+ <h2>Processing Your Music</h2>
14
+ <p>AI is separating your audio into individual tracks...</p>
15
+ </div>
16
+
17
+ <div className="file-info">
18
+ <Music size={20} />
19
+ <span className="file-name">{fileName}</span>
20
+ </div>
21
+
22
+ <div className="progress-section">
23
+ <div className="progress-bar">
24
+ <div
25
+ className="progress-fill"
26
+ style={{ width: `${progress}%` }}
27
+ ></div>
28
+ </div>
29
+ <div className="progress-text">
30
+ {Math.round(progress)}% Complete
31
+ </div>
32
+ </div>
33
+
34
+ <div className="processing-steps">
35
+ <div className={`step ${progress > 20 ? 'completed' : 'active'}`}>
36
+ <div className="step-dot"></div>
37
+ <span>Loading audio</span>
38
+ </div>
39
+ <div className={`step ${progress > 40 ? 'completed' : progress > 20 ? 'active' : ''}`}>
40
+ <div className="step-dot"></div>
41
+ <span>Analyzing frequencies</span>
42
+ </div>
43
+ <div className={`step ${progress > 70 ? 'completed' : progress > 40 ? 'active' : ''}`}>
44
+ <div className="step-dot"></div>
45
+ <span>Separating sources</span>
46
+ </div>
47
+ <div className={`step ${progress > 90 ? 'completed' : progress > 70 ? 'active' : ''}`}>
48
+ <div className="step-dot"></div>
49
+ <span>Finalizing tracks</span>
50
+ </div>
51
+ </div>
52
+
53
+ <div className="estimated-time">
54
+ <p>This usually takes 1-3 minutes depending on song length</p>
55
+ </div>
56
+ </div>
57
+ </div>
58
+ );
59
+ };
60
+
61
+ export default ProcessingStatus;
src/index.css ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ * {
2
+ margin: 0;
3
+ padding: 0;
4
+ box-sizing: border-box;
5
+ }
6
+
7
+ body {
8
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
9
+ 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
10
+ sans-serif;
11
+ -webkit-font-smoothing: antialiased;
12
+ -moz-osx-font-smoothing: grayscale;
13
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
14
+ min-height: 100vh;
15
+ }
16
+
17
+ code {
18
+ font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
19
+ monospace;
20
+ }
src/index.js ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from 'react';
2
+ import ReactDOM from 'react-dom/client';
3
+ import './index.css';
4
+ import App from './App';
5
+
6
+ const root = ReactDOM.createRoot(document.getElementById('root'));
7
+ root.render(
8
+ <React.StrictMode>
9
+ <App />
10
+ </React.StrictMode>
11
+ );
vite.config.js ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { defineConfig } from 'vite'
2
+ import react from '@vitejs/plugin-react'
3
+
4
+ // https://vitejs.dev/config/
5
+ export default defineConfig({
6
+ plugins: [react()],
7
+ esbuild: {
8
+ loader: 'jsx',
9
+ include: /src\/.*\.[jt]sx?$/,
10
+ exclude: [],
11
+ },
12
+ optimizeDeps: {
13
+ esbuildOptions: {
14
+ loader: {
15
+ '.js': 'jsx',
16
+ },
17
+ },
18
+ },
19
+ server: {
20
+ port: 3001,
21
+ proxy: {
22
+ '/api': {
23
+ target: 'http://localhost:5000',
24
+ changeOrigin: true,
25
+ secure: false,
26
+ configure: (proxy, options) => {
27
+ proxy.on('error', (err, req, res) => {
28
+ console.log('proxy error', err);
29
+ });
30
+ proxy.on('proxyReq', (proxyReq, req, res) => {
31
+ console.log('Sending Request to the Target:', req.method, req.url);
32
+ });
33
+ proxy.on('proxyRes', (proxyRes, req, res) => {
34
+ console.log('Received Response from the Target:', proxyRes.statusCode, req.url);
35
+ });
36
+ },
37
+ }
38
+ }
39
+ },
40
+ build: {
41
+ outDir: 'dist',
42
+ assetsDir: 'assets',
43
+ }
44
+ })