├── backend
├── env.example
├── package.json
├── server.js
└── package-lock.json
├── screenshot.png
├── .gitignore
├── frontend
├── tsconfig.node.json
├── src
│ ├── main.tsx
│ ├── index.css
│ ├── types.ts
│ ├── react-easy-crop.d.ts
│ ├── utils.ts
│ ├── api.ts
│ └── App.css
├── vite.config.ts
├── index.html
├── package.json
├── tsconfig.json
└── package-lock.json
├── package.json
├── LICENSE
├── CHANGELOG.md
├── SETUP.md
├── README.md
└── DEVELOPMENT.md
/backend/env.example:
--------------------------------------------------------------------------------
1 | FAL_API_KEY=
2 | PORT=3001
3 |
4 |
--------------------------------------------------------------------------------
/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amrrs/fal-nanobanana-studio/HEAD/screenshot.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | dist/
3 | build/
4 | .env
5 | .DS_Store
6 | *.log
7 | .vscode/
8 | .idea/
9 |
10 |
--------------------------------------------------------------------------------
/frontend/tsconfig.node.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "composite": true,
4 | "skipLibCheck": true,
5 | "module": "ESNext",
6 | "moduleResolution": "bundler",
7 | "allowSyntheticDefaultImports": true
8 | },
9 | "include": ["vite.config.ts"]
10 | }
11 |
12 |
--------------------------------------------------------------------------------
/frontend/src/main.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom/client';
3 | import App from './App';
4 | import './index.css';
5 |
6 | ReactDOM.createRoot(document.getElementById('root')!).render(
7 |
8 |
9 | ,
10 | );
11 |
12 |
--------------------------------------------------------------------------------
/frontend/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite';
2 | import react from '@vitejs/plugin-react';
3 |
4 | export default defineConfig({
5 | plugins: [react()],
6 | server: {
7 | port: 3000,
8 | proxy: {
9 | '/api': {
10 | target: 'http://localhost:3001',
11 | changeOrigin: true,
12 | },
13 | },
14 | },
15 | });
16 |
17 |
--------------------------------------------------------------------------------
/frontend/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | NanoBanana Studio - AI Image Editor
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/backend/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "nanobanana-backend",
3 | "version": "1.0.0",
4 | "type": "module",
5 | "scripts": {
6 | "dev": "node --watch server.js",
7 | "start": "node server.js"
8 | },
9 | "dependencies": {
10 | "express": "^4.18.2",
11 | "cors": "^2.8.5",
12 | "dotenv": "^16.3.1",
13 | "multer": "^1.4.5-lts.1",
14 | "form-data": "^4.0.0",
15 | "node-fetch": "^3.3.2",
16 | "@fal-ai/client": "^1.0.0"
17 | }
18 | }
19 |
20 |
--------------------------------------------------------------------------------
/frontend/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "nanobanana-frontend",
3 | "version": "1.0.0",
4 | "type": "module",
5 | "scripts": {
6 | "dev": "vite",
7 | "build": "tsc && vite build",
8 | "preview": "vite preview"
9 | },
10 | "dependencies": {
11 | "react": "^18.2.0",
12 | "react-dom": "^18.2.0",
13 | "lucide-react": "^0.294.0",
14 | "react-easy-crop": "^5.0.7"
15 | },
16 | "devDependencies": {
17 | "@types/react": "^18.2.43",
18 | "@types/react-dom": "^18.2.17",
19 | "@vitejs/plugin-react": "^4.2.1",
20 | "typescript": "^5.3.3",
21 | "vite": "^5.0.8"
22 | }
23 | }
24 |
25 |
--------------------------------------------------------------------------------
/frontend/src/index.css:
--------------------------------------------------------------------------------
1 | * {
2 | margin: 0;
3 | padding: 0;
4 | box-sizing: border-box;
5 | }
6 |
7 | body {
8 | font-family: -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: #1a1a1a;
14 | color: #e0e0e0;
15 | overflow: hidden;
16 | }
17 |
18 | #root {
19 | width: 100vw;
20 | height: 100vh;
21 | }
22 |
23 | code {
24 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
25 | monospace;
26 | }
27 |
28 |
--------------------------------------------------------------------------------
/frontend/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2020",
4 | "useDefineForClassFields": true,
5 | "lib": ["ES2020", "DOM", "DOM.Iterable"],
6 | "module": "ESNext",
7 | "skipLibCheck": true,
8 | "moduleResolution": "bundler",
9 | "allowImportingTsExtensions": true,
10 | "resolveJsonModule": true,
11 | "isolatedModules": true,
12 | "noEmit": true,
13 | "jsx": "react-jsx",
14 | "strict": true,
15 | "noUnusedLocals": true,
16 | "noUnusedParameters": true,
17 | "noFallthroughCasesInSwitch": true
18 | },
19 | "include": ["src"],
20 | "references": [{ "path": "./tsconfig.node.json" }]
21 | }
22 |
23 |
--------------------------------------------------------------------------------
/frontend/src/types.ts:
--------------------------------------------------------------------------------
1 | export interface EditResponse {
2 | images?: Array<{ url: string }>;
3 | image?: { url: string };
4 | }
5 |
6 | export interface ApiError {
7 | error: string;
8 | details?: string;
9 | timestamp?: string;
10 | }
11 |
12 | export type Mode = 'edit' | 'generate';
13 |
14 | export type ModelId = 'nano' | 'pro';
15 |
16 | export interface EditImageParams {
17 | image: Blob;
18 | prompt: string;
19 | negativePrompt?: string;
20 | model?: ModelId;
21 | }
22 |
23 | export interface InpaintImageParams {
24 | image: Blob | string;
25 | mask: Blob | string;
26 | prompt: string;
27 | }
28 |
29 | export interface GenerateImageParams {
30 | prompt: string;
31 | negativePrompt?: string;
32 | width?: number;
33 | height?: number;
34 | model?: ModelId;
35 | }
36 |
--------------------------------------------------------------------------------
/frontend/src/react-easy-crop.d.ts:
--------------------------------------------------------------------------------
1 | declare module 'react-easy-crop' {
2 | import { ComponentType } from 'react';
3 |
4 | export interface Area {
5 | x: number;
6 | y: number;
7 | width: number;
8 | height: number;
9 | }
10 |
11 | export interface CropCoordinates {
12 | x: number;
13 | y: number;
14 | }
15 |
16 | export interface CropperProps {
17 | image?: string;
18 | crop?: CropCoordinates;
19 | zoom?: number;
20 | aspect?: number;
21 | onCropChange?: (value: CropCoordinates) => void;
22 | onZoomChange?: (value: number) => void;
23 | onCropComplete?: (croppedArea: Area, croppedAreaPixels: Area) => void;
24 | restrictPosition?: boolean;
25 | }
26 |
27 | const Cropper: ComponentType;
28 | export default Cropper;
29 | }
30 |
31 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "nanobanana-studio",
3 | "version": "1.0.0",
4 | "description": "AI-powered image editor using Google's nanobanana API from fal.ai",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "concurrently \"npm run dev:frontend\" \"npm run dev:backend\"",
8 | "dev:frontend": "cd frontend && npm run dev",
9 | "dev:backend": "cd backend && npm run dev",
10 | "build": "cd frontend && npm run build",
11 | "install:all": "npm install && cd frontend && npm install && cd ../backend && npm install"
12 | },
13 | "keywords": [
14 | "image-editor",
15 | "ai",
16 | "fal.ai",
17 | "nanobanana"
18 | ],
19 | "author": "",
20 | "license": "MIT",
21 | "devDependencies": {
22 | "concurrently": "^8.2.2"
23 | },
24 | "dependencies": {
25 | "@fal-ai/client": "^1.7.2"
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2025 NanoBanana Studio
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/frontend/src/utils.ts:
--------------------------------------------------------------------------------
1 | import { EditResponse } from './types';
2 |
3 | const MAX_FILE_SIZE = 50 * 1024 * 1024; // 50MB
4 | const ALLOWED_FILE_TYPES = ['image/jpeg', 'image/png', 'image/jpg', 'image/webp'];
5 |
6 | export function validateImageFile(file: File): string | null {
7 | if (!ALLOWED_FILE_TYPES.includes(file.type)) {
8 | return 'Please upload a valid image file (JPEG, PNG, or WebP)';
9 | }
10 |
11 | if (file.size > MAX_FILE_SIZE) {
12 | return 'File size must be less than 50MB';
13 | }
14 |
15 | return null;
16 | }
17 |
18 | export function extractImageUrl(response: EditResponse): string | null {
19 | return response.images?.[0]?.url || response.image?.url || null;
20 | }
21 |
22 | export async function dataUrlToBlob(dataUrl: string): Promise {
23 | const response = await fetch(dataUrl);
24 | return response.blob();
25 | }
26 |
27 | export function downloadImage(imageUrl: string, filename: string = 'image.png') {
28 | const link = document.createElement('a');
29 | link.href = imageUrl;
30 | link.download = filename;
31 | document.body.appendChild(link);
32 | link.click();
33 | document.body.removeChild(link);
34 | }
35 |
36 | export function getTimestamp(): string {
37 | return new Date().toISOString().replace(/[:.]/g, '-');
38 | }
39 |
40 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ## [1.0.0] - Initial Release
4 |
5 | ### Features
6 | - ✨ AI-powered image editing with natural language prompts
7 | - 🎨 Image generation from text descriptions
8 | - 🖼️ Dark theme UI with modern design
9 | - 📥 Image upload with validation
10 | - 💾 One-click download functionality
11 | - ⚡ Two modes: Edit and Generate
12 |
13 | ### Backend Improvements
14 | - Removed unused imports (FormData)
15 | - Added comprehensive input validation
16 | - Implemented request timeout handling (120s)
17 | - Added file type and size validation
18 | - Improved error messages and logging
19 | - Added health check endpoint with API status
20 | - Structured error handling middleware
21 | - Added constants for configuration
22 | - Better API response handling
23 |
24 | ### Frontend Improvements
25 | - Separated concerns into multiple files:
26 | - `api.ts` - API client functions
27 | - `types.ts` - TypeScript interfaces
28 | - `utils.ts` - Helper functions
29 | - Added file validation before upload
30 | - Improved error handling and user feedback
31 | - Added API health check on startup
32 | - Added character counter for prompts
33 | - Added keyboard shortcuts (Ctrl/Cmd + Enter)
34 | - Better loading states and disabled states
35 | - Improved TypeScript type safety
36 | - Added warning messages for API configuration
37 |
38 | ### Code Quality
39 | - Full TypeScript type coverage
40 | - Separated business logic from UI
41 | - Reusable utility functions
42 | - Better error propagation
43 | - Consistent code style
44 | - Comprehensive validation
45 |
46 | ### Security
47 | - File type validation (JPEG, PNG, WebP only)
48 | - File size limits (50MB max)
49 | - Input sanitization
50 | - Prompt length limits (2000 chars)
51 | - Dimension validation for generated images
52 |
53 | ### Documentation
54 | - Comprehensive README.md
55 | - Quick setup guide (SETUP.md)
56 | - API endpoint documentation
57 | - Troubleshooting section
58 |
59 |
--------------------------------------------------------------------------------
/SETUP.md:
--------------------------------------------------------------------------------
1 | # Quick Setup Guide
2 |
3 | ## Step 1: Install Dependencies
4 |
5 | ```bash
6 | npm run install:all
7 | ```
8 |
9 | This will install dependencies for:
10 | - Root package (concurrently for running both servers)
11 | - Frontend (React + TypeScript + Vite)
12 | - Backend (Express + API dependencies)
13 |
14 | ## Step 2: Configure API Key
15 |
16 | 1. Get your fal.ai API key:
17 | - Visit [fal.ai](https://fal.ai)
18 | - Sign up or log in
19 | - Navigate to your API keys section
20 | - Create a new API key
21 |
22 | 2. Set up environment variables:
23 | ```bash
24 | cd backend
25 | cp env.example .env
26 | ```
27 |
28 | 3. Edit `backend/.env` and paste your API key:
29 | ```
30 | FAL_API_KEY=your_actual_api_key_here
31 | PORT=3001
32 | ```
33 |
34 | ## Step 3: Run the Application
35 |
36 | ```bash
37 | npm run dev
38 | ```
39 |
40 | This starts:
41 | - Frontend on http://localhost:3000
42 | - Backend on http://localhost:3001
43 |
44 | ## Step 4: Use the Application
45 |
46 | 1. Open http://localhost:3000 in your browser
47 | 2. **Edit Mode:**
48 | - Click "Upload Image"
49 | - Enter a prompt like "make the sky more dramatic"
50 | - Click "Edit Image"
51 | - Wait for processing
52 | - Download your result
53 |
54 | 3. **Generate Mode:**
55 | - Switch to "Generate" mode
56 | - Enter a prompt like "a beautiful sunset over mountains"
57 | - Click "Generate Image"
58 | - Download the generated image
59 |
60 | ## Troubleshooting
61 |
62 | ### "FAL_API_KEY not configured" error
63 | - Make sure you created the `.env` file in the `backend` directory
64 | - Verify the API key is correct (no extra spaces)
65 | - Restart the backend server after adding the key
66 |
67 | ### API endpoint errors
68 | - Check that your fal.ai API key is valid
69 | - Verify the endpoint URL in `backend/server.js` matches fal.ai's current API
70 | - Check the browser console and server logs for detailed error messages
71 |
72 | ### Port already in use
73 | - Change the PORT in `backend/.env` to a different number
74 | - Or stop the process using port 3000/3001
75 |
76 | ## Next Steps
77 |
78 | - Customize the UI in `frontend/src/App.tsx` and `frontend/src/App.css`
79 | - Add more editing features
80 | - Implement image history/undo functionality
81 | - Add batch processing capabilities
82 |
83 |
--------------------------------------------------------------------------------
/frontend/src/api.ts:
--------------------------------------------------------------------------------
1 | import { EditResponse, EditImageParams, GenerateImageParams, InpaintImageParams } from './types';
2 |
3 | const API_BASE = '/api';
4 | const DEFAULT_MODEL = 'nano';
5 |
6 | async function handleApiResponse(response: Response): Promise {
7 | if (!response.ok) {
8 | const errorData = await response.json().catch(() => ({ error: 'Failed to process request' }));
9 | throw new Error(errorData.error || `Server error: ${response.status}`);
10 | }
11 | return response.json();
12 | }
13 |
14 | export async function editImage(params: EditImageParams): Promise {
15 | const formData = new FormData();
16 | formData.append('image', params.image, 'image.png');
17 | formData.append('prompt', params.prompt);
18 |
19 | if (params.negativePrompt) {
20 | formData.append('negativePrompt', params.negativePrompt);
21 | }
22 | formData.append('model', params.model ?? DEFAULT_MODEL);
23 |
24 | const response = await fetch(`${API_BASE}/edit-image`, {
25 | method: 'POST',
26 | body: formData,
27 | });
28 |
29 | return handleApiResponse(response);
30 | }
31 |
32 | export async function inpaintImage(params: InpaintImageParams): Promise {
33 | const formData = new FormData();
34 |
35 | if (typeof params.image === 'string') {
36 | formData.append('imageUrl', params.image);
37 | } else {
38 | formData.append('image', params.image, 'image.png');
39 | }
40 |
41 | if (typeof params.mask === 'string') {
42 | formData.append('maskUrl', params.mask);
43 | } else {
44 | formData.append('mask', params.mask, 'mask.png');
45 | }
46 |
47 | formData.append('prompt', params.prompt);
48 |
49 | const response = await fetch(`${API_BASE}/inpaint-image`, {
50 | method: 'POST',
51 | body: formData,
52 | });
53 |
54 | return handleApiResponse(response);
55 | }
56 |
57 | export async function generateImage(params: GenerateImageParams): Promise {
58 | const response = await fetch(`${API_BASE}/generate-image`, {
59 | method: 'POST',
60 | headers: {
61 | 'Content-Type': 'application/json',
62 | },
63 | body: JSON.stringify({
64 | prompt: params.prompt,
65 | negativePrompt: params.negativePrompt || undefined,
66 | width: params.width,
67 | height: params.height,
68 | model: params.model ?? DEFAULT_MODEL,
69 | }),
70 | });
71 |
72 | return handleApiResponse(response);
73 | }
74 |
75 | export async function segmentImage(image: Blob): Promise {
76 | const formData = new FormData();
77 | formData.append('image', image, 'image.png');
78 |
79 | const response = await fetch(`${API_BASE}/segment-image`, {
80 | method: 'POST',
81 | body: formData,
82 | });
83 |
84 | return handleApiResponse(response);
85 | }
86 |
87 | export async function checkHealth(): Promise<{ status: string; apiConfigured: boolean }> {
88 | const response = await fetch(`${API_BASE}/health`);
89 | return handleApiResponse(response);
90 | }
91 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # NanoBanana Studio
2 |
3 | A modern, AI-powered image editor alternative to Photoshop/Photopea, powered by Google's nanobanana API from [fal.ai](https://fal.ai/dashboard).
4 |
5 | 
6 |
7 | ## Features
8 |
9 | - 🎨 **AI-Powered Image Editing**: Edit images using natural language prompts
10 | - ✨ **Image Generation**: Generate new images from text descriptions
11 | - 🖼️ **Intuitive UI**: Clean, modern interface inspired by professional image editors
12 | - ⚡ **Model Switcher**: Toggle between NanoBanana and [NanoBanana Pro](https://fal.ai/models/fal-ai/nano-banana-pro/edit/api) on the fly
13 | - 💾 **Easy Export**: Download your edited images with one click
14 |
15 | ## Prerequisites
16 |
17 | - Node.js 18+ and npm
18 | - A fal.ai API key ([Get one here](https://fal.ai/dashboard/keys))
19 |
20 | ## Installation
21 |
22 | 1. Clone the repository and install dependencies:
23 |
24 | ```bash
25 | npm run install:all
26 | ```
27 |
28 | 2. Set up your environment variables:
29 |
30 | ```bash
31 | cd backend
32 | cp env.example .env
33 | ```
34 |
35 | Edit `backend/.env` and add your fal.ai API key:
36 |
37 | ```
38 | FAL_API_KEY=your_fal_ai_api_key_here
39 | PORT=3001
40 | ```
41 |
42 | **Note:** Get your fal.ai API key from [fal.ai](https://fal.ai). You'll need to sign up and create an API key in your dashboard.
43 |
44 | ## Running the Application
45 |
46 | Start both frontend and backend in development mode:
47 |
48 | ```bash
49 | npm run dev
50 | ```
51 |
52 | - Frontend will be available at: http://localhost:3000
53 | - Backend API will be available at: http://localhost:3001
54 |
55 | ## Usage
56 |
57 | ### Edit Mode
58 |
59 | 1. Click "Upload Image" to select an image file
60 | 2. Enter a natural language prompt describing the edits you want (e.g., "make the sky more dramatic", "add a sunset", "remove the background")
61 | 3. Optionally add a negative prompt to exclude unwanted elements
62 | 4. Click "Edit Image" and wait for processing
63 | 5. Download your edited image
64 |
65 | ## API Configuration
66 |
67 | **Important:** The application uses fal.ai's NanoBanana APIs:
68 |
69 | - Standard: [NanoBanana Edit](https://fal.ai/models/fal-ai/nano-banana/edit/api)
70 | - Pro tier: [NanoBanana Pro Edit](https://fal.ai/models/fal-ai/nano-banana-pro/edit/api)
71 |
72 | Select your model in the UI, and the backend automatically routes to the appropriate endpoint. If fal.ai updates these URLs, adjust `MODEL_ENDPOINTS` in `backend/server.js`.
73 |
74 | To verify the correct endpoint:
75 | 1. Check the [fal.ai documentation](https://fal.ai/models)
76 | 2. Look for the nanobanana model endpoint
77 | 3. Update the fetch URL in `backend/server.js` if needed
78 |
79 | ## API Endpoints
80 |
81 | ### POST `/api/edit-image`
82 |
83 | Edit an existing image using AI.
84 |
85 | **Request:**
86 | - `image` (file): Image file to edit
87 | - `prompt` (string): Natural language editing instructions
88 | - `negativePrompt` (string, optional): Things to avoid in the edit
89 |
90 | **Response:**
91 | ```json
92 | {
93 | "images": [{"url": "data:image/..."}]
94 | }
95 | ```
96 |
97 | ### POST `/api/generate-image`
98 |
99 | Generate a new image from text.
100 |
101 | **Request:**
102 | ```json
103 | {
104 | "prompt": "a beautiful landscape...",
105 | "negativePrompt": "blurry, low quality",
106 | "width": 1024,
107 | "height": 1024
108 | }
109 | ```
110 |
111 | **Response:**
112 | ```json
113 | {
114 | "images": [{"url": "data:image/..."}]
115 | }
116 | ```
117 |
118 | ## Tech Stack
119 |
120 | - **Frontend**: React + TypeScript + Vite
121 | - **Backend**: Node.js + Express
122 | - **AI**: Google nanobanana via fal.ai
123 | - **UI**: Custom CSS with modern design
124 |
125 | ## Project Structure
126 |
127 | ```
128 | nanobanana-studio/
129 | ├── frontend/ # React frontend application
130 | │ ├── src/
131 | │ │ ├── App.tsx # Main application component
132 | │ │ ├── api.ts # API client functions
133 | │ │ ├── types.ts # TypeScript type definitions
134 | │ │ ├── utils.ts # Utility functions
135 | │ │ ├── App.css # Styles
136 | │ │ └── ...
137 | │ └── package.json
138 | ├── backend/ # Express backend server
139 | │ ├── server.js # API server with validation & error handling
140 | │ ├── env.example # Environment variables template
141 | │ └── package.json
142 | ├── package.json # Root package.json
143 | ├── README.md # Full documentation
144 | └── SETUP.md # Quick setup guide
145 | ```
146 |
147 | ## Code Quality Features
148 |
149 | - **TypeScript**: Full type safety on frontend
150 | - **Error Handling**: Comprehensive error handling on both frontend and backend
151 | - **Validation**: Input validation for images, prompts, and parameters
152 | - **API Timeout**: 120-second timeout for API requests
153 | - **File Validation**: Type and size validation for uploaded images
154 | - **Logging**: Structured logging for debugging
155 | - **Security**: File type validation, size limits, and input sanitization
156 |
157 | ## License
158 |
159 | MIT
160 |
161 | ## Contributing
162 |
163 | Contributions are welcome! Please feel free to submit a Pull Request.
164 |
165 |
--------------------------------------------------------------------------------
/DEVELOPMENT.md:
--------------------------------------------------------------------------------
1 | # Development Guide
2 |
3 | ## Project Overview
4 |
5 | NanoBanana Studio is a full-stack AI image editor powered by Google's nanobanana API via fal.ai. The project uses a modern tech stack with strong typing and comprehensive error handling.
6 |
7 | ## Architecture
8 |
9 | ### Backend (Node.js/Express)
10 |
11 | **File**: `backend/server.js`
12 |
13 | **Key Features**:
14 | - RESTful API endpoints for image editing and generation
15 | - Multer middleware for file uploads
16 | - Request validation and sanitization
17 | - Timeout handling (120s)
18 | - Comprehensive error handling
19 | - Health check endpoint
20 |
21 | **Endpoints**:
22 | - `GET /api/health` - Server health and API status
23 | - `POST /api/edit-image` - Edit images with AI
24 | - `POST /api/generate-image` - Generate images from text
25 |
26 | ### Frontend (React/TypeScript/Vite)
27 |
28 | **File Structure**:
29 | - `App.tsx` - Main UI component
30 | - `api.ts` - API client functions
31 | - `types.ts` - TypeScript interfaces
32 | - `utils.ts` - Helper functions
33 | - `App.css` - Styles
34 |
35 | **Key Features**:
36 | - Type-safe API calls
37 | - Input validation
38 | - Error handling with user feedback
39 | - Keyboard shortcuts
40 | - Responsive UI
41 |
42 | ## Development Workflow
43 |
44 | ### 1. Initial Setup
45 |
46 | ```bash
47 | # Install all dependencies
48 | npm run install:all
49 |
50 | # Set up environment
51 | cd backend
52 | cp env.example .env
53 | # Edit .env and add FAL_API_KEY
54 | ```
55 |
56 | ### 2. Running Development Servers
57 |
58 | ```bash
59 | # From root directory - runs both frontend and backend
60 | npm run dev
61 |
62 | # Or run separately:
63 | cd frontend && npm run dev # http://localhost:3000
64 | cd backend && npm run dev # http://localhost:3001
65 | ```
66 |
67 | ### 3. Making Changes
68 |
69 | **Backend Changes**:
70 | - Edit `backend/server.js`
71 | - Server auto-restarts with `--watch` flag
72 | - Check console for errors
73 |
74 | **Frontend Changes**:
75 | - Edit files in `frontend/src/`
76 | - Vite hot-reloads changes automatically
77 | - Check browser console for errors
78 |
79 | ## Code Style Guidelines
80 |
81 | ### Backend
82 |
83 | ```javascript
84 | // Use async/await for async operations
85 | async function callAPI(params) {
86 | try {
87 | const response = await fetch(url, options);
88 | return await response.json();
89 | } catch (error) {
90 | console.error('[CONTEXT] Error:', error.message);
91 | throw error;
92 | }
93 | }
94 |
95 | // Use constants for configuration
96 | const MAX_FILE_SIZE = 50 * 1024 * 1024;
97 |
98 | // Validate inputs
99 | if (!prompt || !prompt.trim()) {
100 | return res.status(400).json({ error: 'Prompt is required' });
101 | }
102 | ```
103 |
104 | ### Frontend
105 |
106 | ```typescript
107 | // Define interfaces for all data structures
108 | interface ApiResponse {
109 | images?: Array<{ url: string }>;
110 | }
111 |
112 | // Use proper typing
113 | const [state, setState] = useState(null);
114 |
115 | // Extract reusable functions
116 | async function handleApiCall() {
117 | try {
118 | const data = await apiFunction(params);
119 | // Handle success
120 | } catch (error) {
121 | // Handle error
122 | }
123 | }
124 | ```
125 |
126 | ## Testing
127 |
128 | ### Manual Testing
129 |
130 | 1. **Edit Mode**:
131 | - Upload various image formats (JPEG, PNG, WebP)
132 | - Test file size limits (try > 50MB)
133 | - Test different prompts
134 | - Test with/without negative prompts
135 |
136 | 2. **Generate Mode**:
137 | - Test various prompts
138 | - Check image generation quality
139 | - Test error scenarios
140 |
141 | 3. **Error Cases**:
142 | - Missing API key
143 | - Invalid file types
144 | - Network errors
145 | - Timeout scenarios
146 |
147 | ### API Testing
148 |
149 | ```bash
150 | # Health check
151 | curl http://localhost:3001/api/health
152 |
153 | # Generate image
154 | curl -X POST http://localhost:3001/api/generate-image \
155 | -H "Content-Type: application/json" \
156 | -d '{"prompt": "a beautiful sunset"}'
157 |
158 | # Edit image (requires multipart/form-data)
159 | curl -X POST http://localhost:3001/api/edit-image \
160 | -F "image=@test.jpg" \
161 | -F "prompt=make it more dramatic"
162 | ```
163 |
164 | ## Common Issues & Solutions
165 |
166 | ### Issue: "FAL_API_KEY not configured"
167 | **Solution**: Create `backend/.env` file with valid API key
168 |
169 | ### Issue: CORS errors
170 | **Solution**: Backend already has CORS enabled; check proxy configuration in `vite.config.ts`
171 |
172 | ### Issue: File upload fails
173 | **Solution**:
174 | - Check file size (max 50MB)
175 | - Check file type (JPEG, PNG, WebP only)
176 | - Check browser console for detailed error
177 |
178 | ### Issue: Timeout errors
179 | **Solution**:
180 | - Increase timeout in `backend/server.js` (API_TIMEOUT constant)
181 | - Check fal.ai API status
182 | - Reduce image size
183 |
184 | ## Adding New Features
185 |
186 | ### Adding a New API Endpoint
187 |
188 | 1. Add endpoint to `backend/server.js`:
189 | ```javascript
190 | app.post('/api/new-feature', async (req, res) => {
191 | try {
192 | // Validation
193 | // API call
194 | // Response
195 | } catch (error) {
196 | // Error handling
197 | }
198 | });
199 | ```
200 |
201 | 2. Add API function to `frontend/src/api.ts`:
202 | ```typescript
203 | export async function newFeature(params: Params): Promise {
204 | const response = await fetch('/api/new-feature', {
205 | method: 'POST',
206 | body: JSON.stringify(params),
207 | });
208 | return handleApiResponse(response);
209 | }
210 | ```
211 |
212 | 3. Use in component:
213 | ```typescript
214 | const handleNewFeature = async () => {
215 | try {
216 | const result = await newFeature(params);
217 | // Handle result
218 | } catch (error) {
219 | // Handle error
220 | }
221 | };
222 | ```
223 |
224 | ## Performance Optimization
225 |
226 | ### Backend
227 | - Use response compression: `npm install compression`
228 | - Cache frequent API responses
229 | - Implement rate limiting
230 |
231 | ### Frontend
232 | - Lazy load components
233 | - Optimize images before upload
234 | - Add request debouncing
235 | - Implement image caching
236 |
237 | ## Deployment
238 |
239 | ### Backend
240 | 1. Build frontend: `cd frontend && npm run build`
241 | 2. Set environment variables
242 | 3. Start server: `cd backend && npm start`
243 |
244 | ### Recommended Platforms
245 | - **Backend**: Railway, Render, Heroku
246 | - **Frontend**: Vercel, Netlify
247 | - **Full Stack**: Railway, Render
248 |
249 | ### Environment Variables for Production
250 | ```
251 | FAL_API_KEY=your_production_key
252 | PORT=3001
253 | NODE_ENV=production
254 | ```
255 |
256 | ## Resources
257 |
258 | - [fal.ai Documentation](https://fal.ai/models)
259 | - [React Documentation](https://react.dev)
260 | - [Express Documentation](https://expressjs.com)
261 | - [Vite Documentation](https://vitejs.dev)
262 |
263 |
--------------------------------------------------------------------------------
/backend/server.js:
--------------------------------------------------------------------------------
1 | import express from 'express';
2 | import cors from 'cors';
3 | import dotenv from 'dotenv';
4 | import multer from 'multer';
5 | import fetch from 'node-fetch';
6 | import { fileURLToPath } from 'url';
7 | import { dirname, join } from 'path';
8 | import { existsSync } from 'fs';
9 | import { Buffer } from 'buffer';
10 | import { fal } from '@fal-ai/client';
11 |
12 | dotenv.config();
13 |
14 | const __filename = fileURLToPath(import.meta.url);
15 | const __dirname = dirname(__filename);
16 |
17 | const app = express();
18 | const PORT = process.env.PORT || 3001;
19 | const FAL_API_KEY = process.env.FAL_API_KEY;
20 |
21 | // Configure fal.ai client
22 | if (FAL_API_KEY) {
23 | fal.config({
24 | credentials: FAL_API_KEY,
25 | });
26 | }
27 |
28 | // Constants
29 | const MAX_FILE_SIZE = 50 * 1024 * 1024; // 50MB
30 | const ALLOWED_MIME_TYPES = ['image/jpeg', 'image/png', 'image/jpg', 'image/webp'];
31 | const API_TIMEOUT = 120000; // 120 seconds
32 | const MODEL_ENDPOINTS = {
33 | nano: 'fal-ai/nano-banana/edit',
34 | pro: 'fal-ai/nano-banana-pro/edit',
35 | };
36 |
37 | // Middleware
38 | app.use(cors());
39 | app.use(express.json({ limit: '10mb' }));
40 |
41 | // Only serve static files in production (when dist folder exists)
42 | const distPath = join(__dirname, '../frontend/dist');
43 | if (existsSync(distPath)) {
44 | app.use(express.static(distPath));
45 | }
46 |
47 | // Configure multer for file uploads
48 | const upload = multer({
49 | storage: multer.memoryStorage(),
50 | limits: { fileSize: MAX_FILE_SIZE },
51 | fileFilter: (req, file, cb) => {
52 | if (ALLOWED_MIME_TYPES.includes(file.mimetype)) {
53 | cb(null, true);
54 | } else {
55 | cb(new Error('Invalid file type. Only JPEG, PNG, and WebP images are allowed.'));
56 | }
57 | }
58 | });
59 |
60 | // Utility function to call fal.ai API using the official client
61 | async function callFalAPI(inputPayload, modelId = 'nano') {
62 | if (!FAL_API_KEY) {
63 | throw new Error('FAL_API_KEY not configured');
64 | }
65 |
66 | try {
67 | const endpoint = MODEL_ENDPOINTS[modelId] || MODEL_ENDPOINTS.nano;
68 | // Use fal.subscribe which handles queue polling automatically
69 | const result = await fal.subscribe(endpoint, {
70 | input: inputPayload,
71 | logs: true,
72 | onQueueUpdate: (update) => {
73 | if (update.status === 'IN_PROGRESS') {
74 | update.logs?.map((log) => log.message).forEach((msg) => {
75 | console.log(`[FAL API] ${msg}`);
76 | });
77 | }
78 | },
79 | });
80 |
81 | console.log(`[FAL API] Request completed, request_id: ${result.requestId}`);
82 | // Return the data field from the result
83 | return result.data;
84 | } catch (error) {
85 | console.error('[FAL API] Error:', error.message);
86 | throw error;
87 | }
88 | }
89 |
90 | // Validate and parse integer parameters
91 | function parseIntParam(value, defaultValue = undefined) {
92 | if (!value) return defaultValue;
93 | const parsed = parseInt(value, 10);
94 | return isNaN(parsed) ? defaultValue : parsed;
95 | }
96 |
97 | // Health check
98 | app.get('/api/health', (req, res) => {
99 | const isConfigured = !!FAL_API_KEY;
100 | res.json({
101 | status: 'ok',
102 | apiConfigured: isConfigured,
103 | timestamp: new Date().toISOString()
104 | });
105 | });
106 |
107 | // Image editing endpoint using fal.ai nanobanana
108 | app.post('/api/edit-image', upload.single('image'), async (req, res) => {
109 | try {
110 | // Validate image
111 | if (!req.file) {
112 | return res.status(400).json({ error: 'No image file provided' });
113 | }
114 |
115 | // Validate prompt
116 | const { prompt, negativePrompt, seed, numInferenceSteps, model } = req.body;
117 | if (!prompt || !prompt.trim()) {
118 | return res.status(400).json({ error: 'Prompt is required' });
119 | }
120 |
121 | if (prompt.length > 2000) {
122 | return res.status(400).json({ error: 'Prompt is too long (max 2000 characters)' });
123 | }
124 |
125 | // Convert image buffer to base64 data URL
126 | const imageBase64 = req.file.buffer.toString('base64');
127 | const imageDataUrl = `data:${req.file.mimetype};base64,${imageBase64}`;
128 |
129 | // Prepare API payload according to fal.ai API schema
130 | // API expects image_urls (array) and prompt
131 | const payload = {
132 | prompt: prompt.trim(),
133 | image_urls: [imageDataUrl], // API expects array of image URLs
134 | };
135 |
136 | // Note: The nano-banana/edit API doesn't support negative_prompt, seed, or num_inference_steps
137 | // These parameters are not in the API schema
138 |
139 | const modelId = model === 'pro' ? 'pro' : 'nano';
140 | console.log(`[EDIT] Processing image edit (${modelId}) with prompt: "${prompt.substring(0, 50)}..."`);
141 |
142 | // Call fal.ai API
143 | const data = await callFalAPI(payload, modelId);
144 |
145 | console.log('[EDIT] Successfully processed image');
146 | // API returns { images: [{ url, ... }], description: "..." }
147 | // Frontend expects this format, so return as-is
148 | res.json(data);
149 | } catch (error) {
150 | console.error('[EDIT] Error processing image:', error.message);
151 |
152 | const statusCode = error.message.includes('timeout') ? 504 :
153 | error.message.includes('not configured') ? 500 : 400;
154 |
155 | res.status(statusCode).json({
156 | error: error.message,
157 | timestamp: new Date().toISOString()
158 | });
159 | }
160 | });
161 |
162 | // Inpainting endpoint using fal-ai/qwen-image-edit/inpaint
163 | app.post('/api/inpaint-image', upload.fields([{ name: 'image', maxCount: 1 }, { name: 'mask', maxCount: 1 }]), async (req, res) => {
164 | try {
165 | const { prompt, imageUrl, maskUrl } = req.body;
166 |
167 | // Validate prompt
168 | if (!prompt || !prompt.trim()) {
169 | return res.status(400).json({ error: 'Prompt is required' });
170 | }
171 |
172 | // Get Image URL (either from file or body)
173 | let finalImageUrl = imageUrl;
174 | if (req.files && req.files['image'] && req.files['image'][0]) {
175 | const imageFile = req.files['image'][0];
176 | const imageBase64 = imageFile.buffer.toString('base64');
177 | finalImageUrl = `data:${imageFile.mimetype};base64,${imageBase64}`;
178 | }
179 |
180 | // Get Mask URL (either from file or body)
181 | let finalMaskUrl = maskUrl;
182 | if (req.files && req.files['mask'] && req.files['mask'][0]) {
183 | const maskFile = req.files['mask'][0];
184 | const maskBase64 = maskFile.buffer.toString('base64');
185 | finalMaskUrl = `data:${maskFile.mimetype};base64,${maskBase64}`;
186 | }
187 |
188 | if (!finalImageUrl) {
189 | return res.status(400).json({ error: 'Image is required (file or imageUrl)' });
190 | }
191 | if (!finalMaskUrl) {
192 | return res.status(400).json({ error: 'Mask is required (file or maskUrl)' });
193 | }
194 |
195 | console.log(`[INPAINT] Processing inpainting with prompt: "${prompt.substring(0, 50)}..."`);
196 | console.log(`[INPAINT] Image source: ${finalImageUrl.substring(0, 30)}...`);
197 | console.log(`[INPAINT] Mask source: ${finalMaskUrl.substring(0, 30)}...`);
198 |
199 | // Call fal.ai API
200 | if (!FAL_API_KEY) {
201 | throw new Error('FAL_API_KEY not configured');
202 | }
203 |
204 | const result = await fal.subscribe('fal-ai/qwen-image-edit/inpaint', {
205 | input: {
206 | prompt: prompt.trim(),
207 | image_url: finalImageUrl,
208 | mask_url: finalMaskUrl,
209 | // Explicitly use the mask as-is, avoiding any bounding box logic if the API defaults to it
210 | use_mask_as_is: true
211 | },
212 | logs: true,
213 | onQueueUpdate: (update) => {
214 | if (update.status === 'IN_PROGRESS') {
215 | update.logs?.map((log) => log.message).forEach((msg) => {
216 | console.log(`[FAL INPAINT] ${msg}`);
217 | });
218 | }
219 | },
220 | });
221 |
222 | console.log(`[INPAINT] Request completed, request_id: ${result.requestId}`);
223 | res.json(result.data);
224 |
225 | } catch (error) {
226 | console.error('[INPAINT] Error processing inpainting:', error.message);
227 | const statusCode = error.message.includes('timeout') ? 504 :
228 | error.message.includes('not configured') ? 500 : 400;
229 | res.status(statusCode).json({
230 | error: error.message,
231 | timestamp: new Date().toISOString()
232 | });
233 | }
234 | });
235 |
236 | // SAM2 auto-segmentation endpoint
237 | app.post('/api/segment-image', upload.single('image'), async (req, res) => {
238 | try {
239 | // Validate image
240 | if (!req.file) {
241 | return res.status(400).json({ error: 'No image file provided' });
242 | }
243 |
244 | // Convert image buffer to base64 data URL
245 | const imageBase64 = req.file.buffer.toString('base64');
246 | const imageDataUrl = `data:${req.file.mimetype};base64,${imageBase64}`;
247 |
248 | console.log(`[SEGMENT] Processing image segmentation`);
249 |
250 | // Call SAM2 API
251 | const data = await callSam2API(imageDataUrl);
252 |
253 | console.log('[SEGMENT] Successfully processed segmentation');
254 | res.json(data);
255 | } catch (error) {
256 | console.error('[SEGMENT] Error processing segmentation:', error.message);
257 |
258 | const statusCode = error.message.includes('timeout') ? 504 :
259 | error.message.includes('not configured') ? 500 : 400;
260 |
261 | res.status(statusCode).json({
262 | error: error.message,
263 | timestamp: new Date().toISOString()
264 | });
265 | }
266 | });
267 |
268 | // Utility function to call SAM2 API
269 | async function callSam2API(imageUrl) {
270 | if (!FAL_API_KEY) {
271 | throw new Error('FAL_API_KEY not configured');
272 | }
273 |
274 | try {
275 | // Use fal.subscribe which handles queue polling automatically
276 | const result = await fal.subscribe('fal-ai/sam2/auto-segment', {
277 | input: {
278 | image_url: imageUrl,
279 | output_format: 'png',
280 | sync_mode: true,
281 | },
282 | logs: true,
283 | onQueueUpdate: (update) => {
284 | if (update.status === 'IN_PROGRESS') {
285 | update.logs?.map((log) => log.message).forEach((msg) => {
286 | console.log(`[SAM2 API] ${msg}`);
287 | });
288 | }
289 | },
290 | });
291 |
292 | console.log(`[SAM2 API] Segmentation completed, request_id: ${result.requestId}`);
293 | const data = result.data;
294 |
295 | // Debug: Log what we received
296 | console.log('[SAM2 API] Response structure:', {
297 | hasCombinedMask: !!data?.combined_mask,
298 | hasIndividualMasks: !!data?.individual_masks,
299 | individualMasksCount: Array.isArray(data?.individual_masks) ? data.individual_masks.length : 0,
300 | hasSegmentedImages: !!data?.segmented_images,
301 | segmentedImagesCount: Array.isArray(data?.segmented_images) ? data.segmented_images.length : 0,
302 | keys: Object.keys(data || {})
303 | });
304 |
305 | const embedMask = async (mask) => {
306 | if (!mask?.url) return mask;
307 | try {
308 | const response = await fetch(mask.url);
309 | if (!response.ok) {
310 | throw new Error(`Failed to fetch mask asset: ${response.status}`);
311 | }
312 | const arrayBuffer = await response.arrayBuffer();
313 | const base64 = Buffer.from(arrayBuffer).toString('base64');
314 | const contentType = mask.content_type || 'image/png';
315 | return {
316 | ...mask,
317 | data_url: `data:${contentType};base64,${base64}`,
318 | };
319 | } catch (maskError) {
320 | console.warn('[SAM2 API] Unable to embed mask asset', maskError.message || maskError);
321 | return mask;
322 | }
323 | };
324 |
325 | if (data?.combined_mask) {
326 | data.combined_mask = await embedMask(data.combined_mask);
327 | }
328 | if (Array.isArray(data?.individual_masks)) {
329 | data.individual_masks = await Promise.all(data.individual_masks.map(embedMask));
330 | }
331 | if (Array.isArray(data?.segmented_images)) {
332 | data.segmented_images = await Promise.all(data.segmented_images.map(embedMask));
333 | }
334 |
335 | return data;
336 | } catch (error) {
337 | console.error('[SAM2 API] Error:', error.message);
338 | throw error;
339 | }
340 | }
341 |
342 | // Generate image from text
343 | app.post('/api/generate-image', async (req, res) => {
344 | try {
345 | const { prompt, negativePrompt, seed, numInferenceSteps, width, height, model } = req.body;
346 |
347 | // Validate prompt
348 | if (!prompt || !prompt.trim()) {
349 | return res.status(400).json({ error: 'Prompt is required' });
350 | }
351 |
352 | if (prompt.length > 2000) {
353 | return res.status(400).json({ error: 'Prompt is too long (max 2000 characters)' });
354 | }
355 |
356 | // Prepare API payload
357 | const payload = {
358 | prompt: prompt.trim(),
359 | };
360 |
361 | // Add optional parameters
362 | if (negativePrompt && negativePrompt.trim()) {
363 | payload.negative_prompt = negativePrompt.trim();
364 | }
365 |
366 | const parsedSeed = parseIntParam(seed);
367 | if (parsedSeed !== undefined) {
368 | payload.seed = parsedSeed;
369 | }
370 |
371 | const parsedSteps = parseIntParam(numInferenceSteps);
372 | if (parsedSteps !== undefined && parsedSteps > 0 && parsedSteps <= 50) {
373 | payload.num_inference_steps = parsedSteps;
374 | }
375 |
376 | // Validate and add dimensions
377 | const parsedWidth = parseIntParam(width, 1024);
378 | const parsedHeight = parseIntParam(height, 1024);
379 |
380 | if (parsedWidth < 256 || parsedWidth > 2048) {
381 | return res.status(400).json({ error: 'Width must be between 256 and 2048' });
382 | }
383 | if (parsedHeight < 256 || parsedHeight > 2048) {
384 | return res.status(400).json({ error: 'Height must be between 256 and 2048' });
385 | }
386 |
387 | payload.width = parsedWidth;
388 | payload.height = parsedHeight;
389 |
390 | const modelId = model === 'pro' ? 'pro' : 'nano';
391 | console.log(`[GENERATE] Generating image (${modelId}) with prompt: "${prompt.substring(0, 50)}..."`);
392 |
393 | // Call fal.ai API
394 | const data = await callFalAPI(payload, modelId);
395 |
396 | console.log('[GENERATE] Successfully generated image');
397 | res.json(data);
398 | } catch (error) {
399 | console.error('[GENERATE] Error generating image:', error.message);
400 |
401 | const statusCode = error.message.includes('timeout') ? 504 :
402 | error.message.includes('not configured') ? 500 : 400;
403 |
404 | res.status(statusCode).json({
405 | error: error.message,
406 | timestamp: new Date().toISOString()
407 | });
408 | }
409 | });
410 |
411 | // Error handling middleware
412 | app.use((err, req, res, next) => {
413 | console.error('Unhandled error:', err);
414 |
415 | if (err instanceof multer.MulterError) {
416 | if (err.code === 'LIMIT_FILE_SIZE') {
417 | return res.status(400).json({ error: 'File is too large (max 50MB)' });
418 | }
419 | return res.status(400).json({ error: err.message });
420 | }
421 |
422 | res.status(500).json({
423 | error: err.message || 'Internal server error',
424 | timestamp: new Date().toISOString()
425 | });
426 | });
427 |
428 | // Serve frontend in production only (must be last)
429 | if (existsSync(distPath)) {
430 | app.get('*', (req, res) => {
431 | res.sendFile(join(__dirname, '../frontend/dist/index.html'));
432 | });
433 | }
434 |
435 | app.listen(PORT, () => {
436 | console.log(`🚀 Server running on http://localhost:${PORT}`);
437 | console.log(`📝 API Key configured: ${FAL_API_KEY ? '✓' : '✗'}`);
438 | if (!FAL_API_KEY) {
439 | console.warn('⚠️ Warning: FAL_API_KEY not set in .env file');
440 | }
441 | });
442 |
--------------------------------------------------------------------------------
/frontend/src/App.css:
--------------------------------------------------------------------------------
1 | .app {
2 | display: flex;
3 | flex-direction: column;
4 | height: 100vh;
5 | background: #e0e7ff;
6 | color: #000000;
7 | font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
8 | }
9 |
10 | .top-bar {
11 | display: flex;
12 | align-items: center;
13 | justify-content: space-between;
14 | padding: 16px 32px;
15 | background: #ffffff;
16 | border-bottom: 3px solid #000000;
17 | z-index: 10;
18 | gap: 16px;
19 | flex-wrap: wrap;
20 | }
21 |
22 | .brand {
23 | display: flex;
24 | align-items: center;
25 | gap: 12px;
26 | }
27 |
28 | .brand-icon {
29 | color: #7c3aed;
30 | font-size: 32px;
31 | filter: drop-shadow(2px 2px 0px #000000);
32 | }
33 |
34 | .brand-title {
35 | font-weight: 900;
36 | font-size: 24px;
37 | margin: 0;
38 | color: #000000;
39 | letter-spacing: -0.5px;
40 | text-transform: uppercase;
41 | }
42 |
43 | .brand-subtitle {
44 | font-size: 12px;
45 | color: #000000;
46 | font-weight: 700;
47 | background: #a7f3d0;
48 | padding: 4px 8px;
49 | border: 2px solid #000000;
50 | border-radius: 6px;
51 | box-shadow: 2px 2px 0px #000000;
52 | }
53 |
54 | .menu-strip {
55 | display: flex;
56 | gap: 12px;
57 | }
58 |
59 | .menu-item {
60 | background: #ffffff;
61 | border: 2px solid #000000;
62 | color: #000000;
63 | font-size: 14px;
64 | padding: 8px 16px;
65 | border-radius: 8px;
66 | cursor: pointer;
67 | font-weight: 700;
68 | transition: all 0.15s ease;
69 | box-shadow: 3px 3px 0px #000000;
70 | }
71 |
72 | .menu-item:hover {
73 | transform: translate(-1px, -1px);
74 | box-shadow: 4px 4px 0px #000000;
75 | background: #fef3c7;
76 | }
77 |
78 | .menu-item:active {
79 | transform: translate(2px, 2px);
80 | box-shadow: 1px 1px 0px #000000;
81 | }
82 |
83 | .status-cluster {
84 | display: flex;
85 | align-items: center;
86 | gap: 16px;
87 | font-size: 14px;
88 | color: #000000;
89 | font-weight: 700;
90 | }
91 |
92 | .status-dot {
93 | width: 12px;
94 | height: 12px;
95 | border-radius: 50%;
96 | display: inline-block;
97 | border: 2px solid #000000;
98 | }
99 |
100 | .status-dot.online {
101 | background: #10b981;
102 | }
103 |
104 | .status-dot.offline {
105 | background: #ef4444;
106 | }
107 |
108 | .quick-action-nav {
109 | display: flex;
110 | gap: 8px;
111 | flex: 1;
112 | justify-content: center;
113 | flex-wrap: wrap;
114 | min-width: 220px;
115 | }
116 |
117 | .quick-action-icon-btn {
118 | display: inline-flex;
119 | align-items: center;
120 | gap: 6px;
121 | border: 2px solid #000000;
122 | background: #ffffff;
123 | color: #000000;
124 | padding: 8px 12px;
125 | border-radius: 999px;
126 | cursor: pointer;
127 | font-size: 12px;
128 | font-weight: 800;
129 | text-transform: uppercase;
130 | transition: all 0.15s ease;
131 | box-shadow: 3px 3px 0px #000000;
132 | }
133 |
134 | .quick-action-icon-btn:disabled {
135 | opacity: 0.4;
136 | cursor: not-allowed;
137 | box-shadow: none;
138 | }
139 |
140 | .quick-action-icon-btn:not(:disabled):hover {
141 | background: #c4b5fd;
142 | transform: translate(-1px, -1px);
143 | box-shadow: 4px 4px 0px #000000;
144 | }
145 |
146 | .secondary-btn {
147 | display: flex;
148 | align-items: center;
149 | gap: 8px;
150 | border: 2px solid #000000;
151 | background: #ffffff;
152 | color: #000000;
153 | padding: 8px 16px;
154 | border-radius: 8px;
155 | cursor: pointer;
156 | font-size: 13px;
157 | font-weight: 700;
158 | transition: all 0.15s ease;
159 | box-shadow: 3px 3px 0px #000000;
160 | }
161 |
162 | .secondary-btn:hover {
163 | transform: translate(-1px, -1px);
164 | box-shadow: 4px 4px 0px #000000;
165 | background: #e9d5ff;
166 | }
167 |
168 | .workspace {
169 | flex: 1;
170 | display: grid;
171 | grid-template-columns: 80px 1fr 380px;
172 | overflow: hidden;
173 | background: #e0e7ff;
174 | gap: 0;
175 | }
176 |
177 | .tool-panel {
178 | display: flex;
179 | flex-direction: column;
180 | gap: 12px;
181 | padding: 24px 12px;
182 | background: #ffffff;
183 | border-right: 3px solid #000000;
184 | z-index: 5;
185 | }
186 |
187 | .tool-btn {
188 | display: flex;
189 | flex-direction: column;
190 | align-items: center;
191 | gap: 8px;
192 | padding: 16px 8px;
193 | border: 2px solid #000000;
194 | background: #ffffff;
195 | color: #000000;
196 | font-size: 11px;
197 | font-weight: 800;
198 | border-radius: 8px;
199 | cursor: pointer;
200 | transition: all 0.15s ease;
201 | text-transform: uppercase;
202 | box-shadow: 3px 3px 0px #000000;
203 | }
204 |
205 | .tool-btn:disabled {
206 | opacity: 0.5;
207 | cursor: not-allowed;
208 | box-shadow: none;
209 | background: #e5e7eb;
210 | }
211 |
212 | .tool-btn svg {
213 | color: inherit;
214 | font-size: 24px;
215 | }
216 |
217 | .tool-btn.active {
218 | background: #fbbf24;
219 | color: #000000;
220 | transform: translate(-1px, -1px);
221 | box-shadow: 4px 4px 0px #000000;
222 | }
223 |
224 | .tool-btn:not(:disabled):not(.active):hover {
225 | background: #e0f2fe;
226 | transform: translate(-1px, -1px);
227 | box-shadow: 4px 4px 0px #000000;
228 | }
229 |
230 | .canvas-shell {
231 | display: flex;
232 | flex-direction: column;
233 | background: #ffffff;
234 | overflow: hidden;
235 | border-right: 3px solid #000000;
236 | }
237 |
238 | .options-bar {
239 | display: flex;
240 | align-items: center;
241 | justify-content: space-between;
242 | padding: 16px 32px;
243 | background: #ffedd5;
244 | border-bottom: 3px solid #000000;
245 | }
246 |
247 | .document-title {
248 | margin: 0;
249 | font-size: 20px;
250 | font-weight: 900;
251 | color: #000000;
252 | letter-spacing: -0.5px;
253 | text-transform: uppercase;
254 | }
255 |
256 | .document-subtitle {
257 | font-size: 13px;
258 | color: #000000;
259 | font-weight: 700;
260 | opacity: 0.7;
261 | }
262 |
263 | .mode-toggle.chips {
264 | display: flex;
265 | gap: 8px;
266 | background: #ffffff;
267 | padding: 6px;
268 | border-radius: 8px;
269 | border: 2px solid #000000;
270 | box-shadow: 3px 3px 0px #000000;
271 | }
272 |
273 | .mode-chip {
274 | display: flex;
275 | align-items: center;
276 | gap: 6px;
277 | padding: 8px 16px;
278 | border-radius: 6px;
279 | border: 2px solid transparent;
280 | background: transparent;
281 | color: #000000;
282 | cursor: pointer;
283 | font-size: 14px;
284 | font-weight: 700;
285 | transition: all 0.15s ease;
286 | }
287 |
288 | .mode-chip.active {
289 | background: #000000;
290 | color: #ffffff;
291 | box-shadow: 2px 2px 0px #a78bfa;
292 | }
293 |
294 | .mode-chip:not(.active):hover {
295 | background: #e5e7eb;
296 | }
297 |
298 | .model-toggle {
299 | display: flex;
300 | gap: 6px;
301 | background: #ffffff;
302 | padding: 6px;
303 | border-radius: 8px;
304 | border: 2px solid #000000;
305 | box-shadow: 3px 3px 0px #000000;
306 | }
307 |
308 | .model-chip {
309 | display: flex;
310 | flex-direction: column;
311 | align-items: flex-start;
312 | padding: 6px 12px;
313 | border-radius: 6px;
314 | border: 2px solid transparent;
315 | background: transparent;
316 | color: #000000;
317 | cursor: pointer;
318 | font-size: 12px;
319 | font-weight: 700;
320 | transition: all 0.15s ease;
321 | min-width: 140px;
322 | }
323 |
324 | .model-chip span {
325 | font-size: 10px;
326 | font-weight: 600;
327 | text-transform: uppercase;
328 | letter-spacing: 0.5px;
329 | opacity: 0.7;
330 | }
331 |
332 | .model-chip.active {
333 | background: #000000;
334 | color: #ffffff;
335 | box-shadow: 2px 2px 0px #a78bfa;
336 | }
337 |
338 | .model-chip:not(.active):hover {
339 | background: #e5e7eb;
340 | transform: translate(-1px, -1px);
341 | box-shadow: 3px 3px 0px #000000;
342 | }
343 |
344 | .canvas-stage {
345 | flex: 1;
346 | display: grid;
347 | grid-template-columns: minmax(0, 1fr) 320px;
348 | overflow: hidden;
349 | gap: 0;
350 | background: #e0e7ff;
351 | }
352 |
353 | .canvas-area {
354 | background-color: #ffffff;
355 | background-image: radial-gradient(#e5e7eb 1px, transparent 1px);
356 | background-size: 20px 20px;
357 | display: flex;
358 | align-items: center;
359 | justify-content: center;
360 | padding: 40px;
361 | position: relative;
362 | }
363 |
364 | .canvas-area.pro {
365 | border-right: 3px solid #000000;
366 | }
367 |
368 | .empty-state {
369 | display: flex;
370 | flex-direction: column;
371 | align-items: center;
372 | gap: 20px;
373 | color: #000000;
374 | text-align: center;
375 | }
376 |
377 | .empty-state svg {
378 | font-size: 80px;
379 | color: #a78bfa;
380 | opacity: 1;
381 | filter: drop-shadow(3px 3px 0px #000000);
382 | }
383 |
384 | .empty-state p {
385 | font-size: 24px;
386 | font-weight: 900;
387 | margin: 0;
388 | color: #000000;
389 | letter-spacing: -0.5px;
390 | text-transform: uppercase;
391 | }
392 |
393 | .hint {
394 | font-size: 14px;
395 | color: #000000;
396 | font-weight: 700;
397 | background: #ddd6fe;
398 | padding: 4px 12px;
399 | border: 2px solid #000000;
400 | border-radius: 6px;
401 | }
402 |
403 | .image-container {
404 | position: relative;
405 | max-width: 100%;
406 | max-height: 100%;
407 | display: flex;
408 | align-items: center;
409 | justify-content: center;
410 | }
411 |
412 | .image-container.framed {
413 | padding: 20px;
414 | border-radius: 12px;
415 | background: #ffffff;
416 | box-shadow: 6px 6px 0px #000000;
417 | border: 3px solid #000000;
418 | }
419 |
420 | .result-image {
421 | max-width: 100%;
422 | max-height: calc(100vh - 200px);
423 | border-radius: 4px;
424 | border: 2px solid #000000;
425 | }
426 |
427 | .image-container.cropping {
428 | overflow: hidden;
429 | }
430 |
431 | .cropper-wrapper {
432 | position: relative;
433 | width: min(90vw, 900px);
434 | height: min(65vh, 520px);
435 | border-radius: 4px;
436 | overflow: hidden;
437 | border: 3px solid #000000;
438 | box-shadow: 6px 6px 0px #000000;
439 | }
440 |
441 | .image-actions.floating {
442 | position: absolute;
443 | right: 32px;
444 | bottom: 32px;
445 | }
446 |
447 | .download-btn {
448 | display: flex;
449 | align-items: center;
450 | gap: 10px;
451 | padding: 12px 24px;
452 | border-radius: 8px;
453 | border: 2px solid #000000;
454 | background: #10b981;
455 | color: #000000;
456 | cursor: pointer;
457 | font-size: 14px;
458 | font-weight: 800;
459 | letter-spacing: 0.5px;
460 | text-transform: uppercase;
461 | box-shadow: 4px 4px 0px #000000;
462 | transition: all 0.15s ease;
463 | }
464 |
465 | .download-btn:hover {
466 | transform: translate(-2px, -2px);
467 | box-shadow: 6px 6px 0px #000000;
468 | background: #34d399;
469 | }
470 |
471 | .stack-panels {
472 | display: flex;
473 | flex-direction: column;
474 | gap: 16px;
475 | padding: 20px;
476 | overflow-y: auto;
477 | background: #ffffff;
478 | border-left: 3px solid #000000;
479 | }
480 |
481 | /* Custom scrollbar */
482 | .stack-panels::-webkit-scrollbar,
483 | .control-panel::-webkit-scrollbar {
484 | width: 10px;
485 | }
486 |
487 | .stack-panels::-webkit-scrollbar-track,
488 | .control-panel::-webkit-scrollbar-track {
489 | background: #f3f4f6;
490 | border-left: 2px solid #000000;
491 | }
492 |
493 | .stack-panels::-webkit-scrollbar-thumb,
494 | .control-panel::-webkit-scrollbar-thumb {
495 | background: #000000;
496 | border: 2px solid #000000;
497 | }
498 |
499 | .stack-panels::-webkit-scrollbar-thumb:hover,
500 | .control-panel::-webkit-scrollbar-thumb:hover {
501 | background: #7c3aed;
502 | }
503 |
504 | .panel-card {
505 | background: #ffffff;
506 | border-radius: 12px;
507 | padding: 20px;
508 | border: 2px solid #000000;
509 | box-shadow: 4px 4px 0px #000000;
510 | }
511 |
512 | .panel-card.subtle {
513 | background: #fef3c7;
514 | border-color: #000000;
515 | }
516 |
517 | .panel-header-info {
518 | display: flex;
519 | align-items: center;
520 | gap: 10px;
521 | color: #000000;
522 | font-weight: 800;
523 | text-transform: uppercase;
524 | }
525 |
526 | .layer-add-btn {
527 | display: inline-flex;
528 | align-items: center;
529 | gap: 6px;
530 | padding: 8px 12px;
531 | border-radius: 6px;
532 | border: 2px solid #000000;
533 | background: #ffffff;
534 | color: #000000;
535 | font-size: 12px;
536 | font-weight: 800;
537 | text-transform: uppercase;
538 | cursor: pointer;
539 | transition: all 0.15s ease;
540 | box-shadow: 2px 2px 0px #000000;
541 | }
542 |
543 | .layer-add-btn:hover {
544 | background: #c4b5fd;
545 | transform: translate(-1px, -1px);
546 | box-shadow: 3px 3px 0px #000000;
547 | }
548 |
549 | .panel-header {
550 | display: flex;
551 | align-items: center;
552 | justify-content: space-between;
553 | gap: 10px;
554 | font-size: 14px;
555 | color: #000000;
556 | margin-bottom: 16px;
557 | }
558 |
559 | .panel-header h2 {
560 | font-size: 18px;
561 | font-weight: 900;
562 | margin: 0;
563 | color: #000000;
564 | letter-spacing: -0.5px;
565 | text-transform: uppercase;
566 | }
567 |
568 | .layer-list {
569 | display: flex;
570 | flex-direction: column;
571 | gap: 12px;
572 | }
573 |
574 | .layer-item {
575 | width: 100%;
576 | text-align: left;
577 | padding: 12px 16px;
578 | border-radius: 8px;
579 | background: #ffffff;
580 | font-size: 14px;
581 | border: 2px solid #000000;
582 | color: #000000;
583 | cursor: pointer;
584 | display: flex;
585 | align-items: center;
586 | justify-content: space-between;
587 | gap: 12px;
588 | transition: all 0.15s ease;
589 | box-shadow: 2px 2px 0px #000000;
590 | }
591 |
592 | .layer-item:hover {
593 | background: #fef3c7;
594 | transform: translate(-1px, -1px);
595 | box-shadow: 3px 3px 0px #000000;
596 | }
597 |
598 | .layer-item.active {
599 | background: #c4b5fd;
600 | color: #000000;
601 | border-color: #000000;
602 | box-shadow: 3px 3px 0px #000000;
603 | transform: translate(-1px, -1px);
604 | }
605 |
606 | .layer-item.muted {
607 | color: #6b7280;
608 | background: #e5e7eb;
609 | }
610 |
611 | .layer-item.hidden {
612 | opacity: 0.6;
613 | }
614 |
615 | .layer-meta {
616 | display: flex;
617 | flex-direction: column;
618 | gap: 2px;
619 | }
620 |
621 | .layer-meta strong {
622 | font-size: 14px;
623 | font-weight: 800;
624 | color: #000000;
625 | }
626 |
627 | .layer-meta span {
628 | font-size: 11px;
629 | color: #000000;
630 | font-weight: 600;
631 | text-transform: uppercase;
632 | }
633 |
634 | .layer-pill {
635 | padding: 4px 8px;
636 | border-radius: 4px;
637 | font-size: 10px;
638 | text-transform: uppercase;
639 | font-weight: 800;
640 | letter-spacing: 0.5px;
641 | border: 2px solid #000000;
642 | box-shadow: 1px 1px 0px #000000;
643 | }
644 |
645 | .layer-pill.source {
646 | background: #ffffff;
647 | color: #000000;
648 | }
649 |
650 | .layer-pill.ai {
651 | background: #a78bfa;
652 | color: #000000;
653 | }
654 |
655 | .layer-pill.segment {
656 | background: #fca5a5;
657 | color: #000000;
658 | }
659 |
660 | .layer-pill.empty {
661 | background: #fcd34d;
662 | color: #000000;
663 | }
664 |
665 | .layer-actions {
666 | display: flex;
667 | align-items: center;
668 | gap: 8px;
669 | }
670 |
671 | .layer-eye-btn {
672 | border: 2px solid #000000;
673 | background: #ffffff;
674 | color: #000000;
675 | padding: 6px;
676 | border-radius: 6px;
677 | cursor: pointer;
678 | display: inline-flex;
679 | align-items: center;
680 | justify-content: center;
681 | transition: all 0.15s ease;
682 | }
683 |
684 | .layer-eye-btn:hover {
685 | background: #a7f3d0;
686 | transform: translate(-1px, -1px);
687 | box-shadow: 2px 2px 0px #000000;
688 | }
689 |
690 | .history-list {
691 | list-style: none;
692 | padding: 0;
693 | margin: 0;
694 | display: flex;
695 | flex-direction: column;
696 | gap: 8px;
697 | font-size: 13px;
698 | color: #000000;
699 | }
700 |
701 | .history-list li {
702 | padding: 8px 12px;
703 | background: #ffffff;
704 | border-radius: 6px;
705 | font-weight: 600;
706 | border: 2px solid #000000;
707 | box-shadow: 2px 2px 0px #000000;
708 | }
709 |
710 | .control-panel {
711 | padding: 20px;
712 | overflow-y: auto;
713 | background: #ffffff;
714 | border-left: 3px solid #000000;
715 | }
716 |
717 | .upload-deck {
718 | display: flex;
719 | flex-direction: column;
720 | gap: 12px;
721 | margin-bottom: 20px;
722 | }
723 |
724 | .file-input {
725 | display: none;
726 | }
727 |
728 | .upload-btn,
729 | .clear-btn {
730 | display: flex;
731 | align-items: center;
732 | justify-content: center;
733 | gap: 10px;
734 | padding: 16px 20px;
735 | border-radius: 8px;
736 | border: 2px dashed #000000;
737 | background: #f3f4f6;
738 | color: #000000;
739 | cursor: pointer;
740 | font-weight: 800;
741 | font-size: 14px;
742 | text-transform: uppercase;
743 | transition: all 0.15s ease;
744 | }
745 |
746 | .upload-btn:hover,
747 | .clear-btn:hover {
748 | background: #e0e7ff;
749 | border-color: #7c3aed;
750 | border-style: solid;
751 | transform: translate(-2px, -2px);
752 | box-shadow: 4px 4px 0px #000000;
753 | }
754 |
755 | .clear-btn.ghost {
756 | background: #fee2e2;
757 | color: #000000;
758 | border: 2px solid #000000;
759 | }
760 |
761 | .clear-btn.ghost:hover {
762 | background: #ef4444;
763 | color: #ffffff;
764 | }
765 |
766 | .prompt-section {
767 | display: flex;
768 | flex-direction: column;
769 | gap: 10px;
770 | margin-bottom: 20px;
771 | }
772 |
773 | .prompt-header {
774 | display: flex;
775 | justify-content: space-between;
776 | font-size: 13px;
777 | color: #000000;
778 | font-weight: 800;
779 | text-transform: uppercase;
780 | }
781 |
782 | .char-count {
783 | color: #7c3aed;
784 | font-weight: 800;
785 | }
786 |
787 | .prompt-input {
788 | width: 100%;
789 | padding: 16px;
790 | border-radius: 8px;
791 | border: 2px solid #000000;
792 | background: #ffffff;
793 | color: #000000;
794 | resize: none;
795 | font-size: 16px;
796 | font-family: inherit;
797 | font-weight: 600;
798 | transition: all 0.15s ease;
799 | box-shadow: 3px 3px 0px #000000;
800 | }
801 |
802 | .prompt-input::placeholder {
803 | color: #9ca3af;
804 | }
805 |
806 | .prompt-input:focus {
807 | outline: none;
808 | border-color: #7c3aed;
809 | background: #ffffff;
810 | transform: translate(-1px, -1px);
811 | box-shadow: 5px 5px 0px #000000;
812 | }
813 |
814 | .crop-controls {
815 | display: flex;
816 | flex-direction: column;
817 | gap: 16px;
818 | padding: 20px;
819 | border-radius: 12px;
820 | border: 2px solid #000000;
821 | background: #f3f4f6;
822 | margin-bottom: 20px;
823 | box-shadow: 4px 4px 0px #000000;
824 | }
825 |
826 | .crop-header p {
827 | margin: 0;
828 | font-weight: 800;
829 | color: #000000;
830 | text-transform: uppercase;
831 | }
832 |
833 | .crop-header span {
834 | font-size: 12px;
835 | color: #000000;
836 | font-weight: 600;
837 | }
838 |
839 | .aspect-options {
840 | display: flex;
841 | flex-wrap: wrap;
842 | gap: 10px;
843 | }
844 |
845 | .aspect-chip {
846 | padding: 8px 16px;
847 | border-radius: 6px;
848 | border: 2px solid #000000;
849 | background: #ffffff;
850 | color: #000000;
851 | font-size: 13px;
852 | font-weight: 700;
853 | cursor: pointer;
854 | transition: all 0.15s ease;
855 | box-shadow: 2px 2px 0px #000000;
856 | }
857 |
858 | .aspect-chip.active {
859 | background: #000000;
860 | color: #ffffff;
861 | transform: translate(-1px, -1px);
862 | box-shadow: 3px 3px 0px #a78bfa;
863 | }
864 |
865 | .aspect-chip:not(.active):hover {
866 | background: #e5e7eb;
867 | transform: translate(-1px, -1px);
868 | box-shadow: 3px 3px 0px #000000;
869 | }
870 |
871 | .crop-label {
872 | font-size: 13px;
873 | color: #000000;
874 | font-weight: 800;
875 | text-transform: uppercase;
876 | }
877 |
878 | .crop-slider {
879 | width: 100%;
880 | height: 12px;
881 | border-radius: 6px;
882 | background: #ffffff;
883 | border: 2px solid #000000;
884 | outline: none;
885 | -webkit-appearance: none;
886 | }
887 |
888 | .crop-slider::-webkit-slider-thumb {
889 | -webkit-appearance: none;
890 | appearance: none;
891 | width: 20px;
892 | height: 20px;
893 | border-radius: 4px;
894 | background: #7c3aed;
895 | border: 2px solid #000000;
896 | cursor: pointer;
897 | box-shadow: 2px 2px 0px #000000;
898 | }
899 |
900 | .crop-slider::-moz-range-thumb {
901 | width: 20px;
902 | height: 20px;
903 | border-radius: 4px;
904 | background: #7c3aed;
905 | border: 2px solid #000000;
906 | cursor: pointer;
907 | box-shadow: 2px 2px 0px #000000;
908 | }
909 |
910 | .crop-actions {
911 | display: flex;
912 | justify-content: flex-end;
913 | gap: 12px;
914 | }
915 |
916 | .primary-btn {
917 | display: inline-flex;
918 | align-items: center;
919 | justify-content: center;
920 | padding: 12px 24px;
921 | border-radius: 8px;
922 | border: 2px solid #000000;
923 | background: #7c3aed;
924 | color: #ffffff;
925 | font-weight: 800;
926 | text-transform: uppercase;
927 | letter-spacing: 0.5px;
928 | cursor: pointer;
929 | box-shadow: 4px 4px 0px #000000;
930 | transition: all 0.15s ease;
931 | }
932 |
933 | .primary-btn:disabled {
934 | opacity: 0.5;
935 | cursor: not-allowed;
936 | background: #9ca3af;
937 | }
938 |
939 | .primary-btn:not(:disabled):hover {
940 | transform: translate(-2px, -2px);
941 | box-shadow: 6px 6px 0px #000000;
942 | background: #8b5cf6;
943 | }
944 |
945 | .filters-panel {
946 | background: #ffffff;
947 | border-radius: 12px;
948 | padding: 20px;
949 | border: 2px solid #000000;
950 | box-shadow: 4px 4px 0px #000000;
951 | margin-bottom: 20px;
952 | }
953 |
954 | .filters-grid {
955 | display: grid;
956 | grid-template-columns: repeat(2, minmax(0, 1fr));
957 | gap: 12px;
958 | margin-bottom: 16px;
959 | }
960 |
961 | @media (max-width: 520px) {
962 | .filters-grid {
963 | grid-template-columns: 1fr;
964 | }
965 | }
966 |
967 | .filter-chip {
968 | border: 2px solid #000000;
969 | background: #ffffff;
970 | color: #000000;
971 | padding: 16px 10px;
972 | border-radius: 8px;
973 | cursor: pointer;
974 | text-align: center;
975 | transition: all 0.15s ease;
976 | box-shadow: 3px 3px 0px #000000;
977 | }
978 |
979 | .filter-chip:hover {
980 | background: #ddd6fe;
981 | transform: translate(-2px, -2px);
982 | box-shadow: 5px 5px 0px #000000;
983 | }
984 |
985 | .filter-label {
986 | font-size: 13px;
987 | font-weight: 800;
988 | text-transform: uppercase;
989 | margin-bottom: 4px;
990 | display: block;
991 | }
992 |
993 | .filter-category {
994 | font-size: 10px;
995 | font-weight: 700;
996 | letter-spacing: 0.5px;
997 | color: #6b7280;
998 | text-transform: uppercase;
999 | background: #e5e7eb;
1000 | padding: 2px 6px;
1001 | border-radius: 4px;
1002 | display: inline-block;
1003 | border: 1px solid #000000;
1004 | }
1005 |
1006 | .filter-instructions {
1007 | background: #fef3c7;
1008 | border: 2px solid #000000;
1009 | padding: 14px;
1010 | border-radius: 8px;
1011 | box-shadow: 3px 3px 0px #000000;
1012 | }
1013 |
1014 | .filter-instructions p {
1015 | margin: 0;
1016 | font-size: 13px;
1017 | font-weight: 700;
1018 | color: #000000;
1019 | }
1020 |
1021 | .preset-grid {
1022 | display: flex;
1023 | flex-wrap: wrap;
1024 | gap: 12px;
1025 | margin-bottom: 24px;
1026 | }
1027 |
1028 | .preset-chip {
1029 | padding: 10px 16px;
1030 | border-radius: 6px;
1031 | border: 2px solid #000000;
1032 | background: #ffffff;
1033 | color: #000000;
1034 | font-size: 13px;
1035 | font-weight: 700;
1036 | text-transform: uppercase;
1037 | cursor: pointer;
1038 | box-shadow: 3px 3px 0px #000000;
1039 | transition: all 0.15s ease;
1040 | }
1041 |
1042 | .preset-chip:hover {
1043 | background: #fef3c7;
1044 | transform: translate(-1px, -1px);
1045 | box-shadow: 4px 4px 0px #000000;
1046 | }
1047 |
1048 | .edit-btn {
1049 | display: flex;
1050 | align-items: center;
1051 | justify-content: center;
1052 | gap: 12px;
1053 | padding: 16px 32px;
1054 | width: 100%;
1055 | border-radius: 8px;
1056 | border: 2px solid #000000;
1057 | background: #7c3aed;
1058 | color: #ffffff;
1059 | font-weight: 900;
1060 | font-size: 16px;
1061 | text-transform: uppercase;
1062 | letter-spacing: 1px;
1063 | cursor: pointer;
1064 | box-shadow: 4px 4px 0px #000000;
1065 | transition: all 0.15s ease;
1066 | }
1067 |
1068 | .edit-btn:hover:not(:disabled) {
1069 | background: #8b5cf6;
1070 | transform: translate(-2px, -2px);
1071 | box-shadow: 6px 6px 0px #000000;
1072 | }
1073 |
1074 | .edit-btn:disabled {
1075 | opacity: 0.5;
1076 | cursor: not-allowed;
1077 | background: #9ca3af;
1078 | }
1079 |
1080 | .warning-message,
1081 | .error-message {
1082 | margin-top: 16px;
1083 | padding: 14px 18px;
1084 | border-radius: 8px;
1085 | font-size: 14px;
1086 | font-weight: 700;
1087 | border: 2px solid #000000;
1088 | box-shadow: 3px 3px 0px #000000;
1089 | }
1090 |
1091 | .warning-message {
1092 | background: #fef3c7;
1093 | color: #000000;
1094 | }
1095 |
1096 | .error-message {
1097 | background: #fee2e2;
1098 | color: #000000;
1099 | }
1100 |
1101 | .status-bar {
1102 | height: 40px;
1103 | display: flex;
1104 | align-items: center;
1105 | justify-content: flex-end;
1106 | gap: 24px;
1107 | padding: 0 32px;
1108 | font-size: 13px;
1109 | background: #ffffff;
1110 | border-top: 3px solid #000000;
1111 | color: #000000;
1112 | font-weight: 700;
1113 | text-transform: uppercase;
1114 | }
1115 |
1116 | .spinner {
1117 | animation: spin 1s linear infinite;
1118 | }
1119 |
1120 | @keyframes spin {
1121 | from {
1122 | transform: rotate(0deg);
1123 | }
1124 | to {
1125 | transform: rotate(360deg);
1126 | }
1127 | }
1128 |
1129 | /* Canvas and segmentation styling */
1130 | .canvas-stack {
1131 | position: relative;
1132 | width: 100%;
1133 | height: 100%;
1134 | /* Checkerboard pattern to show transparency */
1135 | background-image:
1136 | linear-gradient(45deg, #f8fafc 25%, #e2e8f0 25%),
1137 | linear-gradient(-45deg, #f8fafc 25%, #e2e8f0 25%),
1138 | linear-gradient(45deg, #e2e8f0 75%, #f8fafc 75%),
1139 | linear-gradient(-45deg, #e2e8f0 75%, #f8fafc 75%);
1140 | background-size: 20px 20px;
1141 | background-position: 0 0, 0 10px, 10px -10px, -10px 0px;
1142 | border-radius: 12px;
1143 | border: 1px solid rgba(148, 163, 184, 0.2);
1144 | }
1145 |
1146 | .segmentation-overlay {
1147 | position: absolute;
1148 | top: 0;
1149 | left: 0;
1150 | width: 100%;
1151 | height: 100%;
1152 | pointer-events: auto;
1153 | }
1154 |
1155 | .segment-mask {
1156 | transition: all 0.2s ease;
1157 | }
1158 |
1159 | .segment-mask.selected {
1160 | box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.5);
1161 | }
1162 |
1163 | .segment-mask.hidden {
1164 | opacity: 0;
1165 | }
1166 |
1167 | .segmentation-loading {
1168 | position: absolute;
1169 | top: 50%;
1170 | left: 50%;
1171 | transform: translate(-50%, -50%);
1172 | display: flex;
1173 | flex-direction: column;
1174 | align-items: center;
1175 | gap: 16px;
1176 | background: rgba(255, 255, 255, 0.95);
1177 | backdrop-filter: blur(20px);
1178 | padding: 24px 32px;
1179 | border-radius: 16px;
1180 | color: #1e293b;
1181 | font-size: 14px;
1182 | font-weight: 600;
1183 | letter-spacing: -0.2px;
1184 | box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
1185 | border: 1px solid rgba(148, 163, 184, 0.2);
1186 | }
1187 |
1188 | .selection-box {
1189 | position: absolute;
1190 | border: 2px dashed #6366f1;
1191 | border-radius: 8px;
1192 | pointer-events: none;
1193 | box-shadow: 0 0 0 2px rgba(99, 102, 241, 0.2);
1194 | }
1195 |
1196 | @keyframes dash {
1197 | to {
1198 | stroke-dashoffset: -24;
1199 | }
1200 | }
1201 |
--------------------------------------------------------------------------------
/backend/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "nanobanana-backend",
3 | "version": "1.0.0",
4 | "lockfileVersion": 3,
5 | "requires": true,
6 | "packages": {
7 | "": {
8 | "name": "nanobanana-backend",
9 | "version": "1.0.0",
10 | "dependencies": {
11 | "@fal-ai/client": "^1.0.0",
12 | "cors": "^2.8.5",
13 | "dotenv": "^16.3.1",
14 | "express": "^4.18.2",
15 | "form-data": "^4.0.0",
16 | "multer": "^1.4.5-lts.1",
17 | "node-fetch": "^3.3.2"
18 | }
19 | },
20 | "node_modules/@fal-ai/client": {
21 | "version": "1.7.2",
22 | "resolved": "https://registry.npmjs.org/@fal-ai/client/-/client-1.7.2.tgz",
23 | "integrity": "sha512-RZ1Qz2Kza4ExKPy2D+2UUWthNApe+oZe8D1Wcxqleyn4F344MOm8ibgqG2JSVmybEcJAD4q44078WYfb6Q9c6w==",
24 | "license": "MIT",
25 | "dependencies": {
26 | "@msgpack/msgpack": "^3.0.0-beta2",
27 | "eventsource-parser": "^1.1.2",
28 | "robot3": "^0.4.1"
29 | },
30 | "engines": {
31 | "node": ">=18.0.0"
32 | }
33 | },
34 | "node_modules/@msgpack/msgpack": {
35 | "version": "3.1.2",
36 | "resolved": "https://registry.npmjs.org/@msgpack/msgpack/-/msgpack-3.1.2.tgz",
37 | "integrity": "sha512-JEW4DEtBzfe8HvUYecLU9e6+XJnKDlUAIve8FvPzF3Kzs6Xo/KuZkZJsDH0wJXl/qEZbeeE7edxDNY3kMs39hQ==",
38 | "license": "ISC",
39 | "engines": {
40 | "node": ">= 18"
41 | }
42 | },
43 | "node_modules/accepts": {
44 | "version": "1.3.8",
45 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
46 | "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
47 | "license": "MIT",
48 | "dependencies": {
49 | "mime-types": "~2.1.34",
50 | "negotiator": "0.6.3"
51 | },
52 | "engines": {
53 | "node": ">= 0.6"
54 | }
55 | },
56 | "node_modules/append-field": {
57 | "version": "1.0.0",
58 | "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz",
59 | "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==",
60 | "license": "MIT"
61 | },
62 | "node_modules/array-flatten": {
63 | "version": "1.1.1",
64 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
65 | "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
66 | "license": "MIT"
67 | },
68 | "node_modules/asynckit": {
69 | "version": "0.4.0",
70 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
71 | "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
72 | "license": "MIT"
73 | },
74 | "node_modules/body-parser": {
75 | "version": "1.20.3",
76 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz",
77 | "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==",
78 | "license": "MIT",
79 | "dependencies": {
80 | "bytes": "3.1.2",
81 | "content-type": "~1.0.5",
82 | "debug": "2.6.9",
83 | "depd": "2.0.0",
84 | "destroy": "1.2.0",
85 | "http-errors": "2.0.0",
86 | "iconv-lite": "0.4.24",
87 | "on-finished": "2.4.1",
88 | "qs": "6.13.0",
89 | "raw-body": "2.5.2",
90 | "type-is": "~1.6.18",
91 | "unpipe": "1.0.0"
92 | },
93 | "engines": {
94 | "node": ">= 0.8",
95 | "npm": "1.2.8000 || >= 1.4.16"
96 | }
97 | },
98 | "node_modules/buffer-from": {
99 | "version": "1.1.2",
100 | "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
101 | "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
102 | "license": "MIT"
103 | },
104 | "node_modules/busboy": {
105 | "version": "1.6.0",
106 | "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
107 | "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==",
108 | "dependencies": {
109 | "streamsearch": "^1.1.0"
110 | },
111 | "engines": {
112 | "node": ">=10.16.0"
113 | }
114 | },
115 | "node_modules/bytes": {
116 | "version": "3.1.2",
117 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
118 | "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
119 | "license": "MIT",
120 | "engines": {
121 | "node": ">= 0.8"
122 | }
123 | },
124 | "node_modules/call-bind-apply-helpers": {
125 | "version": "1.0.2",
126 | "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
127 | "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
128 | "license": "MIT",
129 | "dependencies": {
130 | "es-errors": "^1.3.0",
131 | "function-bind": "^1.1.2"
132 | },
133 | "engines": {
134 | "node": ">= 0.4"
135 | }
136 | },
137 | "node_modules/call-bound": {
138 | "version": "1.0.4",
139 | "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
140 | "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
141 | "license": "MIT",
142 | "dependencies": {
143 | "call-bind-apply-helpers": "^1.0.2",
144 | "get-intrinsic": "^1.3.0"
145 | },
146 | "engines": {
147 | "node": ">= 0.4"
148 | },
149 | "funding": {
150 | "url": "https://github.com/sponsors/ljharb"
151 | }
152 | },
153 | "node_modules/combined-stream": {
154 | "version": "1.0.8",
155 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
156 | "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
157 | "license": "MIT",
158 | "dependencies": {
159 | "delayed-stream": "~1.0.0"
160 | },
161 | "engines": {
162 | "node": ">= 0.8"
163 | }
164 | },
165 | "node_modules/concat-stream": {
166 | "version": "1.6.2",
167 | "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz",
168 | "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==",
169 | "engines": [
170 | "node >= 0.8"
171 | ],
172 | "license": "MIT",
173 | "dependencies": {
174 | "buffer-from": "^1.0.0",
175 | "inherits": "^2.0.3",
176 | "readable-stream": "^2.2.2",
177 | "typedarray": "^0.0.6"
178 | }
179 | },
180 | "node_modules/content-disposition": {
181 | "version": "0.5.4",
182 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
183 | "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
184 | "license": "MIT",
185 | "dependencies": {
186 | "safe-buffer": "5.2.1"
187 | },
188 | "engines": {
189 | "node": ">= 0.6"
190 | }
191 | },
192 | "node_modules/content-type": {
193 | "version": "1.0.5",
194 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
195 | "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
196 | "license": "MIT",
197 | "engines": {
198 | "node": ">= 0.6"
199 | }
200 | },
201 | "node_modules/cookie": {
202 | "version": "0.7.1",
203 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz",
204 | "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==",
205 | "license": "MIT",
206 | "engines": {
207 | "node": ">= 0.6"
208 | }
209 | },
210 | "node_modules/cookie-signature": {
211 | "version": "1.0.6",
212 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
213 | "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==",
214 | "license": "MIT"
215 | },
216 | "node_modules/core-util-is": {
217 | "version": "1.0.3",
218 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
219 | "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==",
220 | "license": "MIT"
221 | },
222 | "node_modules/cors": {
223 | "version": "2.8.5",
224 | "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
225 | "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
226 | "license": "MIT",
227 | "dependencies": {
228 | "object-assign": "^4",
229 | "vary": "^1"
230 | },
231 | "engines": {
232 | "node": ">= 0.10"
233 | }
234 | },
235 | "node_modules/data-uri-to-buffer": {
236 | "version": "4.0.1",
237 | "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz",
238 | "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==",
239 | "license": "MIT",
240 | "engines": {
241 | "node": ">= 12"
242 | }
243 | },
244 | "node_modules/debug": {
245 | "version": "2.6.9",
246 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
247 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
248 | "license": "MIT",
249 | "dependencies": {
250 | "ms": "2.0.0"
251 | }
252 | },
253 | "node_modules/delayed-stream": {
254 | "version": "1.0.0",
255 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
256 | "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
257 | "license": "MIT",
258 | "engines": {
259 | "node": ">=0.4.0"
260 | }
261 | },
262 | "node_modules/depd": {
263 | "version": "2.0.0",
264 | "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
265 | "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
266 | "license": "MIT",
267 | "engines": {
268 | "node": ">= 0.8"
269 | }
270 | },
271 | "node_modules/destroy": {
272 | "version": "1.2.0",
273 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
274 | "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
275 | "license": "MIT",
276 | "engines": {
277 | "node": ">= 0.8",
278 | "npm": "1.2.8000 || >= 1.4.16"
279 | }
280 | },
281 | "node_modules/dotenv": {
282 | "version": "16.6.1",
283 | "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz",
284 | "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==",
285 | "license": "BSD-2-Clause",
286 | "engines": {
287 | "node": ">=12"
288 | },
289 | "funding": {
290 | "url": "https://dotenvx.com"
291 | }
292 | },
293 | "node_modules/dunder-proto": {
294 | "version": "1.0.1",
295 | "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
296 | "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
297 | "license": "MIT",
298 | "dependencies": {
299 | "call-bind-apply-helpers": "^1.0.1",
300 | "es-errors": "^1.3.0",
301 | "gopd": "^1.2.0"
302 | },
303 | "engines": {
304 | "node": ">= 0.4"
305 | }
306 | },
307 | "node_modules/ee-first": {
308 | "version": "1.1.1",
309 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
310 | "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
311 | "license": "MIT"
312 | },
313 | "node_modules/encodeurl": {
314 | "version": "2.0.0",
315 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
316 | "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
317 | "license": "MIT",
318 | "engines": {
319 | "node": ">= 0.8"
320 | }
321 | },
322 | "node_modules/es-define-property": {
323 | "version": "1.0.1",
324 | "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
325 | "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
326 | "license": "MIT",
327 | "engines": {
328 | "node": ">= 0.4"
329 | }
330 | },
331 | "node_modules/es-errors": {
332 | "version": "1.3.0",
333 | "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
334 | "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
335 | "license": "MIT",
336 | "engines": {
337 | "node": ">= 0.4"
338 | }
339 | },
340 | "node_modules/es-object-atoms": {
341 | "version": "1.1.1",
342 | "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
343 | "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
344 | "license": "MIT",
345 | "dependencies": {
346 | "es-errors": "^1.3.0"
347 | },
348 | "engines": {
349 | "node": ">= 0.4"
350 | }
351 | },
352 | "node_modules/es-set-tostringtag": {
353 | "version": "2.1.0",
354 | "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
355 | "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
356 | "license": "MIT",
357 | "dependencies": {
358 | "es-errors": "^1.3.0",
359 | "get-intrinsic": "^1.2.6",
360 | "has-tostringtag": "^1.0.2",
361 | "hasown": "^2.0.2"
362 | },
363 | "engines": {
364 | "node": ">= 0.4"
365 | }
366 | },
367 | "node_modules/escape-html": {
368 | "version": "1.0.3",
369 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
370 | "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
371 | "license": "MIT"
372 | },
373 | "node_modules/etag": {
374 | "version": "1.8.1",
375 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
376 | "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
377 | "license": "MIT",
378 | "engines": {
379 | "node": ">= 0.6"
380 | }
381 | },
382 | "node_modules/eventsource-parser": {
383 | "version": "1.1.2",
384 | "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-1.1.2.tgz",
385 | "integrity": "sha512-v0eOBUbiaFojBu2s2NPBfYUoRR9GjcDNvCXVaqEf5vVfpIAh9f8RCo4vXTP8c63QRKCFwoLpMpTdPwwhEKVgzA==",
386 | "license": "MIT",
387 | "engines": {
388 | "node": ">=14.18"
389 | }
390 | },
391 | "node_modules/express": {
392 | "version": "4.21.2",
393 | "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz",
394 | "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==",
395 | "license": "MIT",
396 | "dependencies": {
397 | "accepts": "~1.3.8",
398 | "array-flatten": "1.1.1",
399 | "body-parser": "1.20.3",
400 | "content-disposition": "0.5.4",
401 | "content-type": "~1.0.4",
402 | "cookie": "0.7.1",
403 | "cookie-signature": "1.0.6",
404 | "debug": "2.6.9",
405 | "depd": "2.0.0",
406 | "encodeurl": "~2.0.0",
407 | "escape-html": "~1.0.3",
408 | "etag": "~1.8.1",
409 | "finalhandler": "1.3.1",
410 | "fresh": "0.5.2",
411 | "http-errors": "2.0.0",
412 | "merge-descriptors": "1.0.3",
413 | "methods": "~1.1.2",
414 | "on-finished": "2.4.1",
415 | "parseurl": "~1.3.3",
416 | "path-to-regexp": "0.1.12",
417 | "proxy-addr": "~2.0.7",
418 | "qs": "6.13.0",
419 | "range-parser": "~1.2.1",
420 | "safe-buffer": "5.2.1",
421 | "send": "0.19.0",
422 | "serve-static": "1.16.2",
423 | "setprototypeof": "1.2.0",
424 | "statuses": "2.0.1",
425 | "type-is": "~1.6.18",
426 | "utils-merge": "1.0.1",
427 | "vary": "~1.1.2"
428 | },
429 | "engines": {
430 | "node": ">= 0.10.0"
431 | },
432 | "funding": {
433 | "type": "opencollective",
434 | "url": "https://opencollective.com/express"
435 | }
436 | },
437 | "node_modules/fetch-blob": {
438 | "version": "3.2.0",
439 | "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz",
440 | "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==",
441 | "funding": [
442 | {
443 | "type": "github",
444 | "url": "https://github.com/sponsors/jimmywarting"
445 | },
446 | {
447 | "type": "paypal",
448 | "url": "https://paypal.me/jimmywarting"
449 | }
450 | ],
451 | "license": "MIT",
452 | "dependencies": {
453 | "node-domexception": "^1.0.0",
454 | "web-streams-polyfill": "^3.0.3"
455 | },
456 | "engines": {
457 | "node": "^12.20 || >= 14.13"
458 | }
459 | },
460 | "node_modules/finalhandler": {
461 | "version": "1.3.1",
462 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz",
463 | "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==",
464 | "license": "MIT",
465 | "dependencies": {
466 | "debug": "2.6.9",
467 | "encodeurl": "~2.0.0",
468 | "escape-html": "~1.0.3",
469 | "on-finished": "2.4.1",
470 | "parseurl": "~1.3.3",
471 | "statuses": "2.0.1",
472 | "unpipe": "~1.0.0"
473 | },
474 | "engines": {
475 | "node": ">= 0.8"
476 | }
477 | },
478 | "node_modules/form-data": {
479 | "version": "4.0.5",
480 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
481 | "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
482 | "license": "MIT",
483 | "dependencies": {
484 | "asynckit": "^0.4.0",
485 | "combined-stream": "^1.0.8",
486 | "es-set-tostringtag": "^2.1.0",
487 | "hasown": "^2.0.2",
488 | "mime-types": "^2.1.12"
489 | },
490 | "engines": {
491 | "node": ">= 6"
492 | }
493 | },
494 | "node_modules/formdata-polyfill": {
495 | "version": "4.0.10",
496 | "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
497 | "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==",
498 | "license": "MIT",
499 | "dependencies": {
500 | "fetch-blob": "^3.1.2"
501 | },
502 | "engines": {
503 | "node": ">=12.20.0"
504 | }
505 | },
506 | "node_modules/forwarded": {
507 | "version": "0.2.0",
508 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
509 | "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
510 | "license": "MIT",
511 | "engines": {
512 | "node": ">= 0.6"
513 | }
514 | },
515 | "node_modules/fresh": {
516 | "version": "0.5.2",
517 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
518 | "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
519 | "license": "MIT",
520 | "engines": {
521 | "node": ">= 0.6"
522 | }
523 | },
524 | "node_modules/function-bind": {
525 | "version": "1.1.2",
526 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
527 | "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
528 | "license": "MIT",
529 | "funding": {
530 | "url": "https://github.com/sponsors/ljharb"
531 | }
532 | },
533 | "node_modules/get-intrinsic": {
534 | "version": "1.3.0",
535 | "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
536 | "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
537 | "license": "MIT",
538 | "dependencies": {
539 | "call-bind-apply-helpers": "^1.0.2",
540 | "es-define-property": "^1.0.1",
541 | "es-errors": "^1.3.0",
542 | "es-object-atoms": "^1.1.1",
543 | "function-bind": "^1.1.2",
544 | "get-proto": "^1.0.1",
545 | "gopd": "^1.2.0",
546 | "has-symbols": "^1.1.0",
547 | "hasown": "^2.0.2",
548 | "math-intrinsics": "^1.1.0"
549 | },
550 | "engines": {
551 | "node": ">= 0.4"
552 | },
553 | "funding": {
554 | "url": "https://github.com/sponsors/ljharb"
555 | }
556 | },
557 | "node_modules/get-proto": {
558 | "version": "1.0.1",
559 | "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
560 | "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
561 | "license": "MIT",
562 | "dependencies": {
563 | "dunder-proto": "^1.0.1",
564 | "es-object-atoms": "^1.0.0"
565 | },
566 | "engines": {
567 | "node": ">= 0.4"
568 | }
569 | },
570 | "node_modules/gopd": {
571 | "version": "1.2.0",
572 | "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
573 | "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
574 | "license": "MIT",
575 | "engines": {
576 | "node": ">= 0.4"
577 | },
578 | "funding": {
579 | "url": "https://github.com/sponsors/ljharb"
580 | }
581 | },
582 | "node_modules/has-symbols": {
583 | "version": "1.1.0",
584 | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
585 | "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
586 | "license": "MIT",
587 | "engines": {
588 | "node": ">= 0.4"
589 | },
590 | "funding": {
591 | "url": "https://github.com/sponsors/ljharb"
592 | }
593 | },
594 | "node_modules/has-tostringtag": {
595 | "version": "1.0.2",
596 | "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
597 | "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
598 | "license": "MIT",
599 | "dependencies": {
600 | "has-symbols": "^1.0.3"
601 | },
602 | "engines": {
603 | "node": ">= 0.4"
604 | },
605 | "funding": {
606 | "url": "https://github.com/sponsors/ljharb"
607 | }
608 | },
609 | "node_modules/hasown": {
610 | "version": "2.0.2",
611 | "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
612 | "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
613 | "license": "MIT",
614 | "dependencies": {
615 | "function-bind": "^1.1.2"
616 | },
617 | "engines": {
618 | "node": ">= 0.4"
619 | }
620 | },
621 | "node_modules/http-errors": {
622 | "version": "2.0.0",
623 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
624 | "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
625 | "license": "MIT",
626 | "dependencies": {
627 | "depd": "2.0.0",
628 | "inherits": "2.0.4",
629 | "setprototypeof": "1.2.0",
630 | "statuses": "2.0.1",
631 | "toidentifier": "1.0.1"
632 | },
633 | "engines": {
634 | "node": ">= 0.8"
635 | }
636 | },
637 | "node_modules/iconv-lite": {
638 | "version": "0.4.24",
639 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
640 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
641 | "license": "MIT",
642 | "dependencies": {
643 | "safer-buffer": ">= 2.1.2 < 3"
644 | },
645 | "engines": {
646 | "node": ">=0.10.0"
647 | }
648 | },
649 | "node_modules/inherits": {
650 | "version": "2.0.4",
651 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
652 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
653 | "license": "ISC"
654 | },
655 | "node_modules/ipaddr.js": {
656 | "version": "1.9.1",
657 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
658 | "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
659 | "license": "MIT",
660 | "engines": {
661 | "node": ">= 0.10"
662 | }
663 | },
664 | "node_modules/isarray": {
665 | "version": "1.0.0",
666 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
667 | "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
668 | "license": "MIT"
669 | },
670 | "node_modules/math-intrinsics": {
671 | "version": "1.1.0",
672 | "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
673 | "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
674 | "license": "MIT",
675 | "engines": {
676 | "node": ">= 0.4"
677 | }
678 | },
679 | "node_modules/media-typer": {
680 | "version": "0.3.0",
681 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
682 | "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
683 | "license": "MIT",
684 | "engines": {
685 | "node": ">= 0.6"
686 | }
687 | },
688 | "node_modules/merge-descriptors": {
689 | "version": "1.0.3",
690 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
691 | "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==",
692 | "license": "MIT",
693 | "funding": {
694 | "url": "https://github.com/sponsors/sindresorhus"
695 | }
696 | },
697 | "node_modules/methods": {
698 | "version": "1.1.2",
699 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
700 | "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
701 | "license": "MIT",
702 | "engines": {
703 | "node": ">= 0.6"
704 | }
705 | },
706 | "node_modules/mime": {
707 | "version": "1.6.0",
708 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
709 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
710 | "license": "MIT",
711 | "bin": {
712 | "mime": "cli.js"
713 | },
714 | "engines": {
715 | "node": ">=4"
716 | }
717 | },
718 | "node_modules/mime-db": {
719 | "version": "1.52.0",
720 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
721 | "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
722 | "license": "MIT",
723 | "engines": {
724 | "node": ">= 0.6"
725 | }
726 | },
727 | "node_modules/mime-types": {
728 | "version": "2.1.35",
729 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
730 | "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
731 | "license": "MIT",
732 | "dependencies": {
733 | "mime-db": "1.52.0"
734 | },
735 | "engines": {
736 | "node": ">= 0.6"
737 | }
738 | },
739 | "node_modules/minimist": {
740 | "version": "1.2.8",
741 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
742 | "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
743 | "license": "MIT",
744 | "funding": {
745 | "url": "https://github.com/sponsors/ljharb"
746 | }
747 | },
748 | "node_modules/mkdirp": {
749 | "version": "0.5.6",
750 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
751 | "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
752 | "license": "MIT",
753 | "dependencies": {
754 | "minimist": "^1.2.6"
755 | },
756 | "bin": {
757 | "mkdirp": "bin/cmd.js"
758 | }
759 | },
760 | "node_modules/ms": {
761 | "version": "2.0.0",
762 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
763 | "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
764 | "license": "MIT"
765 | },
766 | "node_modules/multer": {
767 | "version": "1.4.5-lts.2",
768 | "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.2.tgz",
769 | "integrity": "sha512-VzGiVigcG9zUAoCNU+xShztrlr1auZOlurXynNvO9GiWD1/mTBbUljOKY+qMeazBqXgRnjzeEgJI/wyjJUHg9A==",
770 | "deprecated": "Multer 1.x is impacted by a number of vulnerabilities, which have been patched in 2.x. You should upgrade to the latest 2.x version.",
771 | "license": "MIT",
772 | "dependencies": {
773 | "append-field": "^1.0.0",
774 | "busboy": "^1.0.0",
775 | "concat-stream": "^1.5.2",
776 | "mkdirp": "^0.5.4",
777 | "object-assign": "^4.1.1",
778 | "type-is": "^1.6.4",
779 | "xtend": "^4.0.0"
780 | },
781 | "engines": {
782 | "node": ">= 6.0.0"
783 | }
784 | },
785 | "node_modules/negotiator": {
786 | "version": "0.6.3",
787 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
788 | "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
789 | "license": "MIT",
790 | "engines": {
791 | "node": ">= 0.6"
792 | }
793 | },
794 | "node_modules/node-domexception": {
795 | "version": "1.0.0",
796 | "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
797 | "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==",
798 | "deprecated": "Use your platform's native DOMException instead",
799 | "funding": [
800 | {
801 | "type": "github",
802 | "url": "https://github.com/sponsors/jimmywarting"
803 | },
804 | {
805 | "type": "github",
806 | "url": "https://paypal.me/jimmywarting"
807 | }
808 | ],
809 | "license": "MIT",
810 | "engines": {
811 | "node": ">=10.5.0"
812 | }
813 | },
814 | "node_modules/node-fetch": {
815 | "version": "3.3.2",
816 | "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz",
817 | "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==",
818 | "license": "MIT",
819 | "dependencies": {
820 | "data-uri-to-buffer": "^4.0.0",
821 | "fetch-blob": "^3.1.4",
822 | "formdata-polyfill": "^4.0.10"
823 | },
824 | "engines": {
825 | "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
826 | },
827 | "funding": {
828 | "type": "opencollective",
829 | "url": "https://opencollective.com/node-fetch"
830 | }
831 | },
832 | "node_modules/object-assign": {
833 | "version": "4.1.1",
834 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
835 | "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
836 | "license": "MIT",
837 | "engines": {
838 | "node": ">=0.10.0"
839 | }
840 | },
841 | "node_modules/object-inspect": {
842 | "version": "1.13.4",
843 | "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
844 | "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
845 | "license": "MIT",
846 | "engines": {
847 | "node": ">= 0.4"
848 | },
849 | "funding": {
850 | "url": "https://github.com/sponsors/ljharb"
851 | }
852 | },
853 | "node_modules/on-finished": {
854 | "version": "2.4.1",
855 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
856 | "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
857 | "license": "MIT",
858 | "dependencies": {
859 | "ee-first": "1.1.1"
860 | },
861 | "engines": {
862 | "node": ">= 0.8"
863 | }
864 | },
865 | "node_modules/parseurl": {
866 | "version": "1.3.3",
867 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
868 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
869 | "license": "MIT",
870 | "engines": {
871 | "node": ">= 0.8"
872 | }
873 | },
874 | "node_modules/path-to-regexp": {
875 | "version": "0.1.12",
876 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz",
877 | "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==",
878 | "license": "MIT"
879 | },
880 | "node_modules/process-nextick-args": {
881 | "version": "2.0.1",
882 | "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
883 | "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
884 | "license": "MIT"
885 | },
886 | "node_modules/proxy-addr": {
887 | "version": "2.0.7",
888 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
889 | "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
890 | "license": "MIT",
891 | "dependencies": {
892 | "forwarded": "0.2.0",
893 | "ipaddr.js": "1.9.1"
894 | },
895 | "engines": {
896 | "node": ">= 0.10"
897 | }
898 | },
899 | "node_modules/qs": {
900 | "version": "6.13.0",
901 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
902 | "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
903 | "license": "BSD-3-Clause",
904 | "dependencies": {
905 | "side-channel": "^1.0.6"
906 | },
907 | "engines": {
908 | "node": ">=0.6"
909 | },
910 | "funding": {
911 | "url": "https://github.com/sponsors/ljharb"
912 | }
913 | },
914 | "node_modules/range-parser": {
915 | "version": "1.2.1",
916 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
917 | "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
918 | "license": "MIT",
919 | "engines": {
920 | "node": ">= 0.6"
921 | }
922 | },
923 | "node_modules/raw-body": {
924 | "version": "2.5.2",
925 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz",
926 | "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==",
927 | "license": "MIT",
928 | "dependencies": {
929 | "bytes": "3.1.2",
930 | "http-errors": "2.0.0",
931 | "iconv-lite": "0.4.24",
932 | "unpipe": "1.0.0"
933 | },
934 | "engines": {
935 | "node": ">= 0.8"
936 | }
937 | },
938 | "node_modules/readable-stream": {
939 | "version": "2.3.8",
940 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
941 | "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
942 | "license": "MIT",
943 | "dependencies": {
944 | "core-util-is": "~1.0.0",
945 | "inherits": "~2.0.3",
946 | "isarray": "~1.0.0",
947 | "process-nextick-args": "~2.0.0",
948 | "safe-buffer": "~5.1.1",
949 | "string_decoder": "~1.1.1",
950 | "util-deprecate": "~1.0.1"
951 | }
952 | },
953 | "node_modules/readable-stream/node_modules/safe-buffer": {
954 | "version": "5.1.2",
955 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
956 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
957 | "license": "MIT"
958 | },
959 | "node_modules/robot3": {
960 | "version": "0.4.1",
961 | "resolved": "https://registry.npmjs.org/robot3/-/robot3-0.4.1.tgz",
962 | "integrity": "sha512-hzjy826lrxzx8eRgv80idkf8ua1JAepRc9Efdtj03N3KNJuznQCPlyCJ7gnUmDFwZCLQjxy567mQVKmdv2BsXQ==",
963 | "license": "BSD-2-Clause"
964 | },
965 | "node_modules/safe-buffer": {
966 | "version": "5.2.1",
967 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
968 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
969 | "funding": [
970 | {
971 | "type": "github",
972 | "url": "https://github.com/sponsors/feross"
973 | },
974 | {
975 | "type": "patreon",
976 | "url": "https://www.patreon.com/feross"
977 | },
978 | {
979 | "type": "consulting",
980 | "url": "https://feross.org/support"
981 | }
982 | ],
983 | "license": "MIT"
984 | },
985 | "node_modules/safer-buffer": {
986 | "version": "2.1.2",
987 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
988 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
989 | "license": "MIT"
990 | },
991 | "node_modules/send": {
992 | "version": "0.19.0",
993 | "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz",
994 | "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==",
995 | "license": "MIT",
996 | "dependencies": {
997 | "debug": "2.6.9",
998 | "depd": "2.0.0",
999 | "destroy": "1.2.0",
1000 | "encodeurl": "~1.0.2",
1001 | "escape-html": "~1.0.3",
1002 | "etag": "~1.8.1",
1003 | "fresh": "0.5.2",
1004 | "http-errors": "2.0.0",
1005 | "mime": "1.6.0",
1006 | "ms": "2.1.3",
1007 | "on-finished": "2.4.1",
1008 | "range-parser": "~1.2.1",
1009 | "statuses": "2.0.1"
1010 | },
1011 | "engines": {
1012 | "node": ">= 0.8.0"
1013 | }
1014 | },
1015 | "node_modules/send/node_modules/encodeurl": {
1016 | "version": "1.0.2",
1017 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
1018 | "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
1019 | "license": "MIT",
1020 | "engines": {
1021 | "node": ">= 0.8"
1022 | }
1023 | },
1024 | "node_modules/send/node_modules/ms": {
1025 | "version": "2.1.3",
1026 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
1027 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
1028 | "license": "MIT"
1029 | },
1030 | "node_modules/serve-static": {
1031 | "version": "1.16.2",
1032 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz",
1033 | "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==",
1034 | "license": "MIT",
1035 | "dependencies": {
1036 | "encodeurl": "~2.0.0",
1037 | "escape-html": "~1.0.3",
1038 | "parseurl": "~1.3.3",
1039 | "send": "0.19.0"
1040 | },
1041 | "engines": {
1042 | "node": ">= 0.8.0"
1043 | }
1044 | },
1045 | "node_modules/setprototypeof": {
1046 | "version": "1.2.0",
1047 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
1048 | "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
1049 | "license": "ISC"
1050 | },
1051 | "node_modules/side-channel": {
1052 | "version": "1.1.0",
1053 | "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
1054 | "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
1055 | "license": "MIT",
1056 | "dependencies": {
1057 | "es-errors": "^1.3.0",
1058 | "object-inspect": "^1.13.3",
1059 | "side-channel-list": "^1.0.0",
1060 | "side-channel-map": "^1.0.1",
1061 | "side-channel-weakmap": "^1.0.2"
1062 | },
1063 | "engines": {
1064 | "node": ">= 0.4"
1065 | },
1066 | "funding": {
1067 | "url": "https://github.com/sponsors/ljharb"
1068 | }
1069 | },
1070 | "node_modules/side-channel-list": {
1071 | "version": "1.0.0",
1072 | "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
1073 | "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
1074 | "license": "MIT",
1075 | "dependencies": {
1076 | "es-errors": "^1.3.0",
1077 | "object-inspect": "^1.13.3"
1078 | },
1079 | "engines": {
1080 | "node": ">= 0.4"
1081 | },
1082 | "funding": {
1083 | "url": "https://github.com/sponsors/ljharb"
1084 | }
1085 | },
1086 | "node_modules/side-channel-map": {
1087 | "version": "1.0.1",
1088 | "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
1089 | "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
1090 | "license": "MIT",
1091 | "dependencies": {
1092 | "call-bound": "^1.0.2",
1093 | "es-errors": "^1.3.0",
1094 | "get-intrinsic": "^1.2.5",
1095 | "object-inspect": "^1.13.3"
1096 | },
1097 | "engines": {
1098 | "node": ">= 0.4"
1099 | },
1100 | "funding": {
1101 | "url": "https://github.com/sponsors/ljharb"
1102 | }
1103 | },
1104 | "node_modules/side-channel-weakmap": {
1105 | "version": "1.0.2",
1106 | "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
1107 | "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
1108 | "license": "MIT",
1109 | "dependencies": {
1110 | "call-bound": "^1.0.2",
1111 | "es-errors": "^1.3.0",
1112 | "get-intrinsic": "^1.2.5",
1113 | "object-inspect": "^1.13.3",
1114 | "side-channel-map": "^1.0.1"
1115 | },
1116 | "engines": {
1117 | "node": ">= 0.4"
1118 | },
1119 | "funding": {
1120 | "url": "https://github.com/sponsors/ljharb"
1121 | }
1122 | },
1123 | "node_modules/statuses": {
1124 | "version": "2.0.1",
1125 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
1126 | "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
1127 | "license": "MIT",
1128 | "engines": {
1129 | "node": ">= 0.8"
1130 | }
1131 | },
1132 | "node_modules/streamsearch": {
1133 | "version": "1.1.0",
1134 | "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",
1135 | "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==",
1136 | "engines": {
1137 | "node": ">=10.0.0"
1138 | }
1139 | },
1140 | "node_modules/string_decoder": {
1141 | "version": "1.1.1",
1142 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
1143 | "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
1144 | "license": "MIT",
1145 | "dependencies": {
1146 | "safe-buffer": "~5.1.0"
1147 | }
1148 | },
1149 | "node_modules/string_decoder/node_modules/safe-buffer": {
1150 | "version": "5.1.2",
1151 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
1152 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
1153 | "license": "MIT"
1154 | },
1155 | "node_modules/toidentifier": {
1156 | "version": "1.0.1",
1157 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
1158 | "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
1159 | "license": "MIT",
1160 | "engines": {
1161 | "node": ">=0.6"
1162 | }
1163 | },
1164 | "node_modules/type-is": {
1165 | "version": "1.6.18",
1166 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
1167 | "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
1168 | "license": "MIT",
1169 | "dependencies": {
1170 | "media-typer": "0.3.0",
1171 | "mime-types": "~2.1.24"
1172 | },
1173 | "engines": {
1174 | "node": ">= 0.6"
1175 | }
1176 | },
1177 | "node_modules/typedarray": {
1178 | "version": "0.0.6",
1179 | "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
1180 | "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==",
1181 | "license": "MIT"
1182 | },
1183 | "node_modules/unpipe": {
1184 | "version": "1.0.0",
1185 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
1186 | "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
1187 | "license": "MIT",
1188 | "engines": {
1189 | "node": ">= 0.8"
1190 | }
1191 | },
1192 | "node_modules/util-deprecate": {
1193 | "version": "1.0.2",
1194 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
1195 | "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
1196 | "license": "MIT"
1197 | },
1198 | "node_modules/utils-merge": {
1199 | "version": "1.0.1",
1200 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
1201 | "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
1202 | "license": "MIT",
1203 | "engines": {
1204 | "node": ">= 0.4.0"
1205 | }
1206 | },
1207 | "node_modules/vary": {
1208 | "version": "1.1.2",
1209 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
1210 | "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
1211 | "license": "MIT",
1212 | "engines": {
1213 | "node": ">= 0.8"
1214 | }
1215 | },
1216 | "node_modules/web-streams-polyfill": {
1217 | "version": "3.3.3",
1218 | "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz",
1219 | "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==",
1220 | "license": "MIT",
1221 | "engines": {
1222 | "node": ">= 8"
1223 | }
1224 | },
1225 | "node_modules/xtend": {
1226 | "version": "4.0.2",
1227 | "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
1228 | "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
1229 | "license": "MIT",
1230 | "engines": {
1231 | "node": ">=0.4"
1232 | }
1233 | }
1234 | }
1235 | }
1236 |
--------------------------------------------------------------------------------
/frontend/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "nanobanana-frontend",
3 | "version": "1.0.0",
4 | "lockfileVersion": 3,
5 | "requires": true,
6 | "packages": {
7 | "": {
8 | "name": "nanobanana-frontend",
9 | "version": "1.0.0",
10 | "dependencies": {
11 | "lucide-react": "^0.294.0",
12 | "react": "^18.2.0",
13 | "react-dom": "^18.2.0",
14 | "react-easy-crop": "^5.0.7"
15 | },
16 | "devDependencies": {
17 | "@types/react": "^18.2.43",
18 | "@types/react-dom": "^18.2.17",
19 | "@vitejs/plugin-react": "^4.2.1",
20 | "typescript": "^5.3.3",
21 | "vite": "^5.0.8"
22 | }
23 | },
24 | "node_modules/@babel/code-frame": {
25 | "version": "7.27.1",
26 | "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
27 | "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==",
28 | "dev": true,
29 | "license": "MIT",
30 | "dependencies": {
31 | "@babel/helper-validator-identifier": "^7.27.1",
32 | "js-tokens": "^4.0.0",
33 | "picocolors": "^1.1.1"
34 | },
35 | "engines": {
36 | "node": ">=6.9.0"
37 | }
38 | },
39 | "node_modules/@babel/compat-data": {
40 | "version": "7.28.5",
41 | "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.5.tgz",
42 | "integrity": "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==",
43 | "dev": true,
44 | "license": "MIT",
45 | "engines": {
46 | "node": ">=6.9.0"
47 | }
48 | },
49 | "node_modules/@babel/core": {
50 | "version": "7.28.5",
51 | "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz",
52 | "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==",
53 | "dev": true,
54 | "license": "MIT",
55 | "peer": true,
56 | "dependencies": {
57 | "@babel/code-frame": "^7.27.1",
58 | "@babel/generator": "^7.28.5",
59 | "@babel/helper-compilation-targets": "^7.27.2",
60 | "@babel/helper-module-transforms": "^7.28.3",
61 | "@babel/helpers": "^7.28.4",
62 | "@babel/parser": "^7.28.5",
63 | "@babel/template": "^7.27.2",
64 | "@babel/traverse": "^7.28.5",
65 | "@babel/types": "^7.28.5",
66 | "@jridgewell/remapping": "^2.3.5",
67 | "convert-source-map": "^2.0.0",
68 | "debug": "^4.1.0",
69 | "gensync": "^1.0.0-beta.2",
70 | "json5": "^2.2.3",
71 | "semver": "^6.3.1"
72 | },
73 | "engines": {
74 | "node": ">=6.9.0"
75 | },
76 | "funding": {
77 | "type": "opencollective",
78 | "url": "https://opencollective.com/babel"
79 | }
80 | },
81 | "node_modules/@babel/generator": {
82 | "version": "7.28.5",
83 | "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz",
84 | "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==",
85 | "dev": true,
86 | "license": "MIT",
87 | "dependencies": {
88 | "@babel/parser": "^7.28.5",
89 | "@babel/types": "^7.28.5",
90 | "@jridgewell/gen-mapping": "^0.3.12",
91 | "@jridgewell/trace-mapping": "^0.3.28",
92 | "jsesc": "^3.0.2"
93 | },
94 | "engines": {
95 | "node": ">=6.9.0"
96 | }
97 | },
98 | "node_modules/@babel/helper-compilation-targets": {
99 | "version": "7.27.2",
100 | "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz",
101 | "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==",
102 | "dev": true,
103 | "license": "MIT",
104 | "dependencies": {
105 | "@babel/compat-data": "^7.27.2",
106 | "@babel/helper-validator-option": "^7.27.1",
107 | "browserslist": "^4.24.0",
108 | "lru-cache": "^5.1.1",
109 | "semver": "^6.3.1"
110 | },
111 | "engines": {
112 | "node": ">=6.9.0"
113 | }
114 | },
115 | "node_modules/@babel/helper-globals": {
116 | "version": "7.28.0",
117 | "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz",
118 | "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==",
119 | "dev": true,
120 | "license": "MIT",
121 | "engines": {
122 | "node": ">=6.9.0"
123 | }
124 | },
125 | "node_modules/@babel/helper-module-imports": {
126 | "version": "7.27.1",
127 | "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz",
128 | "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==",
129 | "dev": true,
130 | "license": "MIT",
131 | "dependencies": {
132 | "@babel/traverse": "^7.27.1",
133 | "@babel/types": "^7.27.1"
134 | },
135 | "engines": {
136 | "node": ">=6.9.0"
137 | }
138 | },
139 | "node_modules/@babel/helper-module-transforms": {
140 | "version": "7.28.3",
141 | "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz",
142 | "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==",
143 | "dev": true,
144 | "license": "MIT",
145 | "dependencies": {
146 | "@babel/helper-module-imports": "^7.27.1",
147 | "@babel/helper-validator-identifier": "^7.27.1",
148 | "@babel/traverse": "^7.28.3"
149 | },
150 | "engines": {
151 | "node": ">=6.9.0"
152 | },
153 | "peerDependencies": {
154 | "@babel/core": "^7.0.0"
155 | }
156 | },
157 | "node_modules/@babel/helper-plugin-utils": {
158 | "version": "7.27.1",
159 | "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz",
160 | "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==",
161 | "dev": true,
162 | "license": "MIT",
163 | "engines": {
164 | "node": ">=6.9.0"
165 | }
166 | },
167 | "node_modules/@babel/helper-string-parser": {
168 | "version": "7.27.1",
169 | "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
170 | "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
171 | "dev": true,
172 | "license": "MIT",
173 | "engines": {
174 | "node": ">=6.9.0"
175 | }
176 | },
177 | "node_modules/@babel/helper-validator-identifier": {
178 | "version": "7.28.5",
179 | "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
180 | "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
181 | "dev": true,
182 | "license": "MIT",
183 | "engines": {
184 | "node": ">=6.9.0"
185 | }
186 | },
187 | "node_modules/@babel/helper-validator-option": {
188 | "version": "7.27.1",
189 | "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz",
190 | "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==",
191 | "dev": true,
192 | "license": "MIT",
193 | "engines": {
194 | "node": ">=6.9.0"
195 | }
196 | },
197 | "node_modules/@babel/helpers": {
198 | "version": "7.28.4",
199 | "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz",
200 | "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==",
201 | "dev": true,
202 | "license": "MIT",
203 | "dependencies": {
204 | "@babel/template": "^7.27.2",
205 | "@babel/types": "^7.28.4"
206 | },
207 | "engines": {
208 | "node": ">=6.9.0"
209 | }
210 | },
211 | "node_modules/@babel/parser": {
212 | "version": "7.28.5",
213 | "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz",
214 | "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==",
215 | "dev": true,
216 | "license": "MIT",
217 | "dependencies": {
218 | "@babel/types": "^7.28.5"
219 | },
220 | "bin": {
221 | "parser": "bin/babel-parser.js"
222 | },
223 | "engines": {
224 | "node": ">=6.0.0"
225 | }
226 | },
227 | "node_modules/@babel/plugin-transform-react-jsx-self": {
228 | "version": "7.27.1",
229 | "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz",
230 | "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==",
231 | "dev": true,
232 | "license": "MIT",
233 | "dependencies": {
234 | "@babel/helper-plugin-utils": "^7.27.1"
235 | },
236 | "engines": {
237 | "node": ">=6.9.0"
238 | },
239 | "peerDependencies": {
240 | "@babel/core": "^7.0.0-0"
241 | }
242 | },
243 | "node_modules/@babel/plugin-transform-react-jsx-source": {
244 | "version": "7.27.1",
245 | "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz",
246 | "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==",
247 | "dev": true,
248 | "license": "MIT",
249 | "dependencies": {
250 | "@babel/helper-plugin-utils": "^7.27.1"
251 | },
252 | "engines": {
253 | "node": ">=6.9.0"
254 | },
255 | "peerDependencies": {
256 | "@babel/core": "^7.0.0-0"
257 | }
258 | },
259 | "node_modules/@babel/template": {
260 | "version": "7.27.2",
261 | "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz",
262 | "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==",
263 | "dev": true,
264 | "license": "MIT",
265 | "dependencies": {
266 | "@babel/code-frame": "^7.27.1",
267 | "@babel/parser": "^7.27.2",
268 | "@babel/types": "^7.27.1"
269 | },
270 | "engines": {
271 | "node": ">=6.9.0"
272 | }
273 | },
274 | "node_modules/@babel/traverse": {
275 | "version": "7.28.5",
276 | "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz",
277 | "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==",
278 | "dev": true,
279 | "license": "MIT",
280 | "dependencies": {
281 | "@babel/code-frame": "^7.27.1",
282 | "@babel/generator": "^7.28.5",
283 | "@babel/helper-globals": "^7.28.0",
284 | "@babel/parser": "^7.28.5",
285 | "@babel/template": "^7.27.2",
286 | "@babel/types": "^7.28.5",
287 | "debug": "^4.3.1"
288 | },
289 | "engines": {
290 | "node": ">=6.9.0"
291 | }
292 | },
293 | "node_modules/@babel/types": {
294 | "version": "7.28.5",
295 | "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz",
296 | "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==",
297 | "dev": true,
298 | "license": "MIT",
299 | "dependencies": {
300 | "@babel/helper-string-parser": "^7.27.1",
301 | "@babel/helper-validator-identifier": "^7.28.5"
302 | },
303 | "engines": {
304 | "node": ">=6.9.0"
305 | }
306 | },
307 | "node_modules/@esbuild/aix-ppc64": {
308 | "version": "0.21.5",
309 | "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
310 | "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==",
311 | "cpu": [
312 | "ppc64"
313 | ],
314 | "dev": true,
315 | "license": "MIT",
316 | "optional": true,
317 | "os": [
318 | "aix"
319 | ],
320 | "engines": {
321 | "node": ">=12"
322 | }
323 | },
324 | "node_modules/@esbuild/android-arm": {
325 | "version": "0.21.5",
326 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz",
327 | "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==",
328 | "cpu": [
329 | "arm"
330 | ],
331 | "dev": true,
332 | "license": "MIT",
333 | "optional": true,
334 | "os": [
335 | "android"
336 | ],
337 | "engines": {
338 | "node": ">=12"
339 | }
340 | },
341 | "node_modules/@esbuild/android-arm64": {
342 | "version": "0.21.5",
343 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz",
344 | "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==",
345 | "cpu": [
346 | "arm64"
347 | ],
348 | "dev": true,
349 | "license": "MIT",
350 | "optional": true,
351 | "os": [
352 | "android"
353 | ],
354 | "engines": {
355 | "node": ">=12"
356 | }
357 | },
358 | "node_modules/@esbuild/android-x64": {
359 | "version": "0.21.5",
360 | "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz",
361 | "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==",
362 | "cpu": [
363 | "x64"
364 | ],
365 | "dev": true,
366 | "license": "MIT",
367 | "optional": true,
368 | "os": [
369 | "android"
370 | ],
371 | "engines": {
372 | "node": ">=12"
373 | }
374 | },
375 | "node_modules/@esbuild/darwin-arm64": {
376 | "version": "0.21.5",
377 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz",
378 | "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==",
379 | "cpu": [
380 | "arm64"
381 | ],
382 | "dev": true,
383 | "license": "MIT",
384 | "optional": true,
385 | "os": [
386 | "darwin"
387 | ],
388 | "engines": {
389 | "node": ">=12"
390 | }
391 | },
392 | "node_modules/@esbuild/darwin-x64": {
393 | "version": "0.21.5",
394 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz",
395 | "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==",
396 | "cpu": [
397 | "x64"
398 | ],
399 | "dev": true,
400 | "license": "MIT",
401 | "optional": true,
402 | "os": [
403 | "darwin"
404 | ],
405 | "engines": {
406 | "node": ">=12"
407 | }
408 | },
409 | "node_modules/@esbuild/freebsd-arm64": {
410 | "version": "0.21.5",
411 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz",
412 | "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==",
413 | "cpu": [
414 | "arm64"
415 | ],
416 | "dev": true,
417 | "license": "MIT",
418 | "optional": true,
419 | "os": [
420 | "freebsd"
421 | ],
422 | "engines": {
423 | "node": ">=12"
424 | }
425 | },
426 | "node_modules/@esbuild/freebsd-x64": {
427 | "version": "0.21.5",
428 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz",
429 | "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==",
430 | "cpu": [
431 | "x64"
432 | ],
433 | "dev": true,
434 | "license": "MIT",
435 | "optional": true,
436 | "os": [
437 | "freebsd"
438 | ],
439 | "engines": {
440 | "node": ">=12"
441 | }
442 | },
443 | "node_modules/@esbuild/linux-arm": {
444 | "version": "0.21.5",
445 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz",
446 | "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==",
447 | "cpu": [
448 | "arm"
449 | ],
450 | "dev": true,
451 | "license": "MIT",
452 | "optional": true,
453 | "os": [
454 | "linux"
455 | ],
456 | "engines": {
457 | "node": ">=12"
458 | }
459 | },
460 | "node_modules/@esbuild/linux-arm64": {
461 | "version": "0.21.5",
462 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz",
463 | "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==",
464 | "cpu": [
465 | "arm64"
466 | ],
467 | "dev": true,
468 | "license": "MIT",
469 | "optional": true,
470 | "os": [
471 | "linux"
472 | ],
473 | "engines": {
474 | "node": ">=12"
475 | }
476 | },
477 | "node_modules/@esbuild/linux-ia32": {
478 | "version": "0.21.5",
479 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz",
480 | "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==",
481 | "cpu": [
482 | "ia32"
483 | ],
484 | "dev": true,
485 | "license": "MIT",
486 | "optional": true,
487 | "os": [
488 | "linux"
489 | ],
490 | "engines": {
491 | "node": ">=12"
492 | }
493 | },
494 | "node_modules/@esbuild/linux-loong64": {
495 | "version": "0.21.5",
496 | "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz",
497 | "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==",
498 | "cpu": [
499 | "loong64"
500 | ],
501 | "dev": true,
502 | "license": "MIT",
503 | "optional": true,
504 | "os": [
505 | "linux"
506 | ],
507 | "engines": {
508 | "node": ">=12"
509 | }
510 | },
511 | "node_modules/@esbuild/linux-mips64el": {
512 | "version": "0.21.5",
513 | "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz",
514 | "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==",
515 | "cpu": [
516 | "mips64el"
517 | ],
518 | "dev": true,
519 | "license": "MIT",
520 | "optional": true,
521 | "os": [
522 | "linux"
523 | ],
524 | "engines": {
525 | "node": ">=12"
526 | }
527 | },
528 | "node_modules/@esbuild/linux-ppc64": {
529 | "version": "0.21.5",
530 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz",
531 | "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==",
532 | "cpu": [
533 | "ppc64"
534 | ],
535 | "dev": true,
536 | "license": "MIT",
537 | "optional": true,
538 | "os": [
539 | "linux"
540 | ],
541 | "engines": {
542 | "node": ">=12"
543 | }
544 | },
545 | "node_modules/@esbuild/linux-riscv64": {
546 | "version": "0.21.5",
547 | "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz",
548 | "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==",
549 | "cpu": [
550 | "riscv64"
551 | ],
552 | "dev": true,
553 | "license": "MIT",
554 | "optional": true,
555 | "os": [
556 | "linux"
557 | ],
558 | "engines": {
559 | "node": ">=12"
560 | }
561 | },
562 | "node_modules/@esbuild/linux-s390x": {
563 | "version": "0.21.5",
564 | "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz",
565 | "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==",
566 | "cpu": [
567 | "s390x"
568 | ],
569 | "dev": true,
570 | "license": "MIT",
571 | "optional": true,
572 | "os": [
573 | "linux"
574 | ],
575 | "engines": {
576 | "node": ">=12"
577 | }
578 | },
579 | "node_modules/@esbuild/linux-x64": {
580 | "version": "0.21.5",
581 | "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz",
582 | "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==",
583 | "cpu": [
584 | "x64"
585 | ],
586 | "dev": true,
587 | "license": "MIT",
588 | "optional": true,
589 | "os": [
590 | "linux"
591 | ],
592 | "engines": {
593 | "node": ">=12"
594 | }
595 | },
596 | "node_modules/@esbuild/netbsd-x64": {
597 | "version": "0.21.5",
598 | "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
599 | "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==",
600 | "cpu": [
601 | "x64"
602 | ],
603 | "dev": true,
604 | "license": "MIT",
605 | "optional": true,
606 | "os": [
607 | "netbsd"
608 | ],
609 | "engines": {
610 | "node": ">=12"
611 | }
612 | },
613 | "node_modules/@esbuild/openbsd-x64": {
614 | "version": "0.21.5",
615 | "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz",
616 | "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==",
617 | "cpu": [
618 | "x64"
619 | ],
620 | "dev": true,
621 | "license": "MIT",
622 | "optional": true,
623 | "os": [
624 | "openbsd"
625 | ],
626 | "engines": {
627 | "node": ">=12"
628 | }
629 | },
630 | "node_modules/@esbuild/sunos-x64": {
631 | "version": "0.21.5",
632 | "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz",
633 | "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==",
634 | "cpu": [
635 | "x64"
636 | ],
637 | "dev": true,
638 | "license": "MIT",
639 | "optional": true,
640 | "os": [
641 | "sunos"
642 | ],
643 | "engines": {
644 | "node": ">=12"
645 | }
646 | },
647 | "node_modules/@esbuild/win32-arm64": {
648 | "version": "0.21.5",
649 | "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz",
650 | "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==",
651 | "cpu": [
652 | "arm64"
653 | ],
654 | "dev": true,
655 | "license": "MIT",
656 | "optional": true,
657 | "os": [
658 | "win32"
659 | ],
660 | "engines": {
661 | "node": ">=12"
662 | }
663 | },
664 | "node_modules/@esbuild/win32-ia32": {
665 | "version": "0.21.5",
666 | "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz",
667 | "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==",
668 | "cpu": [
669 | "ia32"
670 | ],
671 | "dev": true,
672 | "license": "MIT",
673 | "optional": true,
674 | "os": [
675 | "win32"
676 | ],
677 | "engines": {
678 | "node": ">=12"
679 | }
680 | },
681 | "node_modules/@esbuild/win32-x64": {
682 | "version": "0.21.5",
683 | "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz",
684 | "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==",
685 | "cpu": [
686 | "x64"
687 | ],
688 | "dev": true,
689 | "license": "MIT",
690 | "optional": true,
691 | "os": [
692 | "win32"
693 | ],
694 | "engines": {
695 | "node": ">=12"
696 | }
697 | },
698 | "node_modules/@jridgewell/gen-mapping": {
699 | "version": "0.3.13",
700 | "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
701 | "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
702 | "dev": true,
703 | "license": "MIT",
704 | "dependencies": {
705 | "@jridgewell/sourcemap-codec": "^1.5.0",
706 | "@jridgewell/trace-mapping": "^0.3.24"
707 | }
708 | },
709 | "node_modules/@jridgewell/remapping": {
710 | "version": "2.3.5",
711 | "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
712 | "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
713 | "dev": true,
714 | "license": "MIT",
715 | "dependencies": {
716 | "@jridgewell/gen-mapping": "^0.3.5",
717 | "@jridgewell/trace-mapping": "^0.3.24"
718 | }
719 | },
720 | "node_modules/@jridgewell/resolve-uri": {
721 | "version": "3.1.2",
722 | "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
723 | "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
724 | "dev": true,
725 | "license": "MIT",
726 | "engines": {
727 | "node": ">=6.0.0"
728 | }
729 | },
730 | "node_modules/@jridgewell/sourcemap-codec": {
731 | "version": "1.5.5",
732 | "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
733 | "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
734 | "dev": true,
735 | "license": "MIT"
736 | },
737 | "node_modules/@jridgewell/trace-mapping": {
738 | "version": "0.3.31",
739 | "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
740 | "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
741 | "dev": true,
742 | "license": "MIT",
743 | "dependencies": {
744 | "@jridgewell/resolve-uri": "^3.1.0",
745 | "@jridgewell/sourcemap-codec": "^1.4.14"
746 | }
747 | },
748 | "node_modules/@rolldown/pluginutils": {
749 | "version": "1.0.0-beta.27",
750 | "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz",
751 | "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==",
752 | "dev": true,
753 | "license": "MIT"
754 | },
755 | "node_modules/@rollup/rollup-android-arm-eabi": {
756 | "version": "4.53.2",
757 | "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.2.tgz",
758 | "integrity": "sha512-yDPzwsgiFO26RJA4nZo8I+xqzh7sJTZIWQOxn+/XOdPE31lAvLIYCKqjV+lNH/vxE2L2iH3plKxDCRK6i+CwhA==",
759 | "cpu": [
760 | "arm"
761 | ],
762 | "dev": true,
763 | "license": "MIT",
764 | "optional": true,
765 | "os": [
766 | "android"
767 | ]
768 | },
769 | "node_modules/@rollup/rollup-android-arm64": {
770 | "version": "4.53.2",
771 | "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.53.2.tgz",
772 | "integrity": "sha512-k8FontTxIE7b0/OGKeSN5B6j25EuppBcWM33Z19JoVT7UTXFSo3D9CdU39wGTeb29NO3XxpMNauh09B+Ibw+9g==",
773 | "cpu": [
774 | "arm64"
775 | ],
776 | "dev": true,
777 | "license": "MIT",
778 | "optional": true,
779 | "os": [
780 | "android"
781 | ]
782 | },
783 | "node_modules/@rollup/rollup-darwin-arm64": {
784 | "version": "4.53.2",
785 | "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.53.2.tgz",
786 | "integrity": "sha512-A6s4gJpomNBtJ2yioj8bflM2oogDwzUiMl2yNJ2v9E7++sHrSrsQ29fOfn5DM/iCzpWcebNYEdXpaK4tr2RhfQ==",
787 | "cpu": [
788 | "arm64"
789 | ],
790 | "dev": true,
791 | "license": "MIT",
792 | "optional": true,
793 | "os": [
794 | "darwin"
795 | ]
796 | },
797 | "node_modules/@rollup/rollup-darwin-x64": {
798 | "version": "4.53.2",
799 | "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.53.2.tgz",
800 | "integrity": "sha512-e6XqVmXlHrBlG56obu9gDRPW3O3hLxpwHpLsBJvuI8qqnsrtSZ9ERoWUXtPOkY8c78WghyPHZdmPhHLWNdAGEw==",
801 | "cpu": [
802 | "x64"
803 | ],
804 | "dev": true,
805 | "license": "MIT",
806 | "optional": true,
807 | "os": [
808 | "darwin"
809 | ]
810 | },
811 | "node_modules/@rollup/rollup-freebsd-arm64": {
812 | "version": "4.53.2",
813 | "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.53.2.tgz",
814 | "integrity": "sha512-v0E9lJW8VsrwPux5Qe5CwmH/CF/2mQs6xU1MF3nmUxmZUCHazCjLgYvToOk+YuuUqLQBio1qkkREhxhc656ViA==",
815 | "cpu": [
816 | "arm64"
817 | ],
818 | "dev": true,
819 | "license": "MIT",
820 | "optional": true,
821 | "os": [
822 | "freebsd"
823 | ]
824 | },
825 | "node_modules/@rollup/rollup-freebsd-x64": {
826 | "version": "4.53.2",
827 | "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.53.2.tgz",
828 | "integrity": "sha512-ClAmAPx3ZCHtp6ysl4XEhWU69GUB1D+s7G9YjHGhIGCSrsg00nEGRRZHmINYxkdoJehde8VIsDC5t9C0gb6yqA==",
829 | "cpu": [
830 | "x64"
831 | ],
832 | "dev": true,
833 | "license": "MIT",
834 | "optional": true,
835 | "os": [
836 | "freebsd"
837 | ]
838 | },
839 | "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
840 | "version": "4.53.2",
841 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.53.2.tgz",
842 | "integrity": "sha512-EPlb95nUsz6Dd9Qy13fI5kUPXNSljaG9FiJ4YUGU1O/Q77i5DYFW5KR8g1OzTcdZUqQQ1KdDqsTohdFVwCwjqg==",
843 | "cpu": [
844 | "arm"
845 | ],
846 | "dev": true,
847 | "license": "MIT",
848 | "optional": true,
849 | "os": [
850 | "linux"
851 | ]
852 | },
853 | "node_modules/@rollup/rollup-linux-arm-musleabihf": {
854 | "version": "4.53.2",
855 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.2.tgz",
856 | "integrity": "sha512-BOmnVW+khAUX+YZvNfa0tGTEMVVEerOxN0pDk2E6N6DsEIa2Ctj48FOMfNDdrwinocKaC7YXUZ1pHlKpnkja/Q==",
857 | "cpu": [
858 | "arm"
859 | ],
860 | "dev": true,
861 | "license": "MIT",
862 | "optional": true,
863 | "os": [
864 | "linux"
865 | ]
866 | },
867 | "node_modules/@rollup/rollup-linux-arm64-gnu": {
868 | "version": "4.53.2",
869 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.2.tgz",
870 | "integrity": "sha512-Xt2byDZ+6OVNuREgBXr4+CZDJtrVso5woFtpKdGPhpTPHcNG7D8YXeQzpNbFRxzTVqJf7kvPMCub/pcGUWgBjA==",
871 | "cpu": [
872 | "arm64"
873 | ],
874 | "dev": true,
875 | "license": "MIT",
876 | "optional": true,
877 | "os": [
878 | "linux"
879 | ]
880 | },
881 | "node_modules/@rollup/rollup-linux-arm64-musl": {
882 | "version": "4.53.2",
883 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.2.tgz",
884 | "integrity": "sha512-+LdZSldy/I9N8+klim/Y1HsKbJ3BbInHav5qE9Iy77dtHC/pibw1SR/fXlWyAk0ThnpRKoODwnAuSjqxFRDHUQ==",
885 | "cpu": [
886 | "arm64"
887 | ],
888 | "dev": true,
889 | "license": "MIT",
890 | "optional": true,
891 | "os": [
892 | "linux"
893 | ]
894 | },
895 | "node_modules/@rollup/rollup-linux-loong64-gnu": {
896 | "version": "4.53.2",
897 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.53.2.tgz",
898 | "integrity": "sha512-8ms8sjmyc1jWJS6WdNSA23rEfdjWB30LH8Wqj0Cqvv7qSHnvw6kgMMXRdop6hkmGPlyYBdRPkjJnj3KCUHV/uQ==",
899 | "cpu": [
900 | "loong64"
901 | ],
902 | "dev": true,
903 | "license": "MIT",
904 | "optional": true,
905 | "os": [
906 | "linux"
907 | ]
908 | },
909 | "node_modules/@rollup/rollup-linux-ppc64-gnu": {
910 | "version": "4.53.2",
911 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.53.2.tgz",
912 | "integrity": "sha512-3HRQLUQbpBDMmzoxPJYd3W6vrVHOo2cVW8RUo87Xz0JPJcBLBr5kZ1pGcQAhdZgX9VV7NbGNipah1omKKe23/g==",
913 | "cpu": [
914 | "ppc64"
915 | ],
916 | "dev": true,
917 | "license": "MIT",
918 | "optional": true,
919 | "os": [
920 | "linux"
921 | ]
922 | },
923 | "node_modules/@rollup/rollup-linux-riscv64-gnu": {
924 | "version": "4.53.2",
925 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.53.2.tgz",
926 | "integrity": "sha512-fMjKi+ojnmIvhk34gZP94vjogXNNUKMEYs+EDaB/5TG/wUkoeua7p7VCHnE6T2Tx+iaghAqQX8teQzcvrYpaQA==",
927 | "cpu": [
928 | "riscv64"
929 | ],
930 | "dev": true,
931 | "license": "MIT",
932 | "optional": true,
933 | "os": [
934 | "linux"
935 | ]
936 | },
937 | "node_modules/@rollup/rollup-linux-riscv64-musl": {
938 | "version": "4.53.2",
939 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.2.tgz",
940 | "integrity": "sha512-XuGFGU+VwUUV5kLvoAdi0Wz5Xbh2SrjIxCtZj6Wq8MDp4bflb/+ThZsVxokM7n0pcbkEr2h5/pzqzDYI7cCgLQ==",
941 | "cpu": [
942 | "riscv64"
943 | ],
944 | "dev": true,
945 | "license": "MIT",
946 | "optional": true,
947 | "os": [
948 | "linux"
949 | ]
950 | },
951 | "node_modules/@rollup/rollup-linux-s390x-gnu": {
952 | "version": "4.53.2",
953 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.53.2.tgz",
954 | "integrity": "sha512-w6yjZF0P+NGzWR3AXWX9zc0DNEGdtvykB03uhonSHMRa+oWA6novflo2WaJr6JZakG2ucsyb+rvhrKac6NIy+w==",
955 | "cpu": [
956 | "s390x"
957 | ],
958 | "dev": true,
959 | "license": "MIT",
960 | "optional": true,
961 | "os": [
962 | "linux"
963 | ]
964 | },
965 | "node_modules/@rollup/rollup-linux-x64-gnu": {
966 | "version": "4.53.2",
967 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.2.tgz",
968 | "integrity": "sha512-yo8d6tdfdeBArzC7T/PnHd7OypfI9cbuZzPnzLJIyKYFhAQ8SvlkKtKBMbXDxe1h03Rcr7u++nFS7tqXz87Gtw==",
969 | "cpu": [
970 | "x64"
971 | ],
972 | "dev": true,
973 | "license": "MIT",
974 | "optional": true,
975 | "os": [
976 | "linux"
977 | ]
978 | },
979 | "node_modules/@rollup/rollup-linux-x64-musl": {
980 | "version": "4.53.2",
981 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.2.tgz",
982 | "integrity": "sha512-ah59c1YkCxKExPP8O9PwOvs+XRLKwh/mV+3YdKqQ5AMQ0r4M4ZDuOrpWkUaqO7fzAHdINzV9tEVu8vNw48z0lA==",
983 | "cpu": [
984 | "x64"
985 | ],
986 | "dev": true,
987 | "license": "MIT",
988 | "optional": true,
989 | "os": [
990 | "linux"
991 | ]
992 | },
993 | "node_modules/@rollup/rollup-openharmony-arm64": {
994 | "version": "4.53.2",
995 | "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.53.2.tgz",
996 | "integrity": "sha512-4VEd19Wmhr+Zy7hbUsFZ6YXEiP48hE//KPLCSVNY5RMGX2/7HZ+QkN55a3atM1C/BZCGIgqN+xrVgtdak2S9+A==",
997 | "cpu": [
998 | "arm64"
999 | ],
1000 | "dev": true,
1001 | "license": "MIT",
1002 | "optional": true,
1003 | "os": [
1004 | "openharmony"
1005 | ]
1006 | },
1007 | "node_modules/@rollup/rollup-win32-arm64-msvc": {
1008 | "version": "4.53.2",
1009 | "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.53.2.tgz",
1010 | "integrity": "sha512-IlbHFYc/pQCgew/d5fslcy1KEaYVCJ44G8pajugd8VoOEI8ODhtb/j8XMhLpwHCMB3yk2J07ctup10gpw2nyMA==",
1011 | "cpu": [
1012 | "arm64"
1013 | ],
1014 | "dev": true,
1015 | "license": "MIT",
1016 | "optional": true,
1017 | "os": [
1018 | "win32"
1019 | ]
1020 | },
1021 | "node_modules/@rollup/rollup-win32-ia32-msvc": {
1022 | "version": "4.53.2",
1023 | "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.53.2.tgz",
1024 | "integrity": "sha512-lNlPEGgdUfSzdCWU176ku/dQRnA7W+Gp8d+cWv73jYrb8uT7HTVVxq62DUYxjbaByuf1Yk0RIIAbDzp+CnOTFg==",
1025 | "cpu": [
1026 | "ia32"
1027 | ],
1028 | "dev": true,
1029 | "license": "MIT",
1030 | "optional": true,
1031 | "os": [
1032 | "win32"
1033 | ]
1034 | },
1035 | "node_modules/@rollup/rollup-win32-x64-gnu": {
1036 | "version": "4.53.2",
1037 | "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.53.2.tgz",
1038 | "integrity": "sha512-S6YojNVrHybQis2lYov1sd+uj7K0Q05NxHcGktuMMdIQ2VixGwAfbJ23NnlvvVV1bdpR2m5MsNBViHJKcA4ADw==",
1039 | "cpu": [
1040 | "x64"
1041 | ],
1042 | "dev": true,
1043 | "license": "MIT",
1044 | "optional": true,
1045 | "os": [
1046 | "win32"
1047 | ]
1048 | },
1049 | "node_modules/@rollup/rollup-win32-x64-msvc": {
1050 | "version": "4.53.2",
1051 | "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.53.2.tgz",
1052 | "integrity": "sha512-k+/Rkcyx//P6fetPoLMb8pBeqJBNGx81uuf7iljX9++yNBVRDQgD04L+SVXmXmh5ZP4/WOp4mWF0kmi06PW2tA==",
1053 | "cpu": [
1054 | "x64"
1055 | ],
1056 | "dev": true,
1057 | "license": "MIT",
1058 | "optional": true,
1059 | "os": [
1060 | "win32"
1061 | ]
1062 | },
1063 | "node_modules/@types/babel__core": {
1064 | "version": "7.20.5",
1065 | "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
1066 | "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
1067 | "dev": true,
1068 | "license": "MIT",
1069 | "dependencies": {
1070 | "@babel/parser": "^7.20.7",
1071 | "@babel/types": "^7.20.7",
1072 | "@types/babel__generator": "*",
1073 | "@types/babel__template": "*",
1074 | "@types/babel__traverse": "*"
1075 | }
1076 | },
1077 | "node_modules/@types/babel__generator": {
1078 | "version": "7.27.0",
1079 | "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz",
1080 | "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==",
1081 | "dev": true,
1082 | "license": "MIT",
1083 | "dependencies": {
1084 | "@babel/types": "^7.0.0"
1085 | }
1086 | },
1087 | "node_modules/@types/babel__template": {
1088 | "version": "7.4.4",
1089 | "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz",
1090 | "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
1091 | "dev": true,
1092 | "license": "MIT",
1093 | "dependencies": {
1094 | "@babel/parser": "^7.1.0",
1095 | "@babel/types": "^7.0.0"
1096 | }
1097 | },
1098 | "node_modules/@types/babel__traverse": {
1099 | "version": "7.28.0",
1100 | "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz",
1101 | "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==",
1102 | "dev": true,
1103 | "license": "MIT",
1104 | "dependencies": {
1105 | "@babel/types": "^7.28.2"
1106 | }
1107 | },
1108 | "node_modules/@types/estree": {
1109 | "version": "1.0.8",
1110 | "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
1111 | "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
1112 | "dev": true,
1113 | "license": "MIT"
1114 | },
1115 | "node_modules/@types/prop-types": {
1116 | "version": "15.7.15",
1117 | "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz",
1118 | "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==",
1119 | "dev": true,
1120 | "license": "MIT"
1121 | },
1122 | "node_modules/@types/react": {
1123 | "version": "18.3.27",
1124 | "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.27.tgz",
1125 | "integrity": "sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==",
1126 | "dev": true,
1127 | "license": "MIT",
1128 | "peer": true,
1129 | "dependencies": {
1130 | "@types/prop-types": "*",
1131 | "csstype": "^3.2.2"
1132 | }
1133 | },
1134 | "node_modules/@types/react-dom": {
1135 | "version": "18.3.7",
1136 | "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz",
1137 | "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==",
1138 | "dev": true,
1139 | "license": "MIT",
1140 | "peerDependencies": {
1141 | "@types/react": "^18.0.0"
1142 | }
1143 | },
1144 | "node_modules/@vitejs/plugin-react": {
1145 | "version": "4.7.0",
1146 | "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz",
1147 | "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==",
1148 | "dev": true,
1149 | "license": "MIT",
1150 | "dependencies": {
1151 | "@babel/core": "^7.28.0",
1152 | "@babel/plugin-transform-react-jsx-self": "^7.27.1",
1153 | "@babel/plugin-transform-react-jsx-source": "^7.27.1",
1154 | "@rolldown/pluginutils": "1.0.0-beta.27",
1155 | "@types/babel__core": "^7.20.5",
1156 | "react-refresh": "^0.17.0"
1157 | },
1158 | "engines": {
1159 | "node": "^14.18.0 || >=16.0.0"
1160 | },
1161 | "peerDependencies": {
1162 | "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
1163 | }
1164 | },
1165 | "node_modules/baseline-browser-mapping": {
1166 | "version": "2.8.29",
1167 | "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.29.tgz",
1168 | "integrity": "sha512-sXdt2elaVnhpDNRDz+1BDx1JQoJRuNk7oVlAlbGiFkLikHCAQiccexF/9e91zVi6RCgqspl04aP+6Cnl9zRLrA==",
1169 | "dev": true,
1170 | "license": "Apache-2.0",
1171 | "bin": {
1172 | "baseline-browser-mapping": "dist/cli.js"
1173 | }
1174 | },
1175 | "node_modules/browserslist": {
1176 | "version": "4.28.0",
1177 | "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.0.tgz",
1178 | "integrity": "sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ==",
1179 | "dev": true,
1180 | "funding": [
1181 | {
1182 | "type": "opencollective",
1183 | "url": "https://opencollective.com/browserslist"
1184 | },
1185 | {
1186 | "type": "tidelift",
1187 | "url": "https://tidelift.com/funding/github/npm/browserslist"
1188 | },
1189 | {
1190 | "type": "github",
1191 | "url": "https://github.com/sponsors/ai"
1192 | }
1193 | ],
1194 | "license": "MIT",
1195 | "peer": true,
1196 | "dependencies": {
1197 | "baseline-browser-mapping": "^2.8.25",
1198 | "caniuse-lite": "^1.0.30001754",
1199 | "electron-to-chromium": "^1.5.249",
1200 | "node-releases": "^2.0.27",
1201 | "update-browserslist-db": "^1.1.4"
1202 | },
1203 | "bin": {
1204 | "browserslist": "cli.js"
1205 | },
1206 | "engines": {
1207 | "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
1208 | }
1209 | },
1210 | "node_modules/caniuse-lite": {
1211 | "version": "1.0.30001755",
1212 | "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001755.tgz",
1213 | "integrity": "sha512-44V+Jm6ctPj7R52Na4TLi3Zri4dWUljJd+RDm+j8LtNCc/ihLCT+X1TzoOAkRETEWqjuLnh9581Tl80FvK7jVA==",
1214 | "dev": true,
1215 | "funding": [
1216 | {
1217 | "type": "opencollective",
1218 | "url": "https://opencollective.com/browserslist"
1219 | },
1220 | {
1221 | "type": "tidelift",
1222 | "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
1223 | },
1224 | {
1225 | "type": "github",
1226 | "url": "https://github.com/sponsors/ai"
1227 | }
1228 | ],
1229 | "license": "CC-BY-4.0"
1230 | },
1231 | "node_modules/convert-source-map": {
1232 | "version": "2.0.0",
1233 | "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
1234 | "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
1235 | "dev": true,
1236 | "license": "MIT"
1237 | },
1238 | "node_modules/csstype": {
1239 | "version": "3.2.3",
1240 | "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
1241 | "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
1242 | "dev": true,
1243 | "license": "MIT"
1244 | },
1245 | "node_modules/debug": {
1246 | "version": "4.4.3",
1247 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
1248 | "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
1249 | "dev": true,
1250 | "license": "MIT",
1251 | "dependencies": {
1252 | "ms": "^2.1.3"
1253 | },
1254 | "engines": {
1255 | "node": ">=6.0"
1256 | },
1257 | "peerDependenciesMeta": {
1258 | "supports-color": {
1259 | "optional": true
1260 | }
1261 | }
1262 | },
1263 | "node_modules/electron-to-chromium": {
1264 | "version": "1.5.255",
1265 | "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.255.tgz",
1266 | "integrity": "sha512-Z9oIp4HrFF/cZkDPMpz2XSuVpc1THDpT4dlmATFlJUIBVCy9Vap5/rIXsASP1CscBacBqhabwh8vLctqBwEerQ==",
1267 | "dev": true,
1268 | "license": "ISC"
1269 | },
1270 | "node_modules/esbuild": {
1271 | "version": "0.21.5",
1272 | "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
1273 | "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
1274 | "dev": true,
1275 | "hasInstallScript": true,
1276 | "license": "MIT",
1277 | "bin": {
1278 | "esbuild": "bin/esbuild"
1279 | },
1280 | "engines": {
1281 | "node": ">=12"
1282 | },
1283 | "optionalDependencies": {
1284 | "@esbuild/aix-ppc64": "0.21.5",
1285 | "@esbuild/android-arm": "0.21.5",
1286 | "@esbuild/android-arm64": "0.21.5",
1287 | "@esbuild/android-x64": "0.21.5",
1288 | "@esbuild/darwin-arm64": "0.21.5",
1289 | "@esbuild/darwin-x64": "0.21.5",
1290 | "@esbuild/freebsd-arm64": "0.21.5",
1291 | "@esbuild/freebsd-x64": "0.21.5",
1292 | "@esbuild/linux-arm": "0.21.5",
1293 | "@esbuild/linux-arm64": "0.21.5",
1294 | "@esbuild/linux-ia32": "0.21.5",
1295 | "@esbuild/linux-loong64": "0.21.5",
1296 | "@esbuild/linux-mips64el": "0.21.5",
1297 | "@esbuild/linux-ppc64": "0.21.5",
1298 | "@esbuild/linux-riscv64": "0.21.5",
1299 | "@esbuild/linux-s390x": "0.21.5",
1300 | "@esbuild/linux-x64": "0.21.5",
1301 | "@esbuild/netbsd-x64": "0.21.5",
1302 | "@esbuild/openbsd-x64": "0.21.5",
1303 | "@esbuild/sunos-x64": "0.21.5",
1304 | "@esbuild/win32-arm64": "0.21.5",
1305 | "@esbuild/win32-ia32": "0.21.5",
1306 | "@esbuild/win32-x64": "0.21.5"
1307 | }
1308 | },
1309 | "node_modules/escalade": {
1310 | "version": "3.2.0",
1311 | "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
1312 | "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
1313 | "dev": true,
1314 | "license": "MIT",
1315 | "engines": {
1316 | "node": ">=6"
1317 | }
1318 | },
1319 | "node_modules/fsevents": {
1320 | "version": "2.3.3",
1321 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
1322 | "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
1323 | "dev": true,
1324 | "hasInstallScript": true,
1325 | "license": "MIT",
1326 | "optional": true,
1327 | "os": [
1328 | "darwin"
1329 | ],
1330 | "engines": {
1331 | "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
1332 | }
1333 | },
1334 | "node_modules/gensync": {
1335 | "version": "1.0.0-beta.2",
1336 | "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
1337 | "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
1338 | "dev": true,
1339 | "license": "MIT",
1340 | "engines": {
1341 | "node": ">=6.9.0"
1342 | }
1343 | },
1344 | "node_modules/js-tokens": {
1345 | "version": "4.0.0",
1346 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
1347 | "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
1348 | "license": "MIT"
1349 | },
1350 | "node_modules/jsesc": {
1351 | "version": "3.1.0",
1352 | "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
1353 | "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
1354 | "dev": true,
1355 | "license": "MIT",
1356 | "bin": {
1357 | "jsesc": "bin/jsesc"
1358 | },
1359 | "engines": {
1360 | "node": ">=6"
1361 | }
1362 | },
1363 | "node_modules/json5": {
1364 | "version": "2.2.3",
1365 | "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
1366 | "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
1367 | "dev": true,
1368 | "license": "MIT",
1369 | "bin": {
1370 | "json5": "lib/cli.js"
1371 | },
1372 | "engines": {
1373 | "node": ">=6"
1374 | }
1375 | },
1376 | "node_modules/loose-envify": {
1377 | "version": "1.4.0",
1378 | "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
1379 | "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
1380 | "license": "MIT",
1381 | "dependencies": {
1382 | "js-tokens": "^3.0.0 || ^4.0.0"
1383 | },
1384 | "bin": {
1385 | "loose-envify": "cli.js"
1386 | }
1387 | },
1388 | "node_modules/lru-cache": {
1389 | "version": "5.1.1",
1390 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
1391 | "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
1392 | "dev": true,
1393 | "license": "ISC",
1394 | "dependencies": {
1395 | "yallist": "^3.0.2"
1396 | }
1397 | },
1398 | "node_modules/lucide-react": {
1399 | "version": "0.294.0",
1400 | "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.294.0.tgz",
1401 | "integrity": "sha512-V7o0/VECSGbLHn3/1O67FUgBwWB+hmzshrgDVRJQhMh8uj5D3HBuIvhuAmQTtlupILSplwIZg5FTc4tTKMA2SA==",
1402 | "license": "ISC",
1403 | "peerDependencies": {
1404 | "react": "^16.5.1 || ^17.0.0 || ^18.0.0"
1405 | }
1406 | },
1407 | "node_modules/ms": {
1408 | "version": "2.1.3",
1409 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
1410 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
1411 | "dev": true,
1412 | "license": "MIT"
1413 | },
1414 | "node_modules/nanoid": {
1415 | "version": "3.3.11",
1416 | "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
1417 | "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
1418 | "dev": true,
1419 | "funding": [
1420 | {
1421 | "type": "github",
1422 | "url": "https://github.com/sponsors/ai"
1423 | }
1424 | ],
1425 | "license": "MIT",
1426 | "bin": {
1427 | "nanoid": "bin/nanoid.cjs"
1428 | },
1429 | "engines": {
1430 | "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
1431 | }
1432 | },
1433 | "node_modules/node-releases": {
1434 | "version": "2.0.27",
1435 | "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz",
1436 | "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==",
1437 | "dev": true,
1438 | "license": "MIT"
1439 | },
1440 | "node_modules/normalize-wheel": {
1441 | "version": "1.0.1",
1442 | "resolved": "https://registry.npmjs.org/normalize-wheel/-/normalize-wheel-1.0.1.tgz",
1443 | "integrity": "sha512-1OnlAPZ3zgrk8B91HyRj+eVv+kS5u+Z0SCsak6Xil/kmgEia50ga7zfkumayonZrImffAxPU/5WcyGhzetHNPA==",
1444 | "license": "BSD-3-Clause"
1445 | },
1446 | "node_modules/picocolors": {
1447 | "version": "1.1.1",
1448 | "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
1449 | "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
1450 | "dev": true,
1451 | "license": "ISC"
1452 | },
1453 | "node_modules/postcss": {
1454 | "version": "8.5.6",
1455 | "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
1456 | "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
1457 | "dev": true,
1458 | "funding": [
1459 | {
1460 | "type": "opencollective",
1461 | "url": "https://opencollective.com/postcss/"
1462 | },
1463 | {
1464 | "type": "tidelift",
1465 | "url": "https://tidelift.com/funding/github/npm/postcss"
1466 | },
1467 | {
1468 | "type": "github",
1469 | "url": "https://github.com/sponsors/ai"
1470 | }
1471 | ],
1472 | "license": "MIT",
1473 | "dependencies": {
1474 | "nanoid": "^3.3.11",
1475 | "picocolors": "^1.1.1",
1476 | "source-map-js": "^1.2.1"
1477 | },
1478 | "engines": {
1479 | "node": "^10 || ^12 || >=14"
1480 | }
1481 | },
1482 | "node_modules/react": {
1483 | "version": "18.3.1",
1484 | "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
1485 | "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
1486 | "license": "MIT",
1487 | "peer": true,
1488 | "dependencies": {
1489 | "loose-envify": "^1.1.0"
1490 | },
1491 | "engines": {
1492 | "node": ">=0.10.0"
1493 | }
1494 | },
1495 | "node_modules/react-dom": {
1496 | "version": "18.3.1",
1497 | "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
1498 | "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
1499 | "license": "MIT",
1500 | "peer": true,
1501 | "dependencies": {
1502 | "loose-envify": "^1.1.0",
1503 | "scheduler": "^0.23.2"
1504 | },
1505 | "peerDependencies": {
1506 | "react": "^18.3.1"
1507 | }
1508 | },
1509 | "node_modules/react-easy-crop": {
1510 | "version": "5.5.3",
1511 | "resolved": "https://registry.npmjs.org/react-easy-crop/-/react-easy-crop-5.5.3.tgz",
1512 | "integrity": "sha512-iKwFTnAsq+IVuyF6N0Q3zjRx9DG1NMySkwWxVfM/xAOeHYH1vhvM+V2kFiq5HOIQGWouITjfltCx54mbDpMpmA==",
1513 | "license": "MIT",
1514 | "dependencies": {
1515 | "normalize-wheel": "^1.0.1",
1516 | "tslib": "^2.0.1"
1517 | },
1518 | "peerDependencies": {
1519 | "react": ">=16.4.0",
1520 | "react-dom": ">=16.4.0"
1521 | }
1522 | },
1523 | "node_modules/react-refresh": {
1524 | "version": "0.17.0",
1525 | "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz",
1526 | "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==",
1527 | "dev": true,
1528 | "license": "MIT",
1529 | "engines": {
1530 | "node": ">=0.10.0"
1531 | }
1532 | },
1533 | "node_modules/rollup": {
1534 | "version": "4.53.2",
1535 | "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.53.2.tgz",
1536 | "integrity": "sha512-MHngMYwGJVi6Fmnk6ISmnk7JAHRNF0UkuucA0CUW3N3a4KnONPEZz+vUanQP/ZC/iY1Qkf3bwPWzyY84wEks1g==",
1537 | "dev": true,
1538 | "license": "MIT",
1539 | "dependencies": {
1540 | "@types/estree": "1.0.8"
1541 | },
1542 | "bin": {
1543 | "rollup": "dist/bin/rollup"
1544 | },
1545 | "engines": {
1546 | "node": ">=18.0.0",
1547 | "npm": ">=8.0.0"
1548 | },
1549 | "optionalDependencies": {
1550 | "@rollup/rollup-android-arm-eabi": "4.53.2",
1551 | "@rollup/rollup-android-arm64": "4.53.2",
1552 | "@rollup/rollup-darwin-arm64": "4.53.2",
1553 | "@rollup/rollup-darwin-x64": "4.53.2",
1554 | "@rollup/rollup-freebsd-arm64": "4.53.2",
1555 | "@rollup/rollup-freebsd-x64": "4.53.2",
1556 | "@rollup/rollup-linux-arm-gnueabihf": "4.53.2",
1557 | "@rollup/rollup-linux-arm-musleabihf": "4.53.2",
1558 | "@rollup/rollup-linux-arm64-gnu": "4.53.2",
1559 | "@rollup/rollup-linux-arm64-musl": "4.53.2",
1560 | "@rollup/rollup-linux-loong64-gnu": "4.53.2",
1561 | "@rollup/rollup-linux-ppc64-gnu": "4.53.2",
1562 | "@rollup/rollup-linux-riscv64-gnu": "4.53.2",
1563 | "@rollup/rollup-linux-riscv64-musl": "4.53.2",
1564 | "@rollup/rollup-linux-s390x-gnu": "4.53.2",
1565 | "@rollup/rollup-linux-x64-gnu": "4.53.2",
1566 | "@rollup/rollup-linux-x64-musl": "4.53.2",
1567 | "@rollup/rollup-openharmony-arm64": "4.53.2",
1568 | "@rollup/rollup-win32-arm64-msvc": "4.53.2",
1569 | "@rollup/rollup-win32-ia32-msvc": "4.53.2",
1570 | "@rollup/rollup-win32-x64-gnu": "4.53.2",
1571 | "@rollup/rollup-win32-x64-msvc": "4.53.2",
1572 | "fsevents": "~2.3.2"
1573 | }
1574 | },
1575 | "node_modules/scheduler": {
1576 | "version": "0.23.2",
1577 | "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
1578 | "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
1579 | "license": "MIT",
1580 | "dependencies": {
1581 | "loose-envify": "^1.1.0"
1582 | }
1583 | },
1584 | "node_modules/semver": {
1585 | "version": "6.3.1",
1586 | "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
1587 | "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
1588 | "dev": true,
1589 | "license": "ISC",
1590 | "bin": {
1591 | "semver": "bin/semver.js"
1592 | }
1593 | },
1594 | "node_modules/source-map-js": {
1595 | "version": "1.2.1",
1596 | "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
1597 | "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
1598 | "dev": true,
1599 | "license": "BSD-3-Clause",
1600 | "engines": {
1601 | "node": ">=0.10.0"
1602 | }
1603 | },
1604 | "node_modules/tslib": {
1605 | "version": "2.8.1",
1606 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
1607 | "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
1608 | "license": "0BSD"
1609 | },
1610 | "node_modules/typescript": {
1611 | "version": "5.9.3",
1612 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
1613 | "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
1614 | "dev": true,
1615 | "license": "Apache-2.0",
1616 | "bin": {
1617 | "tsc": "bin/tsc",
1618 | "tsserver": "bin/tsserver"
1619 | },
1620 | "engines": {
1621 | "node": ">=14.17"
1622 | }
1623 | },
1624 | "node_modules/update-browserslist-db": {
1625 | "version": "1.1.4",
1626 | "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.4.tgz",
1627 | "integrity": "sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==",
1628 | "dev": true,
1629 | "funding": [
1630 | {
1631 | "type": "opencollective",
1632 | "url": "https://opencollective.com/browserslist"
1633 | },
1634 | {
1635 | "type": "tidelift",
1636 | "url": "https://tidelift.com/funding/github/npm/browserslist"
1637 | },
1638 | {
1639 | "type": "github",
1640 | "url": "https://github.com/sponsors/ai"
1641 | }
1642 | ],
1643 | "license": "MIT",
1644 | "dependencies": {
1645 | "escalade": "^3.2.0",
1646 | "picocolors": "^1.1.1"
1647 | },
1648 | "bin": {
1649 | "update-browserslist-db": "cli.js"
1650 | },
1651 | "peerDependencies": {
1652 | "browserslist": ">= 4.21.0"
1653 | }
1654 | },
1655 | "node_modules/vite": {
1656 | "version": "5.4.21",
1657 | "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz",
1658 | "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==",
1659 | "dev": true,
1660 | "license": "MIT",
1661 | "peer": true,
1662 | "dependencies": {
1663 | "esbuild": "^0.21.3",
1664 | "postcss": "^8.4.43",
1665 | "rollup": "^4.20.0"
1666 | },
1667 | "bin": {
1668 | "vite": "bin/vite.js"
1669 | },
1670 | "engines": {
1671 | "node": "^18.0.0 || >=20.0.0"
1672 | },
1673 | "funding": {
1674 | "url": "https://github.com/vitejs/vite?sponsor=1"
1675 | },
1676 | "optionalDependencies": {
1677 | "fsevents": "~2.3.3"
1678 | },
1679 | "peerDependencies": {
1680 | "@types/node": "^18.0.0 || >=20.0.0",
1681 | "less": "*",
1682 | "lightningcss": "^1.21.0",
1683 | "sass": "*",
1684 | "sass-embedded": "*",
1685 | "stylus": "*",
1686 | "sugarss": "*",
1687 | "terser": "^5.4.0"
1688 | },
1689 | "peerDependenciesMeta": {
1690 | "@types/node": {
1691 | "optional": true
1692 | },
1693 | "less": {
1694 | "optional": true
1695 | },
1696 | "lightningcss": {
1697 | "optional": true
1698 | },
1699 | "sass": {
1700 | "optional": true
1701 | },
1702 | "sass-embedded": {
1703 | "optional": true
1704 | },
1705 | "stylus": {
1706 | "optional": true
1707 | },
1708 | "sugarss": {
1709 | "optional": true
1710 | },
1711 | "terser": {
1712 | "optional": true
1713 | }
1714 | }
1715 | },
1716 | "node_modules/yallist": {
1717 | "version": "3.1.1",
1718 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
1719 | "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
1720 | "dev": true,
1721 | "license": "ISC"
1722 | }
1723 | }
1724 | }
1725 |
--------------------------------------------------------------------------------