├── 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 | ![NanoBanana Studio Screenshot](./screenshot.png) 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 | --------------------------------------------------------------------------------