diff --git a/.gitignore b/.gitignore
index 3d70248ba2..b14ea8cc83 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,4 +12,4 @@ npm-debug.log*
yarn-debug.log*
yarn-error.log*
-package-lock.json
\ No newline at end of file
+local
\ No newline at end of file
diff --git a/API.md b/API.md
new file mode 100644
index 0000000000..dc078dce20
--- /dev/null
+++ b/API.md
@@ -0,0 +1,215 @@
+# 📡 API Documentation - Morbid Gene Backend
+
+A simple overview of the backend endpoints for the Morbid Gene 2.0 application.
+
+**Base URL:** `http://localhost:8080` (development)
+
+---
+
+## 🔐 Authentication Endpoints
+
+### `POST /auth/register`
+Register new user.
+```json
+// Request
+{
+ "name": "John Doe",
+ "email": "john@example.com",
+ "password": "password123"
+}
+
+// Response
+{
+ "message": "User registered successfully",
+ "user": { "id": "...", "name": "John Doe", "email": "john@example.com" }
+}
+```
+
+### `POST /auth/login`
+Login user.
+```json
+// Request
+{
+ "email": "john@example.com",
+ "password": "password123"
+}
+
+// Response - Sets httpOnly cookie
+{
+ "message": "Login successful",
+ "user": { "id": "...", "name": "John Doe", "email": "john@example.com" }
+}
+```
+
+### `POST /auth/logout`
+Logout user (requires auth cookie).
+
+### `GET /auth/me`
+Get current logged in user (requires auth cookie).
+
+---
+
+## 🛍️ Merchandise Endpoints (Spreadshirt API)
+
+### `GET /api/merch/`
+Get all products from Spreadshirt.
+```json
+// Response
+{
+ "products": [
+ {
+ "id": "prod_123",
+ "name": "Morbid Gene T-Shirt",
+ "price": { "amount": 29.99, "currencyId": "EUR" },
+ "previewImage": { "url": "https://..." }
+ }
+ ]
+}
+```
+
+### `GET /api/merch/:productId`
+Get specific product details.
+
+### `GET /api/merch/productType/:productTypeId`
+Get sizes and colors for product type.
+
+### `GET /api/merch/images/:sellableId/:appearanceId`
+Get product images for specific color.
+
+---
+
+## 🛒 Shopping Cart (Basket) Endpoints
+
+### `POST /api/merch/baskets`
+Create new shopping basket at Spreadshirt.
+```json
+// Request
+{
+ "currencyId": "EUR",
+ "countryId": "DE"
+}
+
+// Response
+{
+ "basketId": "12345678-1234-1234-1234-123456789abc",
+ "basketUrl": "https://morbidgene.myspreadshop.net/basket/..."
+}
+```
+
+### `GET /api/merch/baskets/:basketId`
+Get basket contents.
+
+### `PUT /api/merch/baskets/:basketId`
+Add/remove products from basket.
+```json
+// Request (add item)
+{
+ "action": "add",
+ "basketItems": [
+ {
+ "sellableId": "123456789",
+ "sizeId": "2",
+ "appearanceId": "1",
+ "quantity": 1
+ }
+ ]
+}
+```
+
+### `DELETE /api/merch/baskets/:basketId`
+Clear basket.
+
+### `GET /api/merch/baskets/:basketId/checkout`
+Get checkout URL for purchase.
+
+---
+
+## 👤 User Management (Requires auth)
+
+### `PUT /user/favorites`
+Add/remove favorite products.
+```json
+// Request
+{
+ "action": "add", // or "remove"
+ "productId": "prod_123456"
+}
+```
+
+### `GET /user/favorites`
+Get user's favorite products.
+
+### `PUT /user/profile`
+Update user profile.
+```json
+// Request
+{
+ "name": "New Name",
+ "currentPassword": "old123", // for password change
+ "newPassword": "new456" // optional
+}
+```
+
+---
+
+## 📧 Contact Forms
+
+### `POST /contact`
+Send contact form.
+```json
+// Request
+{
+ "name": "Jane Doe",
+ "email": "jane@example.com",
+ "subject": "Question",
+ "message": "Hello..."
+}
+```
+
+### `POST /contact/booking`
+Send booking request for gigs.
+```json
+// Request
+{
+ "name": "Event Organizer",
+ "email": "organizer@venue.com",
+ "eventType": "Concert",
+ "venue": "Stockholm Music Hall",
+ "eventDate": "2024-06-15",
+ "message": "We would like to book..."
+}
+```
+
+---
+
+## ⚙️ Admin Endpoints
+
+### `POST /admin/cleanup`
+Clean up old sessions and temporary data.
+
+---
+
+## 🔒 Authentication
+
+**Authentication:** JWT tokens in httpOnly cookies
+- Cookie name: `authToken`
+- Set automatically on login
+- Required for `/user/*` endpoints and logout
+
+**Error responses:**
+```json
+{
+ "message": "Error message",
+ "status": 400/401/404/500
+}
+```
+
+---
+
+## 🔌 External Integrations
+
+- **Spreadshirt API** - Handles all products and orders
+- **Nodemailer** - Sends emails from contact forms
+- **MongoDB** - Users, sessions, favorites
+
+**This backend serves as a proxy between the React frontend and Spreadshirt e-commerce system, plus handles user authentication and contact forms.**
\ No newline at end of file
diff --git a/README.md b/README.md
index 31466b54c2..1024fcc0ef 100644
--- a/README.md
+++ b/README.md
@@ -1,13 +1,275 @@
-# Final Project
+# 🔥 Morbid Gene 2.0 - Official Band E-Commerce Platform
-Replace this readme with your own information about your project.
+A full-stack modern web application built for the Swedish metal band Morbid Gene, featuring an integrated e-commerce solution with Spreadshirt API integration, user authentication, and a comprehensive content management system.
-Start by briefly describing the assignment in a sentence or two. Keep it short and to the point.
+## 🎯 Project Overview
-## The problem
+This application serves as both the official band website and merchandise store for Morbid Gene. Built with React 19 and Node.js, it provides a seamless user experience across multiple device types with a focus on mobile-first design and accessibility.
-Describe how you approached to problem, and what tools and techniques you used to solve it. How did you plan? What technologies did you use? If you had more time, what would be next?
+**Live Demo:** [www.morbidgeneofficial.com]
-## View it live
+## ✨ Key Features
-Every project should be deployed somewhere. Be sure to include the link to the deployed project so that the viewer can click around and see what it's all about.
\ No newline at end of file
+### 🛍️ E-Commerce
+- **Merchandise Store** with real-time Spreadshirt API integration
+- **Shopping Cart** with persistent state management
+- **Product Catalog** with filtering and search capabilities
+- **Secure Checkout** with external payment processing
+- **User Favorites** with authenticated wishlist functionality
+
+### 👤 User Management
+- **JWT Authentication** with secure httpOnly cookies
+- **User Registration & Login** with password encryption
+- **Account Settings** with profile management
+- **Session Management** with automatic cleanup
+
+### 🎵 Band Content
+- **Media Gallery** with responsive image displays
+- **Gigs Schedule** with venue and ticket information
+- **Contact Forms** for general inquiries and booking requests
+
+### 📱 Technical Excellence
+- **Fully Responsive** design (320px - 1600px+)
+- **Accessibility Compliant** with WCAG standards
+- **Progressive Enhancement** with loading states
+- **Real-time Updates** via Context API state management
+
+## 🏗️ Technical Architecture
+
+### Frontend Stack
+- **React 19** - Latest React with modern hooks
+- **Vite** - Fast build tool and development server
+- **React Router DOM 7** - Client-side routing with nested routes
+- **Styled Components** - CSS-in-JS styling solution
+- **Context API** - Global state management
+- **React Icons** - Comprehensive icon library
+
+### Backend Stack
+- **Node.js** with **Express 4** - RESTful API server
+- **MongoDB** with **Mongoose** - Document database
+- **JWT** - Secure token-based authentication
+- **bcryptjs** - Password hashing and validation
+- **Nodemailer** - Email service integration
+- **Axios** - HTTP client for external APIs
+
+### External Integrations
+- **Spreadshirt API** - Merchandise and fulfillment
+- **Email Service** - Contact form and notifications
+- **Cookie Management** - Secure session handling
+
+## 📦 Installation & Setup
+
+### Prerequisites
+- Node.js 18+
+- MongoDB (local or Atlas)
+- Git
+
+### Environment Variables
+
+Create `.env` files in both frontend and backend directories:
+
+**Backend `.env`:**
+```env
+# Database
+MONGO_URL=mongodb://localhost:
+
+# Authentication
+JWT_SECRET=your-super-secure-jwt-secret-here
+
+# Spreadshirt API
+SPREADSHOP_ID=your-spreadshop-id
+SPREADSHOP_API_KEY=your-api-key
+SPREAD_USER_AGENT=YourApp/1.0
+
+# Email Service (optional)
+EMAIL_HOST=smtp.gmail.com
+EMAIL_PORT=587
+EMAIL_USER=your-email@example.com
+EMAIL_PASSWORD=your-email-password
+
+# Server
+PORT=8080
+```
+
+**Frontend `.env` (if needed):**
+```env
+VITE_API_URL=http://localhost:
+```
+
+### Installation Steps
+
+1. **Clone the repository**
+```bash
+git clone https://github.com/yourusername/morbid-gene-2.0.git
+cd morbid-gene-2.0
+```
+
+2. **Install backend dependencies**
+```bash
+cd backend
+npm install
+```
+
+3. **Install frontend dependencies**
+```bash
+cd ../frontend
+npm install
+```
+
+4. **Start development servers**
+
+In separate terminals:
+
+```bash
+# Backend (from backend directory)
+npm run dev
+
+# Frontend (from frontend directory)
+npm run dev
+```
+
+The application will be available at:
+- Frontend: `http://localhost:5173`
+- Backend API: `http://localhost:8080`
+
+### Production Build
+
+```bash
+# Build frontend
+cd frontend
+npm run build
+
+# Start production backend
+cd ../backend
+npm start
+```
+
+## 🗂️ Project Structure
+
+```
+morbid-gene-2.0/
+├── backend/ # Node.js API server
+│ ├── controllers/ # Business logic
+│ ├── middleware/ # Auth & request processing
+│ ├── models/ # MongoDB schemas
+│ ├── routes/ # API endpoints
+│ ├── services/ # External API integrations
+│ ├── utils/ # Helper functions
+│ └── server.js # Express server setup
+│
+├── frontend/ # React application
+│ ├── src/
+│ │ ├── components/ # Reusable UI components
+│ │ ├── contexts/ # React Context providers
+│ │ ├── hooks/ # Custom React hooks
+│ │ ├── pages/ # Route components
+│ │ ├── styles/ # Global styles & themes
+│ │ ├── utils/ # Helper functions
+│ │ └── App.jsx # Main application component
+│ │
+│ ├── public/ # Static assets
+│ └── package.json # Frontend dependencies
+│
+└── README.md # This file
+```
+
+## 🎮 Usage Guide
+
+### For Users
+
+1. **Browse Merchandise**: Visit the shop to see available products
+2. **Create Account**: Register to save favorites
+3. **Add to Cart**: Select size, color, and quantity for products
+4. **Secure Checkout**: Complete purchase through Spreadshirt
+5. **Manage Account**: Update profilesettings
+
+### For Developers
+
+1. **API Integration**: Use the RESTful endpoints (see API.md)
+2. **Theme Customization**: Modify `styles/theme.js` for visual changes
+3. **Component Development**: Follow the established patterns in `/components`
+4. **State Management**: Use Context providers for global state
+
+## 🚀 Deployment
+
+### Backend Deployment
+The backend can be deployed to any Node.js hosting service:
+- Railway
+- Heroku
+- DigitalOcean App Platform
+- AWS EC2
+
+### Frontend Deployment
+The frontend builds to static files compatible with:
+- Netlify
+- Vercel
+- GitHub Pages
+- Any static hosting service
+
+### Database
+MongoDB can be hosted on:
+- MongoDB Atlas (recommended)
+- Local MongoDB instance
+- DigitalOcean Managed Databases
+
+## 🔧 Development
+
+### Available Scripts
+
+**Frontend:**
+```bash
+npm run dev # Start development server
+npm run build # Build for production
+npm run preview # Preview production build
+npm run lint # Run ESLint
+```
+
+**Backend:**
+```bash
+npm run dev # Start with nodemon (development)
+npm start # Start production server
+```
+
+### Development Guidelines
+
+1. **Code Style**: Follow existing patterns and use ESLint
+2. **Components**: Keep components focused and reusable
+3. **State Management**: Use Context API for global state
+4. **Styling**: Use styled-components with the theme system
+5. **API Calls**: Use the established API utilities
+
+### Custom Hooks
+
+The application includes several custom React hooks:
+
+- `useProduct(productId)` - Fetch single product data
+- `useMerch()` - Fetch product catalog with filtering
+- `useProductImages(productId, colorId)` - Fetch product images
+
+## 🤝 Contributing
+
+1. Fork the repository
+2. Create a feature branch (`git checkout -b feature/amazing-feature`)
+3. Commit your changes (`git commit -m 'Add amazing feature'`)
+4. Push to the branch (`git push origin feature/amazing-feature`)
+5. Open a Pull Request
+
+
+## 🎸 About Morbid Gene
+
+Morbid Gene is a Swedish metal band formed in [2021]. Known for their melodic, crushing riffs and atmospheric soundscapes, they have performed across Sweden and are working on their way to the top.
+
+## 📧 Support
+
+For technical issues or questions:
+- Contact: [cathrineohlsson@live.se]
+
+For band-related inquiries:
+- Use the contact form on the website
+- Email: [morbidgenemusic@gmail.com]
+
+---
+
+**Built with ❤️ and 🤘 by [Cathi]**
+
+*This project represents the culmination of modern full-stack web development practices, combining React 19, Node.js, and MongoDB to create a production-ready e-commerce platform.*
\ No newline at end of file
diff --git a/backend/controllers/merchController.js b/backend/controllers/merchController.js
new file mode 100644
index 0000000000..64812af24c
--- /dev/null
+++ b/backend/controllers/merchController.js
@@ -0,0 +1,176 @@
+import * as spreadshirtService from "../services/spreadshirtService.js";
+import { handleControllerError } from "../utils/errorHandler.js";
+
+// Get all products
+export const getAllProducts = async (req, res) => {
+ try {
+ const limit = req.query.limit || 24;
+ const offset = 0;
+ const searchQuery = req.query.q || '';
+
+ const products = await spreadshirtService.getAllProducts(limit, offset);
+
+ // Filter products based on search query
+ if (searchQuery) {
+ const searchLower = searchQuery.toLowerCase();
+ const filteredSellables = products.sellables.filter(item => {
+ const name = (item.name || '').toLowerCase();
+ const productTypeName = (item.productTypeName || '').toLowerCase();
+ const tags = (item.tags || []).join(' ').toLowerCase();
+
+ return name.includes(searchLower) ||
+ productTypeName.includes(searchLower) ||
+ tags.includes(searchLower);
+ });
+
+ res.json({
+ ...products,
+ sellables: filteredSellables,
+ count: filteredSellables.length
+ });
+ } else {
+ res.json(products);
+ }
+ } catch (error) {
+ handleControllerError(error, res, "Error in getAllProducts");
+ }
+};
+
+export const getProductById = async (req, res) => {
+ try {
+ const { productId } = req.params;
+ const product = await spreadshirtService.getProductById(productId);
+ res.json(product);
+ } catch (error) {
+ if (error.message === "Product not found") {
+ res.status(404).json({ error: "Product not found" });
+ } else {
+ handleControllerError(error, res, "Error in getProductById");
+ }
+ }
+};
+
+export const getProductTypeInfo = async (req, res) => {
+ try {
+ const { productTypeId } = req.params;
+ const productType = await spreadshirtService.getProductTypeInfo(
+ productTypeId
+ );
+ res.json(productType);
+ } catch (error) {
+ handleControllerError(error, res, "Error in getProductTypeInfo");
+ }
+};
+
+export const getSellableImages = async (req, res) => {
+ try {
+ const { sellableId, appearanceId, ideaId } = req.params;
+
+
+ const sellableData = await spreadshirtService.getSellableImages(
+ sellableId,
+ appearanceId,
+ ideaId
+ );
+ res.json(sellableData);
+ } catch (error) {
+ handleControllerError(error, res, "Error in getSellableImages");
+ }
+};
+
+export const createBasket = async (req, res) => {
+ try {
+ const { basketItems } = req.body;
+
+ if (!basketItems || !Array.isArray(basketItems)) {
+ return res.status(400).json({ error: "basketItems array is required" });
+ }
+
+ const basket = await spreadshirtService.createBasket(basketItems);
+ res.status(201).json(basket);
+ } catch (error) {
+ handleControllerError(error, res, "Error in createBasket");
+ }
+};
+
+export const getBasket = async (req, res) => {
+ try {
+ const { basketId } = req.params;
+ const basket = await spreadshirtService.getBasket(basketId);
+
+ if (!basket) {
+ return res.status(404).json({ error: "Basket not found" });
+ }
+
+ res.json(basket);
+ } catch (error) {
+ handleControllerError(error, res, "Error in getBasket");
+ }
+};
+
+export const updateBasket = async (req, res) => {
+ try {
+ const { basketId } = req.params;
+ const { basketItems } = req.body;
+
+ if (!basketItems || !Array.isArray(basketItems)) {
+ return res.status(400).json({ error: "basketItems array is required" });
+ }
+
+ const basket = await spreadshirtService.updateBasket(basketId, basketItems);
+ res.json(basket);
+ } catch (error) {
+ handleControllerError(error, res, "Error in updateBasket");
+ }
+};
+
+export const deleteBasket = async (req, res) => {
+ try {
+ const { basketId } = req.params;
+ await spreadshirtService.deleteBasket(basketId);
+ res.status(204).send();
+ } catch (error) {
+ handleControllerError(error, res, "Error in deleteBasket");
+ }
+};
+
+export const convertToBasketItem = async (req, res) => {
+ try {
+ const { productId, sizeName, colorName, quantity } = req.body;
+
+ const product = await spreadshirtService.getProductById(productId);
+ const productType = await spreadshirtService.getProductTypeInfo(
+ product.productTypeId
+ );
+
+ const enrichedProduct = { ...product, productType };
+
+ const basketItem = spreadshirtService.convertToBasketItem(
+ enrichedProduct,
+ sizeName,
+ colorName,
+ quantity || 1
+ );
+
+ res.json(basketItem);
+ } catch (error) {
+ handleControllerError(error, res, "Error in convertToBasketItem");
+ }
+};
+
+export const getCheckoutUrl = async (req, res) => {
+ try {
+ const { basketId } = req.params;
+
+
+ const checkoutUrl = await spreadshirtService.getCheckoutUrl(basketId);
+
+ res.json({
+ checkoutUrl: checkoutUrl,
+ basketId: basketId,
+ });
+ } catch (error) {
+ handleControllerError(error, res, "Error getting checkout URL");
+ }
+};
+
diff --git a/backend/middleware/auth.js b/backend/middleware/auth.js
new file mode 100644
index 0000000000..f293a9949b
--- /dev/null
+++ b/backend/middleware/auth.js
@@ -0,0 +1,38 @@
+import { getUserFromSession } from "../utils/session.js";
+import { verifyToken } from "../utils/jwt.js";
+
+export default async function authMiddleware(req, res, next) {
+ try {
+ // First, try to get JWT token from Authorization header
+ const authHeader = req.headers.authorization;
+ if (authHeader && authHeader.startsWith('Bearer ')) {
+ const token = authHeader.substring(7);
+ const decoded = verifyToken(token);
+
+ if (decoded && decoded.userId) {
+ req.userId = decoded.userId;
+ return next();
+ }
+ }
+
+ // Fallback to cookie-based authentication
+ const sessionId = req.cookies?.sessionId;
+
+ if (!sessionId) {
+ return res.status(401).json({ message: "No authentication provided" });
+ }
+
+ // Get user from session
+ const user = await getUserFromSession(sessionId);
+
+ if (!user) {
+ return res.status(401).json({ message: "Invalid or expired session" });
+ }
+
+ // Attach user ID to request
+ req.userId = user._id;
+ next();
+ } catch (err) {
+ return res.status(401).json({ message: "Authentication failed" });
+ }
+}
diff --git a/backend/models/Session.js b/backend/models/Session.js
new file mode 100644
index 0000000000..8370b2ec88
--- /dev/null
+++ b/backend/models/Session.js
@@ -0,0 +1,28 @@
+import mongoose from "mongoose";
+
+const sessionSchema = new mongoose.Schema(
+ {
+ sessionId: {
+ type: String,
+ required: true,
+ unique: true,
+ index: true
+ },
+ userId: {
+ type: mongoose.Schema.Types.ObjectId,
+ ref: 'User',
+ required: true
+ },
+ expiresAt: {
+ type: Date,
+ required: true,
+ expires: 0 // MongoDB will auto-delete expired sessions
+ }
+ },
+ {
+ timestamps: true,
+ }
+);
+
+const Session = mongoose.model("Session", sessionSchema);
+export default Session;
\ No newline at end of file
diff --git a/backend/models/User.js b/backend/models/User.js
new file mode 100644
index 0000000000..4b8fb3d95a
--- /dev/null
+++ b/backend/models/User.js
@@ -0,0 +1,80 @@
+import mongoose from "mongoose";
+import bcrypt from "bcryptjs";
+
+const userSchema = new mongoose.Schema(
+ {
+ email: {
+ type: String,
+ required: true,
+ unique: true,
+ lowercase: true,
+ trim: true,
+ match: [/^\w+([.-]?\w+)*@\w+([.-]?\w+)*(\.\w{2,3})+$/, 'Please enter a valid email']
+ },
+ password: {
+ type: String,
+ required: true,
+ minlength: [6, 'Password must be at least 6 characters long']
+ },
+ name: {
+ type: String,
+ required: true,
+ trim: true,
+ maxlength: [50, 'Name cannot exceed 50 characters']
+ },
+ lastLogin: {
+ type: Date,
+ default: Date.now
+ },
+ favorites: [{
+ sellableId: {
+ type: String,
+ required: true
+ },
+ name: String,
+ productTypeName: String,
+ price: {
+ amount: Number,
+ currencyId: String
+ },
+ previewImage: {
+ url: String
+ },
+ addedAt: {
+ type: Date,
+ default: Date.now
+ }
+ }]
+ },
+ {
+ timestamps: true,
+ }
+);
+
+// Hash password before saving
+userSchema.pre('save', async function(next) {
+ if (!this.isModified('password')) return next();
+
+ try {
+ const salt = await bcrypt.genSalt(10);
+ this.password = await bcrypt.hash(this.password, salt);
+ next();
+ } catch (error) {
+ next(error);
+ }
+});
+
+// Compare password method
+userSchema.methods.comparePassword = async function(candidatePassword) {
+ return bcrypt.compare(candidatePassword, this.password);
+};
+
+// Remove password from JSON output
+userSchema.methods.toJSON = function() {
+ const user = this.toObject();
+ delete user.password;
+ return user;
+};
+
+const User = mongoose.model("User", userSchema);
+export default User;
diff --git a/backend/package-lock.json b/backend/package-lock.json
new file mode 100644
index 0000000000..cc570f02f4
--- /dev/null
+++ b/backend/package-lock.json
@@ -0,0 +1,5196 @@
+{
+ "name": "project-final-backend",
+ "version": "1.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "project-final-backend",
+ "version": "1.0.0",
+ "license": "ISC",
+ "dependencies": {
+ "@babel/core": "^7.17.9",
+ "@babel/node": "^7.16.8",
+ "@babel/preset-env": "^7.16.11",
+ "axios": "^1.11.0",
+ "bcryptjs": "^3.0.2",
+ "cookie-parser": "^1.4.7",
+ "cors": "^2.8.5",
+ "dotenv": "^17.2.1",
+ "express": "^4.21.2",
+ "jsonwebtoken": "^9.0.2",
+ "mongoose": "^8.17.0",
+ "node-cron": "^4.2.1",
+ "nodemailer": "^7.0.5"
+ },
+ "devDependencies": {
+ "nodemon": "^3.1.10"
+ }
+ },
+ "node_modules/@ampproject/remapping": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",
+ "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/code-frame": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
+ "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^7.27.1",
+ "js-tokens": "^4.0.0",
+ "picocolors": "^1.1.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/compat-data": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.0.tgz",
+ "integrity": "sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/core": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.0.tgz",
+ "integrity": "sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@ampproject/remapping": "^2.2.0",
+ "@babel/code-frame": "^7.27.1",
+ "@babel/generator": "^7.28.0",
+ "@babel/helper-compilation-targets": "^7.27.2",
+ "@babel/helper-module-transforms": "^7.27.3",
+ "@babel/helpers": "^7.27.6",
+ "@babel/parser": "^7.28.0",
+ "@babel/template": "^7.27.2",
+ "@babel/traverse": "^7.28.0",
+ "@babel/types": "^7.28.0",
+ "convert-source-map": "^2.0.0",
+ "debug": "^4.1.0",
+ "gensync": "^1.0.0-beta.2",
+ "json5": "^2.2.3",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/babel"
+ }
+ },
+ "node_modules/@babel/generator": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.0.tgz",
+ "integrity": "sha512-lJjzvrbEeWrhB4P3QBsH7tey117PjLZnDbLiQEKjQ/fNJTjuq4HSqgFA+UNSwZT8D7dxxbnuSBMsa1lrWzKlQg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.28.0",
+ "@babel/types": "^7.28.0",
+ "@jridgewell/gen-mapping": "^0.3.12",
+ "@jridgewell/trace-mapping": "^0.3.28",
+ "jsesc": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-annotate-as-pure": {
+ "version": "7.27.3",
+ "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz",
+ "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.27.3"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-compilation-targets": {
+ "version": "7.27.2",
+ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz",
+ "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/compat-data": "^7.27.2",
+ "@babel/helper-validator-option": "^7.27.1",
+ "browserslist": "^4.24.0",
+ "lru-cache": "^5.1.1",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-create-class-features-plugin": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.27.1.tgz",
+ "integrity": "sha512-QwGAmuvM17btKU5VqXfb+Giw4JcN0hjuufz3DYnpeVDvZLAObloM77bhMXiqry3Iio+Ai4phVRDwl6WU10+r5A==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-annotate-as-pure": "^7.27.1",
+ "@babel/helper-member-expression-to-functions": "^7.27.1",
+ "@babel/helper-optimise-call-expression": "^7.27.1",
+ "@babel/helper-replace-supers": "^7.27.1",
+ "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1",
+ "@babel/traverse": "^7.27.1",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/helper-create-regexp-features-plugin": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.27.1.tgz",
+ "integrity": "sha512-uVDC72XVf8UbrH5qQTc18Agb8emwjTiZrQE11Nv3CuBEZmVvTwwE9CBUEvHku06gQCAyYf8Nv6ja1IN+6LMbxQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-annotate-as-pure": "^7.27.1",
+ "regexpu-core": "^6.2.0",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/helper-define-polyfill-provider": {
+ "version": "0.6.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.5.tgz",
+ "integrity": "sha512-uJnGFcPsWQK8fvjgGP5LZUZZsYGIoPeRjSF5PGwrelYgq7Q15/Ft9NGFp1zglwgIv//W0uG4BevRuSJRyylZPg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-compilation-targets": "^7.27.2",
+ "@babel/helper-plugin-utils": "^7.27.1",
+ "debug": "^4.4.1",
+ "lodash.debounce": "^4.0.8",
+ "resolve": "^1.22.10"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0"
+ }
+ },
+ "node_modules/@babel/helper-globals": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz",
+ "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-member-expression-to-functions": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.27.1.tgz",
+ "integrity": "sha512-E5chM8eWjTp/aNoVpcbfM7mLxu9XGLWYise2eBKGQomAk/Mb4XoxyqXTZbuTohbsl8EKqdlMhnDI2CCLfcs9wA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/traverse": "^7.27.1",
+ "@babel/types": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-imports": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz",
+ "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/traverse": "^7.27.1",
+ "@babel/types": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-transforms": {
+ "version": "7.27.3",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.3.tgz",
+ "integrity": "sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-module-imports": "^7.27.1",
+ "@babel/helper-validator-identifier": "^7.27.1",
+ "@babel/traverse": "^7.27.3"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/helper-optimise-call-expression": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz",
+ "integrity": "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-plugin-utils": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz",
+ "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-remap-async-to-generator": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.27.1.tgz",
+ "integrity": "sha512-7fiA521aVw8lSPeI4ZOD3vRFkoqkJcS+z4hFo82bFSH/2tNd6eJ5qCVMS5OzDmZh/kaHQeBaeyxK6wljcPtveA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-annotate-as-pure": "^7.27.1",
+ "@babel/helper-wrap-function": "^7.27.1",
+ "@babel/traverse": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/helper-replace-supers": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.27.1.tgz",
+ "integrity": "sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-member-expression-to-functions": "^7.27.1",
+ "@babel/helper-optimise-call-expression": "^7.27.1",
+ "@babel/traverse": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/helper-skip-transparent-expression-wrappers": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz",
+ "integrity": "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/traverse": "^7.27.1",
+ "@babel/types": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-string-parser": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
+ "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz",
+ "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-option": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz",
+ "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-wrap-function": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.27.1.tgz",
+ "integrity": "sha512-NFJK2sHUvrjo8wAU/nQTWU890/zB2jj0qBcCbZbbf+005cAsv6tMjXz31fBign6M5ov1o0Bllu+9nbqkfsjjJQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/template": "^7.27.1",
+ "@babel/traverse": "^7.27.1",
+ "@babel/types": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helpers": {
+ "version": "7.28.2",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.2.tgz",
+ "integrity": "sha512-/V9771t+EgXz62aCcyofnQhGM8DQACbRhvzKFsXKC9QM+5MadF8ZmIm0crDMaz3+o0h0zXfJnd4EhbYbxsrcFw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/template": "^7.27.2",
+ "@babel/types": "^7.28.2"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/node": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@babel/node/-/node-7.28.0.tgz",
+ "integrity": "sha512-6u1Mmn3SIMUH8uwTq543L062X3JDgms9HPf06o/pIGdDjeD/zNQ+dfZPQD27sCyvtP0ZOlJtwnl2RIdPe9bHeQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/register": "^7.27.1",
+ "commander": "^6.2.0",
+ "core-js": "^3.30.2",
+ "node-environment-flags": "^1.0.5",
+ "regenerator-runtime": "^0.14.0",
+ "v8flags": "^3.1.1"
+ },
+ "bin": {
+ "babel-node": "bin/babel-node.js"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/parser": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.0.tgz",
+ "integrity": "sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.28.0"
+ },
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.27.1.tgz",
+ "integrity": "sha512-QPG3C9cCVRQLxAVwmefEmwdTanECuUBMQZ/ym5kiw3XKCGA7qkuQLcjWWHcrD/GKbn/WmJwaezfuuAOcyKlRPA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1",
+ "@babel/traverse": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.27.1.tgz",
+ "integrity": "sha512-qNeq3bCKnGgLkEXUuFry6dPlGfCdQNZbn7yUAPCInwAJHMU7THJfrBSozkcWq5sNM6RcF3S8XyQL2A52KNR9IA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.27.1.tgz",
+ "integrity": "sha512-g4L7OYun04N1WyqMNjldFwlfPCLVkgB54A/YCXICZYBsvJJE3kByKv9c9+R/nAfmIfjl2rKYLNyMHboYbZaWaA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.27.1.tgz",
+ "integrity": "sha512-oO02gcONcD5O1iTLi/6frMJBIwWEHceWGSGqrpCmEL8nogiS6J9PBlE48CaK20/Jx1LuRml9aDftLgdjXT8+Cw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1",
+ "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1",
+ "@babel/plugin-transform-optional-chaining": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.13.0"
+ }
+ },
+ "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.27.1.tgz",
+ "integrity": "sha512-6BpaYGDavZqkI6yT+KSPdpZFfpnd68UKXbcjI9pJ13pvHhPrCKWOOLp+ysvMeA+DxnhuPpgIaRpxRxo5A9t5jw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1",
+ "@babel/traverse": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-proposal-private-property-in-object": {
+ "version": "7.21.0-placeholder-for-preset-env.2",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz",
+ "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-import-assertions": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.27.1.tgz",
+ "integrity": "sha512-UT/Jrhw57xg4ILHLFnzFpPDlMbcdEicaAtjPQpbj9wa8T4r5KVWCimHcL/460g8Ht0DMxDyjsLgiWSkVjnwPFg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-import-attributes": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz",
+ "integrity": "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-unicode-sets-regex": {
+ "version": "7.18.6",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz",
+ "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-create-regexp-features-plugin": "^7.18.6",
+ "@babel/helper-plugin-utils": "^7.18.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-arrow-functions": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.27.1.tgz",
+ "integrity": "sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-async-generator-functions": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.28.0.tgz",
+ "integrity": "sha512-BEOdvX4+M765icNPZeidyADIvQ1m1gmunXufXxvRESy/jNNyfovIqUyE7MVgGBjWktCoJlzvFA1To2O4ymIO3Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1",
+ "@babel/helper-remap-async-to-generator": "^7.27.1",
+ "@babel/traverse": "^7.28.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-async-to-generator": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.27.1.tgz",
+ "integrity": "sha512-NREkZsZVJS4xmTr8qzE5y8AfIPqsdQfRuUiLRTEzb7Qii8iFWCyDKaUV2c0rCuh4ljDZ98ALHP/PetiBV2nddA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-module-imports": "^7.27.1",
+ "@babel/helper-plugin-utils": "^7.27.1",
+ "@babel/helper-remap-async-to-generator": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-block-scoped-functions": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.27.1.tgz",
+ "integrity": "sha512-cnqkuOtZLapWYZUYM5rVIdv1nXYuFVIltZ6ZJ7nIj585QsjKM5dhL2Fu/lICXZ1OyIAFc7Qy+bvDAtTXqGrlhg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-block-scoping": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.0.tgz",
+ "integrity": "sha512-gKKnwjpdx5sER/wl0WN0efUBFzF/56YZO0RJrSYP4CljXnP31ByY7fol89AzomdlLNzI36AvOTmYHsnZTCkq8Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-class-properties": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.27.1.tgz",
+ "integrity": "sha512-D0VcalChDMtuRvJIu3U/fwWjf8ZMykz5iZsg77Nuj821vCKI3zCyRLwRdWbsuJ/uRwZhZ002QtCqIkwC/ZkvbA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-create-class-features-plugin": "^7.27.1",
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-class-static-block": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.27.1.tgz",
+ "integrity": "sha512-s734HmYU78MVzZ++joYM+NkJusItbdRcbm+AGRgJCt3iA+yux0QpD9cBVdz3tKyrjVYWRl7j0mHSmv4lhV0aoA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-create-class-features-plugin": "^7.27.1",
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.12.0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-classes": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.0.tgz",
+ "integrity": "sha512-IjM1IoJNw72AZFlj33Cu8X0q2XK/6AaVC3jQu+cgQ5lThWD5ajnuUAml80dqRmOhmPkTH8uAwnpMu9Rvj0LTRA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-annotate-as-pure": "^7.27.3",
+ "@babel/helper-compilation-targets": "^7.27.2",
+ "@babel/helper-globals": "^7.28.0",
+ "@babel/helper-plugin-utils": "^7.27.1",
+ "@babel/helper-replace-supers": "^7.27.1",
+ "@babel/traverse": "^7.28.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-computed-properties": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.27.1.tgz",
+ "integrity": "sha512-lj9PGWvMTVksbWiDT2tW68zGS/cyo4AkZ/QTp0sQT0mjPopCmrSkzxeXkznjqBxzDI6TclZhOJbBmbBLjuOZUw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1",
+ "@babel/template": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-destructuring": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.28.0.tgz",
+ "integrity": "sha512-v1nrSMBiKcodhsyJ4Gf+Z0U/yawmJDBOTpEB3mcQY52r9RIyPneGyAS/yM6seP/8I+mWI3elOMtT5dB8GJVs+A==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1",
+ "@babel/traverse": "^7.28.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-dotall-regex": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.27.1.tgz",
+ "integrity": "sha512-gEbkDVGRvjj7+T1ivxrfgygpT7GUd4vmODtYpbs0gZATdkX8/iSnOtZSxiZnsgm1YjTgjI6VKBGSJJevkrclzw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-create-regexp-features-plugin": "^7.27.1",
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-duplicate-keys": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.27.1.tgz",
+ "integrity": "sha512-MTyJk98sHvSs+cvZ4nOauwTTG1JeonDjSGvGGUNHreGQns+Mpt6WX/dVzWBHgg+dYZhkC4X+zTDfkTU+Vy9y7Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.27.1.tgz",
+ "integrity": "sha512-hkGcueTEzuhB30B3eJCbCYeCaaEQOmQR0AdvzpD4LoN0GXMWzzGSuRrxR2xTnCrvNbVwK9N6/jQ92GSLfiZWoQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-create-regexp-features-plugin": "^7.27.1",
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-dynamic-import": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.27.1.tgz",
+ "integrity": "sha512-MHzkWQcEmjzzVW9j2q8LGjwGWpG2mjwaaB0BNQwst3FIjqsg8Ct/mIZlvSPJvfi9y2AC8mi/ktxbFVL9pZ1I4A==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-explicit-resource-management": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-explicit-resource-management/-/plugin-transform-explicit-resource-management-7.28.0.tgz",
+ "integrity": "sha512-K8nhUcn3f6iB+P3gwCv/no7OdzOZQcKchW6N389V6PD8NUWKZHzndOd9sPDVbMoBsbmjMqlB4L9fm+fEFNVlwQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1",
+ "@babel/plugin-transform-destructuring": "^7.28.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-exponentiation-operator": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.27.1.tgz",
+ "integrity": "sha512-uspvXnhHvGKf2r4VVtBpeFnuDWsJLQ6MF6lGJLC89jBR1uoVeqM416AZtTuhTezOfgHicpJQmoD5YUakO/YmXQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-export-namespace-from": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.27.1.tgz",
+ "integrity": "sha512-tQvHWSZ3/jH2xuq/vZDy0jNn+ZdXJeM8gHvX4lnJmsc3+50yPlWdZXIc5ay+umX+2/tJIqHqiEqcJvxlmIvRvQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-for-of": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.27.1.tgz",
+ "integrity": "sha512-BfbWFFEJFQzLCQ5N8VocnCtA8J1CLkNTe2Ms2wocj75dd6VpiqS5Z5quTYcUoo4Yq+DN0rtikODccuv7RU81sw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1",
+ "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-function-name": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.27.1.tgz",
+ "integrity": "sha512-1bQeydJF9Nr1eBCMMbC+hdwmRlsv5XYOMu03YSWFwNs0HsAmtSxxF1fyuYPqemVldVyFmlCU7w8UE14LupUSZQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-compilation-targets": "^7.27.1",
+ "@babel/helper-plugin-utils": "^7.27.1",
+ "@babel/traverse": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-json-strings": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.27.1.tgz",
+ "integrity": "sha512-6WVLVJiTjqcQauBhn1LkICsR2H+zm62I3h9faTDKt1qP4jn2o72tSvqMwtGFKGTpojce0gJs+76eZ2uCHRZh0Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-literals": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.27.1.tgz",
+ "integrity": "sha512-0HCFSepIpLTkLcsi86GG3mTUzxV5jpmbv97hTETW3yzrAij8aqlD36toB1D0daVFJM8NK6GvKO0gslVQmm+zZA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-logical-assignment-operators": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.27.1.tgz",
+ "integrity": "sha512-SJvDs5dXxiae4FbSL1aBJlG4wvl594N6YEVVn9e3JGulwioy6z3oPjx/sQBO3Y4NwUu5HNix6KJ3wBZoewcdbw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-member-expression-literals": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.27.1.tgz",
+ "integrity": "sha512-hqoBX4dcZ1I33jCSWcXrP+1Ku7kdqXf1oeah7ooKOIiAdKQ+uqftgCFNOSzA5AMS2XIHEYeGFg4cKRCdpxzVOQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-modules-amd": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.27.1.tgz",
+ "integrity": "sha512-iCsytMg/N9/oFq6n+gFTvUYDZQOMK5kEdeYxmxt91fcJGycfxVP9CnrxoliM0oumFERba2i8ZtwRUCMhvP1LnA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-module-transforms": "^7.27.1",
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-modules-commonjs": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.27.1.tgz",
+ "integrity": "sha512-OJguuwlTYlN0gBZFRPqwOGNWssZjfIUdS7HMYtN8c1KmwpwHFBwTeFZrg9XZa+DFTitWOW5iTAG7tyCUPsCCyw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-module-transforms": "^7.27.1",
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-modules-systemjs": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.27.1.tgz",
+ "integrity": "sha512-w5N1XzsRbc0PQStASMksmUeqECuzKuTJer7kFagK8AXgpCMkeDMO5S+aaFb7A51ZYDF7XI34qsTX+fkHiIm5yA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-module-transforms": "^7.27.1",
+ "@babel/helper-plugin-utils": "^7.27.1",
+ "@babel/helper-validator-identifier": "^7.27.1",
+ "@babel/traverse": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-modules-umd": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.27.1.tgz",
+ "integrity": "sha512-iQBE/xC5BV1OxJbp6WG7jq9IWiD+xxlZhLrdwpPkTX3ydmXdvoCpyfJN7acaIBZaOqTfr76pgzqBJflNbeRK+w==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-module-transforms": "^7.27.1",
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-named-capturing-groups-regex": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.27.1.tgz",
+ "integrity": "sha512-SstR5JYy8ddZvD6MhV0tM/j16Qds4mIpJTOd1Yu9J9pJjH93bxHECF7pgtc28XvkzTD6Pxcm/0Z73Hvk7kb3Ng==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-create-regexp-features-plugin": "^7.27.1",
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-new-target": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.27.1.tgz",
+ "integrity": "sha512-f6PiYeqXQ05lYq3TIfIDu/MtliKUbNwkGApPUvyo6+tc7uaR4cPjPe7DFPr15Uyycg2lZU6btZ575CuQoYh7MQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-nullish-coalescing-operator": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.27.1.tgz",
+ "integrity": "sha512-aGZh6xMo6q9vq1JGcw58lZ1Z0+i0xB2x0XaauNIUXd6O1xXc3RwoWEBlsTQrY4KQ9Jf0s5rgD6SiNkaUdJegTA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-numeric-separator": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.27.1.tgz",
+ "integrity": "sha512-fdPKAcujuvEChxDBJ5c+0BTaS6revLV7CJL08e4m3de8qJfNIuCc2nc7XJYOjBoTMJeqSmwXJ0ypE14RCjLwaw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-object-rest-spread": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.28.0.tgz",
+ "integrity": "sha512-9VNGikXxzu5eCiQjdE4IZn8sb9q7Xsk5EXLDBKUYg1e/Tve8/05+KJEtcxGxAgCY5t/BpKQM+JEL/yT4tvgiUA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-compilation-targets": "^7.27.2",
+ "@babel/helper-plugin-utils": "^7.27.1",
+ "@babel/plugin-transform-destructuring": "^7.28.0",
+ "@babel/plugin-transform-parameters": "^7.27.7",
+ "@babel/traverse": "^7.28.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-object-super": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.27.1.tgz",
+ "integrity": "sha512-SFy8S9plRPbIcxlJ8A6mT/CxFdJx/c04JEctz4jf8YZaVS2px34j7NXRrlGlHkN/M2gnpL37ZpGRGVFLd3l8Ng==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1",
+ "@babel/helper-replace-supers": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-optional-catch-binding": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.27.1.tgz",
+ "integrity": "sha512-txEAEKzYrHEX4xSZN4kJ+OfKXFVSWKB2ZxM9dpcE3wT7smwkNmXo5ORRlVzMVdJbD+Q8ILTgSD7959uj+3Dm3Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-optional-chaining": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.27.1.tgz",
+ "integrity": "sha512-BQmKPPIuc8EkZgNKsv0X4bPmOoayeu4F1YCwx2/CfmDSXDbp7GnzlUH+/ul5VGfRg1AoFPsrIThlEBj2xb4CAg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1",
+ "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-parameters": {
+ "version": "7.27.7",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.27.7.tgz",
+ "integrity": "sha512-qBkYTYCb76RRxUM6CcZA5KRu8K4SM8ajzVeUgVdMVO9NN9uI/GaVmBg/WKJJGnNokV9SY8FxNOVWGXzqzUidBg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-private-methods": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.27.1.tgz",
+ "integrity": "sha512-10FVt+X55AjRAYI9BrdISN9/AQWHqldOeZDUoLyif1Kn05a56xVBXb8ZouL8pZ9jem8QpXaOt8TS7RHUIS+GPA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-create-class-features-plugin": "^7.27.1",
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-private-property-in-object": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.27.1.tgz",
+ "integrity": "sha512-5J+IhqTi1XPa0DXF83jYOaARrX+41gOewWbkPyjMNRDqgOCqdffGh8L3f/Ek5utaEBZExjSAzcyjmV9SSAWObQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-annotate-as-pure": "^7.27.1",
+ "@babel/helper-create-class-features-plugin": "^7.27.1",
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-property-literals": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.27.1.tgz",
+ "integrity": "sha512-oThy3BCuCha8kDZ8ZkgOg2exvPYUlprMukKQXI1r1pJ47NCvxfkEy8vK+r/hT9nF0Aa4H1WUPZZjHTFtAhGfmQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-regenerator": {
+ "version": "7.28.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.28.1.tgz",
+ "integrity": "sha512-P0QiV/taaa3kXpLY+sXla5zec4E+4t4Aqc9ggHlfZ7a2cp8/x/Gv08jfwEtn9gnnYIMvHx6aoOZ8XJL8eU71Dg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-regexp-modifiers": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.27.1.tgz",
+ "integrity": "sha512-TtEciroaiODtXvLZv4rmfMhkCv8jx3wgKpL68PuiPh2M4fvz5jhsA7697N1gMvkvr/JTF13DrFYyEbY9U7cVPA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-create-regexp-features-plugin": "^7.27.1",
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-reserved-words": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.27.1.tgz",
+ "integrity": "sha512-V2ABPHIJX4kC7HegLkYoDpfg9PVmuWy/i6vUM5eGK22bx4YVFD3M5F0QQnWQoDs6AGsUWTVOopBiMFQgHaSkVw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-shorthand-properties": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.27.1.tgz",
+ "integrity": "sha512-N/wH1vcn4oYawbJ13Y/FxcQrWk63jhfNa7jef0ih7PHSIHX2LB7GWE1rkPrOnka9kwMxb6hMl19p7lidA+EHmQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-spread": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.27.1.tgz",
+ "integrity": "sha512-kpb3HUqaILBJcRFVhFUs6Trdd4mkrzcGXss+6/mxUd273PfbWqSDHRzMT2234gIg2QYfAjvXLSquP1xECSg09Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1",
+ "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-sticky-regex": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.27.1.tgz",
+ "integrity": "sha512-lhInBO5bi/Kowe2/aLdBAawijx+q1pQzicSgnkB6dUPc1+RC8QmJHKf2OjvU+NZWitguJHEaEmbV6VWEouT58g==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-template-literals": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.27.1.tgz",
+ "integrity": "sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-typeof-symbol": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.27.1.tgz",
+ "integrity": "sha512-RiSILC+nRJM7FY5srIyc4/fGIwUhyDuuBSdWn4y6yT6gm652DpCHZjIipgn6B7MQ1ITOUnAKWixEUjQRIBIcLw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-unicode-escapes": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.27.1.tgz",
+ "integrity": "sha512-Ysg4v6AmF26k9vpfFuTZg8HRfVWzsh1kVfowA23y9j/Gu6dOuahdUVhkLqpObp3JIv27MLSii6noRnuKN8H0Mg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-unicode-property-regex": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.27.1.tgz",
+ "integrity": "sha512-uW20S39PnaTImxp39O5qFlHLS9LJEmANjMG7SxIhap8rCHqu0Ik+tLEPX5DKmHn6CsWQ7j3lix2tFOa5YtL12Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-create-regexp-features-plugin": "^7.27.1",
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-unicode-regex": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.27.1.tgz",
+ "integrity": "sha512-xvINq24TRojDuyt6JGtHmkVkrfVV3FPT16uytxImLeBZqW3/H52yN+kM1MGuyPkIQxrzKwPHs5U/MP3qKyzkGw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-create-regexp-features-plugin": "^7.27.1",
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-unicode-sets-regex": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.27.1.tgz",
+ "integrity": "sha512-EtkOujbc4cgvb0mlpQefi4NTPBzhSIevblFevACNLUspmrALgmEBdL/XfnyyITfd8fKBZrZys92zOWcik7j9Tw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-create-regexp-features-plugin": "^7.27.1",
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/preset-env": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.28.0.tgz",
+ "integrity": "sha512-VmaxeGOwuDqzLl5JUkIRM1X2Qu2uKGxHEQWh+cvvbl7JuJRgKGJSfsEF/bUaxFhJl/XAyxBe7q7qSuTbKFuCyg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/compat-data": "^7.28.0",
+ "@babel/helper-compilation-targets": "^7.27.2",
+ "@babel/helper-plugin-utils": "^7.27.1",
+ "@babel/helper-validator-option": "^7.27.1",
+ "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.27.1",
+ "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.27.1",
+ "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.27.1",
+ "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.27.1",
+ "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.27.1",
+ "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2",
+ "@babel/plugin-syntax-import-assertions": "^7.27.1",
+ "@babel/plugin-syntax-import-attributes": "^7.27.1",
+ "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6",
+ "@babel/plugin-transform-arrow-functions": "^7.27.1",
+ "@babel/plugin-transform-async-generator-functions": "^7.28.0",
+ "@babel/plugin-transform-async-to-generator": "^7.27.1",
+ "@babel/plugin-transform-block-scoped-functions": "^7.27.1",
+ "@babel/plugin-transform-block-scoping": "^7.28.0",
+ "@babel/plugin-transform-class-properties": "^7.27.1",
+ "@babel/plugin-transform-class-static-block": "^7.27.1",
+ "@babel/plugin-transform-classes": "^7.28.0",
+ "@babel/plugin-transform-computed-properties": "^7.27.1",
+ "@babel/plugin-transform-destructuring": "^7.28.0",
+ "@babel/plugin-transform-dotall-regex": "^7.27.1",
+ "@babel/plugin-transform-duplicate-keys": "^7.27.1",
+ "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.27.1",
+ "@babel/plugin-transform-dynamic-import": "^7.27.1",
+ "@babel/plugin-transform-explicit-resource-management": "^7.28.0",
+ "@babel/plugin-transform-exponentiation-operator": "^7.27.1",
+ "@babel/plugin-transform-export-namespace-from": "^7.27.1",
+ "@babel/plugin-transform-for-of": "^7.27.1",
+ "@babel/plugin-transform-function-name": "^7.27.1",
+ "@babel/plugin-transform-json-strings": "^7.27.1",
+ "@babel/plugin-transform-literals": "^7.27.1",
+ "@babel/plugin-transform-logical-assignment-operators": "^7.27.1",
+ "@babel/plugin-transform-member-expression-literals": "^7.27.1",
+ "@babel/plugin-transform-modules-amd": "^7.27.1",
+ "@babel/plugin-transform-modules-commonjs": "^7.27.1",
+ "@babel/plugin-transform-modules-systemjs": "^7.27.1",
+ "@babel/plugin-transform-modules-umd": "^7.27.1",
+ "@babel/plugin-transform-named-capturing-groups-regex": "^7.27.1",
+ "@babel/plugin-transform-new-target": "^7.27.1",
+ "@babel/plugin-transform-nullish-coalescing-operator": "^7.27.1",
+ "@babel/plugin-transform-numeric-separator": "^7.27.1",
+ "@babel/plugin-transform-object-rest-spread": "^7.28.0",
+ "@babel/plugin-transform-object-super": "^7.27.1",
+ "@babel/plugin-transform-optional-catch-binding": "^7.27.1",
+ "@babel/plugin-transform-optional-chaining": "^7.27.1",
+ "@babel/plugin-transform-parameters": "^7.27.7",
+ "@babel/plugin-transform-private-methods": "^7.27.1",
+ "@babel/plugin-transform-private-property-in-object": "^7.27.1",
+ "@babel/plugin-transform-property-literals": "^7.27.1",
+ "@babel/plugin-transform-regenerator": "^7.28.0",
+ "@babel/plugin-transform-regexp-modifiers": "^7.27.1",
+ "@babel/plugin-transform-reserved-words": "^7.27.1",
+ "@babel/plugin-transform-shorthand-properties": "^7.27.1",
+ "@babel/plugin-transform-spread": "^7.27.1",
+ "@babel/plugin-transform-sticky-regex": "^7.27.1",
+ "@babel/plugin-transform-template-literals": "^7.27.1",
+ "@babel/plugin-transform-typeof-symbol": "^7.27.1",
+ "@babel/plugin-transform-unicode-escapes": "^7.27.1",
+ "@babel/plugin-transform-unicode-property-regex": "^7.27.1",
+ "@babel/plugin-transform-unicode-regex": "^7.27.1",
+ "@babel/plugin-transform-unicode-sets-regex": "^7.27.1",
+ "@babel/preset-modules": "0.1.6-no-external-plugins",
+ "babel-plugin-polyfill-corejs2": "^0.4.14",
+ "babel-plugin-polyfill-corejs3": "^0.13.0",
+ "babel-plugin-polyfill-regenerator": "^0.6.5",
+ "core-js-compat": "^3.43.0",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/preset-modules": {
+ "version": "0.1.6-no-external-plugins",
+ "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz",
+ "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.0.0",
+ "@babel/types": "^7.4.4",
+ "esutils": "^2.0.2"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0"
+ }
+ },
+ "node_modules/@babel/register": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/register/-/register-7.27.1.tgz",
+ "integrity": "sha512-K13lQpoV54LATKkzBpBAEu1GGSIRzxR9f4IN4V8DCDgiUMo2UDGagEZr3lPeVNJPLkWUi5JE4hCHKneVTwQlYQ==",
+ "license": "MIT",
+ "dependencies": {
+ "clone-deep": "^4.0.1",
+ "find-cache-dir": "^2.0.0",
+ "make-dir": "^2.1.0",
+ "pirates": "^4.0.6",
+ "source-map-support": "^0.5.16"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/template": {
+ "version": "7.27.2",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz",
+ "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.27.1",
+ "@babel/parser": "^7.27.2",
+ "@babel/types": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/traverse": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.0.tgz",
+ "integrity": "sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.27.1",
+ "@babel/generator": "^7.28.0",
+ "@babel/helper-globals": "^7.28.0",
+ "@babel/parser": "^7.28.0",
+ "@babel/template": "^7.27.2",
+ "@babel/types": "^7.28.0",
+ "debug": "^4.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/types": {
+ "version": "7.28.2",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.2.tgz",
+ "integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-string-parser": "^7.27.1",
+ "@babel/helper-validator-identifier": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@jridgewell/gen-mapping": {
+ "version": "0.3.12",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz",
+ "integrity": "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==",
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.0",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.5.4",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz",
+ "integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==",
+ "license": "MIT"
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.29",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz",
+ "integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.1.0",
+ "@jridgewell/sourcemap-codec": "^1.4.14"
+ }
+ },
+ "node_modules/@mongodb-js/saslprep": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.3.0.tgz",
+ "integrity": "sha512-zlayKCsIjYb7/IdfqxorK5+xUMyi4vOKcFy10wKJYc63NSdKI8mNME+uJqfatkPmOSMMUiojrL58IePKBm3gvQ==",
+ "license": "MIT",
+ "dependencies": {
+ "sparse-bitfield": "^3.0.3"
+ }
+ },
+ "node_modules/@types/webidl-conversions": {
+ "version": "7.0.3",
+ "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz",
+ "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==",
+ "license": "MIT"
+ },
+ "node_modules/@types/whatwg-url": {
+ "version": "11.0.5",
+ "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.5.tgz",
+ "integrity": "sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/webidl-conversions": "*"
+ }
+ },
+ "node_modules/accepts": {
+ "version": "1.3.8",
+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
+ "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-types": "~2.1.34",
+ "negotiator": "0.6.3"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/anymatch": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
+ "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "normalize-path": "^3.0.0",
+ "picomatch": "^2.0.4"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/array-buffer-byte-length": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz",
+ "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.3",
+ "is-array-buffer": "^3.0.5"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/array-flatten": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
+ "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
+ "license": "MIT"
+ },
+ "node_modules/array.prototype.reduce": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/array.prototype.reduce/-/array.prototype.reduce-1.0.8.tgz",
+ "integrity": "sha512-DwuEqgXFBwbmZSRqt3BpQigWNUoqw9Ml2dTWdF3B2zQlQX4OeUE0zyuzX0fX0IbTvjdkZbcBTU3idgpO78qkTw==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.4",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.9",
+ "es-array-method-boxes-properly": "^1.0.0",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.1.1",
+ "is-string": "^1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/arraybuffer.prototype.slice": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz",
+ "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==",
+ "license": "MIT",
+ "dependencies": {
+ "array-buffer-byte-length": "^1.0.1",
+ "call-bind": "^1.0.8",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.5",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.6",
+ "is-array-buffer": "^3.0.4"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/async-function": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz",
+ "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/asynckit": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
+ "license": "MIT"
+ },
+ "node_modules/available-typed-arrays": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz",
+ "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==",
+ "license": "MIT",
+ "dependencies": {
+ "possible-typed-array-names": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/axios": {
+ "version": "1.11.0",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.11.0.tgz",
+ "integrity": "sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==",
+ "license": "MIT",
+ "dependencies": {
+ "follow-redirects": "^1.15.6",
+ "form-data": "^4.0.4",
+ "proxy-from-env": "^1.1.0"
+ }
+ },
+ "node_modules/babel-plugin-polyfill-corejs2": {
+ "version": "0.4.14",
+ "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.14.tgz",
+ "integrity": "sha512-Co2Y9wX854ts6U8gAAPXfn0GmAyctHuK8n0Yhfjd6t30g7yvKjspvvOo9yG+z52PZRgFErt7Ka2pYnXCjLKEpg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/compat-data": "^7.27.7",
+ "@babel/helper-define-polyfill-provider": "^0.6.5",
+ "semver": "^6.3.1"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0"
+ }
+ },
+ "node_modules/babel-plugin-polyfill-corejs3": {
+ "version": "0.13.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.13.0.tgz",
+ "integrity": "sha512-U+GNwMdSFgzVmfhNm8GJUX88AadB3uo9KpJqS3FaqNIPKgySuvMb+bHPsOmmuWyIcuqZj/pzt1RUIUZns4y2+A==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-define-polyfill-provider": "^0.6.5",
+ "core-js-compat": "^3.43.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0"
+ }
+ },
+ "node_modules/babel-plugin-polyfill-regenerator": {
+ "version": "0.6.5",
+ "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.5.tgz",
+ "integrity": "sha512-ISqQ2frbiNU9vIJkzg7dlPpznPZ4jOiUQ1uSmB0fEHeowtN3COYRsXr/xexn64NpU13P06jc/L5TgiJXOgrbEg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-define-polyfill-provider": "^0.6.5"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0"
+ }
+ },
+ "node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/bcryptjs": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-3.0.2.tgz",
+ "integrity": "sha512-k38b3XOZKv60C4E2hVsXTolJWfkGRMbILBIe2IBITXciy5bOsTKot5kDrf3ZfufQtQOUN5mXceUEpU1rTl9Uog==",
+ "license": "BSD-3-Clause",
+ "bin": {
+ "bcrypt": "bin/bcrypt"
+ }
+ },
+ "node_modules/binary-extensions": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
+ "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/body-parser": {
+ "version": "1.20.3",
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz",
+ "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==",
+ "license": "MIT",
+ "dependencies": {
+ "bytes": "3.1.2",
+ "content-type": "~1.0.5",
+ "debug": "2.6.9",
+ "depd": "2.0.0",
+ "destroy": "1.2.0",
+ "http-errors": "2.0.0",
+ "iconv-lite": "0.4.24",
+ "on-finished": "2.4.1",
+ "qs": "6.13.0",
+ "raw-body": "2.5.2",
+ "type-is": "~1.6.18",
+ "unpipe": "1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8",
+ "npm": "1.2.8000 || >= 1.4.16"
+ }
+ },
+ "node_modules/body-parser/node_modules/debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "2.0.0"
+ }
+ },
+ "node_modules/body-parser/node_modules/ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
+ "license": "MIT"
+ },
+ "node_modules/brace-expansion": {
+ "version": "1.1.12",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
+ "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/braces": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
+ "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fill-range": "^7.1.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/browserslist": {
+ "version": "4.25.1",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.1.tgz",
+ "integrity": "sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "caniuse-lite": "^1.0.30001726",
+ "electron-to-chromium": "^1.5.173",
+ "node-releases": "^2.0.19",
+ "update-browserslist-db": "^1.1.3"
+ },
+ "bin": {
+ "browserslist": "cli.js"
+ },
+ "engines": {
+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+ }
+ },
+ "node_modules/bson": {
+ "version": "6.10.4",
+ "resolved": "https://registry.npmjs.org/bson/-/bson-6.10.4.tgz",
+ "integrity": "sha512-WIsKqkSC0ABoBJuT1LEX+2HEvNmNKKgnTAyd0fL8qzK4SH2i9NXg+t08YtdZp/V9IZ33cxe3iV4yM0qg8lMQng==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=16.20.1"
+ }
+ },
+ "node_modules/buffer-equal-constant-time": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
+ "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/buffer-from": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
+ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
+ "license": "MIT"
+ },
+ "node_modules/bytes": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
+ "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/call-bind": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz",
+ "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.0",
+ "es-define-property": "^1.0.0",
+ "get-intrinsic": "^1.2.4",
+ "set-function-length": "^1.2.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/call-bind-apply-helpers": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
+ "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/call-bound": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
+ "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "get-intrinsic": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/caniuse-lite": {
+ "version": "1.0.30001731",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001731.tgz",
+ "integrity": "sha512-lDdp2/wrOmTRWuoB5DpfNkC0rJDU8DqRa6nYL6HK6sytw70QMopt/NIc/9SM7ylItlBWfACXk0tEn37UWM/+mg==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "CC-BY-4.0"
+ },
+ "node_modules/chokidar": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
+ "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "anymatch": "~3.1.2",
+ "braces": "~3.0.2",
+ "glob-parent": "~5.1.2",
+ "is-binary-path": "~2.1.0",
+ "is-glob": "~4.0.1",
+ "normalize-path": "~3.0.0",
+ "readdirp": "~3.6.0"
+ },
+ "engines": {
+ "node": ">= 8.10.0"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/clone-deep": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz",
+ "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==",
+ "license": "MIT",
+ "dependencies": {
+ "is-plain-object": "^2.0.4",
+ "kind-of": "^6.0.2",
+ "shallow-clone": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/combined-stream": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+ "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+ "license": "MIT",
+ "dependencies": {
+ "delayed-stream": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/commander": {
+ "version": "6.2.1",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz",
+ "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/commondir": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
+ "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==",
+ "license": "MIT"
+ },
+ "node_modules/concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/content-disposition": {
+ "version": "0.5.4",
+ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
+ "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
+ "license": "MIT",
+ "dependencies": {
+ "safe-buffer": "5.2.1"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/content-type": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
+ "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/convert-source-map": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
+ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
+ "license": "MIT"
+ },
+ "node_modules/cookie": {
+ "version": "0.7.1",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz",
+ "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/cookie-parser": {
+ "version": "1.4.7",
+ "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.7.tgz",
+ "integrity": "sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw==",
+ "license": "MIT",
+ "dependencies": {
+ "cookie": "0.7.2",
+ "cookie-signature": "1.0.6"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/cookie-parser/node_modules/cookie": {
+ "version": "0.7.2",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
+ "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/cookie-signature": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
+ "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==",
+ "license": "MIT"
+ },
+ "node_modules/core-js": {
+ "version": "3.45.0",
+ "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.45.0.tgz",
+ "integrity": "sha512-c2KZL9lP4DjkN3hk/an4pWn5b5ZefhRJnAc42n6LJ19kSnbeRbdQZE5dSeE2LBol1OwJD3X1BQvFTAsa8ReeDA==",
+ "hasInstallScript": true,
+ "license": "MIT",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/core-js"
+ }
+ },
+ "node_modules/core-js-compat": {
+ "version": "3.45.0",
+ "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.45.0.tgz",
+ "integrity": "sha512-gRoVMBawZg0OnxaVv3zpqLLxaHmsubEGyTnqdpI/CEBvX4JadI1dMSHxagThprYRtSVbuQxvi6iUatdPxohHpA==",
+ "license": "MIT",
+ "dependencies": {
+ "browserslist": "^4.25.1"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/core-js"
+ }
+ },
+ "node_modules/cors": {
+ "version": "2.8.5",
+ "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
+ "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
+ "license": "MIT",
+ "dependencies": {
+ "object-assign": "^4",
+ "vary": "^1"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/data-view-buffer": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz",
+ "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.3",
+ "es-errors": "^1.3.0",
+ "is-data-view": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/data-view-byte-length": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz",
+ "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.3",
+ "es-errors": "^1.3.0",
+ "is-data-view": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/inspect-js"
+ }
+ },
+ "node_modules/data-view-byte-offset": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz",
+ "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "is-data-view": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/debug": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
+ "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/define-data-property": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
+ "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
+ "license": "MIT",
+ "dependencies": {
+ "es-define-property": "^1.0.0",
+ "es-errors": "^1.3.0",
+ "gopd": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/define-properties": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz",
+ "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==",
+ "license": "MIT",
+ "dependencies": {
+ "define-data-property": "^1.0.1",
+ "has-property-descriptors": "^1.0.0",
+ "object-keys": "^1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/delayed-stream": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+ "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/depd": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
+ "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/destroy": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
+ "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8",
+ "npm": "1.2.8000 || >= 1.4.16"
+ }
+ },
+ "node_modules/dotenv": {
+ "version": "17.2.1",
+ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.1.tgz",
+ "integrity": "sha512-kQhDYKZecqnM0fCnzI5eIv5L4cAe/iRI+HqMbO/hbRdTAeXDG+M9FjipUxNfbARuEg4iHIbhnhs78BCHNbSxEQ==",
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://dotenvx.com"
+ }
+ },
+ "node_modules/dunder-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
+ "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "gopd": "^1.2.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/ecdsa-sig-formatter": {
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
+ "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "node_modules/ee-first": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
+ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
+ "license": "MIT"
+ },
+ "node_modules/electron-to-chromium": {
+ "version": "1.5.198",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.198.tgz",
+ "integrity": "sha512-G5COfnp3w+ydVu80yprgWSfmfQaYRh9DOxfhAxstLyetKaLyl55QrNjx8C38Pc/C+RaDmb1M0Lk8wPEMQ+bGgQ==",
+ "license": "ISC"
+ },
+ "node_modules/encodeurl": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
+ "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/es-abstract": {
+ "version": "1.24.0",
+ "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.0.tgz",
+ "integrity": "sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==",
+ "license": "MIT",
+ "dependencies": {
+ "array-buffer-byte-length": "^1.0.2",
+ "arraybuffer.prototype.slice": "^1.0.4",
+ "available-typed-arrays": "^1.0.7",
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.4",
+ "data-view-buffer": "^1.0.2",
+ "data-view-byte-length": "^1.0.2",
+ "data-view-byte-offset": "^1.0.1",
+ "es-define-property": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.1.1",
+ "es-set-tostringtag": "^2.1.0",
+ "es-to-primitive": "^1.3.0",
+ "function.prototype.name": "^1.1.8",
+ "get-intrinsic": "^1.3.0",
+ "get-proto": "^1.0.1",
+ "get-symbol-description": "^1.1.0",
+ "globalthis": "^1.0.4",
+ "gopd": "^1.2.0",
+ "has-property-descriptors": "^1.0.2",
+ "has-proto": "^1.2.0",
+ "has-symbols": "^1.1.0",
+ "hasown": "^2.0.2",
+ "internal-slot": "^1.1.0",
+ "is-array-buffer": "^3.0.5",
+ "is-callable": "^1.2.7",
+ "is-data-view": "^1.0.2",
+ "is-negative-zero": "^2.0.3",
+ "is-regex": "^1.2.1",
+ "is-set": "^2.0.3",
+ "is-shared-array-buffer": "^1.0.4",
+ "is-string": "^1.1.1",
+ "is-typed-array": "^1.1.15",
+ "is-weakref": "^1.1.1",
+ "math-intrinsics": "^1.1.0",
+ "object-inspect": "^1.13.4",
+ "object-keys": "^1.1.1",
+ "object.assign": "^4.1.7",
+ "own-keys": "^1.0.1",
+ "regexp.prototype.flags": "^1.5.4",
+ "safe-array-concat": "^1.1.3",
+ "safe-push-apply": "^1.0.0",
+ "safe-regex-test": "^1.1.0",
+ "set-proto": "^1.0.0",
+ "stop-iteration-iterator": "^1.1.0",
+ "string.prototype.trim": "^1.2.10",
+ "string.prototype.trimend": "^1.0.9",
+ "string.prototype.trimstart": "^1.0.8",
+ "typed-array-buffer": "^1.0.3",
+ "typed-array-byte-length": "^1.0.3",
+ "typed-array-byte-offset": "^1.0.4",
+ "typed-array-length": "^1.0.7",
+ "unbox-primitive": "^1.1.0",
+ "which-typed-array": "^1.1.19"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/es-array-method-boxes-properly": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz",
+ "integrity": "sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==",
+ "license": "MIT"
+ },
+ "node_modules/es-define-property": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
+ "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-errors": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-object-atoms": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
+ "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-set-tostringtag": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
+ "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.6",
+ "has-tostringtag": "^1.0.2",
+ "hasown": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-to-primitive": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz",
+ "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==",
+ "license": "MIT",
+ "dependencies": {
+ "is-callable": "^1.2.7",
+ "is-date-object": "^1.0.5",
+ "is-symbol": "^1.0.4"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/escalade": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/escape-html": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+ "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
+ "license": "MIT"
+ },
+ "node_modules/esutils": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
+ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/etag": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
+ "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/express": {
+ "version": "4.21.2",
+ "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz",
+ "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==",
+ "license": "MIT",
+ "dependencies": {
+ "accepts": "~1.3.8",
+ "array-flatten": "1.1.1",
+ "body-parser": "1.20.3",
+ "content-disposition": "0.5.4",
+ "content-type": "~1.0.4",
+ "cookie": "0.7.1",
+ "cookie-signature": "1.0.6",
+ "debug": "2.6.9",
+ "depd": "2.0.0",
+ "encodeurl": "~2.0.0",
+ "escape-html": "~1.0.3",
+ "etag": "~1.8.1",
+ "finalhandler": "1.3.1",
+ "fresh": "0.5.2",
+ "http-errors": "2.0.0",
+ "merge-descriptors": "1.0.3",
+ "methods": "~1.1.2",
+ "on-finished": "2.4.1",
+ "parseurl": "~1.3.3",
+ "path-to-regexp": "0.1.12",
+ "proxy-addr": "~2.0.7",
+ "qs": "6.13.0",
+ "range-parser": "~1.2.1",
+ "safe-buffer": "5.2.1",
+ "send": "0.19.0",
+ "serve-static": "1.16.2",
+ "setprototypeof": "1.2.0",
+ "statuses": "2.0.1",
+ "type-is": "~1.6.18",
+ "utils-merge": "1.0.1",
+ "vary": "~1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.10.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/express/node_modules/debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "2.0.0"
+ }
+ },
+ "node_modules/express/node_modules/ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
+ "license": "MIT"
+ },
+ "node_modules/fill-range": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
+ "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "to-regex-range": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/finalhandler": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz",
+ "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "2.6.9",
+ "encodeurl": "~2.0.0",
+ "escape-html": "~1.0.3",
+ "on-finished": "2.4.1",
+ "parseurl": "~1.3.3",
+ "statuses": "2.0.1",
+ "unpipe": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/finalhandler/node_modules/debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "2.0.0"
+ }
+ },
+ "node_modules/finalhandler/node_modules/ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
+ "license": "MIT"
+ },
+ "node_modules/find-cache-dir": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz",
+ "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==",
+ "license": "MIT",
+ "dependencies": {
+ "commondir": "^1.0.1",
+ "make-dir": "^2.0.0",
+ "pkg-dir": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/find-up": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
+ "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
+ "license": "MIT",
+ "dependencies": {
+ "locate-path": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/follow-redirects": {
+ "version": "1.15.11",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
+ "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/RubenVerborgh"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=4.0"
+ },
+ "peerDependenciesMeta": {
+ "debug": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/for-each": {
+ "version": "0.3.5",
+ "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz",
+ "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==",
+ "license": "MIT",
+ "dependencies": {
+ "is-callable": "^1.2.7"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/form-data": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
+ "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
+ "license": "MIT",
+ "dependencies": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.8",
+ "es-set-tostringtag": "^2.1.0",
+ "hasown": "^2.0.2",
+ "mime-types": "^2.1.12"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/forwarded": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
+ "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/fresh": {
+ "version": "0.5.2",
+ "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
+ "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/function-bind": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/function.prototype.name": {
+ "version": "1.1.8",
+ "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz",
+ "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.3",
+ "define-properties": "^1.2.1",
+ "functions-have-names": "^1.2.3",
+ "hasown": "^2.0.2",
+ "is-callable": "^1.2.7"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/functions-have-names": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz",
+ "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/gensync": {
+ "version": "1.0.0-beta.2",
+ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
+ "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/get-intrinsic": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
+ "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "es-define-property": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.1.1",
+ "function-bind": "^1.1.2",
+ "get-proto": "^1.0.1",
+ "gopd": "^1.2.0",
+ "has-symbols": "^1.1.0",
+ "hasown": "^2.0.2",
+ "math-intrinsics": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
+ "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
+ "license": "MIT",
+ "dependencies": {
+ "dunder-proto": "^1.0.1",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/get-symbol-description": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz",
+ "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.3",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.6"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/globalthis": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz",
+ "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==",
+ "license": "MIT",
+ "dependencies": {
+ "define-properties": "^1.2.1",
+ "gopd": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/gopd": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
+ "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-bigints": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz",
+ "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-flag": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
+ "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/has-property-descriptors": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
+ "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
+ "license": "MIT",
+ "dependencies": {
+ "es-define-property": "^1.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-proto": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz",
+ "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==",
+ "license": "MIT",
+ "dependencies": {
+ "dunder-proto": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-symbols": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
+ "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-tostringtag": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
+ "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
+ "license": "MIT",
+ "dependencies": {
+ "has-symbols": "^1.0.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/hasown": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
+ "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+ "license": "MIT",
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/homedir-polyfill": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz",
+ "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==",
+ "license": "MIT",
+ "dependencies": {
+ "parse-passwd": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/http-errors": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
+ "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
+ "license": "MIT",
+ "dependencies": {
+ "depd": "2.0.0",
+ "inherits": "2.0.4",
+ "setprototypeof": "1.2.0",
+ "statuses": "2.0.1",
+ "toidentifier": "1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/iconv-lite": {
+ "version": "0.4.24",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
+ "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
+ "license": "MIT",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/ignore-by-default": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz",
+ "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+ "license": "ISC"
+ },
+ "node_modules/internal-slot": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz",
+ "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "hasown": "^2.0.2",
+ "side-channel": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/ipaddr.js": {
+ "version": "1.9.1",
+ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
+ "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/is-array-buffer": {
+ "version": "3.0.5",
+ "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz",
+ "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.3",
+ "get-intrinsic": "^1.2.6"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-async-function": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz",
+ "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==",
+ "license": "MIT",
+ "dependencies": {
+ "async-function": "^1.0.0",
+ "call-bound": "^1.0.3",
+ "get-proto": "^1.0.1",
+ "has-tostringtag": "^1.0.2",
+ "safe-regex-test": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-bigint": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz",
+ "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==",
+ "license": "MIT",
+ "dependencies": {
+ "has-bigints": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-binary-path": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
+ "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "binary-extensions": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-boolean-object": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz",
+ "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.3",
+ "has-tostringtag": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-callable": {
+ "version": "1.2.7",
+ "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz",
+ "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-core-module": {
+ "version": "2.16.1",
+ "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
+ "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
+ "license": "MIT",
+ "dependencies": {
+ "hasown": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-data-view": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz",
+ "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "get-intrinsic": "^1.2.6",
+ "is-typed-array": "^1.1.13"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-date-object": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz",
+ "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "has-tostringtag": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-finalizationregistry": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz",
+ "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-generator-function": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz",
+ "integrity": "sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.3",
+ "get-proto": "^1.0.0",
+ "has-tostringtag": "^1.0.2",
+ "safe-regex-test": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-glob": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-extglob": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-map": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz",
+ "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-negative-zero": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz",
+ "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.12.0"
+ }
+ },
+ "node_modules/is-number-object": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz",
+ "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.3",
+ "has-tostringtag": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-plain-object": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz",
+ "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==",
+ "license": "MIT",
+ "dependencies": {
+ "isobject": "^3.0.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-regex": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz",
+ "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "gopd": "^1.2.0",
+ "has-tostringtag": "^1.0.2",
+ "hasown": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-set": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz",
+ "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-shared-array-buffer": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz",
+ "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-string": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz",
+ "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.3",
+ "has-tostringtag": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-symbol": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz",
+ "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "has-symbols": "^1.1.0",
+ "safe-regex-test": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-typed-array": {
+ "version": "1.1.15",
+ "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz",
+ "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==",
+ "license": "MIT",
+ "dependencies": {
+ "which-typed-array": "^1.1.16"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-weakmap": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz",
+ "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-weakref": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz",
+ "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-weakset": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz",
+ "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.3",
+ "get-intrinsic": "^1.2.6"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/isarray": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz",
+ "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==",
+ "license": "MIT"
+ },
+ "node_modules/isobject": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
+ "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+ "license": "MIT"
+ },
+ "node_modules/jsesc": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
+ "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
+ "license": "MIT",
+ "bin": {
+ "jsesc": "bin/jsesc"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/json5": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
+ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
+ "license": "MIT",
+ "bin": {
+ "json5": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/jsonwebtoken": {
+ "version": "9.0.2",
+ "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz",
+ "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==",
+ "license": "MIT",
+ "dependencies": {
+ "jws": "^3.2.2",
+ "lodash.includes": "^4.3.0",
+ "lodash.isboolean": "^3.0.3",
+ "lodash.isinteger": "^4.0.4",
+ "lodash.isnumber": "^3.0.3",
+ "lodash.isplainobject": "^4.0.6",
+ "lodash.isstring": "^4.0.1",
+ "lodash.once": "^4.0.0",
+ "ms": "^2.1.1",
+ "semver": "^7.5.4"
+ },
+ "engines": {
+ "node": ">=12",
+ "npm": ">=6"
+ }
+ },
+ "node_modules/jsonwebtoken/node_modules/semver": {
+ "version": "7.7.2",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
+ "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/jwa": {
+ "version": "1.4.2",
+ "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.2.tgz",
+ "integrity": "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==",
+ "license": "MIT",
+ "dependencies": {
+ "buffer-equal-constant-time": "^1.0.1",
+ "ecdsa-sig-formatter": "1.0.11",
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "node_modules/jws": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz",
+ "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==",
+ "license": "MIT",
+ "dependencies": {
+ "jwa": "^1.4.1",
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "node_modules/kareem": {
+ "version": "2.6.3",
+ "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.6.3.tgz",
+ "integrity": "sha512-C3iHfuGUXK2u8/ipq9LfjFfXFxAZMQJJq7vLS45r3D9Y2xQ/m4S8zaR4zMLFWh9AsNPXmcFfUDhTEO8UIC/V6Q==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
+ "node_modules/kind-of": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
+ "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/locate-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
+ "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
+ "license": "MIT",
+ "dependencies": {
+ "p-locate": "^3.0.0",
+ "path-exists": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/lodash.debounce": {
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
+ "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==",
+ "license": "MIT"
+ },
+ "node_modules/lodash.includes": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
+ "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==",
+ "license": "MIT"
+ },
+ "node_modules/lodash.isboolean": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
+ "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==",
+ "license": "MIT"
+ },
+ "node_modules/lodash.isinteger": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
+ "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==",
+ "license": "MIT"
+ },
+ "node_modules/lodash.isnumber": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
+ "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==",
+ "license": "MIT"
+ },
+ "node_modules/lodash.isplainobject": {
+ "version": "4.0.6",
+ "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
+ "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==",
+ "license": "MIT"
+ },
+ "node_modules/lodash.isstring": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
+ "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==",
+ "license": "MIT"
+ },
+ "node_modules/lodash.once": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
+ "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==",
+ "license": "MIT"
+ },
+ "node_modules/lru-cache": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
+ "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+ "license": "ISC",
+ "dependencies": {
+ "yallist": "^3.0.2"
+ }
+ },
+ "node_modules/make-dir": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz",
+ "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==",
+ "license": "MIT",
+ "dependencies": {
+ "pify": "^4.0.1",
+ "semver": "^5.6.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/make-dir/node_modules/semver": {
+ "version": "5.7.2",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz",
+ "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==",
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver"
+ }
+ },
+ "node_modules/math-intrinsics": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
+ "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/media-typer": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
+ "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/memory-pager": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz",
+ "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==",
+ "license": "MIT"
+ },
+ "node_modules/merge-descriptors": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
+ "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/methods": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
+ "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
+ "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
+ "license": "MIT",
+ "bin": {
+ "mime": "cli.js"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/mime-db": {
+ "version": "1.52.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "2.1.35",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "1.52.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/mongodb": {
+ "version": "6.18.0",
+ "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.18.0.tgz",
+ "integrity": "sha512-fO5ttN9VC8P0F5fqtQmclAkgXZxbIkYRTUi1j8JO6IYwvamkhtYDilJr35jOPELR49zqCJgXZWwCtW7B+TM8vQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@mongodb-js/saslprep": "^1.1.9",
+ "bson": "^6.10.4",
+ "mongodb-connection-string-url": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=16.20.1"
+ },
+ "peerDependencies": {
+ "@aws-sdk/credential-providers": "^3.188.0",
+ "@mongodb-js/zstd": "^1.1.0 || ^2.0.0",
+ "gcp-metadata": "^5.2.0",
+ "kerberos": "^2.0.1",
+ "mongodb-client-encryption": ">=6.0.0 <7",
+ "snappy": "^7.2.2",
+ "socks": "^2.7.1"
+ },
+ "peerDependenciesMeta": {
+ "@aws-sdk/credential-providers": {
+ "optional": true
+ },
+ "@mongodb-js/zstd": {
+ "optional": true
+ },
+ "gcp-metadata": {
+ "optional": true
+ },
+ "kerberos": {
+ "optional": true
+ },
+ "mongodb-client-encryption": {
+ "optional": true
+ },
+ "snappy": {
+ "optional": true
+ },
+ "socks": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/mongodb-connection-string-url": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.2.tgz",
+ "integrity": "sha512-rMO7CGo/9BFwyZABcKAWL8UJwH/Kc2x0g72uhDWzG48URRax5TCIcJ7Rc3RZqffZzO/Gwff/jyKwCU9TN8gehA==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@types/whatwg-url": "^11.0.2",
+ "whatwg-url": "^14.1.0 || ^13.0.0"
+ }
+ },
+ "node_modules/mongoose": {
+ "version": "8.17.0",
+ "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.17.0.tgz",
+ "integrity": "sha512-mxW6TBPHViORfNYOFXCVOnT4d5aRr+CgDxTs1ViYXfuHzNpkelgJQrQa+Lz6hofoEQISnKlXv1L3ZnHyJRkhfA==",
+ "license": "MIT",
+ "dependencies": {
+ "bson": "^6.10.4",
+ "kareem": "2.6.3",
+ "mongodb": "~6.18.0",
+ "mpath": "0.9.0",
+ "mquery": "5.0.0",
+ "ms": "2.1.3",
+ "sift": "17.1.3"
+ },
+ "engines": {
+ "node": ">=16.20.1"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/mongoose"
+ }
+ },
+ "node_modules/mpath": {
+ "version": "0.9.0",
+ "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.9.0.tgz",
+ "integrity": "sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=4.0.0"
+ }
+ },
+ "node_modules/mquery": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/mquery/-/mquery-5.0.0.tgz",
+ "integrity": "sha512-iQMncpmEK8R8ncT8HJGsGc9Dsp8xcgYMVSbs5jgnm1lFHTZqMJTUWTDx1LBO8+mK3tPNZWFLBghQEIOULSTHZg==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "4.x"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "license": "MIT"
+ },
+ "node_modules/negotiator": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
+ "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/node-cron": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/node-cron/-/node-cron-4.2.1.tgz",
+ "integrity": "sha512-lgimEHPE/QDgFlywTd8yTR61ptugX3Qer29efeyWw2rv259HtGBNn1vZVmp8lB9uo9wC0t/AT4iGqXxia+CJFg==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/node-environment-flags": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.6.tgz",
+ "integrity": "sha512-5Evy2epuL+6TM0lCQGpFIj6KwiEsGh1SrHUhTbNX+sLbBtjidPZFAnVK9y5yU1+h//RitLbRHTIMyxQPtxMdHw==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "object.getownpropertydescriptors": "^2.0.3",
+ "semver": "^5.7.0"
+ }
+ },
+ "node_modules/node-environment-flags/node_modules/semver": {
+ "version": "5.7.2",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz",
+ "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==",
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver"
+ }
+ },
+ "node_modules/node-releases": {
+ "version": "2.0.19",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz",
+ "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==",
+ "license": "MIT"
+ },
+ "node_modules/nodemailer": {
+ "version": "7.0.5",
+ "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-7.0.5.tgz",
+ "integrity": "sha512-nsrh2lO3j4GkLLXoeEksAMgAOqxOv6QumNRVQTJwKH4nuiww6iC2y7GyANs9kRAxCexg3+lTWM3PZ91iLlVjfg==",
+ "license": "MIT-0",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/nodemon": {
+ "version": "3.1.10",
+ "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.10.tgz",
+ "integrity": "sha512-WDjw3pJ0/0jMFmyNDp3gvY2YizjLmmOUQo6DEBY+JgdvW/yQ9mEeSw6H5ythl5Ny2ytb7f9C2nIbjSxMNzbJXw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "chokidar": "^3.5.2",
+ "debug": "^4",
+ "ignore-by-default": "^1.0.1",
+ "minimatch": "^3.1.2",
+ "pstree.remy": "^1.1.8",
+ "semver": "^7.5.3",
+ "simple-update-notifier": "^2.0.0",
+ "supports-color": "^5.5.0",
+ "touch": "^3.1.0",
+ "undefsafe": "^2.0.5"
+ },
+ "bin": {
+ "nodemon": "bin/nodemon.js"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/nodemon"
+ }
+ },
+ "node_modules/nodemon/node_modules/semver": {
+ "version": "7.7.2",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
+ "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/normalize-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/object-assign": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+ "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/object-inspect": {
+ "version": "1.13.4",
+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
+ "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/object-keys": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
+ "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/object.assign": {
+ "version": "4.1.7",
+ "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz",
+ "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.3",
+ "define-properties": "^1.2.1",
+ "es-object-atoms": "^1.0.0",
+ "has-symbols": "^1.1.0",
+ "object-keys": "^1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/object.getownpropertydescriptors": {
+ "version": "2.1.8",
+ "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.8.tgz",
+ "integrity": "sha512-qkHIGe4q0lSYMv0XI4SsBTJz3WaURhLvd0lKSgtVuOsJ2krg4SgMw3PIRQFMp07yi++UR3se2mkcLqsBNpBb/A==",
+ "license": "MIT",
+ "dependencies": {
+ "array.prototype.reduce": "^1.0.6",
+ "call-bind": "^1.0.7",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.2",
+ "es-object-atoms": "^1.0.0",
+ "gopd": "^1.0.1",
+ "safe-array-concat": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/on-finished": {
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
+ "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
+ "license": "MIT",
+ "dependencies": {
+ "ee-first": "1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/own-keys": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz",
+ "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==",
+ "license": "MIT",
+ "dependencies": {
+ "get-intrinsic": "^1.2.6",
+ "object-keys": "^1.1.1",
+ "safe-push-apply": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/p-limit": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
+ "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
+ "license": "MIT",
+ "dependencies": {
+ "p-try": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-locate": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz",
+ "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
+ "license": "MIT",
+ "dependencies": {
+ "p-limit": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/p-try": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
+ "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/parse-passwd": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz",
+ "integrity": "sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/parseurl": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
+ "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/path-exists": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
+ "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/path-parse": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
+ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
+ "license": "MIT"
+ },
+ "node_modules/path-to-regexp": {
+ "version": "0.1.12",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz",
+ "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==",
+ "license": "MIT"
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "license": "ISC"
+ },
+ "node_modules/picomatch": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/pify": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz",
+ "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/pirates": {
+ "version": "4.0.7",
+ "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz",
+ "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/pkg-dir": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz",
+ "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==",
+ "license": "MIT",
+ "dependencies": {
+ "find-up": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/possible-typed-array-names": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz",
+ "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/proxy-addr": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
+ "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
+ "license": "MIT",
+ "dependencies": {
+ "forwarded": "0.2.0",
+ "ipaddr.js": "1.9.1"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/proxy-from-env": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
+ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
+ "license": "MIT"
+ },
+ "node_modules/pstree.remy": {
+ "version": "1.1.8",
+ "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz",
+ "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/punycode": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
+ "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/qs": {
+ "version": "6.13.0",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
+ "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "side-channel": "^1.0.6"
+ },
+ "engines": {
+ "node": ">=0.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/range-parser": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
+ "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/raw-body": {
+ "version": "2.5.2",
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz",
+ "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==",
+ "license": "MIT",
+ "dependencies": {
+ "bytes": "3.1.2",
+ "http-errors": "2.0.0",
+ "iconv-lite": "0.4.24",
+ "unpipe": "1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/readdirp": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
+ "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "picomatch": "^2.2.1"
+ },
+ "engines": {
+ "node": ">=8.10.0"
+ }
+ },
+ "node_modules/reflect.getprototypeof": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz",
+ "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.9",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.0.0",
+ "get-intrinsic": "^1.2.7",
+ "get-proto": "^1.0.1",
+ "which-builtin-type": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/regenerate": {
+ "version": "1.4.2",
+ "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz",
+ "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==",
+ "license": "MIT"
+ },
+ "node_modules/regenerate-unicode-properties": {
+ "version": "10.2.0",
+ "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.0.tgz",
+ "integrity": "sha512-DqHn3DwbmmPVzeKj9woBadqmXxLvQoQIwu7nopMc72ztvxVmVk2SBhSnx67zuye5TP+lJsb/TBQsjLKhnDf3MA==",
+ "license": "MIT",
+ "dependencies": {
+ "regenerate": "^1.4.2"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/regenerator-runtime": {
+ "version": "0.14.1",
+ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
+ "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==",
+ "license": "MIT"
+ },
+ "node_modules/regexp.prototype.flags": {
+ "version": "1.5.4",
+ "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz",
+ "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "define-properties": "^1.2.1",
+ "es-errors": "^1.3.0",
+ "get-proto": "^1.0.1",
+ "gopd": "^1.2.0",
+ "set-function-name": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/regexpu-core": {
+ "version": "6.2.0",
+ "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.2.0.tgz",
+ "integrity": "sha512-H66BPQMrv+V16t8xtmq+UC0CBpiTBA60V8ibS1QVReIp8T1z8hwFxqcGzm9K6lgsN7sB5edVH8a+ze6Fqm4weA==",
+ "license": "MIT",
+ "dependencies": {
+ "regenerate": "^1.4.2",
+ "regenerate-unicode-properties": "^10.2.0",
+ "regjsgen": "^0.8.0",
+ "regjsparser": "^0.12.0",
+ "unicode-match-property-ecmascript": "^2.0.0",
+ "unicode-match-property-value-ecmascript": "^2.1.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/regjsgen": {
+ "version": "0.8.0",
+ "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.8.0.tgz",
+ "integrity": "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==",
+ "license": "MIT"
+ },
+ "node_modules/regjsparser": {
+ "version": "0.12.0",
+ "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.12.0.tgz",
+ "integrity": "sha512-cnE+y8bz4NhMjISKbgeVJtqNbtf5QpjZP+Bslo+UqkIt9QPnX9q095eiRRASJG1/tz6dlNr6Z5NsBiWYokp6EQ==",
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "jsesc": "~3.0.2"
+ },
+ "bin": {
+ "regjsparser": "bin/parser"
+ }
+ },
+ "node_modules/regjsparser/node_modules/jsesc": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz",
+ "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==",
+ "license": "MIT",
+ "bin": {
+ "jsesc": "bin/jsesc"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/resolve": {
+ "version": "1.22.10",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
+ "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==",
+ "license": "MIT",
+ "dependencies": {
+ "is-core-module": "^2.16.0",
+ "path-parse": "^1.0.7",
+ "supports-preserve-symlinks-flag": "^1.0.0"
+ },
+ "bin": {
+ "resolve": "bin/resolve"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/safe-array-concat": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz",
+ "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.2",
+ "get-intrinsic": "^1.2.6",
+ "has-symbols": "^1.1.0",
+ "isarray": "^2.0.5"
+ },
+ "engines": {
+ "node": ">=0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/safe-buffer": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/safe-push-apply": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz",
+ "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "isarray": "^2.0.5"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/safe-regex-test": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz",
+ "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "is-regex": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+ "license": "MIT"
+ },
+ "node_modules/semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/send": {
+ "version": "0.19.0",
+ "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz",
+ "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "2.6.9",
+ "depd": "2.0.0",
+ "destroy": "1.2.0",
+ "encodeurl": "~1.0.2",
+ "escape-html": "~1.0.3",
+ "etag": "~1.8.1",
+ "fresh": "0.5.2",
+ "http-errors": "2.0.0",
+ "mime": "1.6.0",
+ "ms": "2.1.3",
+ "on-finished": "2.4.1",
+ "range-parser": "~1.2.1",
+ "statuses": "2.0.1"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/send/node_modules/debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "2.0.0"
+ }
+ },
+ "node_modules/send/node_modules/debug/node_modules/ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
+ "license": "MIT"
+ },
+ "node_modules/send/node_modules/encodeurl": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
+ "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/serve-static": {
+ "version": "1.16.2",
+ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz",
+ "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==",
+ "license": "MIT",
+ "dependencies": {
+ "encodeurl": "~2.0.0",
+ "escape-html": "~1.0.3",
+ "parseurl": "~1.3.3",
+ "send": "0.19.0"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/set-function-length": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
+ "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==",
+ "license": "MIT",
+ "dependencies": {
+ "define-data-property": "^1.1.4",
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2",
+ "get-intrinsic": "^1.2.4",
+ "gopd": "^1.0.1",
+ "has-property-descriptors": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/set-function-name": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz",
+ "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==",
+ "license": "MIT",
+ "dependencies": {
+ "define-data-property": "^1.1.4",
+ "es-errors": "^1.3.0",
+ "functions-have-names": "^1.2.3",
+ "has-property-descriptors": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/set-proto": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz",
+ "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==",
+ "license": "MIT",
+ "dependencies": {
+ "dunder-proto": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/setprototypeof": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
+ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
+ "license": "ISC"
+ },
+ "node_modules/shallow-clone": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz",
+ "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==",
+ "license": "MIT",
+ "dependencies": {
+ "kind-of": "^6.0.2"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/side-channel": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
+ "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "object-inspect": "^1.13.3",
+ "side-channel-list": "^1.0.0",
+ "side-channel-map": "^1.0.1",
+ "side-channel-weakmap": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-list": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
+ "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "object-inspect": "^1.13.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-map": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
+ "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.5",
+ "object-inspect": "^1.13.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-weakmap": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
+ "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.5",
+ "object-inspect": "^1.13.3",
+ "side-channel-map": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/sift": {
+ "version": "17.1.3",
+ "resolved": "https://registry.npmjs.org/sift/-/sift-17.1.3.tgz",
+ "integrity": "sha512-Rtlj66/b0ICeFzYTuNvX/EF1igRbbnGSvEyT79McoZa/DeGhMyC5pWKOEsZKnpkqtSeovd5FL/bjHWC3CIIvCQ==",
+ "license": "MIT"
+ },
+ "node_modules/simple-update-notifier": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz",
+ "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "semver": "^7.5.3"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/simple-update-notifier/node_modules/semver": {
+ "version": "7.7.2",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
+ "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/source-map-support": {
+ "version": "0.5.21",
+ "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
+ "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
+ "license": "MIT",
+ "dependencies": {
+ "buffer-from": "^1.0.0",
+ "source-map": "^0.6.0"
+ }
+ },
+ "node_modules/sparse-bitfield": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz",
+ "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==",
+ "license": "MIT",
+ "dependencies": {
+ "memory-pager": "^1.0.2"
+ }
+ },
+ "node_modules/statuses": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
+ "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/stop-iteration-iterator": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz",
+ "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "internal-slot": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/string.prototype.trim": {
+ "version": "1.2.10",
+ "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz",
+ "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.2",
+ "define-data-property": "^1.1.4",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.5",
+ "es-object-atoms": "^1.0.0",
+ "has-property-descriptors": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/string.prototype.trimend": {
+ "version": "1.0.9",
+ "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz",
+ "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.2",
+ "define-properties": "^1.2.1",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/string.prototype.trimstart": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz",
+ "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.7",
+ "define-properties": "^1.2.1",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-flag": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/supports-preserve-symlinks-flag": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
+ "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/to-regex-range": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-number": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=8.0"
+ }
+ },
+ "node_modules/toidentifier": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
+ "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.6"
+ }
+ },
+ "node_modules/touch": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz",
+ "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "nodetouch": "bin/nodetouch.js"
+ }
+ },
+ "node_modules/tr46": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz",
+ "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==",
+ "license": "MIT",
+ "dependencies": {
+ "punycode": "^2.3.1"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/type-is": {
+ "version": "1.6.18",
+ "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
+ "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
+ "license": "MIT",
+ "dependencies": {
+ "media-typer": "0.3.0",
+ "mime-types": "~2.1.24"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/typed-array-buffer": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz",
+ "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.3",
+ "es-errors": "^1.3.0",
+ "is-typed-array": "^1.1.14"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/typed-array-byte-length": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz",
+ "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "for-each": "^0.3.3",
+ "gopd": "^1.2.0",
+ "has-proto": "^1.2.0",
+ "is-typed-array": "^1.1.14"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/typed-array-byte-offset": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz",
+ "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==",
+ "license": "MIT",
+ "dependencies": {
+ "available-typed-arrays": "^1.0.7",
+ "call-bind": "^1.0.8",
+ "for-each": "^0.3.3",
+ "gopd": "^1.2.0",
+ "has-proto": "^1.2.0",
+ "is-typed-array": "^1.1.15",
+ "reflect.getprototypeof": "^1.0.9"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/typed-array-length": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz",
+ "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.7",
+ "for-each": "^0.3.3",
+ "gopd": "^1.0.1",
+ "is-typed-array": "^1.1.13",
+ "possible-typed-array-names": "^1.0.0",
+ "reflect.getprototypeof": "^1.0.6"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/unbox-primitive": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz",
+ "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.3",
+ "has-bigints": "^1.0.2",
+ "has-symbols": "^1.1.0",
+ "which-boxed-primitive": "^1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/undefsafe": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz",
+ "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/unicode-canonical-property-names-ecmascript": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz",
+ "integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/unicode-match-property-ecmascript": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz",
+ "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==",
+ "license": "MIT",
+ "dependencies": {
+ "unicode-canonical-property-names-ecmascript": "^2.0.0",
+ "unicode-property-aliases-ecmascript": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/unicode-match-property-value-ecmascript": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.0.tgz",
+ "integrity": "sha512-4IehN3V/+kkr5YeSSDDQG8QLqO26XpL2XP3GQtqwlT/QYSECAwFztxVHjlbh0+gjJ3XmNLS0zDsbgs9jWKExLg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/unicode-property-aliases-ecmascript": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz",
+ "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/unpipe": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
+ "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/update-browserslist-db": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz",
+ "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "escalade": "^3.2.0",
+ "picocolors": "^1.1.1"
+ },
+ "bin": {
+ "update-browserslist-db": "cli.js"
+ },
+ "peerDependencies": {
+ "browserslist": ">= 4.21.0"
+ }
+ },
+ "node_modules/utils-merge": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
+ "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4.0"
+ }
+ },
+ "node_modules/v8flags": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-3.2.0.tgz",
+ "integrity": "sha512-mH8etigqMfiGWdeXpaaqGfs6BndypxusHHcv2qSHyZkGEznCd/qAXCWWRzeowtL54147cktFOC4P5y+kl8d8Jg==",
+ "license": "MIT",
+ "dependencies": {
+ "homedir-polyfill": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/vary": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
+ "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/webidl-conversions": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
+ "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/whatwg-url": {
+ "version": "14.2.0",
+ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz",
+ "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==",
+ "license": "MIT",
+ "dependencies": {
+ "tr46": "^5.1.0",
+ "webidl-conversions": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/which-boxed-primitive": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz",
+ "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==",
+ "license": "MIT",
+ "dependencies": {
+ "is-bigint": "^1.1.0",
+ "is-boolean-object": "^1.2.1",
+ "is-number-object": "^1.1.1",
+ "is-string": "^1.1.1",
+ "is-symbol": "^1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/which-builtin-type": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz",
+ "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "function.prototype.name": "^1.1.6",
+ "has-tostringtag": "^1.0.2",
+ "is-async-function": "^2.0.0",
+ "is-date-object": "^1.1.0",
+ "is-finalizationregistry": "^1.1.0",
+ "is-generator-function": "^1.0.10",
+ "is-regex": "^1.2.1",
+ "is-weakref": "^1.0.2",
+ "isarray": "^2.0.5",
+ "which-boxed-primitive": "^1.1.0",
+ "which-collection": "^1.0.2",
+ "which-typed-array": "^1.1.16"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/which-collection": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz",
+ "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==",
+ "license": "MIT",
+ "dependencies": {
+ "is-map": "^2.0.3",
+ "is-set": "^2.0.3",
+ "is-weakmap": "^2.0.2",
+ "is-weakset": "^2.0.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/which-typed-array": {
+ "version": "1.1.19",
+ "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz",
+ "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==",
+ "license": "MIT",
+ "dependencies": {
+ "available-typed-arrays": "^1.0.7",
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.4",
+ "for-each": "^0.3.5",
+ "get-proto": "^1.0.1",
+ "gopd": "^1.2.0",
+ "has-tostringtag": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/yallist": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
+ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
+ "license": "ISC"
+ }
+ }
+}
diff --git a/backend/package.json b/backend/package.json
index 08f29f2448..29a99d8906 100644
--- a/backend/package.json
+++ b/backend/package.json
@@ -2,6 +2,7 @@
"name": "project-final-backend",
"version": "1.0.0",
"description": "Server part of final project",
+ "type": "module",
"scripts": {
"start": "babel-node server.js",
"dev": "nodemon server.js --exec babel-node"
@@ -12,9 +13,18 @@
"@babel/core": "^7.17.9",
"@babel/node": "^7.16.8",
"@babel/preset-env": "^7.16.11",
+ "axios": "^1.11.0",
+ "bcryptjs": "^3.0.2",
+ "cookie-parser": "^1.4.7",
"cors": "^2.8.5",
- "express": "^4.17.3",
- "mongoose": "^8.4.0",
- "nodemon": "^3.0.1"
+ "dotenv": "^17.2.1",
+ "express": "^4.21.2",
+ "jsonwebtoken": "^9.0.2",
+ "mongoose": "^8.17.0",
+ "node-cron": "^4.2.1",
+ "nodemailer": "^7.0.5"
+ },
+ "devDependencies": {
+ "nodemon": "^3.1.10"
}
-}
\ No newline at end of file
+}
diff --git a/backend/routes/auth.js b/backend/routes/auth.js
new file mode 100644
index 0000000000..924d83c2b2
--- /dev/null
+++ b/backend/routes/auth.js
@@ -0,0 +1,286 @@
+import express from "express";
+import bcrypt from "bcryptjs";
+import User from "../models/User.js";
+import authMiddleware from "../middleware/auth.js";
+import { createSession, getUserFromSession, deleteSession } from "../utils/session.js";
+import { getCookieConfig, getClearCookieConfig } from "../utils/cookieConfig.js";
+import { generateToken } from "../utils/jwt.js";
+
+const router = express.Router();
+
+// Register a new user
+router.post("/register", async (req, res) => {
+ try {
+ const { email, password, name } = req.body;
+
+ // Validation
+ if (!email || !password || !name) {
+ return res.status(400).json({ message: "All fields are required" });
+ }
+
+ // Check if user already exists
+ const existingUser = await User.findOne({ email });
+ if (existingUser) {
+ return res.status(400).json({ message: "User already exists" });
+ }
+
+ // Create new user (password will be hashed by middleware)
+ const newUser = new User({ email, password, name });
+ await newUser.save();
+
+ // Create session
+ const sessionId = await createSession(newUser._id);
+
+ // Generate JWT token for cross-domain support
+ const token = generateToken(newUser._id);
+
+ // Set httpOnly cookie (fallback for same-domain)
+ res.cookie('sessionId', sessionId, getCookieConfig());
+
+ res.status(201).json({
+ user: {
+ id: newUser._id,
+ email: newUser.email,
+ name: newUser.name,
+ createdAt: newUser.createdAt
+ },
+ token // Send JWT token for localStorage
+ });
+ } catch (error) {
+ res.status(500).json({ message: "Server error" });
+ }
+});
+
+// Login an existing user
+router.post("/login", async (req, res) => {
+ try {
+ const { email, password } = req.body;
+
+ // Validation
+ if (!email || !password) {
+ return res.status(400).json({ message: "Email and password are required" });
+ }
+
+ // Find user by email
+ const user = await User.findOne({ email });
+ if (!user) {
+ return res.status(400).json({ message: "Account not found. It may have been deleted due to inactivity. You can create a new account with this email." });
+ }
+
+ // Compare passwords using the schema method
+ const isMatch = await user.comparePassword(password);
+ if (!isMatch) {
+ return res.status(400).json({ message: "Invalid credentials" });
+ }
+
+ // Update last login timestamp
+ user.lastLogin = new Date();
+ await user.save();
+
+ // Create session
+ const sessionId = await createSession(user._id);
+
+ // Generate JWT token for cross-domain support
+ const token = generateToken(user._id);
+
+ // Set httpOnly cookie (fallback for same-domain)
+ res.cookie('sessionId', sessionId, getCookieConfig());
+
+ res.json({
+ user: {
+ id: user._id,
+ email: user.email,
+ name: user.name,
+ createdAt: user.createdAt
+ },
+ token // Send JWT token for localStorage
+ });
+ } catch (error) {
+ res.status(500).json({ message: "Server error" });
+ }
+});
+
+// Logout user
+router.post("/logout", async (req, res) => {
+ try {
+ const sessionId = req.cookies.sessionId;
+ if (sessionId) {
+ await deleteSession(sessionId);
+ }
+
+ res.clearCookie('sessionId', getClearCookieConfig());
+ res.json({ message: "Logged out successfully" });
+ } catch (error) {
+ res.status(500).json({ message: "Server error" });
+ }
+});
+
+// Debug endpoint - check authentication status
+router.get("/check", async (req, res) => {
+ const sessionId = req.cookies?.sessionId;
+ const authHeader = req.headers.authorization;
+
+ res.json({
+ hasCookie: !!sessionId,
+ cookieValue: sessionId ? "exists" : "missing",
+ hasAuthHeader: !!authHeader,
+ headers: {
+ origin: req.headers.origin,
+ referer: req.headers.referer,
+ userAgent: req.headers['user-agent']
+ },
+ cors: {
+ credentials: req.credentials,
+ origin: req.headers.origin
+ }
+ });
+});
+
+// Get current user
+router.get("/me", authMiddleware, async (req, res) => {
+ try {
+ const user = await User.findById(req.userId);
+ if (!user) {
+ return res.status(404).json({ message: "User not found" });
+ }
+
+ res.json({
+ id: user._id,
+ email: user.email,
+ name: user.name,
+ favoritesCount: user.favorites.length,
+ createdAt: user.createdAt,
+ lastLogin: user.lastLogin
+ });
+ } catch (error) {
+ res.status(500).json({ message: "Server error" });
+ }
+});
+
+// Add product to favorites
+router.post("/favorites", authMiddleware, async (req, res) => {
+ try {
+ const { sellableId, name, productTypeName, price, previewImage } = req.body;
+
+ const user = await User.findById(req.userId);
+ if (!user) {
+ return res.status(404).json({ message: "User not found" });
+ }
+
+ // Check if already in favorites
+ const existingFavorite = user.favorites.find(
+ fav => fav.sellableId === sellableId
+ );
+
+ if (existingFavorite) {
+ return res.status(400).json({ message: "Product already in favorites" });
+ }
+
+ // Add to favorites
+ user.favorites.push({
+ sellableId,
+ name,
+ productTypeName,
+ price,
+ previewImage
+ });
+
+ await user.save();
+
+ res.json({ message: "Added to favorites", favoritesCount: user.favorites.length });
+ } catch (error) {
+ res.status(500).json({ message: "Server error" });
+ }
+});
+
+// Remove product from favorites
+router.delete("/favorites/:sellableId", authMiddleware, async (req, res) => {
+ try {
+ const { sellableId } = req.params;
+
+ const user = await User.findById(req.userId);
+ if (!user) {
+ return res.status(404).json({ message: "User not found" });
+ }
+
+ // Remove from favorites
+ user.favorites = user.favorites.filter(
+ fav => fav.sellableId !== sellableId
+ );
+
+ await user.save();
+
+ res.json({ message: "Removed from favorites", favoritesCount: user.favorites.length });
+ } catch (error) {
+ res.status(500).json({ message: "Server error" });
+ }
+});
+
+// Get user's favorites
+router.get("/favorites", authMiddleware, async (req, res) => {
+ try {
+ const user = await User.findById(req.userId);
+ if (!user) {
+ return res.status(404).json({ message: "User not found" });
+ }
+
+ res.json(user.favorites);
+ } catch (error) {
+ res.status(500).json({ message: "Server error" });
+ }
+});
+
+// Change password
+router.post("/change-password", authMiddleware, async (req, res) => {
+ try {
+ const { currentPassword, newPassword } = req.body;
+
+ // Validation
+ if (!currentPassword || !newPassword) {
+ return res.status(400).json({ message: "Current password and new password are required" });
+ }
+
+ if (newPassword.length < 6) {
+ return res.status(400).json({ message: "New password must be at least 6 characters long" });
+ }
+
+ // Find user
+ const user = await User.findById(req.userId);
+ if (!user) {
+ return res.status(404).json({ message: "User not found" });
+ }
+
+ // Verify current password
+ const isMatch = await user.comparePassword(currentPassword);
+ if (!isMatch) {
+ return res.status(400).json({ message: "Current password is incorrect" });
+ }
+
+ // Update password (will be hashed by the pre-save middleware)
+ user.password = newPassword;
+ await user.save();
+
+ res.json({ message: "Password changed successfully" });
+ } catch (error) {
+ res.status(500).json({ message: "Server error" });
+ }
+});
+
+// Delete account
+router.delete("/delete-account", authMiddleware, async (req, res) => {
+ try {
+ const user = await User.findById(req.userId);
+ if (!user) {
+ return res.status(404).json({ message: "User not found" });
+ }
+
+ // Delete the user
+ await User.findByIdAndDelete(req.userId);
+
+ res.json({ message: "Account deleted successfully" });
+ } catch (error) {
+ res.status(500).json({ message: "Server error" });
+ }
+});
+
+export default router;
diff --git a/backend/routes/cleanup.js b/backend/routes/cleanup.js
new file mode 100644
index 0000000000..a2098ea1dd
--- /dev/null
+++ b/backend/routes/cleanup.js
@@ -0,0 +1,71 @@
+import express from "express";
+import User from "../models/User.js";
+
+const router = express.Router();
+
+// Cleanup inactive accounts
+router.post("/inactive-accounts", async (req, res) => {
+ try {
+ // Calculate the cutoff date (default: 30 months ago)
+ const monthsInactive = parseInt(process.env.INACTIVE_MONTHS) || 30;
+ const cutoffDate = new Date();
+ cutoffDate.setMonth(cutoffDate.getMonth() - monthsInactive);
+
+
+ // Find inactive accounts
+ const inactiveUsers = await User.find({
+ lastLogin: { $lt: cutoffDate }
+ });
+
+
+ if (inactiveUsers.length === 0) {
+ return res.json({
+ message: "No inactive accounts found",
+ deletedCount: 0,
+ cutoffDate: cutoffDate
+ });
+ }
+
+
+ const result = await User.deleteMany({
+ lastLogin: { $lt: cutoffDate }
+ });
+
+
+ res.json({
+ message: `Successfully deleted ${result.deletedCount} inactive accounts`,
+ deletedCount: result.deletedCount,
+ cutoffDate: cutoffDate
+ });
+
+ } catch (error) {
+ res.status(500).json({ message: "Server error during cleanup" });
+ }
+});
+
+// Get statistics about account activity
+router.get("/account-stats", async (req, res) => {
+ try {
+ const monthsInactive = parseInt(process.env.INACTIVE_MONTHS) || 30;
+ const cutoffDate = new Date();
+ cutoffDate.setMonth(cutoffDate.getMonth() - monthsInactive);
+
+ const totalUsers = await User.countDocuments();
+ const activeUsers = await User.countDocuments({
+ lastLogin: { $gte: cutoffDate }
+ });
+ const inactiveUsers = totalUsers - activeUsers;
+
+ res.json({
+ totalUsers,
+ activeUsers,
+ inactiveUsers,
+ inactiveThresholdMonths: monthsInactive,
+ cutoffDate: cutoffDate
+ });
+ } catch (error) {
+ res.status(500).json({ message: "Server error fetching stats" });
+ }
+});
+
+export default router;
\ No newline at end of file
diff --git a/backend/routes/contact.js b/backend/routes/contact.js
new file mode 100644
index 0000000000..07282aa0ac
--- /dev/null
+++ b/backend/routes/contact.js
@@ -0,0 +1,66 @@
+import express from 'express';
+import { sendContactEmail, sendBookingEmail } from '../services/emailService.js';
+
+const router = express.Router();
+
+// Contact form endpoint
+router.post('/contact', async (req, res) => {
+ try {
+ const { name, email, phone, subject, message } = req.body;
+
+ // Validate required fields
+ if (!name || !email || !message) {
+ return res.status(400).json({
+ success: false,
+ error: 'Name, email, and message are required'
+ });
+ }
+
+
+ // Send email notifications
+ await sendContactEmail({ name, email, phone, subject, message });
+
+ res.json({
+ success: true,
+ message: 'Contact form submitted successfully'
+ });
+
+ } catch (error) {
+ res.status(500).json({
+ success: false,
+ error: 'Internal server error'
+ });
+ }
+});
+
+// Booking form endpoint
+router.post('/booking', async (req, res) => {
+ try {
+ const { name, email, phone, eventType, eventDate, venue, message } = req.body;
+
+ // Validate required fields
+ if (!name || !email || !eventType || !eventDate || !venue || !message) {
+ return res.status(400).json({
+ success: false,
+ error: 'All required fields must be filled'
+ });
+ }
+
+
+ // Send booking email notifications
+ await sendBookingEmail({ name, email, phone, eventType, eventDate, venue, message });
+
+ res.json({
+ success: true,
+ message: 'Booking request submitted successfully'
+ });
+
+ } catch (error) {
+ res.status(500).json({
+ success: false,
+ error: 'Internal server error'
+ });
+ }
+});
+
+export default router;
\ No newline at end of file
diff --git a/backend/routes/merch.js b/backend/routes/merch.js
new file mode 100644
index 0000000000..b07f08418f
--- /dev/null
+++ b/backend/routes/merch.js
@@ -0,0 +1,32 @@
+import express from "express";
+import {
+ getAllProducts,
+ getProductById,
+ getProductTypeInfo,
+ getSellableImages,
+ createBasket,
+ getBasket,
+ updateBasket,
+ deleteBasket,
+ convertToBasketItem,
+ getCheckoutUrl,
+
+} from "../controllers/merchController.js";
+
+const router = express.Router();
+
+router.post("/baskets", createBasket);
+router.get("/baskets/:basketId", getBasket);
+router.put("/baskets/:basketId", updateBasket);
+router.delete("/baskets/:basketId", deleteBasket);
+router.post("/baskets/convert", convertToBasketItem);
+
+router.get("/baskets/:basketId/checkout", getCheckoutUrl);
+
+
+router.get("/", getAllProducts);
+router.get("/productType/:productTypeId", getProductTypeInfo);
+router.get("/:productId", getProductById);
+router.get("/sellable/:sellableId/:appearanceId/:ideaId", getSellableImages);
+
+export default router;
diff --git a/backend/server.js b/backend/server.js
index 070c875189..a32cea64ea 100644
--- a/backend/server.js
+++ b/backend/server.js
@@ -1,22 +1,140 @@
+import path from "path";
+import { fileURLToPath } from "url";
+import dotenv from "dotenv";
import express from "express";
import cors from "cors";
+import cookieParser from "cookie-parser";
import mongoose from "mongoose";
+import cron from "node-cron";
-const mongoUrl = process.env.MONGO_URL || "mongodb://localhost/final-project";
-mongoose.connect(mongoUrl);
-mongoose.Promise = Promise;
+import authRoutes from "./routes/auth.js";
+import merchRoutes from "./routes/merch.js";
+import cleanupRoutes from "./routes/cleanup.js";
+import contactRoutes from "./routes/contact.js";
+import User from "./models/User.js";
-const port = process.env.PORT || 8080;
+// Setup for __dirname in ES modules
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = path.dirname(__filename);
+
+// Load .env files (backend/.env first, then root .env as fallback)
+dotenv.config({ path: path.join(__dirname, ".env") });
+dotenv.config({ path: path.join(__dirname, "../.env") });
+
+// Validate critical environment variables
+const requiredEnvVars = [
+ "SPREADSHOP_ID",
+ "SPREADSHOP_API_KEY",
+ "SPREAD_USER_AGENT",
+ "JWT_SECRET",
+];
+const missingVars = requiredEnvVars.filter((varName) => !process.env[varName]);
+
+if (missingVars.length > 0) {
+ process.exit(1);
+}
+
+// Debug info
+
+// Environment variables
+const PORT = process.env.PORT || 8080;
+const MONGO_URL =
+ process.env.MONGO_URL || "mongodb://localhost:27017/morbid-gene-2";
+
+// Connect to MongoDB
+mongoose
+ .connect(MONGO_URL)
+ .then(() => {
+ })
+ .catch((error) => {
+ process.exit(1);
+ });
+
+// Create Express app
const app = express();
-app.use(cors());
+// CORS configuration for production
+const corsOptions = {
+ origin: [
+ 'http://localhost:5173', // Vite dev server
+ 'http://localhost:5174', // Alternative Vite dev port
+ 'http://localhost:3000', // Alternative dev port
+ 'https://morbidgeneofficial.com', // Production frontend (custom domain)
+ process.env.FRONTEND_URL || 'https://morbidgeneofficial.netlify.app', // Production frontend (netlify)
+ ],
+ credentials: true,
+ methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
+ allowedHeaders: ['Content-Type', 'Authorization'],
+ exposedHeaders: ['set-cookie'],
+ preflightContinue: false,
+ optionsSuccessStatus: 204
+};
+
+// Middleware
+app.use(cors(corsOptions));
+app.use(cookieParser());
app.use(express.json());
+
+// Health check endpoint
app.get("/", (req, res) => {
- res.send("Hello Technigo!");
+ res.json({
+ name: "Morbid Gene API",
+ status: "running",
+ version: "1.0.0",
+ endpoints: {
+ auth: "/auth",
+ user: "/user",
+ merch: "/api/merch",
+ contact: "/api/forms",
+ },
+ });
+});
+
+// Routes
+app.use("/auth", authRoutes);
+app.use("/api/merch", merchRoutes);
+app.use("/admin/cleanup", cleanupRoutes);
+app.use("/api/forms", contactRoutes);
+
+// 404 handler for unknown endpoints
+app.use("*", (req, res) => {
+ res.status(404).json({
+ error: "Endpoint not found",
+ message: `${req.method} ${req.originalUrl} finns inte`,
+ });
+});
+
+// Error handling
+app.use((err, req, res, next) => {
+ res.status(500).json({ message: "Something went wrong!" });
+});
+
+// Graceful shutdown
+process.on("SIGTERM", () => {
+ mongoose.connection.close();
+ process.exit(0);
+});
+
+// Schedule automatic cleanup - runs on the 1st of each month at 02:00
+cron.schedule('0 2 1 * *', async () => {
+ try {
+
+ // Calculate the cutoff date (30 months ago)
+ const monthsInactive = parseInt(process.env.INACTIVE_MONTHS) || 30;
+ const cutoffDate = new Date();
+ cutoffDate.setMonth(cutoffDate.getMonth() - monthsInactive);
+
+
+ // Find and delete inactive accounts
+ const result = await User.deleteMany({
+ lastLogin: { $lt: cutoffDate }
+ });
+
+ } catch (error) {
+ }
});
-// Start the server
-app.listen(port, () => {
- console.log(`Server running on http://localhost:${port}`);
+// Start server
+app.listen(PORT, () => {
});
diff --git a/backend/services/emailService.js b/backend/services/emailService.js
new file mode 100644
index 0000000000..9bd83519f7
--- /dev/null
+++ b/backend/services/emailService.js
@@ -0,0 +1,95 @@
+import pkg from "nodemailer";
+const { createTransport } = pkg;
+import {
+ createContactNotificationEmail,
+ createContactAutoReply,
+ createBookingNotificationEmail,
+ createBookingAutoReply
+} from '../utils/emailTemplates.js';
+
+const createTransporter = () => {
+ // Gmail configuration (most common)
+ if (process.env.EMAIL_PROVIDER === "gmail") {
+ return createTransport({
+ service: "gmail",
+ auth: {
+ user: process.env.EMAIL_USER,
+ pass: process.env.EMAIL_PASS, // App password, not regular password
+ },
+ });
+ }
+
+ // Generic SMTP configuration
+ return createTransport({
+ host: process.env.SMTP_HOST,
+ port: process.env.SMTP_PORT || 587,
+ secure: process.env.SMTP_SECURE === "true", // true for 465, false for other ports
+ auth: {
+ user: process.env.SMTP_USER,
+ pass: process.env.SMTP_PASS,
+ },
+ });
+};
+
+export const sendContactEmail = async (formData) => {
+ try {
+ const transporter = createTransporter();
+ const { name, email, phone, subject, message } = formData;
+
+ const mailOptionsToYou = {
+ from: process.env.EMAIL_USER,
+ to: process.env.CONTACT_EMAIL || process.env.EMAIL_USER,
+ replyTo: email,
+ subject: (subject || "General Inquiry").charAt(0).toUpperCase() + (subject || "General Inquiry").slice(1),
+ html: createContactNotificationEmail(formData),
+ };
+
+ const mailOptionsToCustomer = {
+ from: `"Morbid Gene" <${process.env.EMAIL_USER}>`,
+ to: email,
+ replyTo: process.env.EMAIL_USER,
+ subject: "Thanks for contacting Morbid Gene",
+ html: createContactAutoReply(formData),
+ };
+
+ // Send both emails
+ await transporter.sendMail(mailOptionsToYou);
+ await transporter.sendMail(mailOptionsToCustomer);
+
+ return { success: true };
+ } catch (error) {
+ throw error;
+ }
+};
+
+export const sendBookingEmail = async (formData) => {
+ try {
+ const transporter = createTransporter();
+ const { name, email, phone, eventType, eventDate, venue, message } =
+ formData;
+
+ const mailOptionsToYou = {
+ from: process.env.EMAIL_USER,
+ to: process.env.BOOKING_EMAIL || process.env.EMAIL_USER,
+ replyTo: email,
+ subject: `🎤 New booking request - ${eventType} - ${venue}`,
+ html: createBookingNotificationEmail(formData),
+ };
+
+ const mailOptionsToCustomer = {
+ from: `"Morbid Gene Booking" <${process.env.EMAIL_USER}>`,
+ to: email,
+ replyTo: process.env.EMAIL_USER,
+ subject: "Booking Request Received - Morbid Gene",
+ html: createBookingAutoReply(formData),
+ };
+
+ // Send both emails
+ await transporter.sendMail(mailOptionsToYou);
+ await transporter.sendMail(mailOptionsToCustomer);
+
+ return { success: true };
+ } catch (error) {
+ throw error;
+ }
+};
diff --git a/backend/services/spreadshirtService.js b/backend/services/spreadshirtService.js
new file mode 100644
index 0000000000..7b99fc36fb
--- /dev/null
+++ b/backend/services/spreadshirtService.js
@@ -0,0 +1,297 @@
+import axios from "axios";
+
+const apiDomain = "https://api.spreadshirt.net";
+
+// Read environment variables with fallback
+const getShopConfig = () => {
+ const shopId = process.env.SPREADSHOP_ID;
+ const apiKey = process.env.SPREADSHOP_API_KEY;
+ const userAgent = process.env.SPREAD_USER_AGENT;
+
+ // If variables are missing, throw an error with helpful info
+ if (!shopId || !apiKey || !userAgent) {
+ throw new Error(
+ "Environment variables missing for API call (SPREADSHOP_ID, SPREADSHOP_API_KEY, SPREAD_USER_AGENT)"
+ );
+ }
+
+ return { shopId, apiKey, userAgent };
+};
+
+// Smart "lazy-loaded" config - only get on first access
+let config = null;
+const getConfig = () => {
+ if (!config) {
+ config = getShopConfig();
+ }
+ return config;
+};
+
+// Common headers-configuration
+const getHeaders = () => {
+ const { apiKey, userAgent } = getConfig();
+ return {
+ Authorization: `SprdAuth apiKey="${apiKey}"`,
+ "User-Agent": userAgent,
+ };
+};
+
+// Get all products with enriched productType names
+export const getAllProducts = async (limit = 24, offset = 0) => {
+ const { shopId } = getConfig();
+ const url = `${apiDomain}/api/v1/shops/${shopId}/sellables?limit=${limit}&offset=${offset}&fullData=true`;
+
+ try {
+ const response = await axios.get(url, {
+ headers: getHeaders(),
+ });
+
+ const sellablesData = response.data;
+
+ // Enrich each sellable with productType name
+ if (sellablesData.sellables && sellablesData.sellables.length > 0) {
+ const enrichedSellables = await Promise.all(
+ sellablesData.sellables.map(async (sellable) => {
+ try {
+ if (sellable.productTypeId) {
+ const productTypeInfo = await getProductTypeInfo(sellable.productTypeId);
+ sellable.productTypeName = productTypeInfo.name;
+ }
+ return sellable;
+ } catch (error) {
+ return sellable; // Return original if productType fetch fails
+ }
+ })
+ );
+
+ sellablesData.sellables = enrichedSellables;
+ }
+
+ return sellablesData;
+ } catch (error) {
+ throw error;
+ }
+};
+
+// Get product by ID
+export const getProductById = async (productId) => {
+ const { shopId } = getConfig();
+ const url = `${apiDomain}/api/v1/shops/${shopId}/sellables?limit=100&fullData=true`;
+
+ try {
+ const response = await axios.get(url, {
+ headers: getHeaders(),
+ });
+
+ const product = response.data.sellables?.find(
+ (item) => item.sellableId === productId
+ );
+
+ if (!product) {
+ throw new Error("Product not found");
+ }
+ return product;
+ } catch (error) {
+ throw error;
+ }
+};
+
+// Get product type information
+export const getProductTypeInfo = async (productTypeId) => {
+ const { shopId } = getConfig();
+ const url = `${apiDomain}/api/v1/shops/${shopId}/productTypes/${productTypeId}`;
+
+ try {
+ const response = await axios.get(url, {
+ headers: getHeaders(),
+ });
+
+ // Extract comprehensive product information
+ const {
+ name,
+ appearances,
+ sizes,
+ defaultValues,
+ stockStates,
+ description,
+ shortDescription,
+ category,
+ brand,
+ weight,
+ material,
+ careInstructions,
+ features
+ } = response.data;
+
+ return {
+ name,
+ appearances,
+ sizes,
+ defaultValues,
+ stockStates,
+ description,
+ shortDescription,
+ category,
+ brand,
+ weight,
+ material,
+ careInstructions,
+ features
+ };
+ } catch (error) {
+ throw error;
+ }
+};
+
+export const getSellableImages = async (sellableId, appearanceId, ideaId) => {
+ const { shopId } = getConfig();
+ const url = `${apiDomain}/api/v1/shops/${shopId}/sellables/${sellableId}?appearanceId=${appearanceId}&ideaId=${ideaId}`;
+
+
+ try {
+ const response = await axios.get(url, {
+ headers: getHeaders(),
+ });
+
+
+ const { images, sizeIds, name } = response.data;
+
+ return {
+ images,
+ sizeIds,
+ name,
+ };
+ } catch (error) {
+ throw error;
+ }
+};
+
+// Basket-functions
+
+export const createBasket = async (basketItems) => {
+ const { shopId } = getConfig();
+ const url = `${apiDomain}/api/v1/baskets?mediaType=json`;
+
+ try {
+ const response = await axios.post(url, {
+ basketItems: basketItems
+ }, {
+ headers: {
+ ...getHeaders(),
+ 'Content-Type': 'application/json',
+ }
+ });
+
+ return response.data;
+ } catch (error) {
+ throw error;
+ }
+};
+
+export const getBasket = async (basketId) => {
+ const url = `${apiDomain}/api/v1/baskets/${basketId}?mediaType=json&locale=sv_SE`;
+
+ try {
+ const response = await axios.get(url, {
+ headers: getHeaders(),
+ });
+
+ return response.data;
+ } catch (error) {
+ if (error.response?.status === 404) {
+ return null;
+ }
+ throw error;
+ }
+};
+
+export const updateBasket = async (basketId, basketItems) => {
+ const url = `${apiDomain}/api/v1/baskets/${basketId}?mediaType=json`;
+
+ try {
+ const response = await axios.put(url, {
+ basketItems: basketItems
+ }, {
+ headers: {
+ ...getHeaders(),
+ 'Content-Type': 'application/json',
+ }
+ });
+
+ return response.data;
+ } catch (error) {
+ throw error;
+ }
+};
+
+export const deleteBasket = async (basketId) => {
+ const url = `${apiDomain}/api/v1/baskets/${basketId}`;
+
+ try {
+ await axios.delete(url, {
+ headers: getHeaders(),
+ });
+ return true;
+ } catch (error) {
+ throw error;
+ }
+};
+
+export const convertToBasketItem = (product, sizeName, colorName, quantity = 1) => {
+ const { shopId } = getConfig();
+
+ const sizeId = product.productType?.sizes?.find(s => s.name === sizeName)?.id;
+ const appearanceId = product.productType?.appearances?.find(a =>
+ a.name.toLowerCase() === colorName.toLowerCase()
+ )?.id;
+
+ if (!sizeId || !appearanceId) {
+ throw new Error(`Could not find size ID for "${sizeName}" or appearance ID for "${colorName}"`);
+ }
+
+ return {
+ quantity: quantity,
+ element: {
+ id: product.sellableId,
+ type: "sprd:sellable",
+ properties: [
+ {
+ key: "sellable",
+ value: product.sellableId
+ },
+ {
+ key: "size",
+ value: sizeId.toString()
+ },
+ {
+ key: "appearance",
+ value: appearanceId.toString()
+ },
+ {
+ key: "sizeLabel",
+ value: sizeName
+ }
+ ],
+ shop: {
+ id: shopId
+ }
+ }
+ };
+};
+
+// Get checkout URL for a basket
+export const getCheckoutUrl = async (basketId) => {
+ const { shopId } = getConfig();
+ const url = `${apiDomain}/api/v1/baskets/${basketId}/checkout?mediaType=json`;
+
+ try {
+ const response = await axios.get(url, {
+ headers: getHeaders(),
+ });
+
+ // Spreadshirt API returns checkout URL in the response
+ return response.data.checkoutUrl || response.data.href;
+ } catch (error) {
+ throw error;
+ }
+};
\ No newline at end of file
diff --git a/backend/utils/cookieConfig.js b/backend/utils/cookieConfig.js
new file mode 100644
index 0000000000..e4b209973e
--- /dev/null
+++ b/backend/utils/cookieConfig.js
@@ -0,0 +1,17 @@
+export const getCookieConfig = (isProduction = process.env.NODE_ENV === 'production') => ({
+ httpOnly: true,
+ secure: isProduction,
+ sameSite: isProduction ? 'none' : 'strict',
+ maxAge: 24 * 60 * 60 * 1000, // 24 hours
+ path: '/',
+ // Partitioned attribute for CHIPS (Cookies Having Independent Partitioned State)
+ // Helps with Safari's ITP and third-party cookie restrictions
+ ...(isProduction && { partitioned: true })
+});
+
+export const getClearCookieConfig = (isProduction = process.env.NODE_ENV === 'production') => ({
+ httpOnly: true,
+ secure: isProduction,
+ sameSite: isProduction ? 'none' : 'strict',
+ path: '/'
+});
\ No newline at end of file
diff --git a/backend/utils/emailTemplates.js b/backend/utils/emailTemplates.js
new file mode 100644
index 0000000000..ec790c3d39
--- /dev/null
+++ b/backend/utils/emailTemplates.js
@@ -0,0 +1,128 @@
+const createEmailWrapper = (content) => `
+
+
+
+
+ ${content}
+
+ This is an automated response from morbidgeneofficial.com
+
+
+`;
+
+const createInfoSection = (title, fields) => `
+
+
${title}:
+ ${fields.map(field => `
${field.label}: ${field.value}
`).join('')}
+
+`;
+
+const createMessageSection = (message) => `
+
+
Message:
+
${message}
+
+`;
+
+export const createContactNotificationEmail = (formData) => {
+ const { name, email, phone, subject, message } = formData;
+
+ const fields = [
+ { label: 'Name', value: name },
+ { label: 'Email', value: email },
+ ...(phone ? [{ label: 'Phone', value: phone }] : []),
+ { label: 'Subject', value: subject || 'Not specified' }
+ ];
+
+ const content = `
+ New contact request from the website
+ ${createInfoSection('Contact Information', fields)}
+ ${createMessageSection(message)}
+
+ This message was sent from the contact form on morbidgeneofficial.com
+
+ `;
+
+ return createEmailWrapper(content);
+};
+
+export const createContactAutoReply = (formData) => {
+ const { name, subject, message } = formData;
+
+ const content = `
+ Thanks for your message!
+ Hi ${name},
+ Thank you for contacting us! We have received your message and will get back to you as soon as possible.
+
+
+
Your Message:
+
Subject: ${subject || "General Inquiry"}
+
${message}
+
+
+ We strive to respond to you asap otherwise within 24-48 hours.
+ Best regards,Morbid Gene
+ `;
+
+ return createEmailWrapper(content);
+};
+
+export const createBookingNotificationEmail = (formData) => {
+ const { name, email, phone, eventType, eventDate, venue, message } = formData;
+
+ const eventFields = [
+ { label: 'Event type', value: eventType.charAt(0).toUpperCase() + eventType.slice(1) },
+ { label: 'Requested Date', value: eventDate },
+ { label: 'City', value: venue }
+ ];
+
+ const contactFields = [
+ { label: 'Name', value: name },
+ { label: 'Email', value: email },
+ ...(phone ? [{ label: 'Phone', value: phone }] : [])
+ ];
+
+ const content = `
+ New booking request!
+ ${createInfoSection('Event Information', eventFields)}
+
+
Contact Information:
+ ${contactFields.map(field => `
${field.label}: ${field.value}
`).join('')}
+
+ ${createMessageSection(message)}
+
+ This message was sent from the booking form on morbidgeneofficial.com
+
+ `;
+
+ return createEmailWrapper(content);
+};
+
+export const createBookingAutoReply = (formData) => {
+ const { name, eventType, eventDate, venue, message } = formData;
+
+ const content = `
+ Thank you for your booking request!
+ Hi ${name},
+ We have received your booking request for a ${eventType.charAt(0).toUpperCase() + eventType.slice(1)} Event the ${eventDate} in ${venue} .
+
+
+
Your Request:
+
Event Type: ${eventType.charAt(0).toUpperCase() + eventType.slice(1)}
+
Requested Date: ${eventDate}
+
City: ${venue}
+
Message:
+
${message}
+
+
+
+
Next Steps:
+
We will get back to you within 24-48 hours with more information about availability, pricing, and technical requirements.
+
+
+ Thank you for showing interest in Morbid Gene!
+ Best regards,Morbid Gene
+ `;
+
+ return createEmailWrapper(content);
+};
\ No newline at end of file
diff --git a/backend/utils/errorHandler.js b/backend/utils/errorHandler.js
new file mode 100644
index 0000000000..a273e4a47f
--- /dev/null
+++ b/backend/utils/errorHandler.js
@@ -0,0 +1,9 @@
+export const handleControllerError = (error, res, defaultMessage = "Server error") => {
+
+ const statusCode = error.response?.status || 500;
+ const errorMessage = error.message || defaultMessage;
+
+ res.status(statusCode).json({
+ error: errorMessage,
+ });
+};
\ No newline at end of file
diff --git a/backend/utils/jwt.js b/backend/utils/jwt.js
new file mode 100644
index 0000000000..d3ac9b194f
--- /dev/null
+++ b/backend/utils/jwt.js
@@ -0,0 +1,19 @@
+import jwt from 'jsonwebtoken';
+
+const JWT_SECRET = process.env.JWT_SECRET || 'your-secret-key';
+
+export const generateToken = (userId) => {
+ return jwt.sign(
+ { userId },
+ JWT_SECRET,
+ { expiresIn: '24h' }
+ );
+};
+
+export const verifyToken = (token) => {
+ try {
+ return jwt.verify(token, JWT_SECRET);
+ } catch (error) {
+ return null;
+ }
+};
\ No newline at end of file
diff --git a/backend/utils/session.js b/backend/utils/session.js
new file mode 100644
index 0000000000..e40b059272
--- /dev/null
+++ b/backend/utils/session.js
@@ -0,0 +1,46 @@
+import crypto from 'crypto';
+import Session from '../models/Session.js';
+
+export const generateSessionId = () => {
+ return crypto.randomBytes(32).toString('hex');
+};
+
+export const createSession = async (userId) => {
+ const sessionId = generateSessionId();
+ const expiresAt = new Date(Date.now() + 24 * 60 * 60 * 1000); // 24 hours
+
+ const session = new Session({
+ sessionId,
+ userId,
+ expiresAt
+ });
+
+ await session.save();
+ return sessionId;
+};
+
+export const getUserFromSession = async (sessionId) => {
+ if (!sessionId) return null;
+
+ const session = await Session.findOne({
+ sessionId,
+ expiresAt: { $gt: new Date() }
+ }).populate('userId');
+
+ if (!session) return null;
+
+ // Update expiration time (sliding sessions)
+ session.expiresAt = new Date(Date.now() + 24 * 60 * 60 * 1000);
+ await session.save();
+
+ return session.userId;
+};
+
+export const deleteSession = async (sessionId) => {
+ if (!sessionId) return;
+ await Session.deleteOne({ sessionId });
+};
+
+export const cleanupExpiredSessions = async () => {
+ await Session.deleteMany({ expiresAt: { $lt: new Date() } });
+};
\ No newline at end of file
diff --git a/frontend/.env.production b/frontend/.env.production
new file mode 100644
index 0000000000..34624ef83f
--- /dev/null
+++ b/frontend/.env.production
@@ -0,0 +1 @@
+VITE_API_BASE_URL=https://morbid-gene-2-0.onrender.com
\ No newline at end of file
diff --git a/frontend/.gitignore b/frontend/.gitignore
new file mode 100644
index 0000000000..a547bf36d8
--- /dev/null
+++ b/frontend/.gitignore
@@ -0,0 +1,24 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+dist
+dist-ssr
+*.local
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+.DS_Store
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
diff --git a/frontend/README.md b/frontend/README.md
index 5cdb1d9cf3..e69de29bb2 100644
--- a/frontend/README.md
+++ b/frontend/README.md
@@ -1,8 +0,0 @@
-# Frontend part of Final Project
-
-This boilerplate is designed to give you a head start in your React projects, with a focus on understanding the structure and components. As a student of Technigo, you'll find this guide helpful in navigating and utilizing the repository.
-
-## Getting Started
-
-1. Install the required dependencies using `npm install`.
-2. Start the development server using `npm run dev`.
\ No newline at end of file
diff --git a/frontend/eslint.config.js b/frontend/eslint.config.js
new file mode 100644
index 0000000000..cee1e2c788
--- /dev/null
+++ b/frontend/eslint.config.js
@@ -0,0 +1,29 @@
+import js from '@eslint/js'
+import globals from 'globals'
+import reactHooks from 'eslint-plugin-react-hooks'
+import reactRefresh from 'eslint-plugin-react-refresh'
+import { defineConfig, globalIgnores } from 'eslint/config'
+
+export default defineConfig([
+ globalIgnores(['dist']),
+ {
+ files: ['**/*.{js,jsx}'],
+ extends: [
+ js.configs.recommended,
+ reactHooks.configs['recommended-latest'],
+ reactRefresh.configs.vite,
+ ],
+ languageOptions: {
+ ecmaVersion: 2020,
+ globals: globals.browser,
+ parserOptions: {
+ ecmaVersion: 'latest',
+ ecmaFeatures: { jsx: true },
+ sourceType: 'module',
+ },
+ },
+ rules: {
+ 'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }],
+ },
+ },
+])
diff --git a/frontend/index.html b/frontend/index.html
index 664410b5b9..93d0555ef8 100644
--- a/frontend/index.html
+++ b/frontend/index.html
@@ -1,11 +1,46 @@
-
+
-
- Technigo React Vite Boiler Plate
+ Morbid Gene Official
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
new file mode 100644
index 0000000000..1ec40442d8
--- /dev/null
+++ b/frontend/package-lock.json
@@ -0,0 +1,3400 @@
+{
+ "name": "frontend",
+ "version": "0.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "frontend",
+ "version": "0.0.0",
+ "dependencies": {
+ "react": "^19.1.1",
+ "react-dom": "^19.1.1",
+ "react-icons": "^5.5.0",
+ "react-router-dom": "^7.8.0",
+ "recharts": "^3.1.2",
+ "styled-components": "^6.1.19"
+ },
+ "devDependencies": {
+ "@eslint/js": "^9.32.0",
+ "@types/react": "^19.1.9",
+ "@types/react-dom": "^19.1.7",
+ "@vitejs/plugin-react": "^4.7.0",
+ "eslint": "^9.32.0",
+ "eslint-plugin-react-hooks": "^5.2.0",
+ "eslint-plugin-react-refresh": "^0.4.20",
+ "globals": "^16.3.0",
+ "vite": "^7.1.0"
+ }
+ },
+ "node_modules/@ampproject/remapping": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",
+ "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/code-frame": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
+ "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^7.27.1",
+ "js-tokens": "^4.0.0",
+ "picocolors": "^1.1.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/compat-data": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.0.tgz",
+ "integrity": "sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/core": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.0.tgz",
+ "integrity": "sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@ampproject/remapping": "^2.2.0",
+ "@babel/code-frame": "^7.27.1",
+ "@babel/generator": "^7.28.0",
+ "@babel/helper-compilation-targets": "^7.27.2",
+ "@babel/helper-module-transforms": "^7.27.3",
+ "@babel/helpers": "^7.27.6",
+ "@babel/parser": "^7.28.0",
+ "@babel/template": "^7.27.2",
+ "@babel/traverse": "^7.28.0",
+ "@babel/types": "^7.28.0",
+ "convert-source-map": "^2.0.0",
+ "debug": "^4.1.0",
+ "gensync": "^1.0.0-beta.2",
+ "json5": "^2.2.3",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/babel"
+ }
+ },
+ "node_modules/@babel/generator": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.0.tgz",
+ "integrity": "sha512-lJjzvrbEeWrhB4P3QBsH7tey117PjLZnDbLiQEKjQ/fNJTjuq4HSqgFA+UNSwZT8D7dxxbnuSBMsa1lrWzKlQg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.28.0",
+ "@babel/types": "^7.28.0",
+ "@jridgewell/gen-mapping": "^0.3.12",
+ "@jridgewell/trace-mapping": "^0.3.28",
+ "jsesc": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-compilation-targets": {
+ "version": "7.27.2",
+ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz",
+ "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/compat-data": "^7.27.2",
+ "@babel/helper-validator-option": "^7.27.1",
+ "browserslist": "^4.24.0",
+ "lru-cache": "^5.1.1",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-globals": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz",
+ "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-imports": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz",
+ "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/traverse": "^7.27.1",
+ "@babel/types": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-transforms": {
+ "version": "7.27.3",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.3.tgz",
+ "integrity": "sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-module-imports": "^7.27.1",
+ "@babel/helper-validator-identifier": "^7.27.1",
+ "@babel/traverse": "^7.27.3"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/helper-plugin-utils": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz",
+ "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-string-parser": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
+ "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz",
+ "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-option": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz",
+ "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helpers": {
+ "version": "7.28.2",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.2.tgz",
+ "integrity": "sha512-/V9771t+EgXz62aCcyofnQhGM8DQACbRhvzKFsXKC9QM+5MadF8ZmIm0crDMaz3+o0h0zXfJnd4EhbYbxsrcFw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/template": "^7.27.2",
+ "@babel/types": "^7.28.2"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/parser": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.0.tgz",
+ "integrity": "sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.28.0"
+ },
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx-self": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz",
+ "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx-source": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz",
+ "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/template": {
+ "version": "7.27.2",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz",
+ "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.27.1",
+ "@babel/parser": "^7.27.2",
+ "@babel/types": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/traverse": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.0.tgz",
+ "integrity": "sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.27.1",
+ "@babel/generator": "^7.28.0",
+ "@babel/helper-globals": "^7.28.0",
+ "@babel/parser": "^7.28.0",
+ "@babel/template": "^7.27.2",
+ "@babel/types": "^7.28.0",
+ "debug": "^4.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/types": {
+ "version": "7.28.2",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.2.tgz",
+ "integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-string-parser": "^7.27.1",
+ "@babel/helper-validator-identifier": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@emotion/is-prop-valid": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.2.tgz",
+ "integrity": "sha512-uNsoYd37AFmaCdXlg6EYD1KaPOaRWRByMCYzbKUX4+hhMfrxdVSelShywL4JVaAeM/eHUOSprYBQls+/neX3pw==",
+ "license": "MIT",
+ "dependencies": {
+ "@emotion/memoize": "^0.8.1"
+ }
+ },
+ "node_modules/@emotion/memoize": {
+ "version": "0.8.1",
+ "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz",
+ "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==",
+ "license": "MIT"
+ },
+ "node_modules/@emotion/unitless": {
+ "version": "0.8.1",
+ "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz",
+ "integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==",
+ "license": "MIT"
+ },
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.25.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.8.tgz",
+ "integrity": "sha512-urAvrUedIqEiFR3FYSLTWQgLu5tb+m0qZw0NBEasUeo6wuqatkMDaRT+1uABiGXEu5vqgPd7FGE1BhsAIy9QVA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.25.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.8.tgz",
+ "integrity": "sha512-RONsAvGCz5oWyePVnLdZY/HHwA++nxYWIX1atInlaW6SEkwq6XkP3+cb825EUcRs5Vss/lGh/2YxAb5xqc07Uw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.25.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.8.tgz",
+ "integrity": "sha512-OD3p7LYzWpLhZEyATcTSJ67qB5D+20vbtr6vHlHWSQYhKtzUYrETuWThmzFpZtFsBIxRvhO07+UgVA9m0i/O1w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.25.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.8.tgz",
+ "integrity": "sha512-yJAVPklM5+4+9dTeKwHOaA+LQkmrKFX96BM0A/2zQrbS6ENCmxc4OVoBs5dPkCCak2roAD+jKCdnmOqKszPkjA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.25.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.8.tgz",
+ "integrity": "sha512-Jw0mxgIaYX6R8ODrdkLLPwBqHTtYHJSmzzd+QeytSugzQ0Vg4c5rDky5VgkoowbZQahCbsv1rT1KW72MPIkevw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.25.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.8.tgz",
+ "integrity": "sha512-Vh2gLxxHnuoQ+GjPNvDSDRpoBCUzY4Pu0kBqMBDlK4fuWbKgGtmDIeEC081xi26PPjn+1tct+Bh8FjyLlw1Zlg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.25.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.8.tgz",
+ "integrity": "sha512-YPJ7hDQ9DnNe5vxOm6jaie9QsTwcKedPvizTVlqWG9GBSq+BuyWEDazlGaDTC5NGU4QJd666V0yqCBL2oWKPfA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.25.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.8.tgz",
+ "integrity": "sha512-MmaEXxQRdXNFsRN/KcIimLnSJrk2r5H8v+WVafRWz5xdSVmWLoITZQXcgehI2ZE6gioE6HirAEToM/RvFBeuhw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.25.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.8.tgz",
+ "integrity": "sha512-FuzEP9BixzZohl1kLf76KEVOsxtIBFwCaLupVuk4eFVnOZfU+Wsn+x5Ryam7nILV2pkq2TqQM9EZPsOBuMC+kg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.25.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.8.tgz",
+ "integrity": "sha512-WIgg00ARWv/uYLU7lsuDK00d/hHSfES5BzdWAdAig1ioV5kaFNrtK8EqGcUBJhYqotlUByUKz5Qo6u8tt7iD/w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.25.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.8.tgz",
+ "integrity": "sha512-A1D9YzRX1i+1AJZuFFUMP1E9fMaYY+GnSQil9Tlw05utlE86EKTUA7RjwHDkEitmLYiFsRd9HwKBPEftNdBfjg==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.25.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.8.tgz",
+ "integrity": "sha512-O7k1J/dwHkY1RMVvglFHl1HzutGEFFZ3kNiDMSOyUrB7WcoHGf96Sh+64nTRT26l3GMbCW01Ekh/ThKM5iI7hQ==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.25.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.8.tgz",
+ "integrity": "sha512-uv+dqfRazte3BzfMp8PAQXmdGHQt2oC/y2ovwpTteqrMx2lwaksiFZ/bdkXJC19ttTvNXBuWH53zy/aTj1FgGw==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.25.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.8.tgz",
+ "integrity": "sha512-GyG0KcMi1GBavP5JgAkkstMGyMholMDybAf8wF5A70CALlDM2p/f7YFE7H92eDeH/VBtFJA5MT4nRPDGg4JuzQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.25.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.8.tgz",
+ "integrity": "sha512-rAqDYFv3yzMrq7GIcen3XP7TUEG/4LK86LUPMIz6RT8A6pRIDn0sDcvjudVZBiiTcZCY9y2SgYX2lgK3AF+1eg==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.25.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.8.tgz",
+ "integrity": "sha512-Xutvh6VjlbcHpsIIbwY8GVRbwoviWT19tFhgdA7DlenLGC/mbc3lBoVb7jxj9Z+eyGqvcnSyIltYUrkKzWqSvg==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.25.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.8.tgz",
+ "integrity": "sha512-ASFQhgY4ElXh3nDcOMTkQero4b1lgubskNlhIfJrsH5OKZXDpUAKBlNS0Kx81jwOBp+HCeZqmoJuihTv57/jvQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-arm64": {
+ "version": "0.25.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.8.tgz",
+ "integrity": "sha512-d1KfruIeohqAi6SA+gENMuObDbEjn22olAR7egqnkCD9DGBG0wsEARotkLgXDu6c4ncgWTZJtN5vcgxzWRMzcw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.25.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.8.tgz",
+ "integrity": "sha512-nVDCkrvx2ua+XQNyfrujIG38+YGyuy2Ru9kKVNyh5jAys6n+l44tTtToqHjino2My8VAY6Lw9H7RI73XFi66Cg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-arm64": {
+ "version": "0.25.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.8.tgz",
+ "integrity": "sha512-j8HgrDuSJFAujkivSMSfPQSAa5Fxbvk4rgNAS5i3K+r8s1X0p1uOO2Hl2xNsGFppOeHOLAVgYwDVlmxhq5h+SQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.25.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.8.tgz",
+ "integrity": "sha512-1h8MUAwa0VhNCDp6Af0HToI2TJFAn1uqT9Al6DJVzdIBAd21m/G0Yfc77KDM3uF3T/YaOgQq3qTJHPbTOInaIQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openharmony-arm64": {
+ "version": "0.25.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.8.tgz",
+ "integrity": "sha512-r2nVa5SIK9tSWd0kJd9HCffnDHKchTGikb//9c7HX+r+wHYCpQrSgxhlY6KWV1nFo1l4KFbsMlHk+L6fekLsUg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.25.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.8.tgz",
+ "integrity": "sha512-zUlaP2S12YhQ2UzUfcCuMDHQFJyKABkAjvO5YSndMiIkMimPmxA+BYSBikWgsRpvyxuRnow4nS5NPnf9fpv41w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.25.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.8.tgz",
+ "integrity": "sha512-YEGFFWESlPva8hGL+zvj2z/SaK+pH0SwOM0Nc/d+rVnW7GSTFlLBGzZkuSU9kFIGIo8q9X3ucpZhu8PDN5A2sQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.25.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.8.tgz",
+ "integrity": "sha512-hiGgGC6KZ5LZz58OL/+qVVoZiuZlUYlYHNAmczOm7bs2oE1XriPFi5ZHHrS8ACpV5EjySrnoCKmcbQMN+ojnHg==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.25.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.8.tgz",
+ "integrity": "sha512-cn3Yr7+OaaZq1c+2pe+8yxC8E144SReCQjN6/2ynubzYjvyqZjTXfQJpAcQpsdJq3My7XADANiYGHoFC69pLQw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@eslint-community/eslint-utils": {
+ "version": "4.7.0",
+ "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz",
+ "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "eslint-visitor-keys": "^3.4.3"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
+ }
+ },
+ "node_modules/@eslint-community/regexpp": {
+ "version": "4.12.1",
+ "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz",
+ "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^12.0.0 || ^14.0.0 || >=16.0.0"
+ }
+ },
+ "node_modules/@eslint/config-array": {
+ "version": "0.21.0",
+ "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz",
+ "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@eslint/object-schema": "^2.1.6",
+ "debug": "^4.3.1",
+ "minimatch": "^3.1.2"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/config-helpers": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.0.tgz",
+ "integrity": "sha512-ViuymvFmcJi04qdZeDc2whTHryouGcDlaxPqarTD0ZE10ISpxGUVZGZDx4w01upyIynL3iu6IXH2bS1NhclQMw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/core": {
+ "version": "0.15.1",
+ "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.1.tgz",
+ "integrity": "sha512-bkOp+iumZCCbt1K1CmWf0R9pM5yKpDv+ZXtvSyQpudrI9kuFLp+bM2WOPXImuD/ceQuaa8f5pj93Y7zyECIGNA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@types/json-schema": "^7.0.15"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/eslintrc": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz",
+ "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ajv": "^6.12.4",
+ "debug": "^4.3.2",
+ "espree": "^10.0.1",
+ "globals": "^14.0.0",
+ "ignore": "^5.2.0",
+ "import-fresh": "^3.2.1",
+ "js-yaml": "^4.1.0",
+ "minimatch": "^3.1.2",
+ "strip-json-comments": "^3.1.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/@eslint/eslintrc/node_modules/globals": {
+ "version": "14.0.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz",
+ "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@eslint/js": {
+ "version": "9.32.0",
+ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.32.0.tgz",
+ "integrity": "sha512-BBpRFZK3eX6uMLKz8WxFOBIFFcGFJ/g8XuwjTHCqHROSIsopI+ddn/d5Cfh36+7+e5edVS8dbSHnBNhrLEX0zg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://eslint.org/donate"
+ }
+ },
+ "node_modules/@eslint/object-schema": {
+ "version": "2.1.6",
+ "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz",
+ "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/plugin-kit": {
+ "version": "0.3.4",
+ "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.4.tgz",
+ "integrity": "sha512-Ul5l+lHEcw3L5+k8POx6r74mxEYKG5kOb6Xpy2gCRW6zweT6TEhAf8vhxGgjhqrd/VO/Dirhsb+1hNpD1ue9hw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@eslint/core": "^0.15.1",
+ "levn": "^0.4.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@humanfs/core": {
+ "version": "0.19.1",
+ "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
+ "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=18.18.0"
+ }
+ },
+ "node_modules/@humanfs/node": {
+ "version": "0.16.6",
+ "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz",
+ "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@humanfs/core": "^0.19.1",
+ "@humanwhocodes/retry": "^0.3.0"
+ },
+ "engines": {
+ "node": ">=18.18.0"
+ }
+ },
+ "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": {
+ "version": "0.3.1",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz",
+ "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=18.18"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
+ }
+ },
+ "node_modules/@humanwhocodes/module-importer": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
+ "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=12.22"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
+ }
+ },
+ "node_modules/@humanwhocodes/retry": {
+ "version": "0.4.3",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz",
+ "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=18.18"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
+ }
+ },
+ "node_modules/@jridgewell/gen-mapping": {
+ "version": "0.3.12",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz",
+ "integrity": "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.0",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.5.4",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz",
+ "integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.29",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz",
+ "integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.1.0",
+ "@jridgewell/sourcemap-codec": "^1.4.14"
+ }
+ },
+ "node_modules/@reduxjs/toolkit": {
+ "version": "2.8.2",
+ "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.8.2.tgz",
+ "integrity": "sha512-MYlOhQ0sLdw4ud48FoC5w0dH9VfWQjtCjreKwYTT3l+r427qYC5Y8PihNutepr8XrNaBUDQo9khWUwQxZaqt5A==",
+ "license": "MIT",
+ "dependencies": {
+ "@standard-schema/spec": "^1.0.0",
+ "@standard-schema/utils": "^0.3.0",
+ "immer": "^10.0.3",
+ "redux": "^5.0.1",
+ "redux-thunk": "^3.1.0",
+ "reselect": "^5.1.0"
+ },
+ "peerDependencies": {
+ "react": "^16.9.0 || ^17.0.0 || ^18 || ^19",
+ "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0"
+ },
+ "peerDependenciesMeta": {
+ "react": {
+ "optional": true
+ },
+ "react-redux": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@rolldown/pluginutils": {
+ "version": "1.0.0-beta.27",
+ "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz",
+ "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@rollup/rollup-android-arm-eabi": {
+ "version": "4.46.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.46.2.tgz",
+ "integrity": "sha512-Zj3Hl6sN34xJtMv7Anwb5Gu01yujyE/cLBDB2gnHTAHaWS1Z38L7kuSG+oAh0giZMqG060f/YBStXtMH6FvPMA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-android-arm64": {
+ "version": "4.46.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.46.2.tgz",
+ "integrity": "sha512-nTeCWY83kN64oQ5MGz3CgtPx8NSOhC5lWtsjTs+8JAJNLcP3QbLCtDDgUKQc/Ro/frpMq4SHUaHN6AMltcEoLQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-arm64": {
+ "version": "4.46.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.46.2.tgz",
+ "integrity": "sha512-HV7bW2Fb/F5KPdM/9bApunQh68YVDU8sO8BvcW9OngQVN3HHHkw99wFupuUJfGR9pYLLAjcAOA6iO+evsbBaPQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-x64": {
+ "version": "4.46.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.46.2.tgz",
+ "integrity": "sha512-SSj8TlYV5nJixSsm/y3QXfhspSiLYP11zpfwp6G/YDXctf3Xkdnk4woJIF5VQe0of2OjzTt8EsxnJDCdHd2xMA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-arm64": {
+ "version": "4.46.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.46.2.tgz",
+ "integrity": "sha512-ZyrsG4TIT9xnOlLsSSi9w/X29tCbK1yegE49RYm3tu3wF1L/B6LVMqnEWyDB26d9Ecx9zrmXCiPmIabVuLmNSg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-x64": {
+ "version": "4.46.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.46.2.tgz",
+ "integrity": "sha512-pCgHFoOECwVCJ5GFq8+gR8SBKnMO+xe5UEqbemxBpCKYQddRQMgomv1104RnLSg7nNvgKy05sLsY51+OVRyiVw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+ "version": "4.46.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.46.2.tgz",
+ "integrity": "sha512-EtP8aquZ0xQg0ETFcxUbU71MZlHaw9MChwrQzatiE8U/bvi5uv/oChExXC4mWhjiqK7azGJBqU0tt5H123SzVA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+ "version": "4.46.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.46.2.tgz",
+ "integrity": "sha512-qO7F7U3u1nfxYRPM8HqFtLd+raev2K137dsV08q/LRKRLEc7RsiDWihUnrINdsWQxPR9jqZ8DIIZ1zJJAm5PjQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
+ "version": "4.46.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.46.2.tgz",
+ "integrity": "sha512-3dRaqLfcOXYsfvw5xMrxAk9Lb1f395gkoBYzSFcc/scgRFptRXL9DOaDpMiehf9CO8ZDRJW2z45b6fpU5nwjng==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
+ "version": "4.46.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.46.2.tgz",
+ "integrity": "sha512-fhHFTutA7SM+IrR6lIfiHskxmpmPTJUXpWIsBXpeEwNgZzZZSg/q4i6FU4J8qOGyJ0TR+wXBwx/L7Ho9z0+uDg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loongarch64-gnu": {
+ "version": "4.46.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.46.2.tgz",
+ "integrity": "sha512-i7wfGFXu8x4+FRqPymzjD+Hyav8l95UIZ773j7J7zRYc3Xsxy2wIn4x+llpunexXe6laaO72iEjeeGyUFmjKeA==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-gnu": {
+ "version": "4.46.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.46.2.tgz",
+ "integrity": "sha512-B/l0dFcHVUnqcGZWKcWBSV2PF01YUt0Rvlurci5P+neqY/yMKchGU8ullZvIv5e8Y1C6wOn+U03mrDylP5q9Yw==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+ "version": "4.46.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.46.2.tgz",
+ "integrity": "sha512-32k4ENb5ygtkMwPMucAb8MtV8olkPT03oiTxJbgkJa7lJ7dZMr0GCFJlyvy+K8iq7F/iuOr41ZdUHaOiqyR3iQ==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-musl": {
+ "version": "4.46.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.46.2.tgz",
+ "integrity": "sha512-t5B2loThlFEauloaQkZg9gxV05BYeITLvLkWOkRXogP4qHXLkWSbSHKM9S6H1schf/0YGP/qNKtiISlxvfmmZw==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
+ "version": "4.46.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.46.2.tgz",
+ "integrity": "sha512-YKjekwTEKgbB7n17gmODSmJVUIvj8CX7q5442/CK80L8nqOUbMtf8b01QkG3jOqyr1rotrAnW6B/qiHwfcuWQA==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
+ "version": "4.46.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.46.2.tgz",
+ "integrity": "sha512-Jj5a9RUoe5ra+MEyERkDKLwTXVu6s3aACP51nkfnK9wJTraCC8IMe3snOfALkrjTYd2G1ViE1hICj0fZ7ALBPA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-musl": {
+ "version": "4.46.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.46.2.tgz",
+ "integrity": "sha512-7kX69DIrBeD7yNp4A5b81izs8BqoZkCIaxQaOpumcJ1S/kmqNFjPhDu1LHeVXv0SexfHQv5cqHsxLOjETuqDuA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
+ "version": "4.46.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.46.2.tgz",
+ "integrity": "sha512-wiJWMIpeaak/jsbaq2HMh/rzZxHVW1rU6coyeNNpMwk5isiPjSTx0a4YLSlYDwBH/WBvLz+EtsNqQScZTLJy3g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
+ "version": "4.46.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.46.2.tgz",
+ "integrity": "sha512-gBgaUDESVzMgWZhcyjfs9QFK16D8K6QZpwAaVNJxYDLHWayOta4ZMjGm/vsAEy3hvlS2GosVFlBlP9/Wb85DqQ==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
+ "version": "4.46.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.46.2.tgz",
+ "integrity": "sha512-CvUo2ixeIQGtF6WvuB87XWqPQkoFAFqW+HUo/WzHwuHDvIwZCtjdWXoYCcr06iKGydiqTclC4jU/TNObC/xKZg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@standard-schema/spec": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz",
+ "integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==",
+ "license": "MIT"
+ },
+ "node_modules/@standard-schema/utils": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz",
+ "integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==",
+ "license": "MIT"
+ },
+ "node_modules/@types/babel__core": {
+ "version": "7.20.5",
+ "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
+ "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.20.7",
+ "@babel/types": "^7.20.7",
+ "@types/babel__generator": "*",
+ "@types/babel__template": "*",
+ "@types/babel__traverse": "*"
+ }
+ },
+ "node_modules/@types/babel__generator": {
+ "version": "7.27.0",
+ "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz",
+ "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__template": {
+ "version": "7.4.4",
+ "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz",
+ "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.1.0",
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__traverse": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz",
+ "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.28.2"
+ }
+ },
+ "node_modules/@types/d3-array": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.1.tgz",
+ "integrity": "sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-color": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz",
+ "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-ease": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz",
+ "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-interpolate": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz",
+ "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/d3-color": "*"
+ }
+ },
+ "node_modules/@types/d3-path": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz",
+ "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-scale": {
+ "version": "4.0.9",
+ "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz",
+ "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/d3-time": "*"
+ }
+ },
+ "node_modules/@types/d3-shape": {
+ "version": "3.1.7",
+ "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.7.tgz",
+ "integrity": "sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/d3-path": "*"
+ }
+ },
+ "node_modules/@types/d3-time": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz",
+ "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-timer": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz",
+ "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==",
+ "license": "MIT"
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
+ "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/json-schema": {
+ "version": "7.0.15",
+ "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
+ "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/react": {
+ "version": "19.1.9",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.9.tgz",
+ "integrity": "sha512-WmdoynAX8Stew/36uTSVMcLJJ1KRh6L3IZRx1PZ7qJtBqT3dYTgyDTx8H1qoRghErydW7xw9mSJ3wS//tCRpFA==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "csstype": "^3.0.2"
+ }
+ },
+ "node_modules/@types/react-dom": {
+ "version": "19.1.7",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.7.tgz",
+ "integrity": "sha512-i5ZzwYpqjmrKenzkoLM2Ibzt6mAsM7pxB6BCIouEVVmgiqaMj1TjaK7hnA36hbW5aZv20kx7Lw6hWzPWg0Rurw==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "^19.0.0"
+ }
+ },
+ "node_modules/@types/stylis": {
+ "version": "4.2.5",
+ "resolved": "https://registry.npmjs.org/@types/stylis/-/stylis-4.2.5.tgz",
+ "integrity": "sha512-1Xve+NMN7FWjY14vLoY5tL3BVEQ/n42YLwaqJIPYhotZ9uBHt87VceMwWQpzmdEt2TNXIorIFG+YeCUUW7RInw==",
+ "license": "MIT"
+ },
+ "node_modules/@types/use-sync-external-store": {
+ "version": "0.0.6",
+ "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz",
+ "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==",
+ "license": "MIT"
+ },
+ "node_modules/@vitejs/plugin-react": {
+ "version": "4.7.0",
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz",
+ "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/core": "^7.28.0",
+ "@babel/plugin-transform-react-jsx-self": "^7.27.1",
+ "@babel/plugin-transform-react-jsx-source": "^7.27.1",
+ "@rolldown/pluginutils": "1.0.0-beta.27",
+ "@types/babel__core": "^7.20.5",
+ "react-refresh": "^0.17.0"
+ },
+ "engines": {
+ "node": "^14.18.0 || >=16.0.0"
+ },
+ "peerDependencies": {
+ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
+ }
+ },
+ "node_modules/acorn": {
+ "version": "8.15.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
+ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "acorn": "bin/acorn"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/acorn-jsx": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
+ "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ }
+ },
+ "node_modules/ajv": {
+ "version": "6.12.6",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
+ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/argparse": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+ "dev": true,
+ "license": "Python-2.0"
+ },
+ "node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/brace-expansion": {
+ "version": "1.1.12",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
+ "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/browserslist": {
+ "version": "4.25.1",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.1.tgz",
+ "integrity": "sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "caniuse-lite": "^1.0.30001726",
+ "electron-to-chromium": "^1.5.173",
+ "node-releases": "^2.0.19",
+ "update-browserslist-db": "^1.1.3"
+ },
+ "bin": {
+ "browserslist": "cli.js"
+ },
+ "engines": {
+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+ }
+ },
+ "node_modules/callsites": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
+ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/camelize": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz",
+ "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/caniuse-lite": {
+ "version": "1.0.30001731",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001731.tgz",
+ "integrity": "sha512-lDdp2/wrOmTRWuoB5DpfNkC0rJDU8DqRa6nYL6HK6sytw70QMopt/NIc/9SM7ylItlBWfACXk0tEn37UWM/+mg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "CC-BY-4.0"
+ },
+ "node_modules/chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/clsx": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
+ "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/convert-source-map": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
+ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/cookie": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz",
+ "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/cross-spawn": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
+ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/css-color-keywords": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz",
+ "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/css-to-react-native": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz",
+ "integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==",
+ "license": "MIT",
+ "dependencies": {
+ "camelize": "^1.0.0",
+ "css-color-keywords": "^1.0.0",
+ "postcss-value-parser": "^4.0.2"
+ }
+ },
+ "node_modules/csstype": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
+ "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
+ "license": "MIT"
+ },
+ "node_modules/d3-array": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz",
+ "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==",
+ "license": "ISC",
+ "dependencies": {
+ "internmap": "1 - 2"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-color": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz",
+ "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-ease": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz",
+ "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==",
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-format": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz",
+ "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-interpolate": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz",
+ "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-color": "1 - 3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-path": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz",
+ "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-scale": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz",
+ "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-array": "2.10.0 - 3",
+ "d3-format": "1 - 3",
+ "d3-interpolate": "1.2.0 - 3",
+ "d3-time": "2.1.1 - 3",
+ "d3-time-format": "2 - 4"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-shape": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz",
+ "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-path": "^3.1.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-time": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz",
+ "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-array": "2 - 3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-time-format": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz",
+ "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-time": "1 - 3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-timer": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz",
+ "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/debug": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
+ "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/decimal.js-light": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz",
+ "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==",
+ "license": "MIT"
+ },
+ "node_modules/deep-is": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
+ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/electron-to-chromium": {
+ "version": "1.5.198",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.198.tgz",
+ "integrity": "sha512-G5COfnp3w+ydVu80yprgWSfmfQaYRh9DOxfhAxstLyetKaLyl55QrNjx8C38Pc/C+RaDmb1M0Lk8wPEMQ+bGgQ==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/es-toolkit": {
+ "version": "1.39.10",
+ "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.39.10.tgz",
+ "integrity": "sha512-E0iGnTtbDhkeczB0T+mxmoVlT4YNweEKBLq7oaU4p11mecdsZpNWOglI4895Vh4usbQ+LsJiuLuI2L0Vdmfm2w==",
+ "license": "MIT",
+ "workspaces": [
+ "docs",
+ "benchmarks"
+ ]
+ },
+ "node_modules/esbuild": {
+ "version": "0.25.8",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.8.tgz",
+ "integrity": "sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.25.8",
+ "@esbuild/android-arm": "0.25.8",
+ "@esbuild/android-arm64": "0.25.8",
+ "@esbuild/android-x64": "0.25.8",
+ "@esbuild/darwin-arm64": "0.25.8",
+ "@esbuild/darwin-x64": "0.25.8",
+ "@esbuild/freebsd-arm64": "0.25.8",
+ "@esbuild/freebsd-x64": "0.25.8",
+ "@esbuild/linux-arm": "0.25.8",
+ "@esbuild/linux-arm64": "0.25.8",
+ "@esbuild/linux-ia32": "0.25.8",
+ "@esbuild/linux-loong64": "0.25.8",
+ "@esbuild/linux-mips64el": "0.25.8",
+ "@esbuild/linux-ppc64": "0.25.8",
+ "@esbuild/linux-riscv64": "0.25.8",
+ "@esbuild/linux-s390x": "0.25.8",
+ "@esbuild/linux-x64": "0.25.8",
+ "@esbuild/netbsd-arm64": "0.25.8",
+ "@esbuild/netbsd-x64": "0.25.8",
+ "@esbuild/openbsd-arm64": "0.25.8",
+ "@esbuild/openbsd-x64": "0.25.8",
+ "@esbuild/openharmony-arm64": "0.25.8",
+ "@esbuild/sunos-x64": "0.25.8",
+ "@esbuild/win32-arm64": "0.25.8",
+ "@esbuild/win32-ia32": "0.25.8",
+ "@esbuild/win32-x64": "0.25.8"
+ }
+ },
+ "node_modules/escalade": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/eslint": {
+ "version": "9.32.0",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.32.0.tgz",
+ "integrity": "sha512-LSehfdpgMeWcTZkWZVIJl+tkZ2nuSkyyB9C27MZqFWXuph7DvaowgcTvKqxvpLW1JZIk8PN7hFY3Rj9LQ7m7lg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.2.0",
+ "@eslint-community/regexpp": "^4.12.1",
+ "@eslint/config-array": "^0.21.0",
+ "@eslint/config-helpers": "^0.3.0",
+ "@eslint/core": "^0.15.0",
+ "@eslint/eslintrc": "^3.3.1",
+ "@eslint/js": "9.32.0",
+ "@eslint/plugin-kit": "^0.3.4",
+ "@humanfs/node": "^0.16.6",
+ "@humanwhocodes/module-importer": "^1.0.1",
+ "@humanwhocodes/retry": "^0.4.2",
+ "@types/estree": "^1.0.6",
+ "@types/json-schema": "^7.0.15",
+ "ajv": "^6.12.4",
+ "chalk": "^4.0.0",
+ "cross-spawn": "^7.0.6",
+ "debug": "^4.3.2",
+ "escape-string-regexp": "^4.0.0",
+ "eslint-scope": "^8.4.0",
+ "eslint-visitor-keys": "^4.2.1",
+ "espree": "^10.4.0",
+ "esquery": "^1.5.0",
+ "esutils": "^2.0.2",
+ "fast-deep-equal": "^3.1.3",
+ "file-entry-cache": "^8.0.0",
+ "find-up": "^5.0.0",
+ "glob-parent": "^6.0.2",
+ "ignore": "^5.2.0",
+ "imurmurhash": "^0.1.4",
+ "is-glob": "^4.0.0",
+ "json-stable-stringify-without-jsonify": "^1.0.1",
+ "lodash.merge": "^4.6.2",
+ "minimatch": "^3.1.2",
+ "natural-compare": "^1.4.0",
+ "optionator": "^0.9.3"
+ },
+ "bin": {
+ "eslint": "bin/eslint.js"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://eslint.org/donate"
+ },
+ "peerDependencies": {
+ "jiti": "*"
+ },
+ "peerDependenciesMeta": {
+ "jiti": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/eslint-plugin-react-hooks": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.2.0.tgz",
+ "integrity": "sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0"
+ }
+ },
+ "node_modules/eslint-plugin-react-refresh": {
+ "version": "0.4.20",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.20.tgz",
+ "integrity": "sha512-XpbHQ2q5gUF8BGOX4dHe+71qoirYMhApEPZ7sfhF/dNnOF1UXnCMGZf79SFTBO7Bz5YEIT4TMieSlJBWhP9WBA==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "eslint": ">=8.40"
+ }
+ },
+ "node_modules/eslint-scope": {
+ "version": "8.4.0",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz",
+ "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "esrecurse": "^4.3.0",
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint-visitor-keys": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
+ "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint/node_modules/eslint-visitor-keys": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
+ "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/espree": {
+ "version": "10.4.0",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz",
+ "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "acorn": "^8.15.0",
+ "acorn-jsx": "^5.3.2",
+ "eslint-visitor-keys": "^4.2.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/espree/node_modules/eslint-visitor-keys": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
+ "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/esquery": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz",
+ "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "estraverse": "^5.1.0"
+ },
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "node_modules/esrecurse": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
+ "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/estraverse": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/esutils": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
+ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/eventemitter3": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz",
+ "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==",
+ "license": "MIT"
+ },
+ "node_modules/fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fast-json-stable-stringify": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fast-levenshtein": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
+ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fdir": {
+ "version": "6.4.6",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz",
+ "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/file-entry-cache": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
+ "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "flat-cache": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=16.0.0"
+ }
+ },
+ "node_modules/find-up": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
+ "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "locate-path": "^6.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/flat-cache": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz",
+ "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "flatted": "^3.2.9",
+ "keyv": "^4.5.4"
+ },
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/flatted": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz",
+ "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/gensync": {
+ "version": "1.0.0-beta.2",
+ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
+ "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/glob-parent": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
+ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/globals": {
+ "version": "16.3.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-16.3.0.tgz",
+ "integrity": "sha512-bqWEnJ1Nt3neqx2q5SFfGS8r/ahumIakg3HcwtNlrVlwXIeNumWn/c7Pn/wKzGhf6SaW6H6uWXLqC30STCMchQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/ignore": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
+ "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/immer": {
+ "version": "10.1.1",
+ "resolved": "https://registry.npmjs.org/immer/-/immer-10.1.1.tgz",
+ "integrity": "sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==",
+ "license": "MIT",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/immer"
+ }
+ },
+ "node_modules/import-fresh": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
+ "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "parent-module": "^1.0.0",
+ "resolve-from": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/imurmurhash": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+ "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.8.19"
+ }
+ },
+ "node_modules/internmap": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz",
+ "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-glob": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-extglob": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/js-yaml": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
+ "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "argparse": "^2.0.1"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/jsesc": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
+ "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "jsesc": "bin/jsesc"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/json-buffer": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
+ "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json-schema-traverse": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json-stable-stringify-without-jsonify": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
+ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json5": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
+ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "json5": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/keyv": {
+ "version": "4.5.4",
+ "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
+ "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "json-buffer": "3.0.1"
+ }
+ },
+ "node_modules/levn": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
+ "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "prelude-ls": "^1.2.1",
+ "type-check": "~0.4.0"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/locate-path": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
+ "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-locate": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/lodash.merge": {
+ "version": "4.6.2",
+ "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
+ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/lru-cache": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
+ "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "yallist": "^3.0.2"
+ }
+ },
+ "node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.11",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
+ "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/natural-compare": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
+ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/node-releases": {
+ "version": "2.0.19",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz",
+ "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/optionator": {
+ "version": "0.9.4",
+ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
+ "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "deep-is": "^0.1.3",
+ "fast-levenshtein": "^2.0.6",
+ "levn": "^0.4.1",
+ "prelude-ls": "^1.2.1",
+ "type-check": "^0.4.0",
+ "word-wrap": "^1.2.5"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/p-limit": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
+ "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "yocto-queue": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-locate": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
+ "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-limit": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/parent-module": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
+ "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "callsites": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/path-exists": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "license": "ISC"
+ },
+ "node_modules/picomatch": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
+ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.5.6",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
+ "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.11",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/postcss-value-parser": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
+ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
+ "license": "MIT"
+ },
+ "node_modules/prelude-ls": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
+ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/punycode": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
+ "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/react": {
+ "version": "19.1.1",
+ "resolved": "https://registry.npmjs.org/react/-/react-19.1.1.tgz",
+ "integrity": "sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-dom": {
+ "version": "19.1.1",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.1.tgz",
+ "integrity": "sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw==",
+ "license": "MIT",
+ "dependencies": {
+ "scheduler": "^0.26.0"
+ },
+ "peerDependencies": {
+ "react": "^19.1.1"
+ }
+ },
+ "node_modules/react-icons": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz",
+ "integrity": "sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": "*"
+ }
+ },
+ "node_modules/react-is": {
+ "version": "19.1.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.1.1.tgz",
+ "integrity": "sha512-tr41fA15Vn8p4X9ntI+yCyeGSf1TlYaY5vlTZfQmeLBrFo3psOPX6HhTDnFNL9uj3EhP0KAQ80cugCl4b4BERA==",
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/react-redux": {
+ "version": "9.2.0",
+ "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz",
+ "integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/use-sync-external-store": "^0.0.6",
+ "use-sync-external-store": "^1.4.0"
+ },
+ "peerDependencies": {
+ "@types/react": "^18.2.25 || ^19",
+ "react": "^18.0 || ^19",
+ "redux": "^5.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "redux": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/react-refresh": {
+ "version": "0.17.0",
+ "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz",
+ "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-router": {
+ "version": "7.8.0",
+ "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.8.0.tgz",
+ "integrity": "sha512-r15M3+LHKgM4SOapNmsH3smAizWds1vJ0Z9C4mWaKnT9/wD7+d/0jYcj6LmOvonkrO4Rgdyp4KQ/29gWN2i1eg==",
+ "license": "MIT",
+ "dependencies": {
+ "cookie": "^1.0.1",
+ "set-cookie-parser": "^2.6.0"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=18",
+ "react-dom": ">=18"
+ },
+ "peerDependenciesMeta": {
+ "react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/react-router-dom": {
+ "version": "7.8.0",
+ "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.8.0.tgz",
+ "integrity": "sha512-ntInsnDVnVRdtSu6ODmTQ41cbluak/ENeTif7GBce0L6eztFg6/e1hXAysFQI8X25C8ipKmT9cClbJwxx3Kaqw==",
+ "license": "MIT",
+ "dependencies": {
+ "react-router": "7.8.0"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=18",
+ "react-dom": ">=18"
+ }
+ },
+ "node_modules/recharts": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/recharts/-/recharts-3.1.2.tgz",
+ "integrity": "sha512-vhNbYwaxNbk/IATK0Ki29k3qvTkGqwvCgyQAQ9MavvvBwjvKnMTswdbklJpcOAoMPN/qxF3Lyqob0zO+ZXkZ4g==",
+ "license": "MIT",
+ "dependencies": {
+ "@reduxjs/toolkit": "1.x.x || 2.x.x",
+ "clsx": "^2.1.1",
+ "decimal.js-light": "^2.5.1",
+ "es-toolkit": "^1.39.3",
+ "eventemitter3": "^5.0.1",
+ "immer": "^10.1.1",
+ "react-redux": "8.x.x || 9.x.x",
+ "reselect": "5.1.1",
+ "tiny-invariant": "^1.3.3",
+ "use-sync-external-store": "^1.2.2",
+ "victory-vendor": "^37.0.2"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
+ "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
+ "react-is": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
+ "node_modules/redux": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz",
+ "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==",
+ "license": "MIT"
+ },
+ "node_modules/redux-thunk": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz",
+ "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==",
+ "license": "MIT",
+ "peerDependencies": {
+ "redux": "^5.0.0"
+ }
+ },
+ "node_modules/reselect": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz",
+ "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==",
+ "license": "MIT"
+ },
+ "node_modules/resolve-from": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
+ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/rollup": {
+ "version": "4.46.2",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.46.2.tgz",
+ "integrity": "sha512-WMmLFI+Boh6xbop+OAGo9cQ3OgX9MIg7xOQjn+pTCwOkk+FNDAeAemXkJ3HzDJrVXleLOFVa1ipuc1AmEx1Dwg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "1.0.8"
+ },
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=18.0.0",
+ "npm": ">=8.0.0"
+ },
+ "optionalDependencies": {
+ "@rollup/rollup-android-arm-eabi": "4.46.2",
+ "@rollup/rollup-android-arm64": "4.46.2",
+ "@rollup/rollup-darwin-arm64": "4.46.2",
+ "@rollup/rollup-darwin-x64": "4.46.2",
+ "@rollup/rollup-freebsd-arm64": "4.46.2",
+ "@rollup/rollup-freebsd-x64": "4.46.2",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.46.2",
+ "@rollup/rollup-linux-arm-musleabihf": "4.46.2",
+ "@rollup/rollup-linux-arm64-gnu": "4.46.2",
+ "@rollup/rollup-linux-arm64-musl": "4.46.2",
+ "@rollup/rollup-linux-loongarch64-gnu": "4.46.2",
+ "@rollup/rollup-linux-ppc64-gnu": "4.46.2",
+ "@rollup/rollup-linux-riscv64-gnu": "4.46.2",
+ "@rollup/rollup-linux-riscv64-musl": "4.46.2",
+ "@rollup/rollup-linux-s390x-gnu": "4.46.2",
+ "@rollup/rollup-linux-x64-gnu": "4.46.2",
+ "@rollup/rollup-linux-x64-musl": "4.46.2",
+ "@rollup/rollup-win32-arm64-msvc": "4.46.2",
+ "@rollup/rollup-win32-ia32-msvc": "4.46.2",
+ "@rollup/rollup-win32-x64-msvc": "4.46.2",
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/scheduler": {
+ "version": "0.26.0",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz",
+ "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==",
+ "license": "MIT"
+ },
+ "node_modules/semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/set-cookie-parser": {
+ "version": "2.7.1",
+ "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz",
+ "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==",
+ "license": "MIT"
+ },
+ "node_modules/shallowequal": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz",
+ "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==",
+ "license": "MIT"
+ },
+ "node_modules/shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "shebang-regex": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/source-map-js": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/strip-json-comments": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
+ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/styled-components": {
+ "version": "6.1.19",
+ "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.1.19.tgz",
+ "integrity": "sha512-1v/e3Dl1BknC37cXMhwGomhO8AkYmN41CqyX9xhUDxry1ns3BFQy2lLDRQXJRdVVWB9OHemv/53xaStimvWyuA==",
+ "license": "MIT",
+ "dependencies": {
+ "@emotion/is-prop-valid": "1.2.2",
+ "@emotion/unitless": "0.8.1",
+ "@types/stylis": "4.2.5",
+ "css-to-react-native": "3.2.0",
+ "csstype": "3.1.3",
+ "postcss": "8.4.49",
+ "shallowequal": "1.1.0",
+ "stylis": "4.3.2",
+ "tslib": "2.6.2"
+ },
+ "engines": {
+ "node": ">= 16"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/styled-components"
+ },
+ "peerDependencies": {
+ "react": ">= 16.8.0",
+ "react-dom": ">= 16.8.0"
+ }
+ },
+ "node_modules/styled-components/node_modules/postcss": {
+ "version": "8.4.49",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz",
+ "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.7",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/stylis": {
+ "version": "4.3.2",
+ "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.2.tgz",
+ "integrity": "sha512-bhtUjWd/z6ltJiQwg0dUfxEJ+W+jdqQd8TbWLWyeIJHlnsqmGLRFFd8e5mA0AZi/zx90smXRlN66YMTcaSFifg==",
+ "license": "MIT"
+ },
+ "node_modules/supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/tiny-invariant": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz",
+ "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==",
+ "license": "MIT"
+ },
+ "node_modules/tinyglobby": {
+ "version": "0.2.14",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz",
+ "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fdir": "^6.4.4",
+ "picomatch": "^4.0.2"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/SuperchupuDev"
+ }
+ },
+ "node_modules/tslib": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
+ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==",
+ "license": "0BSD"
+ },
+ "node_modules/type-check": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
+ "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "prelude-ls": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/update-browserslist-db": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz",
+ "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "escalade": "^3.2.0",
+ "picocolors": "^1.1.1"
+ },
+ "bin": {
+ "update-browserslist-db": "cli.js"
+ },
+ "peerDependencies": {
+ "browserslist": ">= 4.21.0"
+ }
+ },
+ "node_modules/uri-js": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
+ "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "punycode": "^2.1.0"
+ }
+ },
+ "node_modules/use-sync-external-store": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz",
+ "integrity": "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
+ "node_modules/victory-vendor": {
+ "version": "37.3.6",
+ "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-37.3.6.tgz",
+ "integrity": "sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ==",
+ "license": "MIT AND ISC",
+ "dependencies": {
+ "@types/d3-array": "^3.0.3",
+ "@types/d3-ease": "^3.0.0",
+ "@types/d3-interpolate": "^3.0.1",
+ "@types/d3-scale": "^4.0.2",
+ "@types/d3-shape": "^3.1.0",
+ "@types/d3-time": "^3.0.0",
+ "@types/d3-timer": "^3.0.0",
+ "d3-array": "^3.1.6",
+ "d3-ease": "^3.0.1",
+ "d3-interpolate": "^3.0.1",
+ "d3-scale": "^4.0.2",
+ "d3-shape": "^3.1.0",
+ "d3-time": "^3.0.0",
+ "d3-timer": "^3.0.1"
+ }
+ },
+ "node_modules/vite": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.1.tgz",
+ "integrity": "sha512-yJ+Mp7OyV+4S+afWo+QyoL9jFWD11QFH0i5i7JypnfTcA1rmgxCbiA8WwAICDEtZ1Z1hzrVhN8R8rGTqkTY8ZQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "esbuild": "^0.25.0",
+ "fdir": "^6.4.6",
+ "picomatch": "^4.0.3",
+ "postcss": "^8.5.6",
+ "rollup": "^4.43.0",
+ "tinyglobby": "^0.2.14"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^20.19.0 || >=22.12.0",
+ "jiti": ">=1.21.0",
+ "less": "^4.0.0",
+ "lightningcss": "^1.21.0",
+ "sass": "^1.70.0",
+ "sass-embedded": "^1.70.0",
+ "stylus": ">=0.54.8",
+ "sugarss": "^5.0.0",
+ "terser": "^5.16.0",
+ "tsx": "^4.8.1",
+ "yaml": "^2.4.2"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "jiti": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "lightningcss": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ },
+ "tsx": {
+ "optional": true
+ },
+ "yaml": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/word-wrap": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
+ "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/yallist": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
+ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/yocto-queue": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
+ "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ }
+ }
+}
diff --git a/frontend/package.json b/frontend/package.json
index 7b2747e949..b3e22d5c07 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -1,26 +1,30 @@
{
- "name": "project-final-backend",
- "description": "Client part of final project",
- "version": "1.0.0",
+ "name": "frontend",
+ "private": true,
+ "version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
- "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0",
+ "lint": "eslint .",
"preview": "vite preview"
},
"dependencies": {
- "react": "^18.2.0",
- "react-dom": "^18.2.0"
+ "react": "^19.1.1",
+ "react-dom": "^19.1.1",
+ "react-icons": "^5.5.0",
+ "react-router-dom": "^7.8.0",
+ "styled-components": "^6.1.19"
},
"devDependencies": {
- "@types/react": "^18.2.15",
- "@types/react-dom": "^18.2.7",
- "@vitejs/plugin-react": "^4.0.3",
- "eslint": "^8.45.0",
- "eslint-plugin-react": "^7.32.2",
- "eslint-plugin-react-hooks": "^4.6.0",
- "eslint-plugin-react-refresh": "^0.4.3",
- "vite": "^6.3.5"
+ "@eslint/js": "^9.32.0",
+ "@types/react": "^19.1.9",
+ "@types/react-dom": "^19.1.7",
+ "@vitejs/plugin-react": "^4.7.0",
+ "eslint": "^9.32.0",
+ "eslint-plugin-react-hooks": "^5.2.0",
+ "eslint-plugin-react-refresh": "^0.4.20",
+ "globals": "^16.3.0",
+ "vite": "^7.1.0"
}
}
diff --git a/frontend/public/_redirects b/frontend/public/_redirects
new file mode 100644
index 0000000000..f8243379a0
--- /dev/null
+++ b/frontend/public/_redirects
@@ -0,0 +1 @@
+/* /index.html 200
\ No newline at end of file
diff --git a/frontend/public/icon.png b/frontend/public/icon.png
new file mode 100644
index 0000000000..6a33b4c26a
Binary files /dev/null and b/frontend/public/icon.png differ
diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx
index 0a24275e6e..3c07157535 100644
--- a/frontend/src/App.jsx
+++ b/frontend/src/App.jsx
@@ -1,8 +1,72 @@
-export const App = () => {
+import { CartProvider } from "./contexts/CartProvider";
+import { AuthProvider } from "./contexts/AuthContext";
+import { GlobalStyles } from "./styles/GlobalStyles";
+import CookieConsent from "./components/CookieConsent";
+import {
+ BrowserRouter as Router,
+ Routes,
+ Route,
+ Navigate,
+} from "react-router-dom";
+import { Navbar } from "./components/Navbar";
+import { Breadcrumbs } from "./components/Breadcrumbs";
+import { TopBar } from "./components/TopBar";
+import { Footer } from "./components/Footer";
+import { Home } from "./pages/Home";
+import { Media } from "./pages/Media";
+import { Gigs } from "./pages/Gigs";
+import { Merch } from "./pages/Merch";
+import { ProductPage } from "./pages/ProductPage";
+import { Cart } from "./pages/Cart";
+import { GalleryPage } from "./pages/GalleryPage";
+import { Contact } from "./pages/Contact";
+import { Terms } from "./pages/Terms";
+import { Privacy } from "./pages/Privacy";
+import { Login } from "./pages/Login";
+import { Favorites } from "./pages/Favorites";
+import { AccountSettings } from "./pages/AccountSettings";
+// App content component med routing
+const AppContent = () => {
return (
<>
- Welcome to Final Project!
+ {/* Unified Navbar automatically switches between website and shopping modes */}
+
+
+
+
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+
+
+
+
+
>
);
};
+
+export const App = () => {
+ return (
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/frontend/src/api/basketApi.js b/frontend/src/api/basketApi.js
new file mode 100644
index 0000000000..d72dff1368
--- /dev/null
+++ b/frontend/src/api/basketApi.js
@@ -0,0 +1,93 @@
+import { apiCall } from '../config/api.js';
+
+// Skapa ny basket
+export const createBasket = async (basketItems) => {
+ const response = await apiCall('/api/merch/baskets', {
+ method: 'POST',
+ body: JSON.stringify({ basketItems }),
+ });
+
+ if (!response.ok) {
+ throw new Error(`HTTP error! status: ${response.status}`);
+ }
+
+ return response.json();
+};
+
+// Get existing basket
+export const getBasket = async (basketId) => {
+ const response = await apiCall(`/api/merch/baskets/${basketId}`);
+
+ if (!response.ok) {
+ if (response.status === 404) {
+ return null; // Basket finns inte
+ }
+ throw new Error(`HTTP error! status: ${response.status}`);
+ }
+
+ return response.json();
+};
+
+// Uppdatera basket
+export const updateBasket = async (basketId, basketItems) => {
+ const response = await apiCall(`/api/merch/baskets/${basketId}`, {
+ method: 'PUT',
+ body: JSON.stringify({ basketItems }),
+ });
+
+ if (!response.ok) {
+ throw new Error(`HTTP error! status: ${response.status}`);
+ }
+
+ return response.json();
+};
+
+// Ta bort hela basket
+export const deleteBasket = async (basketId) => {
+ const response = await apiCall(`/api/merch/baskets/${basketId}`, {
+ method: 'DELETE',
+ });
+
+ if (!response.ok) {
+ throw new Error(`HTTP error! status: ${response.status}`);
+ }
+
+ return true;
+};
+
+// Konvertera produktdata till basketItem format
+export const convertToBasketItem = async (
+ productId,
+ sizeName,
+ colorName,
+ quantity = 1
+) => {
+ const response = await apiCall('/api/merch/baskets/convert', {
+ method: 'POST',
+ body: JSON.stringify({
+ productId,
+ sizeName,
+ colorName,
+ quantity,
+ }),
+ });
+
+ if (!response.ok) {
+ throw new Error(`HTTP error! status: ${response.status}`);
+ }
+
+ return response.json();
+};
+
+// Get checkout URL via backend
+export const getCheckoutUrl = async (basketId) => {
+ const response = await apiCall(`/api/merch/baskets/${basketId}/checkout`);
+
+ if (!response.ok) {
+ throw new Error(`HTTP error! status: ${response.status}`);
+ }
+
+ return response.json();
+};
+
+
diff --git a/frontend/src/assets/images/band.jpg b/frontend/src/assets/images/band.jpg
new file mode 100644
index 0000000000..398644c303
Binary files /dev/null and b/frontend/src/assets/images/band.jpg differ
diff --git a/frontend/src/assets/images/fredagsmangel/DSC02947.jpg b/frontend/src/assets/images/fredagsmangel/DSC02947.jpg
new file mode 100644
index 0000000000..da4869862f
Binary files /dev/null and b/frontend/src/assets/images/fredagsmangel/DSC02947.jpg differ
diff --git a/frontend/src/assets/images/fredagsmangel/DSC02951.jpg b/frontend/src/assets/images/fredagsmangel/DSC02951.jpg
new file mode 100644
index 0000000000..48a4c4e4a6
Binary files /dev/null and b/frontend/src/assets/images/fredagsmangel/DSC02951.jpg differ
diff --git a/frontend/src/assets/images/fredagsmangel/DSC02957.jpg b/frontend/src/assets/images/fredagsmangel/DSC02957.jpg
new file mode 100644
index 0000000000..640fb5df4a
Binary files /dev/null and b/frontend/src/assets/images/fredagsmangel/DSC02957.jpg differ
diff --git a/frontend/src/assets/images/fredagsmangel/DSC02960.jpg b/frontend/src/assets/images/fredagsmangel/DSC02960.jpg
new file mode 100644
index 0000000000..e057fe0681
Binary files /dev/null and b/frontend/src/assets/images/fredagsmangel/DSC02960.jpg differ
diff --git a/frontend/src/assets/images/fredagsmangel/DSC02962.jpg b/frontend/src/assets/images/fredagsmangel/DSC02962.jpg
new file mode 100644
index 0000000000..58b07e1b39
Binary files /dev/null and b/frontend/src/assets/images/fredagsmangel/DSC02962.jpg differ
diff --git a/frontend/src/assets/images/fredagsmangel/DSC02967.jpg b/frontend/src/assets/images/fredagsmangel/DSC02967.jpg
new file mode 100644
index 0000000000..5c0c061fe9
Binary files /dev/null and b/frontend/src/assets/images/fredagsmangel/DSC02967.jpg differ
diff --git a/frontend/src/assets/images/fredagsmangel/DSC02968.jpg b/frontend/src/assets/images/fredagsmangel/DSC02968.jpg
new file mode 100644
index 0000000000..9596145355
Binary files /dev/null and b/frontend/src/assets/images/fredagsmangel/DSC02968.jpg differ
diff --git a/frontend/src/assets/images/fredagsmangel/DSC02969.jpg b/frontend/src/assets/images/fredagsmangel/DSC02969.jpg
new file mode 100644
index 0000000000..52d06b68c4
Binary files /dev/null and b/frontend/src/assets/images/fredagsmangel/DSC02969.jpg differ
diff --git a/frontend/src/assets/images/fredagsmangel/DSC02970.jpg b/frontend/src/assets/images/fredagsmangel/DSC02970.jpg
new file mode 100644
index 0000000000..141e33a3fc
Binary files /dev/null and b/frontend/src/assets/images/fredagsmangel/DSC02970.jpg differ
diff --git a/frontend/src/assets/images/fredagsmangel/DSC02973.jpg b/frontend/src/assets/images/fredagsmangel/DSC02973.jpg
new file mode 100644
index 0000000000..925e01547c
Binary files /dev/null and b/frontend/src/assets/images/fredagsmangel/DSC02973.jpg differ
diff --git a/frontend/src/assets/images/fryshuset/1.jpg b/frontend/src/assets/images/fryshuset/1.jpg
new file mode 100644
index 0000000000..880ce24294
Binary files /dev/null and b/frontend/src/assets/images/fryshuset/1.jpg differ
diff --git a/frontend/src/assets/images/fryshuset/10.jpg b/frontend/src/assets/images/fryshuset/10.jpg
new file mode 100644
index 0000000000..1dc2b5d5bd
Binary files /dev/null and b/frontend/src/assets/images/fryshuset/10.jpg differ
diff --git a/frontend/src/assets/images/fryshuset/11.jpg b/frontend/src/assets/images/fryshuset/11.jpg
new file mode 100644
index 0000000000..2ac3c57504
Binary files /dev/null and b/frontend/src/assets/images/fryshuset/11.jpg differ
diff --git a/frontend/src/assets/images/fryshuset/12.jpg b/frontend/src/assets/images/fryshuset/12.jpg
new file mode 100644
index 0000000000..ff5a9954fc
Binary files /dev/null and b/frontend/src/assets/images/fryshuset/12.jpg differ
diff --git a/frontend/src/assets/images/fryshuset/13.jpg b/frontend/src/assets/images/fryshuset/13.jpg
new file mode 100644
index 0000000000..75a4d7e936
Binary files /dev/null and b/frontend/src/assets/images/fryshuset/13.jpg differ
diff --git a/frontend/src/assets/images/fryshuset/14.jpg b/frontend/src/assets/images/fryshuset/14.jpg
new file mode 100644
index 0000000000..a4a7a966b8
Binary files /dev/null and b/frontend/src/assets/images/fryshuset/14.jpg differ
diff --git a/frontend/src/assets/images/fryshuset/15.jpg b/frontend/src/assets/images/fryshuset/15.jpg
new file mode 100644
index 0000000000..cffd586007
Binary files /dev/null and b/frontend/src/assets/images/fryshuset/15.jpg differ
diff --git a/frontend/src/assets/images/fryshuset/16.jpg b/frontend/src/assets/images/fryshuset/16.jpg
new file mode 100644
index 0000000000..1dc514b546
Binary files /dev/null and b/frontend/src/assets/images/fryshuset/16.jpg differ
diff --git a/frontend/src/assets/images/fryshuset/17.jpg b/frontend/src/assets/images/fryshuset/17.jpg
new file mode 100644
index 0000000000..d21e41245f
Binary files /dev/null and b/frontend/src/assets/images/fryshuset/17.jpg differ
diff --git a/frontend/src/assets/images/fryshuset/18.jpg b/frontend/src/assets/images/fryshuset/18.jpg
new file mode 100644
index 0000000000..9345e0f141
Binary files /dev/null and b/frontend/src/assets/images/fryshuset/18.jpg differ
diff --git a/frontend/src/assets/images/fryshuset/19.jpg b/frontend/src/assets/images/fryshuset/19.jpg
new file mode 100644
index 0000000000..f75525098b
Binary files /dev/null and b/frontend/src/assets/images/fryshuset/19.jpg differ
diff --git a/frontend/src/assets/images/fryshuset/2.jpg b/frontend/src/assets/images/fryshuset/2.jpg
new file mode 100644
index 0000000000..07ff23ceb0
Binary files /dev/null and b/frontend/src/assets/images/fryshuset/2.jpg differ
diff --git a/frontend/src/assets/images/fryshuset/20.jpg b/frontend/src/assets/images/fryshuset/20.jpg
new file mode 100644
index 0000000000..3e263e391e
Binary files /dev/null and b/frontend/src/assets/images/fryshuset/20.jpg differ
diff --git a/frontend/src/assets/images/fryshuset/21.jpg b/frontend/src/assets/images/fryshuset/21.jpg
new file mode 100644
index 0000000000..2c0a5e673d
Binary files /dev/null and b/frontend/src/assets/images/fryshuset/21.jpg differ
diff --git a/frontend/src/assets/images/fryshuset/22.jpg b/frontend/src/assets/images/fryshuset/22.jpg
new file mode 100644
index 0000000000..258b65c0f5
Binary files /dev/null and b/frontend/src/assets/images/fryshuset/22.jpg differ
diff --git a/frontend/src/assets/images/fryshuset/23.jpg b/frontend/src/assets/images/fryshuset/23.jpg
new file mode 100644
index 0000000000..e753aaf7d3
Binary files /dev/null and b/frontend/src/assets/images/fryshuset/23.jpg differ
diff --git a/frontend/src/assets/images/fryshuset/24.jpg b/frontend/src/assets/images/fryshuset/24.jpg
new file mode 100644
index 0000000000..ef27b0e2bc
Binary files /dev/null and b/frontend/src/assets/images/fryshuset/24.jpg differ
diff --git a/frontend/src/assets/images/fryshuset/25.jpg b/frontend/src/assets/images/fryshuset/25.jpg
new file mode 100644
index 0000000000..d3eb829f18
Binary files /dev/null and b/frontend/src/assets/images/fryshuset/25.jpg differ
diff --git a/frontend/src/assets/images/fryshuset/26.jpg b/frontend/src/assets/images/fryshuset/26.jpg
new file mode 100644
index 0000000000..b719475e9f
Binary files /dev/null and b/frontend/src/assets/images/fryshuset/26.jpg differ
diff --git a/frontend/src/assets/images/fryshuset/27.jpg b/frontend/src/assets/images/fryshuset/27.jpg
new file mode 100644
index 0000000000..61e1368898
Binary files /dev/null and b/frontend/src/assets/images/fryshuset/27.jpg differ
diff --git a/frontend/src/assets/images/fryshuset/28.jpg b/frontend/src/assets/images/fryshuset/28.jpg
new file mode 100644
index 0000000000..abc69126e2
Binary files /dev/null and b/frontend/src/assets/images/fryshuset/28.jpg differ
diff --git a/frontend/src/assets/images/fryshuset/29.jpg b/frontend/src/assets/images/fryshuset/29.jpg
new file mode 100644
index 0000000000..5a622a2870
Binary files /dev/null and b/frontend/src/assets/images/fryshuset/29.jpg differ
diff --git a/frontend/src/assets/images/fryshuset/3.jpg b/frontend/src/assets/images/fryshuset/3.jpg
new file mode 100644
index 0000000000..4b0a367a48
Binary files /dev/null and b/frontend/src/assets/images/fryshuset/3.jpg differ
diff --git a/frontend/src/assets/images/fryshuset/30.jpg b/frontend/src/assets/images/fryshuset/30.jpg
new file mode 100644
index 0000000000..af01ad80c7
Binary files /dev/null and b/frontend/src/assets/images/fryshuset/30.jpg differ
diff --git a/frontend/src/assets/images/fryshuset/31.jpg b/frontend/src/assets/images/fryshuset/31.jpg
new file mode 100644
index 0000000000..4851f794e3
Binary files /dev/null and b/frontend/src/assets/images/fryshuset/31.jpg differ
diff --git a/frontend/src/assets/images/fryshuset/32.jpg b/frontend/src/assets/images/fryshuset/32.jpg
new file mode 100644
index 0000000000..d06849c4f4
Binary files /dev/null and b/frontend/src/assets/images/fryshuset/32.jpg differ
diff --git a/frontend/src/assets/images/fryshuset/33.jpg b/frontend/src/assets/images/fryshuset/33.jpg
new file mode 100644
index 0000000000..6a25a50dfb
Binary files /dev/null and b/frontend/src/assets/images/fryshuset/33.jpg differ
diff --git a/frontend/src/assets/images/fryshuset/4.jpg b/frontend/src/assets/images/fryshuset/4.jpg
new file mode 100644
index 0000000000..6f8b49aad3
Binary files /dev/null and b/frontend/src/assets/images/fryshuset/4.jpg differ
diff --git a/frontend/src/assets/images/fryshuset/5.jpg b/frontend/src/assets/images/fryshuset/5.jpg
new file mode 100644
index 0000000000..b9c6b1f13d
Binary files /dev/null and b/frontend/src/assets/images/fryshuset/5.jpg differ
diff --git a/frontend/src/assets/images/fryshuset/6.jpg b/frontend/src/assets/images/fryshuset/6.jpg
new file mode 100644
index 0000000000..2e801f68ff
Binary files /dev/null and b/frontend/src/assets/images/fryshuset/6.jpg differ
diff --git a/frontend/src/assets/images/fryshuset/7.jpg b/frontend/src/assets/images/fryshuset/7.jpg
new file mode 100644
index 0000000000..eb17bb61c1
Binary files /dev/null and b/frontend/src/assets/images/fryshuset/7.jpg differ
diff --git a/frontend/src/assets/images/fryshuset/8.jpg b/frontend/src/assets/images/fryshuset/8.jpg
new file mode 100644
index 0000000000..492b627f9b
Binary files /dev/null and b/frontend/src/assets/images/fryshuset/8.jpg differ
diff --git a/frontend/src/assets/images/fryshuset/9.jpg b/frontend/src/assets/images/fryshuset/9.jpg
new file mode 100644
index 0000000000..305dce112b
Binary files /dev/null and b/frontend/src/assets/images/fryshuset/9.jpg differ
diff --git a/frontend/src/assets/images/olearys/DSC02572-Enhanced-NR (1).jpg b/frontend/src/assets/images/olearys/DSC02572-Enhanced-NR (1).jpg
new file mode 100644
index 0000000000..7f2b123557
Binary files /dev/null and b/frontend/src/assets/images/olearys/DSC02572-Enhanced-NR (1).jpg differ
diff --git a/frontend/src/assets/images/olearys/DSC02578-Enhanced-NR.jpg b/frontend/src/assets/images/olearys/DSC02578-Enhanced-NR.jpg
new file mode 100644
index 0000000000..532b911100
Binary files /dev/null and b/frontend/src/assets/images/olearys/DSC02578-Enhanced-NR.jpg differ
diff --git a/frontend/src/assets/images/olearys/DSC02609-Enhanced-NR.jpg b/frontend/src/assets/images/olearys/DSC02609-Enhanced-NR.jpg
new file mode 100644
index 0000000000..9eaa573bf7
Binary files /dev/null and b/frontend/src/assets/images/olearys/DSC02609-Enhanced-NR.jpg differ
diff --git a/frontend/src/assets/images/olearys/DSC02648-Enhanced-NR (1).jpg b/frontend/src/assets/images/olearys/DSC02648-Enhanced-NR (1).jpg
new file mode 100644
index 0000000000..c00f181129
Binary files /dev/null and b/frontend/src/assets/images/olearys/DSC02648-Enhanced-NR (1).jpg differ
diff --git a/frontend/src/assets/images/olearys/DSC02663-Enhanced-NR.jpg b/frontend/src/assets/images/olearys/DSC02663-Enhanced-NR.jpg
new file mode 100644
index 0000000000..26ef596665
Binary files /dev/null and b/frontend/src/assets/images/olearys/DSC02663-Enhanced-NR.jpg differ
diff --git a/frontend/src/assets/images/olearys/DSC02679-Enhanced-NR.jpg b/frontend/src/assets/images/olearys/DSC02679-Enhanced-NR.jpg
new file mode 100644
index 0000000000..c99a87d1fb
Binary files /dev/null and b/frontend/src/assets/images/olearys/DSC02679-Enhanced-NR.jpg differ
diff --git a/frontend/src/assets/images/olearys/DSC02682-Enhanced-NR-Edit.jpg b/frontend/src/assets/images/olearys/DSC02682-Enhanced-NR-Edit.jpg
new file mode 100644
index 0000000000..90b1d56cd7
Binary files /dev/null and b/frontend/src/assets/images/olearys/DSC02682-Enhanced-NR-Edit.jpg differ
diff --git a/frontend/src/assets/logo.svg b/frontend/src/assets/logo.svg
new file mode 100644
index 0000000000..9b8f7e9e63
--- /dev/null
+++ b/frontend/src/assets/logo.svg
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/frontend/src/components/AddToCartPopup.jsx b/frontend/src/components/AddToCartPopup.jsx
new file mode 100644
index 0000000000..0f309a1f80
--- /dev/null
+++ b/frontend/src/components/AddToCartPopup.jsx
@@ -0,0 +1,43 @@
+import { useNavigate } from "react-router-dom";
+import {
+ PopupOverlay,
+ PopupBox,
+ PopupMessage,
+ PopupButtons,
+ ContinueButton,
+ CartButton,
+} from "./shared/PopupComponents";
+
+export const AddToCartPopup = ({ isOpen, onClose, quantity }) => {
+ const navigate = useNavigate();
+
+ if (!isOpen) return null;
+
+ const handleContinueShopping = () => {
+ onClose();
+ navigate("/merch");
+ };
+
+ const handleGoToCart = () => {
+ onClose();
+ navigate("/cart");
+ };
+
+ return (
+
+
+
+ You've added {quantity} item{quantity !== 1 ? "s" : ""} to your cart
+
+
+
+ Continue shopping
+
+
+ Go to cart
+
+
+
+
+ );
+};
\ No newline at end of file
diff --git a/frontend/src/components/BookingForm.jsx b/frontend/src/components/BookingForm.jsx
new file mode 100644
index 0000000000..2cb774f31f
--- /dev/null
+++ b/frontend/src/components/BookingForm.jsx
@@ -0,0 +1,493 @@
+import { useState } from "react";
+import styled from "styled-components";
+import { apiCall } from "../config/api";
+
+const FormWrapper = styled.div`
+ max-width: 100%;
+ margin: 0 auto;
+ padding: 1rem 0.75rem;
+
+ @media (min-width: 480px) {
+ padding: 1.5rem 1rem;
+ }
+
+ @media (min-width: 768px) {
+ max-width: 500px;
+ padding: 2rem 1rem;
+ }
+
+ @media (min-width: 1024px) {
+ max-width: 550px;
+ }
+
+ @media (min-width: 1200px) {
+ max-width: 600px;
+ }
+
+ @media (min-width: 1400px) {
+ max-width: 650px;
+ }
+`;
+
+const FormContainer = styled.div`
+ display: flex;
+ flex-direction: column;
+ gap: 1.25rem;
+ background: #1a1a1a;
+ padding: 1.5rem;
+ border-radius: 12px;
+ border: 1px solid #333;
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
+
+ @media (min-width: 480px) {
+ gap: 1.5rem;
+ padding: 2rem;
+ }
+
+ @media (min-width: 768px) {
+ gap: 1.75rem;
+ padding: 2.5rem;
+ }
+`;
+
+const FormGroup = styled.div`
+ display: flex;
+ flex-direction: column;
+ gap: 0.4rem;
+
+ @media (min-width: 480px) {
+ gap: 0.5rem;
+ }
+`;
+
+const FormRow = styled.div`
+ display: flex;
+ flex-direction: column;
+ gap: 1rem;
+
+ @media (min-width: 768px) {
+ flex-direction: row;
+ gap: 1.5rem;
+
+ > div {
+ flex: 1;
+ }
+ }
+`;
+
+const Label = styled.label`
+ color: #ffffff;
+ font-weight: bold;
+ font-size: 0.95rem;
+ text-transform: uppercase;
+ letter-spacing: 0.5px;
+
+ @media (min-width: 480px) {
+ font-size: 1rem;
+ letter-spacing: 1px;
+ }
+
+ @media (min-width: 768px) {
+ font-size: 1.1rem;
+ }
+`;
+
+const Input = styled.input`
+ padding: 0.65rem;
+ background: #2a2a2a;
+ border: 1px solid #444444;
+ border-radius: 8px;
+ color: #ffffff;
+ font-size: 0.95rem;
+ transition: all 0.2s ease;
+ width: 100%;
+ box-sizing: border-box;
+
+ @media (min-width: 480px) {
+ padding: 0.75rem;
+ font-size: 1rem;
+ }
+
+ @media (min-width: 768px) {
+ padding: 0.85rem;
+ }
+
+ &:focus {
+ outline: none;
+ border-color: #dc2626;
+ background: #333;
+ box-shadow: 0 0 0 3px rgba(220, 38, 38, 0.1);
+ }
+
+ &:hover:not(:focus) {
+ border-color: #555;
+ }
+
+ &::placeholder {
+ color: #cccccc;
+ }
+`;
+
+const Select = styled.select`
+ padding: 0.65rem;
+ background: #2a2a2a;
+ border: 1px solid #444444;
+ border-radius: 8px;
+ color: #ffffff;
+ font-size: 0.95rem;
+ cursor: pointer;
+ transition: all 0.2s ease;
+ width: 100%;
+ box-sizing: border-box;
+
+ @media (min-width: 480px) {
+ padding: 0.75rem;
+ font-size: 1rem;
+ }
+
+ @media (min-width: 768px) {
+ padding: 0.85rem;
+ }
+
+ &:focus {
+ outline: none;
+ border-color: #dc2626;
+ background: #333;
+ box-shadow: 0 0 0 3px rgba(220, 38, 38, 0.1);
+ }
+
+ &:hover:not(:focus) {
+ border-color: #555;
+ }
+
+ option {
+ background: #2a2a2a;
+ color: #ffffff;
+ }
+`;
+
+const TextArea = styled.textarea`
+ padding: 0.65rem;
+ background: #2a2a2a;
+ border: 1px solid #444444;
+ border-radius: 8px;
+ color: #ffffff;
+ font-size: 0.95rem;
+ min-height: 100px;
+ resize: vertical;
+ font-family: inherit;
+ transition: all 0.2s ease;
+ width: 100%;
+ box-sizing: border-box;
+
+ @media (min-width: 480px) {
+ padding: 0.75rem;
+ font-size: 1rem;
+ min-height: 120px;
+ }
+
+ @media (min-width: 768px) {
+ padding: 0.85rem;
+ min-height: 140px;
+ }
+
+ &:focus {
+ outline: none;
+ border-color: #dc2626;
+ background: #333;
+ box-shadow: 0 0 0 3px rgba(220, 38, 38, 0.1);
+ }
+
+ &:hover:not(:focus) {
+ border-color: #555;
+ }
+
+ &::placeholder {
+ color: #cccccc;
+ }
+`;
+
+const SubmitButton = styled.button`
+ background: linear-gradient(45deg, #dc2626, #991b1b);
+ color: #ffffff;
+ border: 1px solid #dc2626;
+ padding: 0.5rem 0.9rem;
+ font-size: 0.75rem;
+ font-weight: bold;
+ text-transform: uppercase;
+ letter-spacing: 0.5px;
+ border-radius: 6px;
+ cursor: pointer;
+ margin-top: 0.5rem;
+ transition: all 0.3s ease;
+ width: 100%;
+ align-self: flex-start;
+
+ @media (min-width: 480px) {
+ padding: 0.6rem 1rem;
+ font-size: 0.8rem;
+ margin-top: 0.75rem;
+ }
+
+ @media (min-width: 768px) {
+ padding: 0.65rem 1.25rem;
+ font-size: 0.85rem;
+ width: auto;
+ align-self: flex-start;
+ }
+
+ @media (min-width: 1024px) {
+ padding: 0.75rem 1.5rem;
+ font-size: 0.9rem;
+ }
+
+ &:hover {
+ background: linear-gradient(45deg, #991b1b, #dc2626);
+ transform: translateY(-2px);
+ }
+
+ &:active {
+ transform: translateY(0);
+ }
+
+ &:disabled {
+ background: #666666;
+ cursor: not-allowed;
+ transform: none;
+ }
+`;
+
+const RequiredStar = styled.span`
+ color: #dc2626;
+ margin-left: 4px;
+`;
+
+const SuccessMessage = styled.div`
+ background: #1a1a1a;
+ border: 1px solid #dc2626;
+ color: #ffffff;
+ padding: 1rem;
+ border-radius: 4px;
+ text-align: center;
+ margin-bottom: 1rem;
+
+ h2 {
+ font-size: 1.3rem;
+ margin: 0 0 1rem 0;
+
+ @media (min-width: 480px) {
+ font-size: 1.5rem;
+ }
+ }
+
+ p {
+ font-size: 0.9rem;
+ margin: 0 0 1rem 0;
+
+ @media (min-width: 480px) {
+ font-size: 1rem;
+ }
+ }
+`;
+
+const BookingForm = () => {
+ const [formData, setFormData] = useState({
+ name: "",
+ email: "",
+ phone: "",
+ eventType: "",
+ eventDate: "",
+ venue: "",
+ message: "",
+ });
+
+ const [isSubmitted, setIsSubmitted] = useState(false);
+ const [isSubmitting, setIsSubmitting] = useState(false);
+
+ const handleChange = (e) => {
+ const { name, value } = e.target;
+ setFormData((prev) => ({
+ ...prev,
+ [name]: value,
+ }));
+ };
+
+ const handleSubmit = async () => {
+ if (
+ !formData.name ||
+ !formData.email ||
+ !formData.eventType ||
+ !formData.eventDate ||
+ !formData.venue ||
+ !formData.message
+ ) {
+ alert("Please fill in all required fields");
+ return;
+ }
+
+ setIsSubmitting(true);
+
+ try {
+ const response = await apiCall("/api/forms/booking", {
+ method: "POST",
+ body: JSON.stringify(formData),
+ });
+
+ const result = await response.json();
+
+ if (result.success) {
+ setIsSubmitted(true);
+ } else {
+ alert("Error: " + result.error);
+ }
+ } catch (error) {
+ alert("Failed to send booking request. Please try again.");
+ }
+
+ setIsSubmitting(false);
+ };
+
+ const resetForm = () => {
+ setIsSubmitted(false);
+ setFormData({
+ name: "",
+ email: "",
+ phone: "",
+ eventType: "",
+ eventDate: "",
+ venue: "",
+ message: "",
+ });
+ };
+
+ if (isSubmitted) {
+ return (
+
+
+ BOOKING REQUEST SENT
+
+ Thanks for your booking request! We will get back to you within
+ 24-48 hours with more information.
+
+ Send another request
+
+
+ );
+ }
+
+ return (
+
+
+
+
+
+ Your Name *
+
+
+
+
+
+
+ Email Address *
+
+
+
+
+
+
+
+ Phone Number
+
+
+
+
+
+ Event Type *
+
+
+ Select event type
+ Festival
+ Club/Bar
+ Private Event
+ Other
+
+
+
+
+
+
+
+ Preferred Event Date *
+
+
+
+
+
+
+ City *
+
+
+
+
+
+
+
+ Additional Information *
+
+
+
+
+
+ {isSubmitting ? "SENDING REQUEST..." : "SEND BOOKING REQUEST"}
+
+
+
+ );
+};
+
+export default BookingForm;
diff --git a/frontend/src/components/Breadcrumbs.jsx b/frontend/src/components/Breadcrumbs.jsx
new file mode 100644
index 0000000000..d8230288b4
--- /dev/null
+++ b/frontend/src/components/Breadcrumbs.jsx
@@ -0,0 +1,206 @@
+import { Link, useLocation, useParams } from "react-router-dom";
+import styled from "styled-components";
+import { useProduct } from "../hooks/useProduct";
+
+const BreadcrumbContainer = styled.nav`
+ padding: 0.75rem 1rem;
+ position: relative;
+ z-index: 100;
+ display: flex;
+ align-items: center;
+ justify-content: flex-start;
+ margin-top: 0; /* No extra margin needed */
+
+ @media (min-width: 480px) {
+ padding: 1rem 1.5rem;
+ }
+
+ @media (min-width: 768px) {
+ padding: 1.25rem 2rem;
+ }
+`;
+
+const BreadcrumbList = styled.ol`
+ display: flex;
+ align-items: center;
+ list-style: none;
+ margin: 0;
+ padding: 0;
+ font-size: 0.75rem;
+ justify-self: start;
+
+ @media (min-width: 480px) {
+ font-size: 0.875rem;
+ }
+`;
+
+const BreadcrumbItem = styled.li`
+ display: flex;
+ align-items: center;
+
+ &:not(:last-child)::after {
+ content: "/";
+ margin: 0 0.5rem;
+ color: #cccccc;
+ font-size: 0.75rem;
+
+ @media (min-width: 480px) {
+ margin: 0 0.75rem;
+ font-size: 0.8rem;
+ }
+ }
+`;
+
+const BreadcrumbLink = styled(Link)`
+ color: #cccccc;
+ text-decoration: none;
+ font-size: 14px;
+ margin: 0;
+ padding: 0;
+
+ &:hover {
+ color: #ffffff;
+ }
+`;
+
+const BreadcrumbCurrent = styled.span`
+ color: #ffffffff;
+ font-size: 14px;
+ margin: 0;
+ padding: 0;
+`;
+
+
+export const Breadcrumbs = () => {
+ const location = useLocation();
+ const { productId } = useParams();
+ const { product, loading } = useProduct(productId);
+
+ // Show breadcrumbs only on shopping-related pages
+ const showBreadcrumbs =
+ location.pathname === "/merch" ||
+ location.pathname.startsWith("/product") ||
+ location.pathname === "/cart" ||
+ location.pathname === "/favorites" ||
+ location.pathname === "/settings" ||
+ location.pathname === "/login";
+
+ if (!showBreadcrumbs) return null;
+
+ const renderBreadcrumbs = () => {
+ const path = location.pathname;
+
+ if (path === "/merch") {
+ return (
+ <>
+
+ Home
+
+
+ Merch
+
+ >
+ );
+ }
+
+ if (path.startsWith("/product")) {
+ return (
+ <>
+
+
+ Home
+
+
+
+ Merch
+
+
+
+ {loading ? "Loading..." : product?.name || "Product"}
+
+
+ >
+ );
+ }
+
+ if (path === "/cart") {
+ return (
+ <>
+
+
+ Home
+
+
+
+ Merch
+
+
+ Cart
+
+ >
+ );
+ }
+
+ if (path === "/settings") {
+ return (
+ <>
+
+
+ Home
+
+
+
+ Merch
+
+
+ Settings
+
+ >
+ );
+ }
+
+ if (path === "/favorites") {
+ return (
+ <>
+
+
+ Home
+
+
+
+ Merch
+
+
+ Favorites
+
+ >
+ );
+ }
+
+ if (path === "/login") {
+ return (
+ <>
+
+
+ Home
+
+
+
+ Merch
+
+
+ Login
+
+ >
+ );
+ }
+
+ return null;
+ };
+
+ return (
+
+ {renderBreadcrumbs()}
+
+ );
+};
diff --git a/frontend/src/components/CartItem.jsx b/frontend/src/components/CartItem.jsx
new file mode 100644
index 0000000000..2ec9bff9e4
--- /dev/null
+++ b/frontend/src/components/CartItem.jsx
@@ -0,0 +1,302 @@
+import { useMemo } from "react";
+import styled from "styled-components";
+import { Link } from "react-router-dom";
+import { formatPrice } from "../utils/formatPrice";
+import { translateSize, translateColor, translateProductType } from "../utils/translations";
+import { FaTrash } from "react-icons/fa";
+
+const CartItemContainer = styled.div`
+ display: flex;
+ padding: 0.75rem;
+ border: 1px solid #e0e0e0;
+ background-color: #ffffff;
+ margin-bottom: 0.75rem;
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+ gap: 0.75rem;
+ width: 100%;
+ box-sizing: border-box;
+
+ @media (min-width: 480px) {
+ padding: 1rem;
+ gap: 1rem;
+ }
+
+ @media (min-width: 768px) {
+ padding: 1.25rem;
+ margin-bottom: 1rem;
+ }
+`;
+
+const ProductImageLink = styled(Link)`
+ display: block;
+ align-self: flex-start;
+ flex-shrink: 0;
+ cursor: pointer;
+ transition: opacity 0.2s;
+
+ &:hover {
+ opacity: 0.8;
+ }
+`;
+
+const ProductImage = styled.img`
+ object-fit: cover;
+ width: 60px;
+ height: 60px;
+ display: block;
+
+ @media (min-width: 480px) {
+ width: 80px;
+ height: 80px;
+ }
+
+ @media (min-width: 768px) {
+ width: 120px;
+ height: 120px;
+ }
+`;
+
+const ProductDetails = styled.div`
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ gap: 0.75rem;
+ min-width: 0; /* Prevent overflow */
+
+ @media (min-width: 480px) {
+ gap: 1rem;
+ }
+
+ @media (min-width: 768px) {
+ flex-direction: row;
+ justify-content: space-between;
+ align-items: flex-start;
+ }
+`;
+
+const ProductInfo = styled.div`
+ flex: 1; /* Takes up most space to the left */
+`;
+
+const PriceSection = styled.div`
+ display: flex;
+ flex-direction: column;
+ gap: 0.5rem;
+ align-items: flex-end;
+`;
+
+const QuantityAndActions = styled.div`
+ display: flex;
+ align-items: center;
+ justify-content: flex-start;
+
+ @media (min-width: 768px) {
+ justify-content: flex-end;
+ }
+`;
+
+const ProductName = styled(Link)`
+ margin: 0 0 0.5rem 0;
+ font-size: 16px;
+ color: #333333;
+ text-align: left;
+ font-weight: bold;
+ text-decoration: none;
+ display: block;
+ transition: color 0.2s;
+
+ &:hover {
+ color: #666666;
+ }
+
+ @media (min-width: 480px) {
+ font-size: 18px;
+ }
+`;
+
+const ProductInfoDetails = styled.div`
+ display: flex;
+ flex-direction: column;
+ gap: 0.25rem;
+ margin-bottom: 0.75rem;
+ font-size: 16px;
+ color: #666666;
+ text-align: left;
+`;
+
+const InfoSpan = styled.span`
+ font-size: 16px;
+
+ &.price {
+ font-weight: bold;
+ color: #333333;
+ font-size: 16px;
+ }
+`;
+
+const QuantityContainer = styled.div`
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ margin-bottom: 0.75rem;
+
+ @media (min-width: 480px) {
+ justify-content: flex-end;
+ }
+`;
+
+const QuantityButton = styled.button`
+ padding: 0.4rem 0.6rem;
+ background-color: #f5f5f5;
+ border: 1px solid #ddd;
+ color: #333333;
+ cursor: pointer;
+ font-size: 16px;
+ min-width: 36px;
+ font-weight: bold;
+
+ @media (min-width: 480px) {
+ padding: 0.5rem 0.75rem;
+ min-width: 40px;
+ }
+
+ &:hover {
+ background-color: #e5e5e5;
+ }
+
+ &:disabled {
+ background-color: #f9f9f9;
+ color: #999;
+ cursor: not-allowed;
+ }
+`;
+
+const QuantityDisplay = styled.span`
+ margin: 0 0.4rem;
+ font-size: 16px;
+ font-weight: bold;
+ color: #333333;
+ min-width: 1.5rem;
+ text-align: center;
+
+ @media (min-width: 480px) {
+ margin: 0 0.75rem;
+ min-width: 2rem;
+ }
+`;
+
+
+
+
+export const CartItem = ({ item, onUpdateQuantity, onRemove }) => {
+ if (!item) return null;
+
+ const getSavedImage = () => {
+ // First: check if we have a valid selectedImage
+ if (
+ item.selectedImage &&
+ typeof item.selectedImage === "string" &&
+ item.selectedImage.startsWith("http")
+ ) {
+ return item.selectedImage;
+ }
+
+ // Then: search in localStorage with multiple key formats
+ const possibleKeys = [
+ `cart-image-${item.sellableId}-${item.size}-${item.appearanceId}`,
+ `cart-image-${item.sellableId}-${item.size}-default`,
+ `cart-image-${item.sellableId}-${item.size}`, // Gamla formatet
+ `cart-image-${item.sellableId}-${item.size}-undefined`,
+ ];
+
+
+ for (const imageKey of possibleKeys) {
+ const savedImage = localStorage.getItem(imageKey);
+
+ if (
+ savedImage &&
+ typeof savedImage === "string" &&
+ savedImage.startsWith("http")
+ ) {
+ return savedImage;
+ }
+ }
+
+ return "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iODAiIGhlaWdodD0iODAiIHZpZXdCb3g9IjAgMCA4MCA4MCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHJlY3Qgd2lkdGg9IjgwIiBoZWlnaHQ9IjgwIiBmaWxsPSIjQ0NDIi8+Cjx0ZXh0IHg9IjQwIiB5PSI0NSIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZm9udC1mYW1pbHk9IkFyaWFsIiBmb250LXNpemU9IjEyIiBmaWxsPSIjNjY2Ij5ObyBJbWFnZTwvdGV4dD4KPHN2Zz4K";
+ };
+
+ const imageUrl = useMemo(() => getSavedImage(), [item.sellableId, item.size, item.appearanceId, item.selectedImage]);
+
+ const handleQuantityChange = (newQuantity) => {
+ if (newQuantity <= 0) {
+ onRemove();
+ } else {
+ onUpdateQuantity(newQuantity);
+ }
+ };
+
+ const handleRemove = () => {
+ onRemove();
+ };
+
+ return (
+
+
+ {
+ // Prevent infinite loop - just set to gray box and don't log
+ e.target.src =
+ "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iODAiIGhlaWdodD0iODAiIHZpZXdCb3g9IjAgMCA4MCA4MCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHJlY3Qgd2lkdGg9IjgwIiBoZWlnaHQ9IjgwIiBmaWxsPSIjQ0NDIi8+Cjx0ZXh0IHg9IjQwIiB5PSI0NSIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZm9udC1mYW1pbHk9IkFyaWFsIiBmb250LXNpemU9IjEyIiBmaWxsPSIjNjY2Ij5ObyBJbWFnZTwvdGV4dD4KPHN2Zz4K";
+ }}
+ />
+
+
+
+
+
+ {translateProductType(item.name)}
+
+
+ Size: {translateSize(item.size)}
+ {item.color && (
+
+ Color: {translateColor(item.color)}
+
+ )}
+
+ Price: {formatPrice(item.price)}
+
+
+
+
+
+
+ {
+ handleQuantityChange(item.quantity - 1);
+ }}
+ aria-label={item.quantity === 1 ? "Remove item from cart" : "Decrease quantity"}
+ title={item.quantity === 1 ? "Remove item from cart" : "Decrease quantity"}
+ >
+ {item.quantity === 1 ? : '-'}
+
+ {item.quantity}
+ {
+ handleQuantityChange(item.quantity + 1);
+ }}
+ aria-label="Increase quantity"
+ title="Increase quantity"
+ >
+ +
+
+
+
+
+
+ );
+};
diff --git a/frontend/src/components/ClearCartPopup.jsx b/frontend/src/components/ClearCartPopup.jsx
new file mode 100644
index 0000000000..d6fbdb3ce4
--- /dev/null
+++ b/frontend/src/components/ClearCartPopup.jsx
@@ -0,0 +1,35 @@
+import {
+ PopupOverlay,
+ PopupBox,
+ PopupMessage,
+ PopupButtons,
+ ContinueButton,
+ CartButton,
+} from "./shared/PopupComponents";
+
+export const ClearCartPopup = ({ isOpen, onClose, onConfirm }) => {
+ if (!isOpen) return null;
+
+ const handleConfirm = () => {
+ onConfirm();
+ onClose();
+ };
+
+ return (
+
+
+
+ Are you sure you want to clear your entire cart? This action cannot be undone.
+
+
+
+ Cancel
+
+
+ Clear Cart
+
+
+
+
+ );
+};
\ No newline at end of file
diff --git a/frontend/src/components/ContactForm.jsx b/frontend/src/components/ContactForm.jsx
new file mode 100644
index 0000000000..b78f827fc6
--- /dev/null
+++ b/frontend/src/components/ContactForm.jsx
@@ -0,0 +1,439 @@
+import { useState } from "react";
+import styled from "styled-components";
+import { apiCall } from "../config/api";
+
+const FormWrapper = styled.div`
+ max-width: 100%;
+ margin: 0 auto;
+ padding: 1rem 0.75rem;
+
+ @media (min-width: 480px) {
+ padding: 1.5rem 1rem;
+ }
+
+ @media (min-width: 768px) {
+ max-width: 600px;
+ padding: 2rem 1rem;
+ }
+`;
+
+const FormContainer = styled.div`
+ display: flex;
+ flex-direction: column;
+ gap: 1.25rem;
+ background: #1a1a1a;
+ padding: 1.5rem;
+ border-radius: 12px;
+ border: 1px solid #333;
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
+
+ @media (min-width: 480px) {
+ gap: 1.5rem;
+ padding: 2rem;
+ }
+
+ @media (min-width: 768px) {
+ gap: 1.75rem;
+ padding: 2.5rem;
+ }
+`;
+
+const FormGroup = styled.div`
+ display: flex;
+ flex-direction: column;
+ gap: 0.4rem;
+
+ @media (min-width: 480px) {
+ gap: 0.5rem;
+ }
+`;
+
+const FormRow = styled.div`
+ display: flex;
+ flex-direction: column;
+ gap: 1rem;
+
+ @media (min-width: 768px) {
+ flex-direction: row;
+ gap: 1.5rem;
+
+ > div {
+ flex: 1;
+ }
+ }
+`;
+
+const Label = styled.label`
+ color: #ffffff;
+ font-weight: bold;
+ font-size: 0.95rem;
+ text-transform: uppercase;
+ letter-spacing: 0.5px;
+
+ @media (min-width: 480px) {
+ font-size: 1rem;
+ letter-spacing: 1px;
+ }
+
+ @media (min-width: 768px) {
+ font-size: 1.1rem;
+ }
+`;
+
+const Input = styled.input`
+ padding: 0.65rem;
+ background: #2a2a2a;
+ border: 1px solid #444444;
+ border-radius: 8px;
+ color: #ffffff;
+ font-size: 0.95rem;
+ transition: all 0.2s ease;
+ width: 100%;
+ box-sizing: border-box;
+
+ @media (min-width: 480px) {
+ padding: 0.75rem;
+ font-size: 1rem;
+ }
+
+ @media (min-width: 768px) {
+ padding: 0.85rem;
+ }
+
+ &:focus {
+ outline: none;
+ border-color: #dc2626;
+ background: #333;
+ box-shadow: 0 0 0 3px rgba(220, 38, 38, 0.1);
+ }
+
+ &::placeholder {
+ color: #cccccc;
+ }
+
+ &:hover:not(:focus) {
+ border-color: #555;
+ }
+`;
+
+const Select = styled.select`
+ padding: 0.65rem;
+ background: #2a2a2a;
+ border: 1px solid #444444;
+ border-radius: 8px;
+ color: #ffffff;
+ font-size: 0.95rem;
+ cursor: pointer;
+ transition: all 0.2s ease;
+ width: 100%;
+ box-sizing: border-box;
+
+ @media (min-width: 480px) {
+ padding: 0.75rem;
+ font-size: 1rem;
+ }
+
+ @media (min-width: 768px) {
+ padding: 0.85rem;
+ }
+
+ &:focus {
+ outline: none;
+ border-color: #dc2626;
+ background: #333;
+ box-shadow: 0 0 0 3px rgba(220, 38, 38, 0.1);
+ }
+
+ &:hover:not(:focus) {
+ border-color: #555;
+ }
+
+ option {
+ background: #2a2a2a;
+ color: #ffffff;
+ }
+`;
+
+const TextArea = styled.textarea`
+ padding: 0.65rem;
+ background: #2a2a2a;
+ border: 1px solid #444444;
+ border-radius: 8px;
+ color: #ffffff;
+ font-size: 0.95rem;
+ min-height: 100px;
+ resize: vertical;
+ font-family: inherit;
+ transition: all 0.2s ease;
+ width: 100%;
+ box-sizing: border-box;
+
+ @media (min-width: 480px) {
+ padding: 0.75rem;
+ font-size: 1rem;
+ min-height: 120px;
+ }
+
+ @media (min-width: 768px) {
+ padding: 0.85rem;
+ min-height: 140px;
+ }
+
+ &:focus {
+ outline: none;
+ border-color: #dc2626;
+ background: #333;
+ box-shadow: 0 0 0 3px rgba(220, 38, 38, 0.1);
+ }
+
+ &:hover:not(:focus) {
+ border-color: #555;
+ }
+
+ &::placeholder {
+ color: #cccccc;
+ }
+`;
+
+const SubmitButton = styled.button`
+ background: linear-gradient(45deg, #dc2626, #991b1b);
+ color: #ffffff;
+ border: 1px solid #dc2626;
+ padding: 0.5rem 0.9rem;
+ font-size: 0.75rem;
+ font-weight: bold;
+ text-transform: uppercase;
+ letter-spacing: 0.5px;
+ border-radius: 6px;
+ cursor: pointer;
+ margin-top: 0.5rem;
+ transition: all 0.3s ease;
+ width: 100%;
+ align-self: flex-start;
+
+ @media (min-width: 480px) {
+ padding: 0.6rem 1rem;
+ font-size: 0.8rem;
+ margin-top: 0.75rem;
+ }
+
+ @media (min-width: 768px) {
+ padding: 0.65rem 1.25rem;
+ font-size: 0.85rem;
+ width: auto;
+ align-self: flex-start;
+ }
+
+ @media (min-width: 1024px) {
+ padding: 0.75rem 1.5rem;
+ font-size: 0.9rem;
+ }
+
+ &:hover {
+ background: linear-gradient(45deg, #991b1b, #dc2626);
+ transform: translateY(-2px);
+ }
+
+ &:active {
+ transform: translateY(0);
+ }
+
+ &:disabled {
+ background: #666666;
+ cursor: not-allowed;
+ transform: none;
+ }
+`;
+
+const RequiredStar = styled.span`
+ color: #dc2626;
+ margin-left: 4px;
+`;
+
+const SuccessMessage = styled.div`
+ background: #1a1a1a;
+ border: 1px solid #dc2626;
+ color: #ffffff;
+ padding: 1rem;
+ border-radius: 4px;
+ text-align: center;
+ margin-bottom: 1rem;
+
+ h2 {
+ font-size: 1.3rem;
+ margin: 0 0 1rem 0;
+
+ @media (min-width: 480px) {
+ font-size: 1.5rem;
+ }
+ }
+
+ p {
+ font-size: 0.9rem;
+ margin: 0 0 1rem 0;
+
+ @media (min-width: 480px) {
+ font-size: 1rem;
+ }
+ }
+`;
+
+const ContactForm = () => {
+ const [formData, setFormData] = useState({
+ name: "",
+ email: "",
+ phone: "",
+ subject: "",
+ message: "",
+ });
+
+ const [isSubmitted, setIsSubmitted] = useState(false);
+ const [isSubmitting, setIsSubmitting] = useState(false);
+
+ const handleChange = (e) => {
+ const { name, value } = e.target;
+ setFormData((prev) => ({
+ ...prev,
+ [name]: value,
+ }));
+ };
+
+ const handleSubmit = async () => {
+ if (!formData.name || !formData.email || !formData.message) {
+ alert("Please fill in all required fields");
+ return;
+ }
+
+ setIsSubmitting(true);
+
+ try {
+ const response = await apiCall("/api/forms/contact", {
+ method: "POST",
+ body: JSON.stringify(formData),
+ });
+
+ const result = await response.json();
+
+ if (result.success) {
+ setIsSubmitted(true);
+ } else {
+ alert("Error: " + result.error);
+ }
+ } catch (error) {
+ alert("Failed to send message. Please try again.");
+ }
+
+ setIsSubmitting(false);
+ };
+
+ const resetForm = () => {
+ setIsSubmitted(false);
+ setFormData({
+ name: "",
+ email: "",
+ phone: "",
+ subject: "",
+ message: "",
+ });
+ };
+
+ if (isSubmitted) {
+ return (
+
+
+ MESSAGE SENT
+
+ Thank you for your message! We'll get back to you as soon as possible.
+
+ SEND ANOTHER MESSAGE
+
+
+ );
+ }
+
+ return (
+
+
+
+
+
+ Your Name *
+
+
+
+
+
+
+ Email Address *
+
+
+
+
+
+
+
+ Phone Number
+
+
+
+
+ Subject
+
+ Select a subject
+ General Inquiry
+ Collaboration
+ Press & Media
+ Technical Support
+ Other
+
+
+
+
+
+
+ Message *
+
+
+
+
+
+ {isSubmitting ? "SENDING MESSAGE..." : "SEND MESSAGE"}
+
+
+
+ );
+};
+
+export default ContactForm;
\ No newline at end of file
diff --git a/frontend/src/components/CookieConsent.jsx b/frontend/src/components/CookieConsent.jsx
new file mode 100644
index 0000000000..ff9c5c9f97
--- /dev/null
+++ b/frontend/src/components/CookieConsent.jsx
@@ -0,0 +1,135 @@
+import { useState, useEffect } from 'react';
+import styled from 'styled-components';
+
+const CookieBanner = styled.div`
+ position: fixed;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ background: #1a1a1a;
+ border-top: 2px solid #dc2626;
+ padding: 1rem;
+ z-index: 1000;
+ display: ${props => props.$show ? 'block' : 'none'};
+
+ @media (min-width: 768px) {
+ padding: 1.5rem;
+ }
+`;
+
+const CookieContent = styled.div`
+ max-width: 1200px;
+ margin: 0 auto;
+ display: flex;
+ flex-direction: column;
+ gap: 1rem;
+
+ @media (min-width: 768px) {
+ flex-direction: row;
+ align-items: center;
+ justify-content: space-between;
+ }
+`;
+
+const CookieText = styled.div`
+ color: #ffffff;
+ font-size: 0.875rem;
+ line-height: 1.5;
+
+ @media (min-width: 768px) {
+ font-size: 1rem;
+ }
+
+ a {
+ color: #dc2626;
+ text-decoration: underline;
+
+ &:hover {
+ color: #b91c1c;
+ }
+ }
+`;
+
+const CookieButtons = styled.div`
+ display: flex;
+ gap: 0.75rem;
+ flex-wrap: wrap;
+`;
+
+const CookieButton = styled.button`
+ padding: 0.75rem 1.5rem;
+ border: none;
+ border-radius: 4px;
+ font-weight: bold;
+ cursor: pointer;
+ transition: background-color 0.2s;
+ font-size: 0.875rem;
+
+ @media (min-width: 768px) {
+ font-size: 1rem;
+ }
+`;
+
+const AcceptButton = styled(CookieButton)`
+ background-color: #dc2626;
+ color: #ffffff;
+
+ &:hover {
+ background-color: #b91c1c;
+ }
+`;
+
+const DeclineButton = styled(CookieButton)`
+ background-color: #374151;
+ color: #ffffff;
+
+ &:hover {
+ background-color: #4b5563;
+ }
+`;
+
+const CookieConsent = () => {
+ const [showBanner, setShowBanner] = useState(false);
+
+ useEffect(() => {
+ // Check if user has already made a choice
+ const consent = localStorage.getItem('cookieConsent');
+ if (!consent) {
+ setShowBanner(true);
+ }
+ }, []);
+
+ const handleAccept = () => {
+ localStorage.setItem('cookieConsent', 'accepted');
+ setShowBanner(false);
+ };
+
+ const handleDecline = () => {
+ localStorage.setItem('cookieConsent', 'declined');
+ setShowBanner(false);
+ // Here you could disable non-essential cookies/tracking
+ // For now, we'll just hide the banner since authentication cookies are necessary
+ };
+
+ return (
+
+
+
+ We use cookies to improve your experience on our website.
+ Essential cookies are used for login and shopping cart functionality.
+ Read more in our privacy policy
+
+
+
+ Essential only
+
+
+ Accept all
+
+
+
+
+ );
+};
+
+export default CookieConsent;
\ No newline at end of file
diff --git a/frontend/src/components/FavoriteButton.jsx b/frontend/src/components/FavoriteButton.jsx
new file mode 100644
index 0000000000..a547b4197e
--- /dev/null
+++ b/frontend/src/components/FavoriteButton.jsx
@@ -0,0 +1,128 @@
+import { useState } from 'react';
+import styled from 'styled-components';
+import { useAuth } from '../contexts/AuthContext';
+
+const HeartButton = styled.button`
+ background: none;
+ border: none;
+ cursor: pointer;
+ padding: 8px;
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ transition: all 0.2s ease;
+ position: relative;
+
+ &:hover {
+ background: rgba(0, 0, 0, 0.1);
+ transform: scale(1.1);
+ }
+
+ &:active {
+ transform: scale(0.95);
+ }
+
+ &:disabled {
+ opacity: 0.5;
+ cursor: not-allowed;
+ }
+
+ /* Tooltip for non-logged in users */
+ ${props => !props.$isAuthenticated && props.$showTooltip ? `
+ &:hover::after {
+ content: "Login to add favorites";
+ position: absolute;
+ top: 100%;
+ left: 50%;
+ transform: translateX(-50%);
+ margin-top: 5px;
+ background: #333;
+ color: white;
+ padding: 4px 8px;
+ border-radius: 4px;
+ font-size: 12px;
+ white-space: nowrap;
+ z-index: 1000;
+ }
+ ` : ''}
+`;
+
+const HeartIcon = styled.svg`
+ width: 20px;
+ height: 20px;
+ transition: all 0.2s ease;
+
+ path {
+ fill: ${props => props.$isFavorite ? '#dc2626' : 'transparent'};
+ stroke: ${props => props.$isFavorite ? '#dc2626' : '#666'};
+ stroke-width: 2;
+ transition: all 0.2s ease;
+ }
+
+ ${HeartButton}:hover & path {
+ stroke: #dc2626;
+ }
+`;
+
+const FavoriteButton = ({ product, size = 20, showTooltip = false }) => {
+ const { isAuthenticated, isFavorite, addToFavorites, removeFromFavorites } = useAuth();
+ const [loading, setLoading] = useState(false);
+
+ const productIsFavorite = isFavorite(product.sellableId);
+
+ const handleClick = async (e) => {
+ e.preventDefault(); // Prevent navigation if button is inside a Link
+ e.stopPropagation(); // Prevent event bubbling
+
+ if (!isAuthenticated) {
+ return;
+ }
+
+ setLoading(true);
+
+ try {
+ if (productIsFavorite) {
+ await removeFromFavorites(product.sellableId);
+ } else {
+ await addToFavorites(product);
+ }
+ } catch (error) {
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ return (
+
+
+
+
+
+ );
+};
+
+export default FavoriteButton;
\ No newline at end of file
diff --git a/frontend/src/components/Footer.jsx b/frontend/src/components/Footer.jsx
new file mode 100644
index 0000000000..58d90136db
--- /dev/null
+++ b/frontend/src/components/Footer.jsx
@@ -0,0 +1,162 @@
+import { Link } from 'react-router-dom';
+import styled from 'styled-components';
+import { FaSpotify, FaInstagram, FaYoutube, FaFacebook } from 'react-icons/fa';
+
+const FooterContainer = styled.footer`
+ background-color: #000;
+ border-top: 1px solid #333;
+ padding: 2rem 1rem;
+ margin-top: auto;
+
+ @media (min-width: 768px) {
+ padding: 2rem 2rem;
+ }
+`;
+
+const FooterContent = styled.div`
+ max-width: 1200px;
+ margin: 0 auto;
+ display: grid;
+ grid-template-columns: 1fr;
+ gap: 2rem;
+ text-align: center;
+
+ @media (min-width: 768px) {
+ grid-template-columns: 1fr 1fr 1fr;
+ text-align: left;
+ align-items: center;
+ }
+`;
+
+const SocialSection = styled.div`
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ gap: 1rem;
+
+ @media (min-width: 768px) {
+ justify-content: flex-start;
+ }
+`;
+
+const SocialLink = styled.a`
+ color: white;
+ font-size: 1.5rem;
+ transition: color 0.2s ease;
+
+ &:hover {
+ color: #dc2626;
+ }
+`;
+
+const CopyrightSection = styled.div`
+ color: #ccc;
+ font-size: 0.9rem;
+ display: flex;
+ flex-direction: column;
+ gap: 0.5rem;
+ align-items: center;
+
+ @media (min-width: 768px) {
+ text-align: center;
+ }
+`;
+
+const LegalLinks = styled.div`
+ display: flex;
+ gap: 1rem;
+
+ @media (min-width: 768px) {
+ font-size: 12px;
+ }
+`;
+
+const LegalLink = styled(Link)`
+ color: #999;
+ text-decoration: none;
+ transition: color 0.2s ease;
+
+ &:hover {
+ color: #ccc;
+ }
+`;
+
+const ContactSection = styled.div`
+ color: #ccc;
+ font-size: 0.9rem;
+
+ @media (min-width: 768px) {
+ text-align: right;
+ }
+`;
+
+const EmailLink = styled.a`
+ color: #ccc;
+ text-decoration: none;
+ transition: color 0.2s ease;
+
+ &:hover {
+ color: white;
+ }
+`;
+
+export const Footer = () => {
+ const currentYear = new Date().getFullYear();
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ © {currentYear} Morbid Gene
+
+ Terms of Use
+ Privacy Policy
+
+
+
+
+
+ morbidgenemusic@gmail.com
+
+
+
+
+ );
+};
\ No newline at end of file
diff --git a/frontend/src/components/HeroSection.jsx b/frontend/src/components/HeroSection.jsx
new file mode 100644
index 0000000000..e39809b734
--- /dev/null
+++ b/frontend/src/components/HeroSection.jsx
@@ -0,0 +1,96 @@
+import styled from "styled-components";
+import { Link } from "react-router-dom";
+import backgroundImage from "../assets/images/band.jpg";
+import logo from "../assets/logo.svg";
+
+const Hero = styled.section`
+ min-height: 100dvh;
+ width: 100dvw;
+ position: relative;
+ background: url(${backgroundImage}) no-repeat center center/cover;
+ display: flex;
+ flex-direction: column;
+ justify-content: flex-end;
+ align-items: center;
+ padding: 2rem;
+ padding-bottom: 4rem;
+ text-align: center;
+ overflow: hidden;
+`;
+
+const HeroContent = styled.div`
+ background-color: rgba(0, 0, 0, 0.6);
+ padding: 1.5rem 2rem;
+ max-width: 90%;
+ border-radius: 8px;
+ overflow-x: hidden;
+
+ @media (min-width: 768px) {
+ backdrop-filter: blur(4px);
+ }
+`;
+
+const HeroTitle = styled.h1`
+ font-size: 2.5rem;
+ color: #f5f5f5;
+ text-shadow: 2px 2px 6px #000;
+ margin: 0;
+ word-break: break-word;
+
+ @media (min-width: 768px) {
+ font-size: 4rem;
+ }
+`;
+
+const HeroLink = styled.a`
+ display: inline-block;
+ margin-top: 1rem;
+ font-size: 1.2rem;
+ color: #ccc;
+ text-decoration: none;
+ text-shadow: 1px 1px 3px #000;
+
+ &:hover {
+ text-decoration: underline;
+ color: #fff;
+ }
+
+ @media (min-width: 768px) {
+ font-size: 1.5rem;
+ }
+`;
+
+const LogoLink = styled(Link)`
+ position: absolute;
+ top: 0rem;
+ left: 2rem;
+ z-index: 10;
+
+ img {
+ height: 80px;
+
+ @media (min-width: 768px) {
+ height: 200px;
+ }
+ }
+`;
+
+export const HeroSection = () => {
+ return (
+
+
+
+
+
+ Morbid Gene Official
+
+ Our EP 'Face Your Maker' out now – Listen here!
+
+
+
+ );
+};
diff --git a/frontend/src/components/InstagramFeed.jsx b/frontend/src/components/InstagramFeed.jsx
new file mode 100644
index 0000000000..8242db4c90
--- /dev/null
+++ b/frontend/src/components/InstagramFeed.jsx
@@ -0,0 +1,238 @@
+import styled from "styled-components";
+import { FaInstagram, FaPlay } from "react-icons/fa";
+
+const FeedContainer = styled.section`
+ background: #000;
+ padding: 3rem 1rem;
+ text-align: center;
+
+ @media (min-width: 768px) {
+ padding: 4rem 2rem;
+ }
+`;
+
+const FeedTitle = styled.a`
+ color: #fff;
+ font-size: 1.1rem;
+ margin-bottom: 2rem;
+ text-transform: uppercase;
+ letter-spacing: 1px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 0.5rem;
+ text-decoration: none;
+ transition: color 0.2s;
+ flex-wrap: wrap;
+
+ &:hover {
+ color: #9ca3af;
+ }
+
+ @media (min-width: 768px) {
+ font-size: 1.8rem;
+ letter-spacing: 2px;
+ flex-wrap: nowrap;
+ }
+`;
+
+const InstagramHandle = styled.a`
+ color: #ffffff;
+ text-decoration: none;
+ font-size: 1rem;
+ margin-bottom: 2rem;
+ display: inline-block;
+ transition: color 0.2s;
+
+ &:hover {
+ color: #9ca3af;
+ }
+
+ @media (min-width: 768px) {
+ font-size: 1.2rem;
+ }
+`;
+
+const ImageGrid = styled.div`
+ max-width: 1200px;
+ margin: 0 auto;
+ display: grid;
+ grid-template-columns: repeat(2, 1fr);
+ gap: 0.5rem;
+
+ @media (min-width: 768px) {
+ grid-template-columns: repeat(3, 1fr);
+ gap: 1rem;
+ }
+`;
+
+const ImageLink = styled.a`
+ position: relative;
+ aspect-ratio: 1;
+ overflow: hidden;
+ background: #1a1a1a;
+ transition: transform 0.2s;
+
+ &:hover {
+ transform: scale(1.05);
+ z-index: 1;
+
+ &::after {
+ opacity: 1;
+ }
+ }
+
+ &::after {
+ content: '';
+ position: absolute;
+ inset: 0;
+ background: linear-gradient(to bottom, transparent, rgba(220, 38, 38, 0.4));
+ opacity: 0;
+ transition: opacity 0.2s;
+ }
+`;
+
+const InstagramImage = styled.img`
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+`;
+
+const PlayButton = styled.div`
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ background: rgba(0, 0, 0, 0.7);
+ color: white;
+ width: 3rem;
+ height: 3rem;
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 1.2rem;
+ z-index: 2;
+`;
+
+const PlaceholderImage = styled.div`
+ width: 100%;
+ height: 100%;
+ background: #1a1a1a;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: #333;
+ font-size: 3rem;
+`;
+
+export const InstagramFeed = () => {
+ // Real Instagram data from behold.io
+ const instagramData = {
+ posts: [
+ {
+ id: "18056736512621615",
+ permalink: "https://www.instagram.com/reel/DNl8nxOCD-X/",
+ mediaType: "VIDEO",
+ thumbnailUrl: "https://scontent-sof1-2.cdninstagram.com/v/t51.71878-15/535765604_3228535713971134_5922814022170085326_n.jpg?stp=dst-jpg_e35_tt6&_nc_cat=111&ccb=1-7&_nc_sid=18de74&_nc_ohc=QTFFIrIuJ-sQ7kNvwH0UGZi&_nc_oc=AdmMF782hc4K9iFkI5P1pw87Ctr4QJzHO3G7Ac__qcyU8B5LhRqtcejILe3G1c_RGrFC1MCQ4F42adducJZdpiM2&_nc_zt=23&_nc_ht=scontent-sof1-2.cdninstagram.com&edm=ANo9K5cEAAAA&_nc_gid=hq3oAYY2XfShs1uiDlo4Sg&oh=00_AfXjk5_VN1AjX28JIjUtqx-nOWKTOQzmICFi4MekOYjN7g&oe=68B5265F",
+ sizes: { medium: { mediaUrl: "https://behold.pictures/XPDtsfFk8wQ6ty5tqpTOBI9KPaC2/AmMaOJ50cbyW838WVB8L/18056736512621615/medium.jpg" } },
+ caption: "NEW EP, \"FACE YOUR MAKER\" - OUT NOW ON ALL STREAMING PLATFORMS! 💿"
+ },
+ {
+ id: "17870368617419948",
+ permalink: "https://www.instagram.com/reel/DNNxbP_Mhzr/",
+ mediaType: "VIDEO",
+ thumbnailUrl: "https://scontent-sof1-1.cdninstagram.com/v/t51.82787-15/530233102_17962684250951301_4624421791502455443_n.jpg?stp=dst-jpg_e35_tt6&_nc_cat=101&ccb=1-7&_nc_sid=18de74&_nc_ohc=9taS___u5ZQQ7kNvwEIgOUk&_nc_oc=Adkjtp6w3yLq_woDq46EIlk3ZeDAKuFFOBHTcHS1FycDEooTqVuPgtoz2iJ8O9D3r9wvefVFXP0JDRtDKKzjmPYN&_nc_zt=23&_nc_ht=scontent-sof1-1.cdninstagram.com&edm=ANo9K5cEAAAA&_nc_gid=hq3oAYY2XfShs1uiDlo4Sg&oh=00_AfVRw_V7T1463gJBbjucfSAs_wDyqlW4CwtLxMOTJC6bxQ&oe=68B522C3",
+ sizes: { medium: { mediaUrl: "https://behold.pictures/XPDtsfFk8wQ6ty5tqpTOBI9KPaC2/AmMaOJ50cbyW838WVB8L/17870368617419948/medium.jpg" } },
+ caption: "🖤🖤"
+ },
+ {
+ id: "17895729507266608",
+ permalink: "https://www.instagram.com/reel/DM_h-1aiOia/",
+ mediaType: "VIDEO",
+ thumbnailUrl: "https://scontent-sof1-1.cdninstagram.com/v/t51.71878-15/528297148_3941584452819681_394528058147860578_n.jpg?stp=dst-jpg_e35_tt6&_nc_cat=105&ccb=1-7&_nc_sid=18de74&_nc_ohc=xwxXfyxHVNoQ7kNvwGmwwXe&_nc_oc=Adk_rU8oQi9THt_QiJRgwK1wKSyU-phmlYCGvmRtD1Gp463KIH1l8dZSFWxuPdEz4PIm_R11ALTaJ74XzGUu7beZ&_nc_zt=23&_nc_ht=scontent-sof1-1.cdninstagram.com&edm=ANo9K5cEAAAA&_nc_gid=hq3oAYY2XfShs1uiDlo4Sg&oh=00_AfUR953u02e4MUMHACbjq5yRe9n0qSNLXC_zemy8t-X2pA&oe=68B4FACD",
+ sizes: { medium: { mediaUrl: "https://behold.pictures/XPDtsfFk8wQ6ty5tqpTOBI9KPaC2/AmMaOJ50cbyW838WVB8L/17895729507266608/medium.jpg" } },
+ caption: "Morbid Gene - Purpose. Out now on all platforms!"
+ },
+ {
+ id: "18101034772492536",
+ permalink: "https://www.instagram.com/reel/DK5CUfvifej/",
+ mediaType: "VIDEO",
+ thumbnailUrl: "https://scontent-sof1-2.cdninstagram.com/v/t51.75761-15/503873013_17956392338951301_8694256387169534820_n.jpg?stp=dst-jpg_e35_tt6&_nc_cat=111&ccb=1-7&_nc_sid=18de74&_nc_ohc=QefLR353MEAQ7kNvwG9CDHK&_nc_oc=Adnm0ifct6tIUl1HHhvfGpMiQfdp5Um34NZ7JYqKDNiy8zjtHzbf7akYsUx3LcG3tPIuSWuqPyAI7hswuJgJVXbC&_nc_zt=23&_nc_ht=scontent-sof1-2.cdninstagram.com&edm=ANo9K5cEAAAA&_nc_gid=hq3oAYY2XfShs1uiDlo4Sg&oh=00_AfUBpbNxAqyA8-8-UR6sb309smhOeT3orrlGptJ7IP0ujg&oe=68B501D7",
+ sizes: { medium: { mediaUrl: "https://behold.pictures/XPDtsfFk8wQ6ty5tqpTOBI9KPaC2/AmMaOJ50cbyW838WVB8L/18101034772492536/medium.jpg" } },
+ caption: "\"PURPOSE\" out now on all streaming platforms!"
+ },
+ {
+ id: "17991048590811446",
+ permalink: "https://www.instagram.com/p/DK0TS3VC4Ig/",
+ mediaType: "IMAGE",
+ mediaUrl: "https://scontent-sof1-2.cdninstagram.com/v/t51.75761-15/504509917_17956183985951301_3036917802775333302_n.jpg?stp=dst-jpg_e35_tt6&_nc_cat=111&ccb=1-7&_nc_sid=18de74&_nc_ohc=tv0GgcMUp3wQ7kNvwEF4nLR&_nc_oc=AdkpfTHh2drjU8zx7sB29DIF5kGKHdjm27SnkwNm8ThKsFFjQgiwR94DXEmzcy1WzUiwvNWvX2Iq-6eBEuEpp3bH&_nc_zt=23&_nc_ht=scontent-sof1-2.cdninstagram.com&edm=ANo9K5cEAAAA&_nc_gid=hq3oAYY2XfShs1uiDlo4Sg&oh=00_AfXjZRQNu4lXpliJE2Kwe0JkytxpdiQRIdz9fRaGzfYK_A&oe=68B5287B",
+ sizes: { medium: { mediaUrl: "https://behold.pictures/XPDtsfFk8wQ6ty5tqpTOBI9KPaC2/AmMaOJ50cbyW838WVB8L/17991048590811446/medium.jpg" } },
+ caption: "🤜🤛🔥"
+ },
+ {
+ id: "18314806621226775",
+ permalink: "https://www.instagram.com/p/DKeU1JDC3yg/",
+ mediaType: "CAROUSEL_ALBUM",
+ mediaUrl: "https://scontent-sof1-1.cdninstagram.com/v/t51.75761-15/503150253_17955203873951301_2085565770835587894_n.jpg?stp=dst-jpg_e35_tt6&_nc_cat=100&ccb=1-7&_nc_sid=18de74&_nc_ohc=VIDsyCS8NykQ7kNvwGJ0xB8&_nc_oc=AdkfR7_SEy287uXv3bk8CuO6NUkGJniGouN1ftwQZghv9l17HGi7IeUmpHOOv117UsJoq1Cvx9IFY-V-sUOmwWLK&_nc_zt=23&_nc_ht=scontent-sof1-1.cdninstagram.com&edm=ANo9K5cEAAAA&_nc_gid=hq3oAYY2XfShs1uiDlo4Sg&oh=00_AfXQC8x3PsgFuIfANaUKnlflp9Lfxr-bZ5jhEFP1Tnoc5A&oe=68B51281",
+ sizes: { medium: { mediaUrl: "https://behold.pictures/XPDtsfFk8wQ6ty5tqpTOBI9KPaC2/AmMaOJ50cbyW838WVB8L/18314806621226775/medium.jpg" } },
+ caption: "Kråkan, Vocals 🔥"
+ }
+ ]
+ };
+
+ // Get all 6 posts and prepare them for display
+ const instagramPosts = instagramData.posts.slice(0, 6).map((post, index) => {
+ // Determine the best image URL based on media type
+ let imageUrl;
+ if (post.mediaType === "VIDEO") {
+ imageUrl = post.sizes?.medium?.mediaUrl || post.thumbnailUrl;
+ } else {
+ imageUrl = post.sizes?.medium?.mediaUrl || post.mediaUrl;
+ }
+
+ return {
+ id: post.id,
+ imageUrl: imageUrl,
+ postUrl: post.permalink,
+ alt: post.caption ? `${post.caption.substring(0, 50)}...` : `Morbid Gene Instagram post ${index + 1}`,
+ mediaType: post.mediaType,
+ isVideo: post.mediaType === "VIDEO"
+ };
+ });
+
+ return (
+
+
+ Follow Us morbidgenebandpage
+
+
+
+ {instagramPosts.map(post => (
+
+
+ {post.isVideo && (
+
+
+
+ )}
+
+ ))}
+
+
+ );
+};
\ No newline at end of file
diff --git a/frontend/src/components/LoadingSpinner.jsx b/frontend/src/components/LoadingSpinner.jsx
new file mode 100644
index 0000000000..423cf8567f
--- /dev/null
+++ b/frontend/src/components/LoadingSpinner.jsx
@@ -0,0 +1,78 @@
+import styled, { keyframes } from 'styled-components';
+
+const spin = keyframes`
+ 0% {
+ transform: rotate(0deg);
+ }
+ 100% {
+ transform: rotate(360deg);
+ }
+`;
+
+const SpinnerContainer = styled.div`
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ min-height: ${props => props.$fullScreen ? '80vh' : props.$minHeight || '200px'};
+ padding: ${props => props.$padding || '2rem'};
+`;
+
+const Spinner = styled.div`
+ border: 3px solid rgba(255, 255, 255, 0.1);
+ border-top: 3px solid #dc2626;
+ border-radius: 50%;
+ width: ${props => props.$size || '50px'};
+ height: ${props => props.$size || '50px'};
+ animation: ${spin} 1s linear infinite;
+`;
+
+const LoadingText = styled.p`
+ color: #ffffff;
+ margin-top: 1rem;
+ font-size: 1rem;
+ text-align: center;
+`;
+
+const LoadingWrapper = styled.div`
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+`;
+
+export const LoadingSpinner = ({
+ size = '50px',
+ fullScreen = false,
+ minHeight = '200px',
+ padding = '2rem',
+ text = 'Loading...',
+ showText = true
+}) => {
+ return (
+
+
+
+ {showText && {text} }
+
+
+ );
+};
+
+const InlineSpinner = styled.div`
+ display: inline-block;
+ border: 2px solid rgba(255, 255, 255, 0.3);
+ border-top: 2px solid currentColor;
+ border-radius: 50%;
+ width: ${props => props.$size || '16px'};
+ height: ${props => props.$size || '16px'};
+ animation: ${spin} 1s linear infinite;
+ margin-right: ${props => props.$marginRight || '8px'};
+ vertical-align: middle;
+`;
+
+export const InlineLoadingSpinner = ({ size = '16px', marginRight = '8px' }) => {
+ return ;
+};
\ No newline at end of file
diff --git a/frontend/src/components/MerchCard.jsx b/frontend/src/components/MerchCard.jsx
new file mode 100644
index 0000000000..28816edae0
--- /dev/null
+++ b/frontend/src/components/MerchCard.jsx
@@ -0,0 +1,68 @@
+import { Link } from "react-router-dom";
+import { formatPrice } from "../utils/formatPrice.js";
+import { translateProductType } from "../utils/translations";
+import FavoriteButton from "./FavoriteButton";
+
+const MerchCard = ({ item }) => {
+ // Use the enriched productTypeName from backend
+ const name = item.productTypeName || item.productType?.name || item.name || "Unknown Product";
+ const imageUrl = item.previewImage?.url;
+ const priceAmount = item.price?.amount;
+ const currencyId = item.price?.currencyId;
+ const productId = item.sellableId;
+
+ return (
+
+
+ {imageUrl && (
+
+ )}
+
{translateProductType(name)}
+ {priceAmount != null && (
+
+ {formatPrice(priceAmount, currencyId)}
+
+ )}
+
+
+
+
+
+
+ );
+};
+
+export default MerchCard;
diff --git a/frontend/src/components/Navbar/MobileMenu.jsx b/frontend/src/components/Navbar/MobileMenu.jsx
new file mode 100644
index 0000000000..4428339d92
--- /dev/null
+++ b/frontend/src/components/Navbar/MobileMenu.jsx
@@ -0,0 +1,99 @@
+import { useContext } from 'react';
+import { Link } from 'react-router-dom';
+import { useAuth } from '../../contexts/AuthContext';
+import { CartContext } from '../../contexts/CartProvider';
+import { FaShoppingCart, FaHeart, FaSignOutAlt, FaTimes, FaUser } from 'react-icons/fa';
+import {
+ MobileMenuOverlay,
+ BurgerToggle,
+ Bar,
+ MobileCloseButton,
+ MobileNavLink,
+ MobileAuthSection,
+ MobileWelcomeText,
+ MobileLogoutButton,
+ MobileContactSection
+} from './Navbar.styles';
+
+/**
+ * Mobile Menu Component
+ *
+ * Shared mobile navigation that adapts content based on website/shopping mode.
+ * Provides full-screen overlay with navigation links and user actions.
+ */
+export const MobileMenu = ({ isOpen, onClose, mode, isHomepage }) => {
+ const { user, logout } = useAuth();
+ const { getTotalItems } = useContext(CartContext);
+
+ const handleLinkClick = () => {
+ onClose();
+ };
+
+ const handleLogout = () => {
+ logout();
+ onClose();
+ };
+
+ // Website mode navigation links
+ const websiteLinks = [
+ { to: '/', label: 'Home' },
+ { to: '/media', label: 'Media' },
+ { to: '/gigs', label: 'Gigs' },
+ { to: '/merch', label: 'Merch' },
+ { to: '/contact', label: 'Contact' }
+ ];
+
+ // Shopping mode navigation links
+ const shoppingLinks = [
+ { to: '/', label: 'Home' },
+ { to: '/merch', label: 'Merch' },
+ { to: '/contact', label: 'Contact' }
+ ];
+
+ const navigationLinks = mode === 'shopping' ? shoppingLinks : websiteLinks;
+
+ return (
+
+ {/* Close button */}
+
+
+
+
+
+
+ {/* Navigation links */}
+ {navigationLinks.map((link) => (
+
+
+ {link.icon && link.icon}
+ {link.label}
+
+
+ ))}
+
+ {/* User-specific navigation - removed settings since it's now an icon */}
+
+ {/* Auth section - only show on shopping pages */}
+ {mode === 'shopping' && (
+
+ {user ? (
+
+
+ Logout
+
+ ) : (
+
+ Login
+
+ )}
+
+ )}
+
+ );
+};
\ No newline at end of file
diff --git a/frontend/src/components/Navbar/Navbar.styles.js b/frontend/src/components/Navbar/Navbar.styles.js
new file mode 100644
index 0000000000..26162edf8a
--- /dev/null
+++ b/frontend/src/components/Navbar/Navbar.styles.js
@@ -0,0 +1,965 @@
+/**
+ * NAVBAR STYLED COMPONENTS
+ *
+ * All styling for the navigation system in one place.
+ * Uses theme tokens for consistency across the application.
+ * Preserves all original styling from MerchNavbar and Navbar components.
+ */
+
+import styled from "styled-components";
+import { Link } from "react-router-dom";
+import { theme } from "../../styles/theme";
+import { FaUser } from "react-icons/fa";
+
+/* ========================================
+ WEBSITE MODE STYLES
+
+ Original styling from Navbar.jsx
+======================================== */
+
+export const WebsiteNavWrapper = styled.nav`
+ position: ${({ $isHomepage }) => $isHomepage ? 'absolute' : 'relative'};
+ top: ${({ $isHomepage }) => $isHomepage ? '0' : 'auto'};
+ left: ${({ $isHomepage }) => $isHomepage ? '0' : 'auto'};
+ width: 100%;
+ padding: 1.5rem 2rem;
+ display: flex;
+ justify-content: ${({ $hasPageTitle }) => $hasPageTitle ? 'space-between' : 'flex-end'};
+ align-items: center;
+ z-index: 999;
+ background: ${({ $isHomepage }) => $isHomepage ? 'transparent' : 'black'};
+ box-sizing: border-box;
+ min-height: 72px;
+
+ /* Homepage animation */
+ transform: ${({ $isHomepage, $hasAnimated }) =>
+ $isHomepage && !$hasAnimated ? 'translateX(100%)' : 'translateX(0)'};
+ transition: transform 0.8s cubic-bezier(0.25, 0.46, 0.45, 0.94);
+
+ @media (min-width: 768px) {
+ justify-content: ${({ $hasPageTitle }) => $hasPageTitle ? 'space-between' : 'flex-end'};
+ padding: 2rem 3rem;
+ min-height: 96px;
+ }
+`;
+
+export const PageTitle = styled.h1`
+ color: #ffffff;
+ font-size: 1.2rem;
+ font-weight: 800;
+ text-transform: uppercase;
+ letter-spacing: 1px;
+ margin: 0;
+
+ @media (min-width: 480px) {
+ font-size: 1.4rem;
+ }
+
+ @media (min-width: 768px) {
+ font-size: 1.6rem;
+ letter-spacing: 1.5px;
+ }
+
+ @media (min-width: 1024px) {
+ font-size: 1.8rem;
+ }
+`;
+
+export const WebsiteNavSection = styled.div`
+ display: flex;
+ align-items: center;
+ gap: 1rem;
+
+ @media (min-width: 768px) {
+ gap: 2rem;
+ }
+`;
+
+export const DesktopNav = styled.nav`
+ display: none;
+
+ @media (min-width: 768px) {
+ display: flex;
+ align-items: center;
+ gap: 2rem;
+ }
+
+ a {
+ color: white;
+ text-decoration: none;
+ font-weight: bold;
+ text-transform: uppercase;
+ font-size: 1.1rem;
+ letter-spacing: 1px;
+ transition: color 0.2s;
+
+ &:hover {
+ color: #dc2626;
+ }
+ }
+`;
+
+export const BackToMediaButton = styled(Link)`
+ background: none;
+ border: none;
+ color: white;
+ padding: 0;
+ text-decoration: none;
+ font-size: 0.9rem;
+ transition: color 0.2s;
+ display: inline-flex;
+ align-items: center;
+ gap: 0.5rem;
+
+ &:hover {
+ color: #ccc;
+ }
+
+ &::before {
+ content: "←";
+ font-size: 1.2rem;
+ }
+`;
+
+/* ========================================
+ SHOPPING MODE STYLES
+
+ Original styling from MerchNavbar.jsx
+======================================== */
+
+export const ShoppingNavWrapper = styled.nav`
+ background: #000;
+ padding: 1.5rem 1rem;
+ position: static;
+ z-index: 200;
+
+ display: grid;
+ grid-template-columns: 1fr auto;
+ grid-template-rows: auto auto auto auto;
+ align-items: center;
+ gap: 1rem;
+
+ @media (min-width: 768px) {
+ padding: 1.5rem 2rem;
+ padding-top: 0.5rem;
+ grid-template-columns: 1fr 2fr 1fr;
+ grid-template-rows: auto auto auto;
+ column-gap: 2rem;
+ row-gap: 0.5rem;
+ }
+`;
+
+// Logo section
+export const LeftSection = styled.div`
+ display: flex;
+ justify-content: center;
+ grid-column: 1 / -1;
+ grid-row: 2;
+
+ @media (min-width: 768px) {
+ justify-content: flex-start;
+ grid-column: 1;
+ grid-row: 1;
+ align-self: start;
+ }
+`;
+
+export const LogoLink = styled(Link)`
+ display: inline-flex;
+ align-items: flex-start;
+ text-decoration: none;
+ position: relative;
+
+ @media (min-width: 768px) {
+ &:hover::after {
+ content: "Home";
+ position: absolute;
+ top: 100%;
+ left: 50%;
+ transform: translateX(-50%);
+ margin-top: 5px;
+ background: #333;
+ color: white;
+ padding: 4px 8px;
+ border-radius: 4px;
+ font-size: 12px;
+ white-space: nowrap;
+ z-index: 1000;
+ }
+ }
+`;
+
+export const LogoImg = styled.img`
+ height: 120px;
+ width: auto;
+ display: block;
+ margin-top: 2rem;
+
+ @media (min-width: 768px) {
+ height: 90px;
+ margin-top: 0;
+ }
+
+ @media (min-width: 1024px) {
+ height: 100px;
+ }
+`;
+
+// Shop title section
+export const CenterSection = styled.div`
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ text-align: center;
+ color: #fff;
+ grid-row: 3;
+ grid-column: 1 / -1;
+
+ @media (min-width: 768px) {
+ grid-row: 2;
+ grid-column: 2;
+ justify-content: flex-start;
+ margin-top: -0.5rem;
+ }
+`;
+
+export const ShopTitle = styled(Link)`
+ color: white;
+ font-size: 18px;
+ font-weight: 800;
+ text-decoration: none;
+ text-transform: uppercase;
+ letter-spacing: 0.05em;
+ display: inline-block;
+
+ @media (min-width: 480px) {
+ font-size: 20px;
+ }
+
+ @media (min-width: 768px) {
+ font-size: 22px;
+ }
+
+ @media (min-width: 1024px) {
+ font-size: 24px;
+ }
+
+ &:hover {
+ color: #ccc;
+ }
+`;
+
+export const ShopSubtitle = styled.div`
+ font-size: 14px;
+ opacity: 0.8;
+ margin-top: 4px;
+
+ @media (min-width: 480px) {
+ font-size: 16px;
+ }
+`;
+
+/* ========================================
+ USER MENU STYLES
+
+ Auth, cart, favorites etc.
+======================================== */
+
+export const AuthLink = styled(Link)`
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ line-height: 1;
+ color: white;
+ text-decoration: none;
+ font-weight: bold;
+ font-size: 14px;
+ text-transform: uppercase;
+ padding: 10px 16px;
+ border-radius: 4px;
+ transition: all 0.2s ease;
+ height: 44px;
+ box-sizing: border-box;
+ margin: 0;
+ vertical-align: middle;
+
+ &:hover {
+ background: rgba(220, 38, 38, 0.1);
+ color: #dc2626;
+ }
+
+ @media (min-width: 768px) {
+ padding: 12px 20px;
+ font-size: 14px;
+ border-radius: 25px;
+ min-width: 80px;
+ margin-right: -1rem;
+ }
+`;
+
+export const AuthButton = styled.button`
+ display: inline-flex;
+ align-items: baseline;
+ justify-content: center;
+ line-height: 1;
+ color: white;
+ background: none;
+ border: none;
+ font-weight: bold;
+ font-size: 14px;
+ text-transform: uppercase;
+ padding: 10px 16px;
+ margin: 0;
+ border-radius: 4px;
+ cursor: pointer;
+ transition: all 0.2s ease;
+ font-family: inherit;
+ height: 44px;
+ box-sizing: border-box;
+ vertical-align: baseline;
+
+ &:hover {
+ background: rgba(220, 38, 38, 0.1);
+ color: #dc2626;
+ }
+`;
+
+export const DesktopLogoutButton = styled.button`
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ color: white;
+ background: none;
+ border: none;
+ font-size: 20px;
+ padding: 12px;
+ margin: 0;
+ border-radius: 50%;
+ cursor: pointer;
+ transition: all 0.2s ease;
+ position: relative;
+ height: 44px;
+ width: 44px;
+ box-sizing: border-box;
+ vertical-align: middle;
+
+ &:hover {
+ background: rgba(220, 38, 38, 0.1);
+ color: #dc2626;
+ }
+
+ &:hover::after {
+ content: "Logout";
+ position: absolute;
+ top: 100%;
+ left: 50%;
+ transform: translateX(-50%);
+ margin-top: 5px;
+ background: #333;
+ color: white;
+ padding: 4px 8px;
+ border-radius: 4px;
+ font-size: 12px;
+ white-space: nowrap;
+ z-index: 1000;
+ }
+`;
+
+export const FavoritesLink = styled(Link)`
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ line-height: 1;
+ color: white;
+ text-decoration: none;
+ font-size: 18px;
+ padding: 10px;
+ margin: 0;
+ transition: all 0.2s ease;
+ position: relative;
+ height: 38px;
+ width: 38px;
+ box-sizing: border-box;
+ vertical-align: middle;
+ border-radius: 50%;
+
+ @media (min-width: 768px) {
+ font-size: 20px;
+ padding: 12px;
+ height: 44px;
+ width: 44px;
+ }
+
+ &:hover {
+ background: rgba(220, 38, 38, 0.1);
+ color: #dc2626;
+ }
+
+ @media (min-width: 768px) {
+ &:hover::after {
+ content: attr(aria-label);
+ position: absolute;
+ top: 100%;
+ left: 50%;
+ transform: translateX(-50%);
+ margin-top: 5px;
+ background: #333;
+ color: white;
+ padding: 4px 8px;
+ border-radius: 4px;
+ font-size: 12px;
+ white-space: nowrap;
+ z-index: 1000;
+ }
+ }
+`;
+
+export const CartLink = styled(Link)`
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ line-height: 1;
+ color: white;
+ text-decoration: none;
+ font-size: 18px;
+ padding: 10px;
+ margin: 0;
+ transition: all 0.2s ease;
+ position: relative;
+ height: 38px;
+ width: 38px;
+ box-sizing: border-box;
+ vertical-align: middle;
+ border-radius: 50%;
+
+ @media (min-width: 768px) {
+ font-size: 20px;
+ padding: 12px;
+ height: 44px;
+ width: 44px;
+ }
+
+ &:hover {
+ background: rgba(220, 38, 38, 0.1);
+ color: #dc2626;
+ }
+
+ @media (min-width: 768px) {
+ &:hover::after {
+ content: attr(aria-label);
+ position: absolute;
+ top: 100%;
+ left: 50%;
+ transform: translateX(-50%);
+ margin-top: 5px;
+ background: #333;
+ color: white;
+ padding: 4px 8px;
+ border-radius: 4px;
+ font-size: 12px;
+ white-space: nowrap;
+ z-index: 1000;
+ }
+ }
+`;
+
+export const CartBadge = styled.span`
+ position: absolute;
+ top: -5px;
+ right: -5px;
+ background-color: #dc2626;
+ color: white;
+ border-radius: 50%;
+ font-size: 11px;
+ font-weight: bold;
+ width: 18px;
+ height: 18px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ line-height: 1;
+
+ @media (min-width: 768px) {
+ width: 20px;
+ height: 20px;
+ font-size: 12px;
+ top: -6px;
+ right: -6px;
+ }
+`;
+
+export const FavoritesBadge = styled(CartBadge)``;
+
+export const AccountLink = styled(Link)`
+ background: none;
+ border: none;
+ color: white;
+ cursor: pointer;
+ padding: 10px;
+ margin: 0;
+ border-radius: 50%;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ text-decoration: none;
+ transition: all 0.2s ease;
+ line-height: 1;
+ vertical-align: middle;
+ font-size: 16px;
+ height: 38px;
+ width: 38px;
+ box-sizing: border-box;
+
+ &:hover {
+ color: #dc2626;
+ background: rgba(220, 38, 38, 0.1);
+ }
+
+ @media (min-width: 768px) {
+ padding: 12px;
+ font-size: 20px;
+ height: 44px;
+ width: 44px;
+ position: relative;
+
+ &:hover::after {
+ content: attr(aria-label);
+ position: absolute;
+ top: 100%;
+ left: 50%;
+ transform: translateX(-50%);
+ margin-top: 5px;
+ background: #333;
+ color: white;
+ padding: 4px 8px;
+ border-radius: 4px;
+ font-size: 12px;
+ white-space: nowrap;
+ z-index: 1000;
+ }
+ }
+`;
+
+export const SettingsIcon = styled(FaUser)`
+ font-size: 16px;
+ margin: 0;
+ padding: 0;
+
+ @media (min-width: 768px) {
+ font-size: 20px;
+ }
+`;
+
+/* ========================================
+ MOBILE MENU STYLES
+
+ Shared mobile navigation
+======================================== */
+
+export const BurgerToggle = styled.div`
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
+ align-items: center;
+ width: 38px;
+ height: 38px;
+ cursor: pointer;
+ z-index: 1001;
+ position: relative;
+ border-radius: 50%;
+ padding: 10px;
+
+ &:hover {
+ background: rgba(220, 38, 38, 0.1);
+ }
+
+ @media (min-width: 768px) {
+ display: none;
+ }
+`;
+
+export const Bar = styled.div`
+ width: 18px;
+ height: 2px;
+ background-color: white;
+ border-radius: 5px;
+ transition: 0.4s;
+
+ &:nth-child(1) {
+ transform: ${({ $isOpen }) =>
+ $isOpen ? "rotate(45deg) translateY(6px)" : "none"};
+ }
+ &:nth-child(2) {
+ opacity: ${({ $isOpen }) => ($isOpen ? 0 : 1)};
+ }
+ &:nth-child(3) {
+ transform: ${({ $isOpen }) =>
+ $isOpen ? "rotate(-45deg) translateY(-6px)" : "none"};
+ }
+`;
+
+export const MobileMenuOverlay = styled.ul`
+ position: fixed;
+ top: ${({ $isOpen }) => ($isOpen ? "0" : "-100vh")};
+ left: 0;
+ width: 100%;
+ height: 100vh;
+ background-color: rgba(0, 0, 0, 0.95);
+ list-style: none;
+ padding: 5rem 2rem 2rem;
+ margin: 0;
+ transition: top 0.5s cubic-bezier(0.25, 0.46, 0.45, 0.94);
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ z-index: 1002;
+ transform: none !important;
+ visibility: ${({ $isOpen }) => ($isOpen ? "visible" : "hidden")};
+
+ li {
+ margin-bottom: 1.5rem;
+
+ a, button {
+ color: white;
+ text-decoration: none;
+ font-size: 1.2rem;
+ text-transform: uppercase;
+ font-weight: bold;
+ background: none;
+ border: none;
+ cursor: pointer;
+
+ &:hover {
+ color: #dc2626;
+ }
+ }
+ }
+
+ @media (min-width: 768px) {
+ display: none !important;
+ }
+`;
+
+export const MobileCloseButton = styled.button`
+ position: absolute;
+ top: 2rem;
+ right: 2rem;
+ background: none;
+ border: none;
+ color: white;
+ font-size: 1.5rem;
+ cursor: pointer;
+ padding: 0.5rem;
+`;
+
+export const MobileNavLink = styled(Link)`
+ color: white;
+ text-decoration: none;
+ font-size: 1.2rem;
+ text-transform: uppercase;
+ font-weight: bold;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 0.5rem;
+
+ &:hover {
+ color: #dc2626;
+ }
+`;
+
+export const MobileAuthSection = styled.li`
+ margin-top: 2rem;
+ padding-top: 1rem;
+ border-top: 1px solid #333;
+`;
+
+export const MobileWelcomeText = styled.div`
+ color: white;
+ font-size: 0.9rem;
+ margin-bottom: 1rem;
+ text-align: center;
+`;
+
+export const MobileLogoutButton = styled.button`
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 0.5rem;
+ background: none;
+ border: none;
+ color: white;
+ font-size: 1.2rem;
+ text-transform: uppercase;
+ font-weight: bold;
+ cursor: pointer;
+
+ &:hover {
+ color: #dc2626;
+ }
+`;
+
+export const MobileContactSection = styled.li`
+ margin-top: 1rem;
+`;
+
+/* ========================================
+ SHOPPING MODE SPECIFIC LAYOUTS
+======================================== */
+
+export const LeftIconSection = styled.div`
+ position: absolute;
+ top: 14px;
+ left: 15px;
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ gap: 0.75rem;
+ z-index: 1001;
+
+ @media (min-width: 768px) {
+ display: none;
+ }
+`;
+
+export const RightSection = styled.div`
+ position: absolute;
+ top: 14px;
+ right: 15px;
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ gap: 0.75rem;
+ z-index: 1001;
+
+ @media (min-width: 768px) {
+ display: none;
+ }
+`;
+
+export const WelcomeTopSection = styled.div`
+ display: none;
+
+ @media (min-width: 768px) {
+ display: flex;
+ justify-content: center;
+ align-items: flex-start;
+ grid-column: 2;
+ grid-row: 1;
+ padding-top: 0;
+ margin-top: 0.5rem;
+ align-self: start;
+ }
+`;
+
+export const TopUserBar = styled.div`
+ display: none;
+
+ @media (min-width: 768px) {
+ display: flex;
+ align-items: flex-start;
+ justify-content: flex-end;
+ grid-column: 3;
+ grid-row: 1;
+ padding-top: 0;
+ margin-top: 0.5rem;
+ gap: 1rem;
+ align-self: start;
+ }
+`;
+
+export const BottomRowContainer = styled.div`
+ display: none;
+
+ @media (min-width: 768px) {
+ display: block;
+ grid-column: 1 / -1;
+ grid-row: 3;
+ padding-top: 1rem;
+ }
+`;
+
+// Desktop icons container for shopping navigation
+export const DesktopIconsContainer = styled.div`
+ display: none;
+
+ @media (min-width: 768px) {
+ display: flex;
+ align-items: center;
+ justify-content: flex-end;
+ gap: 1rem;
+ grid-column: 3;
+ grid-row: 1;
+ align-self: start;
+ }
+`;
+
+// Navigation section (breadcrumbs)
+export const NavigationSection = styled.div`
+ grid-column: 1 / -1;
+ grid-row: 4;
+ display: flex;
+ justify-content: flex-start;
+ margin-top: 1rem;
+ position: relative;
+
+ /* Full-width border that extends beyond container */
+ &::after {
+ content: '';
+ position: absolute;
+ bottom: 0;
+ left: 50%;
+ transform: translateX(-50%);
+ width: 100vw;
+ height: 1px;
+ background-color: #333;
+ z-index: -1;
+ }
+
+ @media (min-width: 768px) {
+ grid-column: 1 / -1;
+ grid-row: 3;
+ margin-top: 0.5rem;
+ }
+`;
+
+export const WelcomeMessage = styled.div`
+ color: #ffffff;
+ font-size: 12px;
+ text-align: center;
+ margin: 0;
+ padding: 0;
+ margin-bottom: 0.25rem;
+
+ @media (min-width: 768px) {
+ text-align: left;
+ margin-bottom: 0;
+ white-space: nowrap;
+ flex-shrink: 0;
+ }
+`;
+
+export const MobileWelcomeSection = styled.div`
+ position: absolute;
+ top: 14px;
+ left: 15px;
+ right: 200px; /* Leave space for icons on the right */
+ display: flex;
+ align-items: center;
+ color: #ffffff;
+ font-size: 11px;
+ z-index: 1000; /* Lower than RightSection */
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+
+ @media (min-width: 768px) {
+ display: none;
+ }
+`;
+
+export const MobileBreadcrumbWrapper = styled.div`
+ @media (min-width: 768px) {
+ display: none;
+ }
+`;
+
+/* ========================================
+ DIALOG STYLES
+
+ Logout confirmation etc.
+======================================== */
+
+export const ConfirmationOverlay = styled.div`
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: rgba(0, 0, 0, 0.8);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ z-index: 1000;
+`;
+
+export const ConfirmationDialog = styled.div`
+ background: #1a1a1a;
+ border: 2px solid #333;
+ border-radius: 8px;
+ padding: 2rem;
+ max-width: 400px;
+ text-align: center;
+
+ h3 {
+ color: #fff;
+ margin: 0 0 1rem 0;
+ font-size: 1.2rem;
+ }
+
+ p {
+ color: #ccc;
+ margin: 0 0 1.5rem 0;
+ font-size: 0.95rem;
+ }
+`;
+
+export const ConfirmationButtons = styled.div`
+ display: flex;
+ gap: 1rem;
+ justify-content: center;
+`;
+
+export const ConfirmButton = styled.button`
+ background-color: #dc2626;
+ color: #ffffff;
+ border: none;
+ padding: 0.75rem 1.5rem;
+ cursor: pointer;
+ font-weight: bold;
+ transition: background-color 0.2s;
+
+ &:hover {
+ background-color: #b91c1c;
+ }
+`;
+
+export const CancelButton = styled.button`
+ background-color: #333333;
+ color: #ffffff;
+ border: none;
+ padding: 0.75rem 1.5rem;
+ cursor: pointer;
+ font-weight: bold;
+ transition: background-color 0.2s;
+
+ &:hover {
+ background-color: #555555;
+ }
+`;
+
+/* ========================================
+ WEBSITE MODE SPECIFIC
+======================================== */
+
+export const UserSection = styled.div`
+ display: flex;
+ align-items: center;
+ gap: 1rem;
+`;
+
+export const UserName = styled.span`
+ color: #ffffff;
+ font-size: 14px;
+ font-weight: bold;
+ text-transform: uppercase;
+ padding: 8px 12px;
+ border-radius: 4px;
+ transition: all 0.2s ease;
+
+ &:hover {
+ background: rgba(220, 38, 38, 0.1);
+ color: #dc2626;
+ }
+`;
\ No newline at end of file
diff --git a/frontend/src/components/Navbar/ShoppingNav.jsx b/frontend/src/components/Navbar/ShoppingNav.jsx
new file mode 100644
index 0000000000..a1f2a92f70
--- /dev/null
+++ b/frontend/src/components/Navbar/ShoppingNav.jsx
@@ -0,0 +1,168 @@
+import { useState, useContext } from 'react';
+import { Link } from 'react-router-dom';
+import { useAuth } from '../../contexts/AuthContext';
+import { CartContext } from '../../contexts/CartProvider';
+import { FaShoppingCart, FaHeart, FaSignOutAlt, FaBars, FaUser } from 'react-icons/fa';
+import logoUrl from '../../assets/logo.svg';
+import { Breadcrumbs } from '../Breadcrumbs';
+import { IconLink, IconButton } from '../shared/IconLinkComponents';
+import {
+ ConfirmationOverlay,
+ ConfirmationDialog,
+ ConfirmationButtons,
+ ConfirmButton,
+ CancelButton
+} from '../shared/DialogComponents';
+import {
+ ShoppingNavWrapper,
+ LeftSection,
+ LogoLink,
+ LogoImg,
+ CenterSection,
+ ShopTitle,
+ ShopSubtitle,
+ RightSection,
+ DesktopIconsContainer,
+ NavigationSection,
+ BurgerToggle,
+ Bar,
+ FavoritesLink,
+ CartLink,
+ CartBadge,
+ FavoritesBadge,
+ AccountLink,
+ WelcomeTopSection,
+ WelcomeMessage,
+ MobileWelcomeSection
+} from './Navbar.styles';
+
+/**
+ * Shopping Navigation Component
+ *
+ * Handles navigation for e-commerce pages (merch, product, cart, etc.).
+ * Features logo, cart counter, breadcrumbs, user accounts, and logout confirmation.
+ */
+export const ShoppingNav = ({ isMobileMenuOpen, toggleMobileMenu }) => {
+ const { user, logout, favorites } = useAuth();
+ const { cartItems, getTotalItems } = useContext(CartContext);
+ const [showLogoutConfirm, setShowLogoutConfirm] = useState(false);
+
+ const handleLogoutClick = () => setShowLogoutConfirm(true);
+
+ const handleLogoutConfirm = () => {
+ logout();
+ setShowLogoutConfirm(false);
+ };
+
+ const handleLogoutCancel = () => setShowLogoutConfirm(false);
+
+ return (
+
+ {/* Mobile welcome message */}
+
+ {user && Welcome, {user.name || user.email} }
+
+
+ {/* Logo section */}
+
+
+
+
+
+
+ {/* Welcome message (desktop only) */}
+
+ {user && (
+ Welcome, {user.name || user.email}
+ )}
+
+
+ {/* Shop title section */}
+
+ The Official Morbid Gene Shop
+ Powered by Spreadshirt
+
+
+ {/* Mobile icons and menu button (mobile only) */}
+
+ {user && (
+
+
+
+ )}
+
+
+
+ {favorites.length > 0 && (
+ {favorites.length}
+ )}
+
+
+
+
+ {cartItems.length > 0 && (
+ {getTotalItems()}
+ )}
+
+
+
+
+
+
+
+
+
+ {/* Desktop icons (desktop only) */}
+
+ {user && (
+
+
+
+ )}
+
+
+
+ {favorites.length > 0 && (
+ {favorites.length}
+ )}
+
+
+
+
+ {cartItems.length > 0 && (
+ {getTotalItems()}
+ )}
+
+
+ {user ? (
+
+
+
+ ) : (
+
+
+
+ )}
+
+
+ {/* Breadcrumbs section */}
+
+
+
+
+ {/* Logout confirmation dialog */}
+ {showLogoutConfirm && (
+
+ e.stopPropagation()}>
+ Confirm Logout
+ Are you sure you want to log out?
+
+ Cancel
+ Logout
+
+
+
+ )}
+
+ );
+};
\ No newline at end of file
diff --git a/frontend/src/components/Navbar/WebsiteNav.jsx b/frontend/src/components/Navbar/WebsiteNav.jsx
new file mode 100644
index 0000000000..ac8ea2da4d
--- /dev/null
+++ b/frontend/src/components/Navbar/WebsiteNav.jsx
@@ -0,0 +1,87 @@
+import { Link, useLocation } from 'react-router-dom';
+import { useAuth } from '../../contexts/AuthContext';
+import { FaBars } from 'react-icons/fa';
+import {
+ WebsiteNavWrapper,
+ PageTitle,
+ WebsiteNavSection,
+ DesktopNav,
+ BackToMediaButton,
+ UserSection,
+ UserName,
+ AuthLink,
+ AuthButton,
+ BurgerToggle,
+ Bar
+} from './Navbar.styles';
+
+/**
+ * Website Navigation Component
+ *
+ * Handles navigation for content pages (home, media, gigs, contact).
+ * Features page titles, homepage animation, and basic auth functionality.
+ */
+export const WebsiteNav = ({
+ isHomepage,
+ hasAnimated,
+ isMobileMenuOpen,
+ toggleMobileMenu
+}) => {
+ const location = useLocation();
+ const { user, logout } = useAuth();
+
+ // Determine page title based on current route
+ const getPageTitle = (pathname) => {
+ const titleMap = {
+ '/contact': 'Contact',
+ '/media': 'Media',
+ '/gigs': 'Gigs'
+ };
+
+ // Don't show title for media subpages
+ if (pathname.startsWith('/media/')) return null;
+ return titleMap[pathname] || null;
+ };
+
+ const pageTitle = getPageTitle(location.pathname);
+ const isMediaSubpage = location.pathname.startsWith('/media/') && location.pathname !== '/media';
+
+ return (
+
+ {/* Page title section */}
+ {pageTitle && {pageTitle} }
+
+ {/* Back to media button for media subpages */}
+ {isMediaSubpage && (
+
+ Back to Media
+
+ )}
+
+ {/* Navigation section */}
+
+ {/* Mobile menu button */}
+
+
+
+
+
+
+ {/* Desktop navigation */}
+
+ Home
+ Media
+ Gigs
+ Merch
+ Contact
+
+
+ {/* User section - removed login/logout from website pages */}
+
+
+ );
+};
\ No newline at end of file
diff --git a/frontend/src/components/Navbar/index.jsx b/frontend/src/components/Navbar/index.jsx
new file mode 100644
index 0000000000..7b5abf738a
--- /dev/null
+++ b/frontend/src/components/Navbar/index.jsx
@@ -0,0 +1,79 @@
+import { useState, useEffect, useCallback } from 'react';
+import { useLocation } from 'react-router-dom';
+import { WebsiteNav } from './WebsiteNav';
+import { ShoppingNav } from './ShoppingNav';
+import { MobileMenu } from './MobileMenu';
+
+/**
+ * Main Navbar component that automatically switches between website and shopping modes
+ * based on the current route. Consolidates all navigation logic into one component.
+ */
+export const Navbar = () => {
+ const location = useLocation();
+ const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
+ const [hasAnimated, setHasAnimated] = useState(false);
+
+ // Determine navigation mode based on current path
+ const getNavigationMode = (pathname) => {
+ const shoppingPaths = ['/merch', '/product', '/cart', '/favorites', '/login', '/register', '/settings'];
+ return shoppingPaths.some(path => pathname.startsWith(path)) ? 'shopping' : 'website';
+ };
+
+ const mode = getNavigationMode(location.pathname);
+ const isHomepage = location.pathname === '/';
+
+ // Handle animation state for homepage
+ useEffect(() => {
+ if (isHomepage && !hasAnimated) {
+ const timer = setTimeout(() => setHasAnimated(true), 100);
+ return () => clearTimeout(timer);
+ }
+ }, [isHomepage, hasAnimated]);
+
+ // Close mobile menu on route change
+ useEffect(() => {
+ setIsMobileMenuOpen(false);
+ }, [location.pathname]);
+
+ // Toggle mobile menu
+ const toggleMobileMenu = useCallback(() => {
+ setIsMobileMenuOpen(prev => !prev);
+ }, []);
+
+ // Render appropriate navigation based on mode
+ if (mode === 'shopping') {
+ return (
+ <>
+
+ setIsMobileMenuOpen(false)}
+ mode="shopping"
+ />
+ >
+ );
+ }
+
+ return (
+ <>
+
+ setIsMobileMenuOpen(false)}
+ mode="website"
+ isHomepage={isHomepage}
+ />
+ >
+ );
+};
+
+// Re-export for backwards compatibility if needed
+export default Navbar;
\ No newline at end of file
diff --git a/frontend/src/components/ProductImageGallery.jsx b/frontend/src/components/ProductImageGallery.jsx
new file mode 100644
index 0000000000..94e7c78900
--- /dev/null
+++ b/frontend/src/components/ProductImageGallery.jsx
@@ -0,0 +1,226 @@
+import { useState } from "react";
+import styled from "styled-components";
+import { FaChevronLeft, FaChevronRight } from "react-icons/fa";
+
+const GalleryContainer = styled.div`
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 1rem;
+`;
+
+const MainImage = styled.img`
+ width: 100%;
+ max-width: 500px;
+ height: auto;
+ border-radius: 8px;
+ border: 1px solid #e2e8f0;
+ cursor: pointer;
+`;
+
+const ThumbnailContainer = styled.div`
+ position: relative;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 0.5rem 0;
+`;
+
+const ThumbnailSlider = styled.div`
+ display: flex;
+ gap: 0.5rem;
+ transition: transform 0.3s ease;
+ transform: translateX(${props => props.$offset}px);
+ width: max-content;
+ overflow: visible;
+`;
+
+const SliderArrow = styled.button.withConfig({
+ shouldForwardProp: (prop) => !["$direction"].includes(prop),
+})`
+ background-color: #dc2626;
+ color: #ffffff;
+ border: none;
+ width: 32px;
+ height: 32px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ cursor: pointer;
+ transition: background-color 0.2s;
+ position: absolute;
+ top: 50%;
+ transform: translateY(-50%);
+ z-index: 2;
+
+ &:hover {
+ background-color: #b91c1c;
+ }
+
+ &:disabled {
+ background-color: #666666;
+ cursor: not-allowed;
+ }
+
+ ${props => props.$direction === 'left' && `
+ left: -20px;
+ `}
+
+ ${props => props.$direction === 'right' && `
+ right: -20px;
+ `}
+`;
+
+const ThumbnailWrapper = styled.div`
+ overflow: hidden;
+ margin: 0 20px;
+ width: 480px; /* Show 5 full thumbnails + hint of the 6th */
+
+ @media (max-width: 767px) {
+ width: 176px; /* Show exactly 2 full thumbnails (80px * 2 + 16px for gap and safety) */
+ margin: 0 15px;
+ }
+`;
+
+const Thumbnail = styled.img`
+ width: 80px;
+ height: 80px;
+ object-fit: cover;
+ border-radius: 4px;
+ border: 2px solid ${props => props.$isActive ? '#dc2626' : '#e2e8f0'};
+ cursor: pointer;
+ transition: border-color 0.2s ease;
+ flex-shrink: 0;
+
+ &:hover {
+ border-color: #dc2626;
+ }
+`;
+
+const LoadingPlaceholder = styled.div`
+ width: 100%;
+ max-width: 500px;
+ height: 300px;
+ background-color: #f3f4f6;
+ border-radius: 8px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: #6b7280;
+`;
+
+export const ProductImageGallery = ({ images, productName, fallbackImage }) => {
+ const [selectedImageIndex, setSelectedImageIndex] = useState(0);
+ const [sliderOffset, setSliderOffset] = useState(0);
+
+ const VISIBLE_THUMBNAILS_DESKTOP = 5;
+ const VISIBLE_THUMBNAILS_MOBILE = 2;
+ const THUMBNAIL_WIDTH = 80;
+ const GAP_SIZE = 8;
+ const TOTAL_WIDTH_PER_THUMBNAIL = THUMBNAIL_WIDTH + GAP_SIZE;
+
+ // Check if we're on mobile (simplified check)
+ const isMobile = window.innerWidth <= 767;
+ const visibleThumbnails = isMobile ? VISIBLE_THUMBNAILS_MOBILE : VISIBLE_THUMBNAILS_DESKTOP;
+ const shouldShowSlider = images && images.length > visibleThumbnails;
+
+ // Calculate disable states for arrows
+ const isFirstPage = selectedImageIndex === 0;
+ const isLastPage = selectedImageIndex === (images ? images.length - 1 : 0);
+
+ const moveSlider = (direction) => {
+ if (direction === 'right') {
+ // Move to next image if possible
+ if (selectedImageIndex < images.length - 1) {
+ const newIndex = selectedImageIndex + 1;
+ setSelectedImageIndex(newIndex);
+
+ // Check if we need to scroll the thumbnails
+ const currentPosition = Math.abs(Math.round(sliderOffset / TOTAL_WIDTH_PER_THUMBNAIL));
+ const maxVisibleIndex = currentPosition + visibleThumbnails - 1;
+
+ if (newIndex > maxVisibleIndex) {
+ // Need to scroll right
+ const newOffset = sliderOffset - TOTAL_WIDTH_PER_THUMBNAIL;
+ setSliderOffset(newOffset);
+ }
+ }
+ } else {
+ // Move to previous image if possible
+ if (selectedImageIndex > 0) {
+ const newIndex = selectedImageIndex - 1;
+ setSelectedImageIndex(newIndex);
+
+ // Check if we need to scroll the thumbnails
+ const currentPosition = Math.abs(Math.round(sliderOffset / TOTAL_WIDTH_PER_THUMBNAIL));
+
+ if (newIndex < currentPosition) {
+ // Need to scroll left
+ const newOffset = sliderOffset + TOTAL_WIDTH_PER_THUMBNAIL;
+ setSliderOffset(newOffset);
+ }
+ }
+ }
+ };
+
+ if (!images || images.length === 0) {
+ if (fallbackImage) {
+ return ;
+ }
+ return Loading images... ;
+ }
+
+ const currentImage = images[selectedImageIndex] || images[0];
+
+ return (
+
+ {
+ // Optional: Could add lightbox functionality here
+ }}
+ />
+
+ {images.length > 1 && (
+
+ {shouldShowSlider && (
+ moveSlider('left')}
+ disabled={isFirstPage}
+ aria-label="Previous image"
+ >
+
+
+ )}
+
+
+
+ {images.map((image, index) => (
+ setSelectedImageIndex(index)}
+ />
+ ))}
+
+
+
+ {shouldShowSlider && (
+ moveSlider('right')}
+ disabled={isLastPage}
+ aria-label="Next image"
+ >
+
+
+ )}
+
+ )}
+
+ );
+};
\ No newline at end of file
diff --git a/frontend/src/components/ProductSelectors.jsx b/frontend/src/components/ProductSelectors.jsx
new file mode 100644
index 0000000000..3acf2740eb
--- /dev/null
+++ b/frontend/src/components/ProductSelectors.jsx
@@ -0,0 +1,65 @@
+import { SelectionContainer, Select } from "./shared/FormComponents";
+
+export const ProductSelectors = ({
+ productType,
+ product,
+ selectedColor,
+ selectedSize,
+ onColorChange,
+ onSizeChange,
+}) => {
+ // Function to check availability
+ const isAvailable = (sizeId, appearanceId) => {
+ return productType?.stockStates?.some(
+ (stock) =>
+ stock.size.id === sizeId &&
+ stock.appearance.id === appearanceId &&
+ stock.available === true
+ );
+ };
+
+ // Filter sizes based on selected color
+ const getAvailableSizes = () => {
+ if (!selectedColor || !productType) return productType?.sizes || [];
+
+ return productType.sizes.map((size) => ({
+ ...size,
+ available: isAvailable(size.id, selectedColor),
+ }));
+ };
+
+ // Format color name (first letter uppercase)
+ const formatColorName = (name) => {
+ return name.charAt(0).toUpperCase() + name.slice(1);
+ };
+
+ if (!productType) return null;
+
+ return (
+
+
+ Select Color
+ {productType.appearances
+ ?.filter((color) => product.appearanceIds?.includes(color.id))
+ ?.map((color) => (
+
+ {formatColorName(color.name)}
+
+ ))}
+
+
+
+ Select Size
+ {getAvailableSizes().map((size) => (
+
+ {size.name} {!size.available && "(Out of stock)"}
+
+ ))}
+
+
+ );
+};
\ No newline at end of file
diff --git a/frontend/src/components/QuantitySelector.jsx b/frontend/src/components/QuantitySelector.jsx
new file mode 100644
index 0000000000..44a39794c2
--- /dev/null
+++ b/frontend/src/components/QuantitySelector.jsx
@@ -0,0 +1,28 @@
+import {
+ QuantityContainer,
+ QuantityLabel,
+ QuantityInput,
+} from "./shared/FormComponents";
+
+export const QuantitySelector = ({ quantity, onChange, min = 1, max = 10 }) => {
+ const handleChange = (e) => {
+ const value = parseInt(e.target.value) || min;
+ // Ensure the value is within valid limits
+ const clampedValue = Math.max(min, Math.min(max, value));
+ onChange(clampedValue);
+ };
+
+ return (
+
+ Quantity:
+
+
+ );
+};
\ No newline at end of file
diff --git a/frontend/src/components/ScrollToTop.jsx b/frontend/src/components/ScrollToTop.jsx
new file mode 100644
index 0000000000..99ca65916e
--- /dev/null
+++ b/frontend/src/components/ScrollToTop.jsx
@@ -0,0 +1,84 @@
+import { useState, useEffect } from 'react';
+import styled from 'styled-components';
+
+const ScrollButton = styled.button`
+ position: fixed;
+ bottom: 40px;
+ right: 40px;
+ width: 50px;
+ height: 50px;
+ border-radius: 50%;
+ background-color: #000;
+ color: #fff;
+ border: 2px solid #fff;
+ cursor: pointer;
+ display: ${props => props.$isVisible ? 'flex' : 'none'};
+ align-items: center;
+ justify-content: center;
+ font-size: 20px;
+ z-index: 1000;
+ transition: all 0.3s ease;
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3);
+
+ &:hover {
+ background-color: #333;
+ transform: scale(1.1);
+ }
+
+ &:active {
+ transform: scale(0.95);
+ }
+
+ @media (max-width: 768px) {
+ bottom: 20px;
+ right: 20px;
+ width: 45px;
+ height: 45px;
+ }
+`;
+
+const ArrowUp = styled.span`
+ &::before {
+ content: "↑";
+ font-weight: bold;
+ }
+`;
+
+const ScrollToTop = () => {
+ const [isVisible, setIsVisible] = useState(false);
+
+ useEffect(() => {
+ const toggleVisibility = () => {
+ if (window.pageYOffset > 300) {
+ setIsVisible(true);
+ } else {
+ setIsVisible(false);
+ }
+ };
+
+ window.addEventListener('scroll', toggleVisibility);
+
+ return () => {
+ window.removeEventListener('scroll', toggleVisibility);
+ };
+ }, []);
+
+ const scrollToTop = () => {
+ window.scrollTo({
+ top: 0,
+ behavior: 'smooth'
+ });
+ };
+
+ return (
+
+
+
+ );
+};
+
+export default ScrollToTop;
\ No newline at end of file
diff --git a/frontend/src/components/TopBar.jsx b/frontend/src/components/TopBar.jsx
new file mode 100644
index 0000000000..08b538e3df
--- /dev/null
+++ b/frontend/src/components/TopBar.jsx
@@ -0,0 +1,179 @@
+import { useContext } from "react";
+import { Link } from "react-router-dom";
+import styled from "styled-components";
+import { CartContext } from "../contexts/CartProvider";
+import { useAuth } from "../contexts/AuthContext";
+import { GiShoppingCart } from "react-icons/gi";
+
+const TopBarContainer = styled.div`
+ background-color: #000;
+ padding: 0.5rem 1rem;
+ border-bottom: 1px solid #333;
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ z-index: 1000;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+
+ @media (min-width: 480px) {
+ padding: 0.75rem 1.5rem;
+ }
+`;
+
+const ActionsSection = styled.div`
+ display: flex;
+ align-items: center;
+ gap: 0.75rem;
+
+ @media (min-width: 480px) {
+ gap: 1rem;
+ }
+`;
+
+const LoginLink = styled(Link)`
+ display: inline-flex;
+ align-items: center;
+ color: white;
+ text-decoration: none;
+ font-weight: bold;
+ font-size: 0.75rem;
+ text-transform: uppercase;
+ padding: 0.25rem 0.5rem;
+ transition: all 0.2s ease;
+ line-height: 1;
+
+ @media (min-width: 480px) {
+ font-size: 0.875rem;
+ padding: 0.5rem;
+ }
+
+ &:hover {
+ background: rgba(220, 38, 38, 0.1);
+ color: #dc2626;
+ }
+`;
+
+const FavoritesLink = styled(Link)`
+ display: inline-flex;
+ align-items: center;
+ color: white;
+ text-decoration: none;
+ font-weight: bold;
+ font-size: 0.75rem;
+ text-transform: uppercase;
+ padding: 0.25rem 0.5rem;
+ transition: all 0.2s ease;
+ line-height: 1;
+
+ @media (min-width: 480px) {
+ font-size: 0.875rem;
+ padding: 0.5rem;
+ }
+
+ &:hover {
+ background: rgba(220, 38, 38, 0.1);
+ color: #dc2626;
+ }
+
+ &::before {
+ content: "♥";
+ margin-right: 0.25rem;
+ color: #dc2626;
+
+ @media (min-width: 480px) {
+ margin-right: 0.5rem;
+ }
+ }
+`;
+
+const CartButton = styled(Link)`
+ display: inline-flex;
+ align-items: center;
+ gap: 0.25rem;
+ color: white;
+ text-decoration: none;
+ padding: 0.25rem 0.5rem;
+ transition: all 0.2s ease;
+ line-height: 1;
+
+ @media (min-width: 480px) {
+ gap: 0.4rem;
+ padding: 0.5rem;
+ }
+
+ &:hover {
+ background: rgba(220, 38, 38, 0.1);
+ color: #dc2626;
+ }
+`;
+
+const CartIcon = styled(GiShoppingCart)`
+ font-size: 16px;
+ margin: 0;
+ padding: 0;
+ display: block;
+ font-weight: bold;
+
+ @media (min-width: 480px) {
+ font-size: 18px;
+ }
+`;
+
+const CartCount = styled.span`
+ font-size: 0.75rem;
+ line-height: 1;
+ margin: 0;
+ padding: 0;
+ display: block;
+ font-weight: bold;
+
+ @media (min-width: 480px) {
+ font-size: 0.875rem;
+ }
+`;
+
+const MobileMenuButton = styled.button`
+ background: none;
+ border: none;
+ color: white;
+ font-size: 1.25rem;
+ cursor: pointer;
+ padding: 0.25rem 0.5rem;
+ transition: all 0.2s ease;
+
+ @media (min-width: 768px) {
+ display: none;
+ }
+
+ &:hover {
+ background: rgba(220, 38, 38, 0.1);
+ color: #dc2626;
+ }
+`;
+
+export const TopBar = () => {
+ const { getTotalItems } = useContext(CartContext);
+ const { isAuthenticated } = useAuth();
+
+ return (
+
+ ☰
+
+
+ {isAuthenticated ? (
+ Favorites
+ ) : (
+ Login
+ )}
+
+
+
+ {getTotalItems()}
+
+
+
+ );
+};
\ No newline at end of file
diff --git a/frontend/src/components/shared/BadgeComponents.jsx b/frontend/src/components/shared/BadgeComponents.jsx
new file mode 100644
index 0000000000..ae81ef5cbe
--- /dev/null
+++ b/frontend/src/components/shared/BadgeComponents.jsx
@@ -0,0 +1,29 @@
+import styled from "styled-components";
+import { theme } from "../../styles/theme";
+
+export const Badge = styled.span`
+ position: absolute;
+ top: -8px;
+ right: -8px;
+ background-color: ${theme.colors.red};
+ color: ${theme.colors.primaryText};
+ border-radius: 50%;
+ padding: 2px 4px;
+ font-size: ${theme.typography.sizes.xs};
+ font-weight: ${theme.typography.weights.bold};
+ min-width: 16px;
+ height: 16px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ line-height: 1;
+
+ @media (min-width: ${theme.breakpoints.tablet}) {
+ top: -${theme.spacing.xs};
+ right: -${theme.spacing.xs};
+ padding: 2px 6px;
+ font-size: ${theme.typography.sizes.xs};
+ min-width: 18px;
+ height: 18px;
+ }
+`;
\ No newline at end of file
diff --git a/frontend/src/components/shared/ButtonComponents.jsx b/frontend/src/components/shared/ButtonComponents.jsx
new file mode 100644
index 0000000000..5ad89f20cd
--- /dev/null
+++ b/frontend/src/components/shared/ButtonComponents.jsx
@@ -0,0 +1,201 @@
+import styled from "styled-components";
+import { theme } from "../../styles/theme";
+
+export const AddToCartButton = styled.button`
+ background-color: ${(props) => (!props.$enabled ? theme.colors.buttonDisabled : theme.colors.buttonPrimary)};
+ color: ${(props) => (!props.$enabled ? theme.colors.secondaryText : theme.colors.primaryText)};
+ padding: ${theme.spacing.md} ${theme.spacing.base};
+ border-radius: 8px;
+ border: none;
+ font-size: ${theme.typography.sizes.sm};
+ font-weight: ${theme.typography.weights.bold};
+ cursor: ${(props) => (!props.$enabled ? "not-allowed" : "pointer")};
+ transition: background-color ${theme.transitions.normal};
+ opacity: ${(props) => (!props.$enabled ? 0.9 : 1)};
+ width: 100%;
+ min-height: 44px;
+
+ @media (min-width: ${theme.breakpoints.mobile}) {
+ padding: ${theme.spacing.md} ${theme.spacing.xl};
+ font-size: ${theme.typography.sizes.base};
+ }
+
+ @media (min-width: ${theme.breakpoints.tablet}) {
+ width: auto;
+ padding: ${theme.spacing.base} ${theme.spacing['2xl']};
+ font-size: ${theme.typography.sizes.lg};
+ }
+
+ &:hover {
+ background-color: ${(props) => (props.$enabled ? theme.colors.buttonPrimaryHover : theme.colors.buttonDisabled)};
+ }
+
+ &:active {
+ background-color: ${(props) => (props.$enabled ? theme.colors.darkRed : theme.colors.buttonDisabled)};
+ }
+
+ /* Mobile touch optimization */
+ -webkit-tap-highlight-color: transparent;
+ touch-action: manipulation;
+`;
+
+// Flexible Button component with variants, sizes, and states
+export const Button = styled.button.withConfig({
+ shouldForwardProp: (prop) => !['$variant', '$size', '$fullWidth', '$uppercase'].includes(prop),
+})`
+ border: none;
+ font-weight: ${theme.typography.weights.bold};
+ cursor: pointer;
+ transition: background-color ${theme.transitions.normal};
+ text-transform: ${props => props.$uppercase ? 'uppercase' : 'none'};
+ letter-spacing: ${props => props.$uppercase ? theme.typography.letterSpacing.uppercase : theme.typography.letterSpacing.normal};
+ width: ${props => props.$fullWidth ? '100%' : 'auto'};
+ min-height: 44px; /* Touch target accessibility */
+
+ /* Mobile touch optimization */
+ -webkit-tap-highlight-color: transparent;
+ touch-action: manipulation;
+
+ /* Variant styles */
+ ${props => {
+ switch (props.$variant) {
+ case 'secondary':
+ return `
+ background-color: ${theme.colors.buttonSecondary};
+ color: ${theme.colors.primaryText};
+
+ &:hover:not(:disabled) {
+ background-color: ${theme.colors.buttonSecondaryHover};
+ }
+ `;
+ case 'danger':
+ return `
+ background-color: ${theme.colors.red};
+ color: ${theme.colors.primaryText};
+
+ &:hover:not(:disabled) {
+ background-color: ${theme.colors.darkRed};
+ }
+ `;
+ case 'outline':
+ return `
+ background-color: transparent;
+ color: ${theme.colors.primaryText};
+ border: 1px solid ${theme.colors.borderLight};
+
+ &:hover:not(:disabled) {
+ background-color: ${theme.colors.sectionBg};
+ border-color: ${theme.colors.borderRed};
+ }
+ `;
+ default: // 'primary'
+ return `
+ background-color: ${theme.colors.buttonPrimary};
+ color: ${theme.colors.primaryText};
+
+ &:hover:not(:disabled) {
+ background-color: ${theme.colors.buttonPrimaryHover};
+ }
+ `;
+ }
+ }}
+
+ /* Size variants */
+ ${props => {
+ switch (props.$size) {
+ case 'sm':
+ return `
+ padding: ${theme.spacing.sm} ${theme.spacing.md};
+ font-size: ${theme.typography.sizes.sm};
+
+ @media (min-width: ${theme.breakpoints.mobile}) {
+ padding: ${theme.spacing.md} ${theme.spacing.base};
+ font-size: ${theme.typography.sizes.base};
+ }
+ `;
+ case 'lg':
+ return `
+ padding: ${theme.spacing.base} ${theme.spacing['2xl']};
+ font-size: ${theme.typography.sizes.base};
+
+ @media (min-width: ${theme.breakpoints.mobile}) {
+ padding: ${theme.spacing.xl} ${theme.spacing['3xl']};
+ font-size: ${theme.typography.sizes.lg};
+ }
+ `;
+ default: // 'md'
+ return `
+ padding: ${theme.spacing.md} ${theme.spacing.base};
+ font-size: ${theme.typography.sizes.sm};
+
+ @media (min-width: ${theme.breakpoints.mobile}) {
+ padding: ${theme.spacing.md} ${theme.spacing.xl};
+ font-size: ${theme.typography.sizes.base};
+ }
+ `;
+ }
+ }}
+
+ &:disabled {
+ background-color: ${theme.colors.buttonDisabled} !important;
+ color: ${theme.colors.mutedText} !important;
+ cursor: not-allowed;
+ border-color: ${theme.colors.buttonDisabled};
+ }
+
+ &:focus {
+ outline: 2px solid ${theme.colors.buttonPrimary};
+ outline-offset: 2px;
+ }
+`;
+
+// Link-styled button for navigation actions
+export const LinkButton = styled.button.withConfig({
+ shouldForwardProp: (prop) => !['$variant'].includes(prop),
+})`
+ background: none;
+ border: none;
+ color: ${props => props.$variant === 'primary' ? theme.colors.buttonPrimary : theme.colors.primaryText};
+ text-decoration: none;
+ cursor: pointer;
+ font-size: ${theme.typography.sizes.sm};
+ font-weight: ${theme.typography.weights.bold};
+ transition: color ${theme.transitions.normal};
+ padding: ${theme.spacing.sm};
+ min-height: 44px;
+ display: inline-flex;
+ align-items: center;
+ gap: ${theme.spacing.sm};
+
+ &:hover {
+ color: ${theme.colors.buttonPrimary};
+ text-decoration: underline;
+ }
+
+ &:focus {
+ outline: 2px solid ${theme.colors.buttonPrimary};
+ outline-offset: 2px;
+ }
+
+ @media (min-width: ${theme.breakpoints.mobile}) {
+ font-size: ${theme.typography.sizes.base};
+ }
+`;
+
+// Loading spinner component for buttons
+export const LoadingSpinner = styled.div`
+ display: inline-block;
+ width: 16px;
+ height: 16px;
+ border: 2px solid currentColor;
+ border-radius: 50%;
+ border-top-color: transparent;
+ animation: spin 1s ease-in-out infinite;
+ margin-right: ${theme.spacing.sm};
+
+ @keyframes spin {
+ to {
+ transform: rotate(360deg);
+ }
+ }
+`;
\ No newline at end of file
diff --git a/frontend/src/components/shared/DialogComponents.jsx b/frontend/src/components/shared/DialogComponents.jsx
new file mode 100644
index 0000000000..d27a0c6181
--- /dev/null
+++ b/frontend/src/components/shared/DialogComponents.jsx
@@ -0,0 +1,72 @@
+import styled from "styled-components";
+import { theme } from "../../styles/theme";
+
+export const ConfirmationOverlay = styled.div`
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background-color: rgba(0, 0, 0, 0.5);
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ z-index: 9999;
+`;
+
+export const ConfirmationDialog = styled.div`
+ background-color: ${theme.colors.sectionBg};
+ padding: ${theme.spacing['2xl']};
+ border-radius: 8px;
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
+ max-width: 400px;
+ width: 90%;
+
+ h3 {
+ color: ${theme.colors.primaryText};
+ margin-bottom: ${theme.spacing.base};
+ font-size: ${theme.typography.sizes.xl};
+ }
+
+ p {
+ color: ${theme.colors.mutedText};
+ margin-bottom: ${theme.spacing.xl};
+ font-size: ${theme.typography.sizes.base};
+ }
+`;
+
+export const ConfirmationButtons = styled.div`
+ display: flex;
+ gap: ${theme.spacing.base};
+ justify-content: flex-end;
+`;
+
+export const ConfirmButton = styled.button`
+ background-color: ${theme.colors.red};
+ color: ${theme.colors.primaryText};
+ border: none;
+ padding: ${theme.spacing.sm} ${theme.spacing.xl};
+ border-radius: 4px;
+ font-weight: ${theme.typography.weights.bold};
+ cursor: pointer;
+ transition: background-color ${theme.transitions.fast};
+
+ &:hover {
+ background-color: ${theme.colors.darkRed};
+ }
+`;
+
+export const CancelButton = styled.button`
+ background-color: ${theme.colors.charcoal};
+ color: ${theme.colors.primaryText};
+ border: none;
+ padding: ${theme.spacing.sm} ${theme.spacing.xl};
+ border-radius: 4px;
+ font-weight: ${theme.typography.weights.bold};
+ cursor: pointer;
+ transition: background-color ${theme.transitions.fast};
+
+ &:hover {
+ background-color: ${theme.colors.darkCharcoal};
+ }
+`;
\ No newline at end of file
diff --git a/frontend/src/components/shared/EmptyStateComponents.jsx b/frontend/src/components/shared/EmptyStateComponents.jsx
new file mode 100644
index 0000000000..fc61565fb1
--- /dev/null
+++ b/frontend/src/components/shared/EmptyStateComponents.jsx
@@ -0,0 +1,147 @@
+import styled from "styled-components";
+import { Link } from "react-router-dom";
+import { theme } from "../../styles/theme";
+
+// Main EmptyState container
+const EmptyStateContainer = styled.div`
+ text-align: center;
+ padding: 2rem 1rem;
+ color: ${theme.colors.secondaryText};
+
+ @media (min-width: ${theme.breakpoints.tablet}) {
+ padding: 3rem;
+ }
+`;
+
+// Icon/Symbol display
+const EmptyStateIcon = styled.div.withConfig({
+ shouldForwardProp: (prop) => !["$size"].includes(prop),
+})`
+ font-size: ${props => props.$size === 'large' ? '3rem' : '2rem'};
+ margin-bottom: 1rem;
+
+ &::before {
+ content: ${props => props.children || '"❌"'};
+ }
+`;
+
+// Title for empty state
+const EmptyStateTitle = styled.h1.withConfig({
+ shouldForwardProp: (prop) => !["$size"].includes(prop),
+})`
+ color: ${theme.colors.primaryText};
+ margin-bottom: ${props => props.$size === 'large' ? '1rem' : '0.5rem'};
+
+ ${props => {
+ switch (props.$size) {
+ case 'large':
+ return `
+ font-size: 1.5rem;
+
+ @media (min-width: ${theme.breakpoints.mobile}) {
+ font-size: 2rem;
+ }
+ `;
+ default:
+ return `
+ font-size: 1.25rem;
+
+ @media (min-width: ${theme.breakpoints.mobile}) {
+ font-size: 1.5rem;
+ }
+ `;
+ }
+ }}
+`;
+
+// Description text
+const EmptyStateText = styled.p`
+ font-size: 1rem;
+ margin-bottom: 2rem;
+ line-height: 1.6;
+ color: ${theme.colors.secondaryText};
+`;
+
+// Actions container for buttons
+const EmptyStateActions = styled.div`
+ display: flex;
+ flex-direction: column;
+ gap: 0.75rem;
+ align-items: center;
+
+ @media (min-width: ${theme.breakpoints.mobile}) {
+ flex-direction: row;
+ justify-content: center;
+ gap: 1rem;
+ }
+`;
+
+// Styled Link as button (matching Cart.jsx EmptyLink)
+const EmptyStateLink = styled(Link)`
+ text-decoration: none;
+ font-weight: bold;
+ padding: 0.75rem 1.5rem;
+ background-color: ${theme.colors.buttonPrimary};
+ color: ${theme.colors.primaryText};
+ border: none;
+ display: inline-block;
+ text-transform: uppercase;
+ letter-spacing: 1px;
+ transition: background-color ${theme.transitions.normal};
+ font-size: 0.9rem;
+
+ @media (min-width: ${theme.breakpoints.mobile}) {
+ font-size: 1rem;
+ }
+
+ &:hover {
+ background-color: ${theme.colors.buttonPrimaryHover};
+ text-decoration: none;
+ }
+`;
+
+// Reusable EmptyState component
+export const EmptyState = ({
+ icon,
+ title,
+ message,
+ actions,
+ titleSize = "medium",
+ iconSize = "medium"
+}) => {
+ return (
+
+ {icon && (
+
+ {icon}
+
+ )}
+
+
+ {title}
+
+
+ {message && (
+
+ {message}
+
+ )}
+
+ {actions && (
+
+ {actions}
+
+ )}
+
+ );
+};
+
+// Export styled components for custom use cases
+export {
+ EmptyStateContainer,
+ EmptyStateIcon,
+ EmptyStateTitle,
+ EmptyStateText,
+ EmptyStateActions,
+ EmptyStateLink
+};
\ No newline at end of file
diff --git a/frontend/src/components/shared/FormComponents.jsx b/frontend/src/components/shared/FormComponents.jsx
new file mode 100644
index 0000000000..333ad046be
--- /dev/null
+++ b/frontend/src/components/shared/FormComponents.jsx
@@ -0,0 +1,87 @@
+import styled from "styled-components";
+import { theme } from "../../styles/theme";
+
+export const SelectionContainer = styled.div`
+ margin-bottom: ${theme.spacing.xl};
+ display: flex;
+ flex-direction: column;
+ gap: ${theme.spacing.base};
+
+ @media (min-width: ${theme.breakpoints.tablet}) {
+ flex-direction: row;
+ gap: ${theme.spacing.base};
+ align-items: center;
+ }
+`;
+
+export const Select = styled.select`
+ padding: ${theme.spacing.md};
+ background-color: ${theme.colors.inputBg};
+ border: 2px solid ${theme.colors.borderLight};
+ border-radius: 4px;
+ color: ${theme.colors.primaryText};
+ font-size: ${theme.typography.sizes.base};
+ width: 100%;
+ cursor: pointer;
+ transition: border-color ${theme.transitions.normal};
+
+ &:focus {
+ outline: none;
+ border-color: ${theme.colors.borderRed};
+ }
+
+ option {
+ background-color: ${theme.colors.inputBg};
+ color: ${theme.colors.primaryText};
+ }
+
+ @media (min-width: ${theme.breakpoints.mobile}) {
+ padding: 0.85rem;
+ }
+`;
+
+export const QuantityContainer = styled.div`
+ display: flex;
+ align-items: center;
+ gap: ${theme.spacing.md};
+ margin-bottom: ${theme.spacing.xl};
+
+ @media (min-width: ${theme.breakpoints.tablet}) {
+ gap: ${theme.spacing.base};
+ }
+`;
+
+export const QuantityLabel = styled.label`
+ color: ${theme.colors.primaryText};
+ font-weight: ${theme.typography.weights.bold};
+ font-size: ${theme.typography.sizes.base};
+ min-width: 80px;
+`;
+
+export const QuantityInput = styled.input`
+ padding: ${theme.spacing.sm};
+ width: 80px;
+ background-color: ${theme.colors.inputBg};
+ border: 2px solid ${theme.colors.borderLight};
+ border-radius: 4px;
+ color: ${theme.colors.primaryText};
+ font-size: ${theme.typography.sizes.base};
+ text-align: center;
+
+ &:focus {
+ outline: none;
+ border-color: ${theme.colors.borderRed};
+ }
+
+ /* Hide spin buttons in webkit browsers */
+ &::-webkit-outer-spin-button,
+ &::-webkit-inner-spin-button {
+ -webkit-appearance: none;
+ margin: 0;
+ }
+
+ /* Hide spin buttons in Firefox */
+ &[type=number] {
+ -moz-appearance: textfield;
+ }
+`;
\ No newline at end of file
diff --git a/frontend/src/components/shared/IconLinkComponents.jsx b/frontend/src/components/shared/IconLinkComponents.jsx
new file mode 100644
index 0000000000..e728d7f16a
--- /dev/null
+++ b/frontend/src/components/shared/IconLinkComponents.jsx
@@ -0,0 +1,73 @@
+import styled from "styled-components";
+import { Link } from "react-router-dom";
+import { theme } from "../../styles/theme";
+
+export const IconLink = styled(Link)`
+ background: none;
+ border: none;
+ color: white;
+ cursor: pointer;
+ padding: 10px;
+ margin: 0;
+ border-radius: 50%;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ text-decoration: none;
+ transition: all 0.2s ease;
+ line-height: 1;
+ vertical-align: middle;
+ font-size: 16px;
+ height: 38px;
+ width: 38px;
+ box-sizing: border-box;
+
+ &:hover {
+ color: #dc2626;
+ background: rgba(220, 38, 38, 0.1);
+ }
+
+ @media (min-width: 768px) {
+ padding: 12px;
+ font-size: 20px;
+ height: 44px;
+ width: 44px;
+ position: relative;
+
+ &:hover::after {
+ content: attr(aria-label);
+ position: absolute;
+ top: 100%;
+ left: 50%;
+ transform: translateX(-50%);
+ margin-top: 5px;
+ background: #333;
+ color: white;
+ padding: 4px 8px;
+ border-radius: 4px;
+ font-size: 12px;
+ white-space: nowrap;
+ z-index: 1000;
+ }
+ }
+`;
+
+export const IconButton = styled.button`
+ background: none;
+ border: none;
+ color: ${theme.colors.primaryText};
+ font-size: ${theme.typography.sizes.lg};
+ cursor: pointer;
+ transition: color ${theme.transitions.fast};
+ padding: 0;
+ display: inline-flex;
+ align-items: center;
+
+ &:hover {
+ color: ${theme.colors.mutedText};
+ }
+
+ @media (min-width: ${theme.breakpoints.tablet}) {
+ font-size: ${theme.typography.sizes.lg};
+ }
+`;
\ No newline at end of file
diff --git a/frontend/src/components/shared/LayoutComponents.jsx b/frontend/src/components/shared/LayoutComponents.jsx
new file mode 100644
index 0000000000..8aaf1884e0
--- /dev/null
+++ b/frontend/src/components/shared/LayoutComponents.jsx
@@ -0,0 +1,194 @@
+import styled from 'styled-components';
+import { theme } from '../../styles/theme';
+
+/**
+ * Shared Container Component
+ *
+ * A flexible, responsive container component that provides consistent
+ * layout patterns across all pages. Uses theme values for consistency.
+ *
+ * Props:
+ * - $maxWidth: 'sm' (800px), 'md' (1200px), 'lg' (1600px), or custom CSS value
+ * - $minHeight: 'viewport' (100vh), 'auto', or custom CSS value
+ * - $variant: 'page' (standard page), 'form' (centered form), 'grid' (product grid)
+ * - $padding: 'sm', 'md', 'lg', or 'xl' for different padding scales
+ */
+export const Container = styled.div.withConfig({
+ shouldForwardProp: (prop) => !['$maxWidth', '$minHeight', '$variant', '$padding'].includes(prop),
+})`
+ margin: 0 auto;
+ width: 100%;
+ box-sizing: border-box;
+
+ /* Max-width variants */
+ max-width: ${(props) => {
+ const { $maxWidth = 'md' } = props;
+ switch ($maxWidth) {
+ case 'sm': return '800px';
+ case 'md': return '1200px';
+ case 'lg': return '1600px';
+ default: return $maxWidth;
+ }
+ }};
+
+ /* Min-height variants */
+ min-height: ${(props) => {
+ const { $minHeight = 'auto' } = props;
+ switch ($minHeight) {
+ case 'viewport': return '100vh';
+ case 'auto': return 'auto';
+ default: return $minHeight;
+ }
+ }};
+
+ /* Responsive padding based on size variant */
+ padding: ${(props) => {
+ const { $padding = 'md' } = props;
+ switch ($padding) {
+ case 'sm':
+ return `${theme.spacing.base}`;
+ case 'md':
+ return `${theme.spacing.base}`;
+ case 'lg':
+ return `${theme.spacing.xl}`;
+ case 'xl':
+ return `${theme.spacing['2xl']}`;
+ default:
+ return `${theme.spacing.base}`;
+ }
+ }};
+
+ /* Responsive padding adjustments */
+ @media (min-width: ${theme.breakpoints.mobile}) {
+ padding: ${(props) => {
+ const { $padding = 'md' } = props;
+ switch ($padding) {
+ case 'sm':
+ return `${theme.spacing.lg}`;
+ case 'md':
+ return `${theme.spacing.xl}`;
+ case 'lg':
+ return `${theme.spacing['2xl']}`;
+ case 'xl':
+ return `${theme.spacing['2xl']}`;
+ default:
+ return `${theme.spacing.xl}`;
+ }
+ }};
+ }
+
+ @media (min-width: ${theme.breakpoints.tablet}) {
+ padding: ${(props) => {
+ const { $padding = 'md' } = props;
+ switch ($padding) {
+ case 'sm':
+ return `${theme.spacing['2xl']}`;
+ case 'md':
+ return `${theme.spacing['2xl']}`;
+ case 'lg':
+ return `${theme.spacing['3xl']}`;
+ case 'xl':
+ return `${theme.spacing['3xl']}`;
+ default:
+ return `${theme.spacing['2xl']}`;
+ }
+ }};
+ }
+
+ @media (min-width: ${theme.breakpoints.desktop}) {
+ padding: ${(props) => {
+ const { $padding = 'md' } = props;
+ switch ($padding) {
+ case 'sm':
+ return `${theme.spacing['2xl']}`;
+ case 'md':
+ return `${theme.spacing['3xl']}`;
+ case 'lg':
+ return `${theme.spacing['3xl']}`;
+ case 'xl':
+ return `${theme.spacing['4xl']}`;
+ default:
+ return `${theme.spacing['3xl']}`;
+ }
+ }};
+ }
+
+ /* Layout variants */
+ ${(props) => {
+ const { $variant } = props;
+ switch ($variant) {
+ case 'form':
+ return `
+ display: flex;
+ flex-direction: column;
+ justify-content: flex-start;
+ `;
+ case 'grid':
+ return `
+ display: flex;
+ flex-direction: column;
+ gap: ${theme.spacing.base};
+
+ @media (min-width: ${theme.breakpoints.tablet}) {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: ${theme.spacing['2xl']};
+ }
+
+ @media (min-width: ${theme.breakpoints.desktop}) {
+ gap: ${theme.spacing['3xl']};
+ }
+ `;
+ case 'page':
+ default:
+ return `
+ display: block;
+ `;
+ }
+ }}
+`;
+
+/**
+ * Centered Content Container
+ *
+ * A specialized container for forms and centered content like login/register pages.
+ * Provides consistent centering and responsive sizing.
+ *
+ * Props:
+ * - $maxWidth: Maximum width of the centered box (default: 600px)
+ */
+export const CenteredContainer = styled.div.withConfig({
+ shouldForwardProp: (prop) => !['$maxWidth'].includes(prop),
+})`
+ max-width: ${(props) => props.$maxWidth || '600px'};
+ margin: 0 auto;
+ padding: ${theme.spacing.base};
+ width: 100%;
+ box-sizing: border-box;
+
+ @media (min-width: ${theme.breakpoints.mobile}) {
+ padding: ${theme.spacing['2xl']};
+ }
+`;
+
+/**
+ * Section Container
+ *
+ * A reusable container for content sections with consistent background and borders.
+ * Commonly used for form sections, info boxes, etc.
+ */
+export const SectionContainer = styled.div`
+ background: ${theme.colors.sectionBg};
+ border: 1px solid ${theme.colors.borderLight};
+ padding: ${theme.spacing.base};
+ margin-bottom: ${theme.spacing.base};
+
+ @media (min-width: ${theme.breakpoints.mobile}) {
+ padding: ${theme.spacing.xl};
+ }
+
+ @media (min-width: ${theme.breakpoints.tablet}) {
+ padding: ${theme.spacing['2xl']};
+ margin-bottom: ${theme.spacing['2xl']};
+ }
+`;
\ No newline at end of file
diff --git a/frontend/src/components/shared/LegalPageComponents.jsx b/frontend/src/components/shared/LegalPageComponents.jsx
new file mode 100644
index 0000000000..816720f583
--- /dev/null
+++ b/frontend/src/components/shared/LegalPageComponents.jsx
@@ -0,0 +1,102 @@
+import styled from "styled-components";
+import { theme } from "../../styles/theme";
+
+// Last updated date styling for legal pages
+export const LastUpdated = styled.p`
+ color: ${theme.colors.mutedText};
+ text-align: center;
+ margin-bottom: ${theme.spacing['2xl']};
+ font-size: ${theme.typography.sizes.sm};
+
+ @media (max-width: ${theme.breakpoints.mobile}) {
+ margin-bottom: ${theme.spacing.xl};
+ font-size: ${theme.typography.sizes.xs};
+ }
+`;
+
+// Section container for legal page content
+export const Section = styled.section`
+ margin-bottom: ${theme.spacing['2xl']};
+
+ @media (max-width: ${theme.breakpoints.mobile}) {
+ margin-bottom: ${theme.spacing.xl};
+ }
+`;
+
+// Paragraph styling for legal content
+export const Paragraph = styled.p`
+ color: ${theme.colors.secondaryText};
+ line-height: 1.8;
+ margin-bottom: ${theme.spacing.base};
+ font-size: ${theme.typography.sizes.base};
+
+ @media (max-width: ${theme.breakpoints.mobile}) {
+ line-height: 1.6;
+ font-size: ${theme.typography.sizes.sm};
+ margin-bottom: ${theme.spacing.md};
+ }
+`;
+
+// List styling for legal content
+export const List = styled.ul`
+ color: ${theme.colors.secondaryText};
+ line-height: 1.8;
+ margin-left: ${theme.spacing.xl};
+ margin-bottom: ${theme.spacing.base};
+ font-size: ${theme.typography.sizes.base};
+
+ li {
+ margin-bottom: ${theme.spacing.sm};
+ }
+
+ a {
+ color: ${theme.colors.red};
+ text-decoration: underline;
+ transition: color ${theme.transitions.normal};
+
+ &:hover {
+ color: ${theme.colors.darkRed};
+ }
+ }
+
+ @media (max-width: ${theme.breakpoints.mobile}) {
+ line-height: 1.6;
+ font-size: ${theme.typography.sizes.sm};
+ margin-left: ${theme.spacing.lg};
+ margin-bottom: ${theme.spacing.md};
+
+ li {
+ margin-bottom: ${theme.spacing.xs};
+ }
+ }
+`;
+
+// Info box styling for contact sections and important notes
+export const InfoBox = styled.div`
+ background: ${theme.colors.sectionBg};
+ border: 1px solid ${theme.colors.borderLight};
+ border-radius: 8px;
+ padding: ${theme.spacing.xl};
+ margin: ${theme.spacing['2xl']} 0;
+
+ @media (max-width: ${theme.breakpoints.mobile}) {
+ padding: ${theme.spacing.base};
+ margin: ${theme.spacing.xl} 0;
+ }
+`;
+
+// Sub-section heading styling (used in Privacy.jsx)
+export const SubSection = styled.h3`
+ color: ${theme.colors.primaryText};
+ font-size: ${theme.typography.sizes.xl};
+ font-weight: ${theme.typography.weights.semibold};
+ margin-top: ${theme.spacing.xl};
+ margin-bottom: ${theme.spacing.md};
+ font-family: ${theme.typography.fontFamily};
+
+ @media (max-width: ${theme.breakpoints.mobile}) {
+ font-size: ${theme.typography.sizes.lg};
+ margin-top: ${theme.spacing.base};
+ margin-bottom: ${theme.spacing.sm};
+ }
+`;
\ No newline at end of file
diff --git a/frontend/src/components/shared/PopupComponents.jsx b/frontend/src/components/shared/PopupComponents.jsx
new file mode 100644
index 0000000000..77178ac849
--- /dev/null
+++ b/frontend/src/components/shared/PopupComponents.jsx
@@ -0,0 +1,80 @@
+import styled from "styled-components";
+import { theme } from "../../styles/theme";
+
+export const PopupOverlay = styled.div`
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background-color: rgba(0, 0, 0, 0.7);
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ z-index: 1000;
+ padding: ${theme.spacing.base};
+`;
+
+export const PopupBox = styled.div`
+ background-color: ${theme.colors.sectionBg};
+ padding: ${theme.spacing['2xl']};
+ border-radius: 8px;
+ border: 1px solid ${theme.colors.borderLight};
+ max-width: 400px;
+ width: 100%;
+ text-align: center;
+ box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
+
+ @media (min-width: ${theme.breakpoints.mobile}) {
+ padding: ${theme.spacing['3xl']};
+ }
+`;
+
+export const PopupMessage = styled.p`
+ color: ${theme.colors.primaryText};
+ margin-bottom: ${theme.spacing.xl};
+ font-size: ${theme.typography.sizes.base};
+ line-height: 1.5;
+
+ @media (min-width: ${theme.breakpoints.mobile}) {
+ font-size: ${theme.typography.sizes.lg};
+ margin-bottom: ${theme.spacing['2xl']};
+ }
+`;
+
+export const PopupButtons = styled.div`
+ display: flex;
+ gap: ${theme.spacing.base};
+ justify-content: center;
+ flex-wrap: wrap;
+`;
+
+export const ContinueButton = styled.button`
+ background-color: ${theme.colors.buttonSecondary};
+ color: ${theme.colors.primaryText};
+ border: none;
+ padding: ${theme.spacing.md} ${theme.spacing.xl};
+ border-radius: 4px;
+ font-weight: ${theme.typography.weights.bold};
+ cursor: pointer;
+ transition: background-color ${theme.transitions.normal};
+
+ &:hover {
+ background-color: ${theme.colors.buttonSecondaryHover};
+ }
+`;
+
+export const CartButton = styled.button`
+ background-color: ${theme.colors.buttonPrimary};
+ color: ${theme.colors.primaryText};
+ border: none;
+ padding: ${theme.spacing.md} ${theme.spacing.xl};
+ border-radius: 4px;
+ font-weight: ${theme.typography.weights.bold};
+ cursor: pointer;
+ transition: background-color ${theme.transitions.normal};
+
+ &:hover {
+ background-color: ${theme.colors.buttonPrimaryHover};
+ }
+`;
\ No newline at end of file
diff --git a/frontend/src/components/shared/StatusComponents.jsx b/frontend/src/components/shared/StatusComponents.jsx
new file mode 100644
index 0000000000..af95a8218d
--- /dev/null
+++ b/frontend/src/components/shared/StatusComponents.jsx
@@ -0,0 +1,12 @@
+import styled from "styled-components";
+import { theme } from "../../styles/theme";
+
+export const LoadingMessage = styled.div`
+ padding: ${theme.spacing['2xl']};
+ color: ${theme.colors.primaryText};
+ text-align: center;
+`;
+
+export const ErrorMessage = styled(LoadingMessage)`
+ color: ${theme.colors.red};
+`;
\ No newline at end of file
diff --git a/frontend/src/components/shared/TypographyComponents.jsx b/frontend/src/components/shared/TypographyComponents.jsx
new file mode 100644
index 0000000000..5be18a1d76
--- /dev/null
+++ b/frontend/src/components/shared/TypographyComponents.jsx
@@ -0,0 +1,169 @@
+import styled from "styled-components";
+import { theme } from "../../styles/theme";
+
+export const PageTitle = styled.h1`
+ font-size: ${theme.typography.sizes.xl};
+ font-weight: ${theme.typography.weights.bold};
+ margin-bottom: ${theme.spacing.md};
+ color: ${theme.colors.primaryText};
+ word-break: break-word;
+
+ @media (min-width: ${theme.breakpoints.mobile}) {
+ font-size: ${theme.typography.sizes['2xl']};
+ margin-bottom: ${theme.spacing.base};
+ }
+
+ @media (min-width: ${theme.breakpoints.tablet}) {
+ font-size: ${theme.typography.sizes['4xl']};
+ }
+`;
+
+// Flexible Title component that can be h1, h2, or h3 with different sizes and alignment
+export const Title = styled.h1.withConfig({
+ shouldForwardProp: (prop) => !['$size', '$align', '$uppercase', '$spacing'].includes(prop),
+})`
+ color: ${theme.colors.primaryText};
+ font-weight: ${theme.typography.weights.bold};
+ margin: 0;
+ text-align: ${props => props.$align || 'left'};
+ text-transform: ${props => props.$uppercase ? 'uppercase' : 'none'};
+ letter-spacing: ${props => {
+ if (props.$uppercase) return theme.typography.letterSpacing.uppercase;
+ if (props.$spacing === 'wide') return theme.typography.letterSpacing.wide;
+ if (props.$spacing === 'wider') return theme.typography.letterSpacing.wider;
+ if (props.$spacing === 'widest') return theme.typography.letterSpacing.widest;
+ return theme.typography.letterSpacing.normal;
+ }};
+
+ /* Size variants */
+ ${props => {
+ switch (props.$size) {
+ case 'sm':
+ return `
+ font-size: ${theme.typography.sizes.lg};
+ margin-bottom: ${theme.spacing.sm};
+
+ @media (min-width: ${theme.breakpoints.mobile}) {
+ font-size: ${theme.typography.sizes.xl};
+ margin-bottom: ${theme.spacing.md};
+ }
+
+ @media (min-width: ${theme.breakpoints.tablet}) {
+ font-size: ${theme.typography.sizes['2xl']};
+ }
+ `;
+ case 'md':
+ return `
+ font-size: ${theme.typography.sizes.xl};
+ margin-bottom: ${theme.spacing.md};
+
+ @media (min-width: ${theme.breakpoints.mobile}) {
+ font-size: ${theme.typography.sizes['2xl']};
+ margin-bottom: ${theme.spacing.base};
+ }
+
+ @media (min-width: ${theme.breakpoints.tablet}) {
+ font-size: ${theme.typography.sizes['3xl']};
+ }
+ `;
+ case 'lg':
+ return `
+ font-size: ${theme.typography.sizes['2xl']};
+ margin-bottom: ${theme.spacing.base};
+
+ @media (min-width: ${theme.breakpoints.mobile}) {
+ font-size: ${theme.typography.sizes['3xl']};
+ margin-bottom: ${theme.spacing.xl};
+ }
+
+ @media (min-width: ${theme.breakpoints.tablet}) {
+ font-size: ${theme.typography.sizes['4xl']};
+ }
+ `;
+ case 'xl':
+ return `
+ font-size: ${theme.typography.sizes['3xl']};
+ margin-bottom: ${theme.spacing.xl};
+
+ @media (min-width: ${theme.breakpoints.mobile}) {
+ font-size: ${theme.typography.sizes['4xl']};
+ margin-bottom: ${theme.spacing['2xl']};
+ }
+
+ @media (min-width: ${theme.breakpoints.tablet}) {
+ font-size: 3.5rem;
+ margin-bottom: ${theme.spacing['3xl']};
+ }
+ `;
+ default: // 'md' is default
+ return `
+ font-size: ${theme.typography.sizes.xl};
+ margin-bottom: ${theme.spacing.md};
+
+ @media (min-width: ${theme.breakpoints.mobile}) {
+ font-size: ${theme.typography.sizes['2xl']};
+ margin-bottom: ${theme.spacing.base};
+ }
+
+ @media (min-width: ${theme.breakpoints.tablet}) {
+ font-size: ${theme.typography.sizes['3xl']};
+ }
+ `;
+ }
+ }}
+`;
+
+// Section Title component for h2 headings
+export const SectionTitle = styled.h2.withConfig({
+ shouldForwardProp: (prop) => !['$border', '$align'].includes(prop),
+})`
+ color: ${theme.colors.primaryText};
+ font-size: ${theme.typography.sizes.lg};
+ font-weight: ${theme.typography.weights.bold};
+ margin-bottom: ${theme.spacing.base};
+ text-align: ${props => props.$align || 'left'};
+ border-bottom: ${props => props.$border ? `1px solid ${theme.colors.borderLight}` : 'none'};
+ padding-bottom: ${props => props.$border ? theme.spacing.sm : '0'};
+
+ @media (min-width: ${theme.breakpoints.mobile}) {
+ font-size: ${theme.typography.sizes.xl};
+ margin-bottom: ${theme.spacing.xl};
+ }
+
+ @media (min-width: ${theme.breakpoints.tablet}) {
+ font-size: ${theme.typography.sizes['2xl']};
+ }
+`;
+
+export const Description = styled.p`
+ font-size: ${theme.typography.sizes.sm};
+ margin-bottom: ${theme.spacing.base};
+ line-height: 1.5;
+ color: ${theme.colors.secondaryText};
+
+ @media (min-width: ${theme.breakpoints.mobile}) {
+ font-size: ${theme.typography.sizes.base};
+ margin-bottom: ${theme.spacing.xl};
+ line-height: 1.6;
+ }
+
+ @media (min-width: ${theme.breakpoints.tablet}) {
+ font-size: ${theme.typography.sizes.lg};
+ }
+`;
+
+export const Price = styled.div`
+ font-size: ${theme.typography.sizes.lg};
+ font-weight: ${theme.typography.weights.bold};
+ margin-bottom: ${theme.spacing.base};
+ color: ${theme.colors.buttonPrimary};
+
+ @media (min-width: ${theme.breakpoints.mobile}) {
+ font-size: ${theme.typography.sizes.xl};
+ margin-bottom: ${theme.spacing.xl};
+ }
+
+ @media (min-width: ${theme.breakpoints.tablet}) {
+ font-size: ${theme.typography.sizes['2xl']};
+ }
+`;
\ No newline at end of file
diff --git a/frontend/src/config/.env.production b/frontend/src/config/.env.production
new file mode 100644
index 0000000000..debff8dd93
--- /dev/null
+++ b/frontend/src/config/.env.production
@@ -0,0 +1 @@
+VITE_API_BASE_URL=https://morbidgeneoffical.com
\ No newline at end of file
diff --git a/frontend/src/config/api.js b/frontend/src/config/api.js
new file mode 100644
index 0000000000..f6a59dff9f
--- /dev/null
+++ b/frontend/src/config/api.js
@@ -0,0 +1,29 @@
+const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || 'https://morbid-gene-2-0.onrender.com';
+
+export const apiCall = async (endpoint, options = {}) => {
+ const url = `${API_BASE_URL}${endpoint}`;
+
+ // Get JWT token from localStorage
+ const token = localStorage.getItem('authToken');
+
+ const headers = {
+ 'Content-Type': 'application/json',
+ ...options.headers,
+ };
+
+ // Add Authorization header if token exists
+ if (token) {
+ headers['Authorization'] = `Bearer ${token}`;
+ }
+
+ const defaultOptions = {
+ headers,
+ credentials: 'include', // Still include cookies as fallback
+ ...options,
+ };
+
+ const response = await fetch(url, defaultOptions);
+ return response;
+};
+
+export default API_BASE_URL;
\ No newline at end of file
diff --git a/frontend/src/contexts/AuthContext.jsx b/frontend/src/contexts/AuthContext.jsx
new file mode 100644
index 0000000000..c2f69ebbed
--- /dev/null
+++ b/frontend/src/contexts/AuthContext.jsx
@@ -0,0 +1,183 @@
+import { createContext, useState, useContext, useEffect } from 'react';
+import { apiCall } from '../config/api.js';
+
+const AuthContext = createContext();
+
+export const useAuth = () => {
+ const context = useContext(AuthContext);
+ if (!context) {
+ throw new Error('useAuth must be used within AuthProvider');
+ }
+ return context;
+};
+
+export const AuthProvider = ({ children }) => {
+ const [user, setUser] = useState(null);
+ const [loading, setLoading] = useState(true);
+ const [favorites, setFavorites] = useState([]);
+
+ // Helper function for common auth API call patterns
+ const makeAuthCall = async (endpoint, options = {}) => {
+ try {
+ // Get token from localStorage
+ const token = localStorage.getItem('authToken');
+ const headers = {
+ ...options.headers
+ };
+
+ // Add Authorization header if token exists
+ if (token) {
+ headers['Authorization'] = `Bearer ${token}`;
+ }
+
+ const response = await apiCall(endpoint, {
+ credentials: 'include',
+ ...options,
+ headers
+ });
+ const data = await response.json();
+ return { success: response.ok, data, response };
+ } catch (error) {
+ return { success: false, error: 'Network error' };
+ }
+ };
+
+ // Check if user is authenticated on app load
+ useEffect(() => {
+ fetchCurrentUser();
+ }, []);
+
+ const fetchCurrentUser = async () => {
+ const result = await makeAuthCall('/auth/me');
+
+ if (result.success) {
+ setUser(result.data);
+ await fetchFavorites(result.data);
+ } else {
+ setUser(null);
+ }
+ setLoading(false);
+ };
+
+ const fetchFavorites = async (userData = user) => {
+ if (!userData) return;
+
+ const result = await makeAuthCall('/auth/favorites');
+ if (result.success) {
+ setFavorites(result.data);
+ }
+ };
+
+ const login = async (email, password) => {
+ const result = await makeAuthCall('/auth/login', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ email, password })
+ });
+
+ if (result.success) {
+ // Save JWT token to localStorage
+ if (result.data.token) {
+ localStorage.setItem('authToken', result.data.token);
+ }
+ setUser(result.data.user);
+ await fetchFavorites(result.data.user);
+ return { success: true };
+ }
+ return { success: false, error: result.data?.message || result.error };
+ };
+
+ const register = async (name, email, password) => {
+ const result = await makeAuthCall('/auth/register', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ name, email, password })
+ });
+
+ if (result.success) {
+ // Save JWT token to localStorage
+ if (result.data.token) {
+ localStorage.setItem('authToken', result.data.token);
+ }
+ setUser(result.data.user);
+ return { success: true };
+ }
+ return { success: false, error: result.data?.message || result.error };
+ };
+
+ const logout = async () => {
+ await makeAuthCall('/auth/logout', { method: 'POST' });
+
+ // Clear JWT token from localStorage
+ localStorage.removeItem('authToken');
+
+ // Clear local state regardless of backend response
+ setUser(null);
+ setFavorites([]);
+
+ // Redirect to merch shop if on protected pages
+ const currentPath = window.location.pathname;
+ if (currentPath === '/settings' || currentPath === '/favorites') {
+ window.location.href = '/merch';
+ }
+ };
+
+ const addToFavorites = async (product) => {
+ if (!user) return { success: false, error: 'Please login to add favorites' };
+
+ const result = await makeAuthCall('/auth/favorites', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ sellableId: product.sellableId,
+ name: product.name,
+ productTypeName: product.productTypeName,
+ price: product.price,
+ previewImage: product.previewImage
+ })
+ });
+
+ if (result.success) {
+ fetchFavorites();
+ return { success: true, message: result.data.message };
+ }
+ return { success: false, error: result.data?.message || result.error };
+ };
+
+ const removeFromFavorites = async (sellableId) => {
+ if (!user) return { success: false, error: 'Please login first' };
+
+ const result = await makeAuthCall(`/auth/favorites/${sellableId}`, {
+ method: 'DELETE'
+ });
+
+ if (result.success) {
+ fetchFavorites();
+ return { success: true, message: result.data.message };
+ }
+ return { success: false, error: result.data?.message || result.error };
+ };
+
+ const isFavorite = (sellableId) => {
+ return favorites.some(fav => fav.sellableId === sellableId);
+ };
+
+ const value = {
+ user,
+ loading,
+ favorites,
+ login,
+ register,
+ logout,
+ addToFavorites,
+ removeFromFavorites,
+ isFavorite,
+ isAuthenticated: !!user
+ };
+
+ return (
+
+ {children}
+
+ );
+};
\ No newline at end of file
diff --git a/frontend/src/contexts/CartProvider.jsx b/frontend/src/contexts/CartProvider.jsx
new file mode 100644
index 0000000000..f2ec7d4a50
--- /dev/null
+++ b/frontend/src/contexts/CartProvider.jsx
@@ -0,0 +1,272 @@
+import { useState, useEffect, createContext } from "react";
+import * as basketApi from "../api/basketApi";
+import { convertSpreadshirtItem } from "../utils/basketUtils";
+
+export const CartContext = createContext();
+
+const extractItemProperties = (item) => {
+ const props = item.element.properties;
+ return {
+ sellableId: props.find((p) => p.key === "sellable")?.value,
+ sizeLabel: props.find((p) => p.key === "sizeLabel")?.value,
+ };
+};
+
+const findCartItem = (cartItems, sellableId, size) => {
+ return cartItems.find(
+ (item) => item.sellableId === sellableId && item.size === size
+ );
+};
+
+const findBasketItemIndex = (basketItems, sellableId, size) => {
+ return basketItems.findIndex((item) => {
+ const { sellableId: itemSellableId, sizeLabel } = extractItemProperties(item);
+ return itemSellableId === sellableId && sizeLabel === size;
+ });
+};
+
+const convertBasketToCartItems = (basketItems, cartItems, selectedImage = null, productPrice = null, targetIndex = -1) => {
+ return basketItems.map((item, index) => {
+ const { sellableId, sizeLabel } = extractItemProperties(item);
+ const existingItem = findCartItem(cartItems, sellableId, sizeLabel);
+
+ // Use selected image/price for newly added items or preserve existing
+ const isTargetItem = targetIndex >= 0 ? index === targetIndex : false;
+ const imageToUse = isTargetItem ? selectedImage : (existingItem?.selectedImage || null);
+ const priceToUse = isTargetItem ? productPrice : (existingItem?.originalPrice || null);
+
+ if (existingItem) {
+ return { ...existingItem, quantity: item.quantity };
+ }
+ return convertSpreadshirtItem(item, imageToUse, priceToUse);
+ });
+};
+
+export const CartProvider = ({ children }) => {
+ const [cartItems, setCartItems] = useState([]);
+ const [basketId, setBasketId] = useState(null);
+ const [loading, setLoading] = useState(false);
+ const [error, setError] = useState(null);
+
+ useEffect(() => {
+ const loadBasket = async () => {
+ const savedBasketId = localStorage.getItem("spreadshirt-basket-id");
+ if (!savedBasketId) return;
+
+ setLoading(true);
+ try {
+ const basket = await basketApi.getBasket(savedBasketId);
+ if (basket?.basketItems) {
+ setBasketId(savedBasketId);
+ setCartItems(convertBasketToCartItems(basket.basketItems, []));
+ } else {
+ localStorage.removeItem("spreadshirt-basket-id");
+ }
+ } catch (err) {
+ localStorage.removeItem("spreadshirt-basket-id");
+ setError("Could not load cart");
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ loadBasket();
+ }, []);
+
+ const updateBasketState = async (newBasketItems) => {
+ if (newBasketItems.length === 0) {
+ await basketApi.deleteBasket(basketId);
+ setBasketId(null);
+ setCartItems([]);
+ localStorage.removeItem("spreadshirt-basket-id");
+ } else {
+ const updatedBasket = await basketApi.updateBasket(basketId, newBasketItems);
+ setCartItems(convertBasketToCartItems(updatedBasket.basketItems, cartItems));
+ }
+ };
+
+ const createNewBasket = async (basketItem, selectedImage, productPrice) => {
+ const newBasket = await basketApi.createBasket([basketItem]);
+ setBasketId(newBasket.id);
+ localStorage.setItem("spreadshirt-basket-id", newBasket.id);
+
+ const fullBasket = await basketApi.getBasket(newBasket.id);
+ const lastIndex = fullBasket.basketItems.length - 1;
+ setCartItems(convertBasketToCartItems(fullBasket.basketItems, [], selectedImage, productPrice, lastIndex));
+ };
+
+ const updateExistingBasket = async (product, selectedSize, selectedImage, selectedColor, quantity, productPrice, basketItem) => {
+ const currentBasket = await basketApi.getBasket(basketId);
+ const existingItemIndex = findBasketItemIndex(currentBasket.basketItems, product.sellableId, selectedSize);
+
+ let newBasketItems;
+ if (existingItemIndex >= 0) {
+ // Increase quantity by the specified amount
+ newBasketItems = [...currentBasket.basketItems];
+ newBasketItems[existingItemIndex].quantity += quantity;
+ } else {
+ // Add new item
+ newBasketItems = [...currentBasket.basketItems, basketItem];
+ }
+
+ const updatedBasket = await basketApi.updateBasket(basketId, newBasketItems);
+ const targetIndex = existingItemIndex < 0 ? updatedBasket.basketItems.length - 1 : -1;
+ setCartItems(convertBasketToCartItems(updatedBasket.basketItems, cartItems, selectedImage, productPrice, targetIndex));
+ };
+
+ const addToCart = async (
+ product,
+ selectedSize,
+ selectedImage,
+ selectedColor,
+ quantity = 1,
+ productPrice = null
+ ) => {
+ setLoading(true);
+ setError(null);
+
+ try {
+ const basketItem = await basketApi.convertToBasketItem(
+ product.sellableId,
+ selectedSize,
+ selectedColor,
+ quantity
+ );
+
+ if (!basketId) {
+ await createNewBasket(basketItem, selectedImage, productPrice);
+ } else {
+ await updateExistingBasket(product, selectedSize, selectedImage, selectedColor, quantity, productPrice, basketItem);
+ }
+ } catch (err) {
+ setError("Could not add item to cart");
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ const removeFromCart = async (sellableId, size) => {
+ if (!basketId) return;
+
+ setLoading(true);
+ setError(null);
+
+ try {
+ const currentBasket = await basketApi.getBasket(basketId);
+ const newBasketItems = currentBasket.basketItems.filter((item) => {
+ const { sellableId: itemSellableId, sizeLabel } = extractItemProperties(item);
+ return !(itemSellableId === sellableId && sizeLabel === size);
+ });
+
+ await updateBasketState(newBasketItems);
+ } catch (err) {
+ setError("Could not remove item from cart");
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ const optimisticallyUpdateQuantity = (sellableId, size, newQuantity) => {
+ setCartItems(prevItems =>
+ prevItems.map(item => {
+ if (item.sellableId === sellableId && item.size === size) {
+ return { ...item, quantity: newQuantity };
+ }
+ return item;
+ })
+ );
+ };
+
+ const updateQuantity = async (sellableId, size, newQuantity) => {
+ if (newQuantity <= 0) {
+ return removeFromCart(sellableId, size);
+ }
+
+ if (!basketId) return;
+
+ // Optimistic update - update UI immediately
+ optimisticallyUpdateQuantity(sellableId, size, newQuantity);
+ setError(null);
+
+ try {
+ const currentBasket = await basketApi.getBasket(basketId);
+ const newBasketItems = currentBasket.basketItems.map((item) => {
+ const { sellableId: itemSellableId, sizeLabel } = extractItemProperties(item);
+ if (itemSellableId === sellableId && sizeLabel === size) {
+ return { ...item, quantity: newQuantity };
+ }
+ return item;
+ });
+
+ // Update backend without loading state
+ const updatedBasket = await basketApi.updateBasket(basketId, newBasketItems);
+
+ // Sync with actual data from backend
+ setCartItems(convertBasketToCartItems(updatedBasket.basketItems, cartItems));
+ } catch (err) {
+ setError("Could not update quantity");
+
+ // Restore on error
+ try {
+ const currentBasket = await basketApi.getBasket(basketId);
+ setCartItems(convertBasketToCartItems(currentBasket.basketItems, cartItems));
+ } catch (restoreErr) {
+ }
+ }
+ };
+
+ // Clear entire cart
+ const clearCart = async () => {
+ if (!basketId) return;
+
+ setLoading(true);
+ setError(null);
+
+ try {
+ await basketApi.deleteBasket(basketId);
+ setBasketId(null);
+ setCartItems([]);
+ localStorage.removeItem("spreadshirt-basket-id");
+ } catch (err) {
+ setError("Could not clear cart");
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ // Calculate total number of items
+ const getTotalItems = () => {
+ return cartItems.reduce((total, item) => total + item.quantity, 0);
+ };
+
+ // Calculate total price
+ const getTotalPrice = () => {
+ return cartItems.reduce((total, item) => {
+ const price = parseFloat(item.price?.display || item.price?.amount || 0);
+ return total + price * item.quantity;
+ }, 0);
+ };
+
+ // Rensa felmeddelanden
+ const clearError = () => setError(null);
+
+ return (
+
+ {children}
+
+ );
+};
diff --git a/frontend/src/hooks/useMerch.js b/frontend/src/hooks/useMerch.js
new file mode 100644
index 0000000000..9feb767a25
--- /dev/null
+++ b/frontend/src/hooks/useMerch.js
@@ -0,0 +1,33 @@
+import { useState, useEffect } from "react";
+import { apiCall } from "../config/api.js";
+
+export function useMerch({ q, limit, offset }) {
+ const [items, setItems] = useState([]);
+ const [total, setTotal] = useState(0);
+ const [loading, setLoading] = useState(false);
+ const [error, setError] = useState(null);
+
+ useEffect(() => {
+ setLoading(true);
+ setError(null);
+
+ const params = new URLSearchParams();
+ if (limit) params.append("limit", limit);
+ if (offset) params.append("offset", offset);
+ if (q) params.append("q", q);
+
+ apiCall(`/api/merch?${params.toString()}`)
+ .then((res) => {
+ if (!res.ok) throw new Error("Something went wrong while fetching products");
+ return res.json();
+ })
+ .then((data) => {
+ setItems(data.sellables || []);
+ setTotal(data.count || 0);
+ })
+ .catch((err) => setError(err.message))
+ .finally(() => setLoading(false));
+ }, [q, limit, offset]);
+
+ return { items, total, loading, error };
+}
diff --git a/frontend/src/hooks/useProduct.js b/frontend/src/hooks/useProduct.js
new file mode 100644
index 0000000000..ce143a6621
--- /dev/null
+++ b/frontend/src/hooks/useProduct.js
@@ -0,0 +1,48 @@
+import { useState, useEffect } from "react";
+import { apiCall } from "../config/api.js";
+
+export const useProduct = (productId) => {
+ const [product, setProduct] = useState(null);
+ const [productType, setProductType] = useState(null); // Ny
+ const [loading, setLoading] = useState(false);
+ const [error, setError] = useState(null);
+
+ useEffect(() => {
+
+ if (!productId) {
+ return;
+ }
+
+ const fetchProduct = async () => {
+ setLoading(true);
+ setError(null);
+
+ try {
+ // Fetch product
+ const productResponse = await apiCall(`/api/merch/${productId}`);
+ if (!productResponse.ok) throw new Error("Failed to fetch product");
+ const productData = await productResponse.json();
+ setProduct(productData);
+
+ // Fetch ProductType (sizes/colors)
+ if (productData.productTypeId) {
+ const typeResponse = await apiCall(
+ `/api/merch/productType/${productData.productTypeId}`
+ );
+ if (typeResponse.ok) {
+ const typeData = await typeResponse.json();
+ setProductType(typeData);
+ }
+ }
+ } catch (err) {
+ setError(err.message);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ fetchProduct();
+ }, [productId]);
+
+ return { product, productType, loading, error };
+};
diff --git a/frontend/src/hooks/useProductImages.js b/frontend/src/hooks/useProductImages.js
new file mode 100644
index 0000000000..90ec2a4356
--- /dev/null
+++ b/frontend/src/hooks/useProductImages.js
@@ -0,0 +1,37 @@
+import { useState, useEffect } from "react";
+import { apiCall } from "../config/api";
+
+export const useProductImages = (sellableId, appearanceId, ideaId) => {
+ const [images, setImages] = useState([]);
+ const [loading, setLoading] = useState(false);
+ const [error, setError] = useState(null);
+
+ useEffect(() => {
+ if (!sellableId || !appearanceId) {
+ return;
+ }
+
+ const fetchImages = async () => {
+ setLoading(true);
+ setError(null);
+
+ try {
+ const response = await apiCall(
+ `/api/merch/sellable/${sellableId}/${appearanceId}/${ideaId}`
+ );
+ if (!response.ok) throw new Error("Failed to fetch images");
+
+ const data = await response.json();
+ setImages(data.images || []);
+ } catch (err) {
+ setError(err.message);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ fetchImages();
+ }, [sellableId, appearanceId, ideaId]);
+
+ return { images, loading, error };
+};
diff --git a/frontend/src/main.jsx b/frontend/src/main.jsx
index 51294f3998..65f0bfb2da 100644
--- a/frontend/src/main.jsx
+++ b/frontend/src/main.jsx
@@ -1,10 +1,9 @@
-import React from "react";
-import ReactDOM from "react-dom/client";
+import { StrictMode } from "react";
+import { createRoot } from "react-dom/client";
import { App } from "./App.jsx";
-import "./index.css";
-ReactDOM.createRoot(document.getElementById("root")).render(
-
+createRoot(document.getElementById("root")).render(
+
-
+
);
diff --git a/frontend/src/pages/AccountSettings.jsx b/frontend/src/pages/AccountSettings.jsx
new file mode 100644
index 0000000000..82bb248c7b
--- /dev/null
+++ b/frontend/src/pages/AccountSettings.jsx
@@ -0,0 +1,572 @@
+import { useState } from 'react';
+import styled from 'styled-components';
+import { useAuth } from '../contexts/AuthContext';
+import { useNavigate } from 'react-router-dom';
+import { apiCall } from '../config/api.js';
+import { Container } from "../components/shared/LayoutComponents";
+import { theme } from "../styles/theme";
+
+
+const SettingsContainer = styled.div`
+ max-width: 800px;
+ margin: 0 auto;
+ padding: 2rem;
+
+ @media (min-width: 480px) {
+ padding: 2.5rem;
+ }
+
+ @media (min-width: 768px) {
+ padding: 3rem;
+ }
+`;
+
+const Header = styled.div`
+ text-align: center;
+ margin-bottom: 3rem;
+
+ @media (min-width: 768px) {
+ margin-bottom: 1.5rem;
+ }
+
+ h1 {
+ color: ${theme.colors.primaryText};
+ font-size: 1.5rem;
+ font-weight: 800;
+ text-transform: uppercase;
+ letter-spacing: 2px;
+ margin-bottom: 0.5rem;
+
+ @media (min-width: 480px) {
+ font-size: 1.75rem;
+ }
+
+ @media (min-width: 768px) {
+ font-size: 1.75rem;
+ }
+ }
+
+ p {
+ color: ${theme.colors.secondaryText};
+ font-style: italic;
+ font-size: 1rem;
+ font-weight: 300;
+
+ @media (min-width: 480px) {
+ font-size: 1.1rem;
+ }
+ }
+`;
+
+const SettingsSection = styled.div`
+ background: ${theme.colors.sectionBg};
+ border: 1px solid ${theme.colors.charcoal};
+ padding: 1.5rem;
+ margin-bottom: 2rem;
+
+ h2 {
+ color: ${theme.colors.primaryText};
+ font-size: 1.3rem;
+ font-weight: 700;
+ text-transform: uppercase;
+ letter-spacing: 1px;
+ margin: 0 0 1.5rem 0;
+ border-bottom: 1px solid ${theme.colors.charcoal};
+ padding-bottom: 0.5rem;
+ }
+
+ @media (min-width: 480px) {
+ padding: 2.5rem;
+ }
+`;
+
+const Form = styled.form`
+ display: flex;
+ flex-direction: column;
+ gap: 1.5rem;
+`;
+
+const InfoGrid = styled.div`
+ display: grid;
+ gap: 1rem;
+
+ @media (min-width: 768px) {
+ grid-template-columns: repeat(2, 1fr);
+ gap: 1.5rem;
+ }
+`;
+
+const InfoItem = styled.div`
+ display: flex;
+ flex-direction: column;
+ gap: 0.25rem;
+`;
+
+const InfoLabel = styled.span`
+ color: ${theme.colors.secondaryText};
+ font-size: 0.85rem;
+ text-transform: uppercase;
+ letter-spacing: 0.5px;
+ font-weight: 600;
+`;
+
+const InfoValue = styled.span`
+ color: ${theme.colors.primaryText};
+ font-size: 1rem;
+ word-break: break-all;
+
+ &.password {
+ font-family: monospace;
+ letter-spacing: 2px;
+ }
+`;
+
+const FormGroup = styled.div`
+ display: flex;
+ flex-direction: column;
+ gap: 0.5rem;
+`;
+
+const Label = styled.label`
+ color: ${theme.colors.primaryText};
+ font-weight: bold;
+ font-size: 0.95rem;
+ text-transform: uppercase;
+ letter-spacing: 0.5px;
+
+ @media (min-width: 480px) {
+ font-size: 1rem;
+ letter-spacing: 1px;
+ }
+`;
+
+const Input = styled.input`
+ padding: 0.75rem;
+ background: ${theme.colors.inputBg};
+ border: 2px solid #444444;
+ color: ${theme.colors.primaryText};
+ font-size: 1rem;
+ transition: all 0.3s ease;
+
+ &:focus {
+ outline: none;
+ border-color: ${theme.colors.buttonPrimary};
+ box-shadow: 0 0 10px rgba(220, 38, 38, 0.3);
+ }
+
+ &::placeholder {
+ color: ${theme.colors.mediumGray};
+ font-size: 0.875rem;
+
+ @media (max-width: 768px) {
+ font-size: 1rem;
+ }
+ }
+
+ @media (min-width: 480px) {
+ padding: 0.85rem;
+ }
+`;
+
+const ButtonGroup = styled.div`
+ display: flex;
+ gap: 1rem;
+ justify-content: flex-start;
+ margin-top: 1rem;
+
+ @media (max-width: 480px) {
+ flex-direction: column;
+ }
+`;
+
+const SaveButton = styled.button`
+ background-color: ${theme.colors.buttonPrimary};
+ color: ${theme.colors.primaryText};
+ border: none;
+ padding: 0.75rem 1.25rem;
+ font-size: 0.9rem;
+ font-weight: bold;
+ text-transform: uppercase;
+ letter-spacing: 0.5px;
+ cursor: pointer;
+ transition: background-color 0.2s;
+
+ &:hover:not(:disabled) {
+ background-color: ${theme.colors.buttonPrimaryHover};
+ }
+
+ &:disabled {
+ background-color: ${theme.colors.buttonDisabled};
+ cursor: not-allowed;
+ }
+
+ @media (min-width: 480px) {
+ padding: 0.85rem 1.5rem;
+ font-size: 0.95rem;
+ }
+`;
+
+const DangerZone = styled.div`
+ border: 1px solid ${theme.colors.buttonPrimary};
+ padding: 1.5rem;
+ background: rgba(220, 38, 38, 0.03);
+
+ h2 {
+ color: ${theme.colors.buttonPrimary};
+ border-bottom-color: ${theme.colors.buttonPrimary};
+ }
+
+ p {
+ color: ${theme.colors.secondaryText};
+ font-size: 1rem;
+ margin: 0 0 1.5rem 0;
+ line-height: 1.5;
+ }
+
+ @media (min-width: 480px) {
+ padding: 2.5rem;
+ }
+`;
+
+const DangerButton = styled.button`
+ background-color: ${theme.colors.buttonPrimary};
+ color: ${theme.colors.primaryText};
+ border: none;
+ padding: 0.75rem 1.25rem;
+ font-size: 0.9rem;
+ font-weight: bold;
+ text-transform: uppercase;
+ letter-spacing: 0.5px;
+ cursor: pointer;
+ transition: background-color 0.2s;
+
+ &:hover {
+ background-color: ${theme.colors.buttonPrimaryHover};
+ }
+
+ @media (min-width: 480px) {
+ padding: 0.85rem 1.5rem;
+ font-size: 0.95rem;
+ }
+`;
+
+const ErrorMessage = styled.div`
+ color: ${theme.colors.primaryText};
+ background: ${theme.colors.buttonPrimary};
+ border: 1px solid #991b1b;
+ padding: 0.75rem;
+ text-align: center;
+ font-size: 0.9rem;
+ margin-bottom: 1rem;
+`;
+
+const SuccessMessage = styled.div`
+ color: ${theme.colors.primaryText};
+ background: ${theme.colors.buttonPrimary};
+ border: 1px solid #991b1b;
+ padding: 0.75rem;
+ text-align: center;
+ font-size: 0.9rem;
+ margin-bottom: 1rem;
+`;
+
+const ConfirmationOverlay = styled.div`
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: rgba(0, 0, 0, 0.8);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ z-index: 1000;
+`;
+
+const ConfirmationDialog = styled.div`
+ background: ${theme.colors.sectionBg};
+ border: 1px solid ${theme.colors.charcoal};
+ padding: 1.5rem;
+ max-width: 400px;
+ text-align: center;
+
+ h3 {
+ color: ${theme.colors.primaryText};
+ margin: 0 0 1rem 0;
+ font-size: 1.2rem;
+ }
+
+ p {
+ color: ${theme.colors.secondaryText};
+ margin: 0 0 1.5rem 0;
+ font-size: 0.95rem;
+ }
+`;
+
+const ConfirmationButtons = styled.div`
+ display: flex;
+ gap: 1rem;
+ justify-content: center;
+`;
+
+const ConfirmButton = styled.button`
+ background-color: ${theme.colors.buttonPrimary};
+ color: ${theme.colors.primaryText};
+ border: none;
+ padding: 0.6rem 1rem;
+ cursor: pointer;
+ font-weight: bold;
+ font-size: 0.9rem;
+ transition: background-color 0.2s;
+
+ &:hover {
+ background-color: ${theme.colors.buttonPrimaryHover};
+ }
+`;
+
+const CancelButton = styled.button`
+ background-color: ${theme.colors.charcoal};
+ color: ${theme.colors.primaryText};
+ border: none;
+ padding: 0.6rem 1rem;
+ cursor: pointer;
+ font-weight: bold;
+ font-size: 0.9rem;
+ transition: background-color 0.2s;
+
+ &:hover {
+ background-color: ${theme.colors.darkCharcoal};
+ }
+`;
+
+export const AccountSettings = () => {
+ const { user, logout } = useAuth();
+ const navigate = useNavigate();
+ const [loading, setLoading] = useState(false);
+ const [message, setMessage] = useState('');
+ const [error, setError] = useState('');
+ const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
+ const [showDeleteSuccess, setShowDeleteSuccess] = useState(false);
+
+ const [passwordForm, setPasswordForm] = useState({
+ currentPassword: '',
+ newPassword: '',
+ confirmPassword: ''
+ });
+
+ const handlePasswordChange = (e) => {
+ setPasswordForm({
+ ...passwordForm,
+ [e.target.name]: e.target.value
+ });
+ };
+
+ const handlePasswordSubmit = async (e) => {
+ e.preventDefault();
+ setError('');
+ setMessage('');
+
+ if (passwordForm.newPassword !== passwordForm.confirmPassword) {
+ setError('New passwords do not match');
+ return;
+ }
+
+ if (passwordForm.newPassword.length < 6) {
+ setError('New password must be at least 6 characters long');
+ return;
+ }
+
+ setLoading(true);
+
+ try {
+ const response = await apiCall('/auth/change-password', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify({
+ currentPassword: passwordForm.currentPassword,
+ newPassword: passwordForm.newPassword
+ })
+ });
+
+ const data = await response.json();
+
+ if (response.ok) {
+ setMessage('Password changed successfully');
+ setPasswordForm({ currentPassword: '', newPassword: '', confirmPassword: '' });
+ } else {
+ setError(data.message || 'Failed to change password');
+ }
+ } catch (error) {
+ setError('Network error');
+ }
+
+ setLoading(false);
+ };
+
+ const handleDeleteAccount = () => {
+ setShowDeleteConfirm(true);
+ };
+
+ const handleConfirmDelete = async () => {
+ try {
+ const response = await apiCall('/auth/delete-account', {
+ method: 'DELETE'
+ });
+
+ if (response.ok) {
+ setShowDeleteSuccess(true);
+ setTimeout(() => {
+ logout();
+ navigate('/');
+ }, 2000); // Visa popup i 2 sekunder innan omdirigering
+ } else {
+ const data = await response.json();
+ setError(data.message || 'Failed to delete account');
+ }
+ } catch (error) {
+ setError('Network error');
+ }
+
+ setShowDeleteConfirm(false);
+ };
+
+ const handleCancelDelete = () => {
+ setShowDeleteConfirm(false);
+ };
+
+ return (
+
+
+
+
+
+ Account Information
+
+
+ Name
+ {user?.name || 'Not set'}
+
+
+ Email
+ {user?.email || 'Not set'}
+
+
+ Password
+ Protected
+
+
+ Account Created
+
+ {user?.createdAt ? new Date(user.createdAt).toLocaleDateString() : 'Unknown'}
+
+
+
+ Last Login
+
+ {user?.lastLogin ? new Date(user.lastLogin).toLocaleString() : 'Unknown'}
+
+
+
+
+
+
+ Change Password
+
+ {error && {error} }
+ {message && {message} }
+
+
+
+
+
+ Danger Zone
+
+ Permanently delete your account and all associated data. This action cannot be undone.
+ All your favorites, account information, and preferences will be lost forever.
+
+
+ Delete Account
+
+
+
+ {/* Delete Account Confirmation Dialog */}
+ {showDeleteConfirm && (
+
+ e.stopPropagation()}>
+ Delete Account
+ Are you absolutely sure? This will permanently delete your account and all your data. This action cannot be undone.
+
+
+ Cancel
+
+
+ Delete Forever
+
+
+
+
+ )}
+
+ {/* Delete Account Success Dialog */}
+ {showDeleteSuccess && (
+
+
+ Account Deleted Successfully
+ Your account and all associated data have been permanently deleted. You will be redirected to the homepage shortly.
+
+
+ )}
+
+
+ );
+};
\ No newline at end of file
diff --git a/frontend/src/pages/Cart.jsx b/frontend/src/pages/Cart.jsx
new file mode 100644
index 0000000000..3a9d55626e
--- /dev/null
+++ b/frontend/src/pages/Cart.jsx
@@ -0,0 +1,453 @@
+import { useContext, useState } from "react";
+import { Link } from "react-router-dom";
+import styled from "styled-components";
+import { CartItem } from "../components/CartItem";
+import { formatPrice } from "../utils/formatPrice";
+import { CartContext } from "../contexts/CartProvider";
+import { getCheckoutUrl } from "../api/basketApi"; // ÄNDRAT: Importera den nya funktionen
+import ScrollToTop from "../components/ScrollToTop";
+import { ClearCartPopup } from "../components/ClearCartPopup";
+import { Container } from "../components/shared/LayoutComponents";
+import { EmptyState, EmptyStateLink } from "../components/shared/EmptyStateComponents";
+import { theme } from "../styles/theme";
+
+
+const Header = styled.div`
+ margin-bottom: 1rem;
+
+ @media (min-width: 768px) {
+ margin-bottom: 2rem;
+ }
+`;
+
+const Title = styled.h1`
+ color: ${theme.colors.primaryText};
+ font-size: 1.25rem;
+ margin-bottom: 0.5rem;
+
+ @media (min-width: 480px) {
+ font-size: 1.5rem;
+ margin-bottom: 1rem;
+ }
+`;
+
+const CartList = styled.div`
+ display: flex;
+ flex-direction: column;
+ gap: 0.75rem;
+ margin-bottom: 1.5rem;
+
+ @media (min-width: 768px) {
+ gap: 1rem;
+ margin-bottom: 2rem;
+ }
+`;
+
+// Ordersammanfattning sektion
+const OrderSummarySection = styled.div`
+ background: #1f2937;
+ border: 1px solid #374151;
+ padding: 1rem;
+ margin-bottom: 1.5rem;
+
+ @media (min-width: 480px) {
+ padding: 1.5rem;
+ }
+
+ @media (min-width: 768px) {
+ padding: 2rem;
+ margin-bottom: 2rem;
+ }
+`;
+
+const SummaryTitle = styled.h2`
+ color: ${theme.colors.primaryText};
+ font-size: 1.1rem;
+ margin-bottom: 1rem;
+ border-bottom: 1px solid #374151;
+ padding-bottom: 0.5rem;
+
+ @media (min-width: 480px) {
+ font-size: 1.3rem;
+ margin-bottom: 1.5rem;
+ }
+`;
+
+const SummaryItem = styled.div`
+ display: flex;
+ justify-content: space-between;
+ align-items: flex-start;
+ padding: 0.5rem 0;
+ color: #e5e7eb;
+ border-bottom: 1px solid #374151;
+ gap: 0.5rem;
+
+ @media (min-width: 480px) {
+ align-items: center;
+ padding: 0.75rem 0;
+ }
+
+ &:last-child {
+ border-bottom: none;
+ padding-top: 0.75rem;
+ margin-top: 0.5rem;
+ font-weight: bold;
+ font-size: 1rem;
+ color: white;
+
+ @media (min-width: 480px) {
+ padding-top: 1rem;
+ font-size: 1.1rem;
+ }
+ }
+`;
+
+const SummaryLabel = styled.span.withConfig({
+ shouldForwardProp: (prop) => !["bold"].includes(prop),
+})`
+ font-weight: ${(props) => (props.bold ? "bold" : "normal")};
+ font-size: 0.9rem;
+
+ @media (min-width: 480px) {
+ font-size: 1rem;
+ }
+`;
+
+const SummaryValue = styled.span.withConfig({
+ shouldForwardProp: (prop) => !["bold"].includes(prop),
+})`
+ font-weight: ${(props) => (props.bold ? "bold" : "normal")};
+ font-size: 0.9rem;
+ text-align: right;
+
+ @media (min-width: 480px) {
+ font-size: 1rem;
+ }
+`;
+
+// Checkout info sektion
+const CheckoutInfoSection = styled.div`
+ background: ${theme.colors.sectionBg};
+ border: 1px solid ${theme.colors.buttonPrimary};
+ padding: 1rem;
+ margin-bottom: 1.5rem;
+
+ @media (min-width: 480px) {
+ padding: 1.5rem;
+ }
+
+ @media (min-width: 768px) {
+ margin-bottom: 2rem;
+ }
+`;
+
+const InfoTitle = styled.h3`
+ color: ${theme.colors.primaryText};
+ margin: 0 0 0.5rem 0;
+ font-size: 1rem;
+ font-weight: 700;
+ text-transform: uppercase;
+ letter-spacing: 1px;
+
+ @media (min-width: 480px) {
+ margin: 0 0 0.75rem 0;
+ font-size: 1.1rem;
+ }
+`;
+
+const InfoText = styled.p`
+ color: ${theme.colors.secondaryText};
+ margin: 0;
+ font-size: 0.85rem;
+ line-height: 1.4;
+
+ @media (min-width: 480px) {
+ font-size: 0.9rem;
+ line-height: 1.5;
+ }
+`;
+
+const InfoList = styled.ul`
+ color: ${theme.colors.secondaryText};
+ margin: 0.5rem 0 0 0.75rem;
+ font-size: 0.85rem;
+ line-height: 1.3;
+
+ @media (min-width: 480px) {
+ margin: 0.5rem 0 0 1rem;
+ font-size: 0.9rem;
+ line-height: 1.4;
+ }
+`;
+
+const LegalLinksSection = styled.div`
+ text-align: center;
+ margin: 1rem 0;
+ padding: 0.75rem 0;
+ border-top: 1px solid #374151;
+ border-bottom: 1px solid #374151;
+
+ @media (min-width: 768px) {
+ margin: 1.5rem 0;
+ padding: 1rem 0;
+ }
+`;
+
+const LegalText = styled.p`
+ color: #9ca3af;
+ font-size: 0.8rem;
+ margin: 0;
+ line-height: 1.4;
+
+ @media (min-width: 480px) {
+ font-size: 0.85rem;
+ }
+`;
+
+const LegalLink = styled(Link)`
+ color: ${theme.colors.primaryText};
+ text-decoration: none;
+ margin: 0 0.5rem;
+ transition: color 0.2s ease;
+ font-weight: bold;
+ font-size: 0.8rem;
+
+ @media (min-width: 480px) {
+ font-size: 0.85rem;
+ }
+
+ &:hover {
+ color: ${theme.colors.buttonPrimary};
+ text-decoration: underline;
+ }
+`;
+
+// Footer med knappar
+const Footer = styled.div`
+ display: flex;
+ flex-direction: column;
+ gap: 0.75rem;
+
+ @media (min-width: 480px) {
+ flex-direction: row;
+ gap: 1rem;
+ justify-content: space-between;
+ align-items: center;
+ }
+`;
+
+const Button = styled.button.withConfig({
+ shouldForwardProp: (prop) => !["variant"].includes(prop),
+})`
+ padding: 0.75rem 1rem;
+ border: none;
+ font-weight: bold;
+ cursor: pointer;
+ font-size: 0.9rem;
+ transition: background-color 0.2s;
+ width: 100%;
+
+ @media (min-width: 480px) {
+ padding: 0.75rem 1.5rem;
+ font-size: 1rem;
+ width: auto;
+ }
+
+ ${(props) =>
+ props.variant === "primary" &&
+ `
+ background-color: ${theme.colors.buttonPrimary};
+ color: ${theme.colors.primaryText};
+ font-weight: bold;
+ letter-spacing: 1px;
+ transition: background-color 0.2s;
+
+ &:hover {
+ background-color: ${theme.colors.buttonPrimaryHover};
+ }
+
+ &:disabled {
+ background-color: #6b7280;
+ cursor: not-allowed;
+ }
+ `}
+
+ ${(props) =>
+ props.variant === "secondary" &&
+ `
+ background-color: ${theme.colors.buttonPrimary};
+ color: white;
+
+ &:hover {
+ background-color: ${theme.colors.buttonPrimaryHover};
+ }
+ `}
+`;
+
+const LoadingSpinner = styled.div`
+ display: inline-block;
+ width: 16px;
+ height: 16px;
+ border: 2px solid ${theme.colors.primaryText};
+ border-radius: 50%;
+ border-top-color: transparent;
+ animation: spin 1s ease-in-out infinite;
+ margin-right: 8px;
+
+ @keyframes spin {
+ to {
+ transform: rotate(360deg);
+ }
+ }
+`;
+
+// Note: EmptyState components moved to shared/EmptyStateComponents.jsx
+
+export const Cart = () => {
+ const {
+ cartItems,
+ updateQuantity,
+ removeFromCart,
+ getTotalPrice,
+ getTotalItems,
+ clearCart,
+ basketId,
+ } = useContext(CartContext);
+
+ const [checkoutLoading, setCheckoutLoading] = useState(false);
+ const [showClearCartPopup, setShowClearCartPopup] = useState(false);
+
+ // KORRIGERAD handleCheckout funktion
+ const handleCheckout = async () => {
+ if (!basketId) {
+ alert("No basket found. Please try refreshing the page.");
+ return;
+ }
+
+
+ setCheckoutLoading(true);
+
+ try {
+ // CHANGED: Use getCheckoutUrl instead of createCheckout
+ const data = await getCheckoutUrl(basketId);
+
+ if (!data.checkoutUrl) {
+ throw new Error("No checkout URL received from server");
+ }
+
+ window.location.href = data.checkoutUrl;
+ } catch (error) {
+ alert(`An error occurred during checkout: ${error.message}`);
+ setCheckoutLoading(false);
+ }
+ };
+
+ const totalWithShipping = getTotalPrice();
+
+ if (cartItems.length === 0) {
+ return (
+ Continue shopping}
+ />
+ );
+ }
+
+ return (
+
+
+ Shopping Cart ({getTotalItems()} items)
+
+
+ {/* Kundvagn med produkter */}
+
+ {cartItems.map((item) => (
+
+ updateQuantity(item.sellableId, item.size, qty)
+ }
+ onRemove={() => removeFromCart(item.sellableId, item.size)}
+ />
+ ))}
+
+
+ {/* Ordersammanfattning */}
+
+ Order Summary
+
+
+ Items ({getTotalItems()}):
+ {formatPrice(getTotalPrice())}
+
+
+
+
+ Shipping:
+
+ Calculated at checkout
+
+
+
+ Total (excl. shipping):
+ {formatPrice(totalWithShipping)}
+
+
+
+ {/* Checkout information */}
+
+ Secure Payment via Spreadshirt
+
+ When you click "Proceed to Checkout" you will be redirected to
+ Spreadshirt's secure payment page where you can:
+
+
+ Enter delivery address
+ Choose payment method (card, PayPal etc.)
+ See exact delivery time estimates (typically ~7 days)
+ Review and complete your order
+ Receive order confirmation via email
+
+
+ Delivery: Spreadshirt typically delivers within 7 days. The exact estimated delivery date will be shown during checkout based on your location and chosen shipping method.
+
+
+
+ {/* Legal links */}
+
+
+ By proceeding to checkout, you agree to our
+ Terms of Use
+ and
+ Privacy Policy
+
+
+
+ {/* Footer med knappar */}
+
+ setShowClearCartPopup(true)}>
+ Clear Cart
+
+
+
+ {checkoutLoading && }
+ {checkoutLoading ? "Preparing checkout..." : "Proceed to Checkout"}
+
+
+
+ setShowClearCartPopup(false)}
+ onConfirm={clearCart}
+ />
+
+
+
+ );
+};
diff --git a/frontend/src/pages/Contact.jsx b/frontend/src/pages/Contact.jsx
new file mode 100644
index 0000000000..1b7b7c9589
--- /dev/null
+++ b/frontend/src/pages/Contact.jsx
@@ -0,0 +1,214 @@
+import { useState } from "react";
+import styled from "styled-components";
+import BookingForm from "../components/BookingForm";
+import ContactForm from "../components/ContactForm";
+import { theme } from "../styles/theme";
+
+const ContactContainer = styled.div`
+ min-height: 100vh;
+ padding: 1rem;
+ max-width: 1600px;
+ margin: 0 auto;
+
+ @media (min-width: 480px) {
+ padding: 1.5rem;
+ }
+
+ @media (min-width: 768px) {
+ padding: 2rem;
+ }
+
+ @media (min-width: 1024px) {
+ padding: 3rem;
+ }
+
+ @media (min-width: 1400px) {
+ padding: 4rem;
+ }
+`;
+
+const Header = styled.div`
+ text-align: center;
+ margin-bottom: 1.5rem;
+ padding: 0;
+
+ @media (min-width: 480px) {
+ margin-bottom: 2rem;
+ }
+
+ @media (min-width: 768px) {
+ margin-bottom: 2.5rem;
+ }
+
+ @media (min-width: 1024px) {
+ margin-bottom: 3rem;
+ }
+`;
+
+
+const SubHeading = styled.h2`
+ color: ${theme.colors.secondaryText};
+ font-size: 0.9rem;
+ font-style: italic;
+ margin: 0.5rem 0 1.5rem 0;
+ padding: 0 0.5rem;
+ font-weight: 300;
+ line-height: 1.5;
+
+ @media (min-width: 480px) {
+ font-size: 1rem;
+ margin: 0.75rem 0 1.75rem 0;
+ padding: 0 1rem;
+ }
+
+ @media (min-width: 768px) {
+ font-size: 1.1rem;
+ margin: 1rem 0 2rem 0;
+ padding: 0;
+ }
+
+ @media (min-width: 1024px) {
+ font-size: 1.2rem;
+ margin: 1.5rem 0 2.5rem 0;
+ }
+`;
+
+const ToggleSection = styled.div`
+ display: flex;
+ justify-content: center;
+ margin-bottom: 1.5rem;
+ gap: 0;
+
+ @media (min-width: 480px) {
+ margin-bottom: 2rem;
+ }
+
+ @media (min-width: 768px) {
+ margin-bottom: 2.5rem;
+ }
+
+ @media (min-width: 1024px) {
+ margin-bottom: 3rem;
+ }
+`;
+
+const ToggleButton = styled.button`
+ background: ${(props) =>
+ props.$active
+ ? `linear-gradient(45deg, ${theme.colors.buttonPrimary}, ${theme.colors.buttonPrimaryHover})`
+ : theme.colors.charcoal
+ };
+ color: ${(props) => (props.$active ? theme.colors.primaryText : theme.colors.secondaryText)};
+ border: 1px solid ${(props) => (props.$active ? theme.colors.buttonPrimary : theme.colors.darkCharcoal)};
+ padding: 0.5rem 0.9rem;
+ font-size: 0.75rem;
+ font-weight: bold;
+ text-transform: uppercase;
+ letter-spacing: 0.5px;
+ cursor: pointer;
+ transition: all 0.3s ease;
+
+ &:first-child {
+ border-radius: 6px 0 0 6px;
+ border-right: none;
+ }
+
+ &:last-child {
+ border-radius: 0 6px 6px 0;
+ border-left: none;
+ }
+
+ @media (min-width: 480px) {
+ padding: 0.6rem 1rem;
+ font-size: 0.8rem;
+ }
+
+ @media (min-width: 768px) {
+ padding: 0.65rem 1.25rem;
+ font-size: 0.85rem;
+ letter-spacing: 0.5px;
+ }
+
+ @media (min-width: 1024px) {
+ padding: 0.75rem 1.5rem;
+ font-size: 0.9rem;
+ letter-spacing: 0.75px;
+ }
+
+ &:hover {
+ background: ${(props) =>
+ props.$active
+ ? `linear-gradient(45deg, ${theme.colors.buttonPrimaryHover}, ${theme.colors.buttonPrimary})`
+ : "#444444"
+ };
+ color: ${theme.colors.primaryText};
+ border-color: ${(props) => (props.$active ? theme.colors.buttonPrimary : theme.colors.secondaryText)};
+ }
+`;
+
+const FormDescription = styled.p`
+ text-align: center;
+ color: ${theme.colors.secondaryText};
+ font-size: 0.8rem;
+ margin: 0 0 1rem 0;
+ line-height: 1.4;
+
+ @media (min-width: 480px) {
+ font-size: 0.9rem;
+ margin-bottom: 1.25rem;
+ }
+
+ @media (min-width: 768px) {
+ font-size: 0.95rem;
+ margin-bottom: 1.5rem;
+ line-height: 1.5;
+ }
+
+ @media (min-width: 1024px) {
+ font-size: 1rem;
+ margin-bottom: 2rem;
+ }
+`;
+
+export const Contact = () => {
+ const [activeForm, setActiveForm] = useState("contact");
+
+ const getSubHeading = () => {
+ return activeForm === "booking"
+ ? "Ready to bring the darkness to your event?"
+ : "Get in touch with us!";
+ };
+
+ const getDescription = () => {
+ return activeForm === "booking"
+ ? "Fill out the booking form below to request Morbid Gene for your event."
+ : "Send us a message for general inquiries, collaboration, or any other questions.";
+ };
+
+ return (
+
+
+ {getSubHeading()}
+
+
+ setActiveForm("contact")}
+ >
+ General Contact
+
+ setActiveForm("booking")}
+ >
+ Booking Request
+
+
+
+ {getDescription()}
+
+
+ {activeForm === "booking" ? : }
+
+ );
+};
diff --git a/frontend/src/pages/Favorites.jsx b/frontend/src/pages/Favorites.jsx
new file mode 100644
index 0000000000..f439bac806
--- /dev/null
+++ b/frontend/src/pages/Favorites.jsx
@@ -0,0 +1,106 @@
+import { Link } from 'react-router-dom';
+import styled from 'styled-components';
+import { useAuth } from '../contexts/AuthContext';
+import MerchCard from '../components/MerchCard';
+import ScrollToTop from '../components/ScrollToTop';
+import { Container } from '../components/shared/LayoutComponents';
+import { Title } from '../components/shared/TypographyComponents';
+import { Button } from '../components/shared/ButtonComponents';
+import { EmptyState } from '../components/shared/EmptyStateComponents';
+import { theme } from '../styles/theme';
+
+
+const Header = styled.div`
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 2rem;
+
+ @media (max-width: 768px) {
+ flex-direction: column;
+ align-items: flex-start;
+ gap: 1rem;
+ }
+`;
+
+const Grid = styled.div`
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
+ gap: 1rem;
+
+ @media (min-width: 768px) {
+ gap: 1.5rem;
+ }
+`;
+
+const StyledLink = styled(Link)`
+ text-decoration: none;
+`;
+
+export const Favorites = () => {
+ const { isAuthenticated, favorites, user, loading } = useAuth();
+
+ // Show loading state
+ if (loading) {
+ return (
+
+ My Favorites
+ Loading...
+
+ );
+ }
+
+ // Show login prompt if not authenticated
+ if (!isAuthenticated) {
+ return (
+
+
+ Login
+
+ }
+ />
+
+
+ );
+ }
+
+ return (
+
+ {favorites.length > 0 && (
+
+ )}
+
+ {favorites.length === 0 ? (
+
+ Browse Merch
+
+ }
+ />
+ ) : (
+
+ {favorites.map((item) => (
+
+ ))}
+
+ )}
+
+
+
+ );
+};
\ No newline at end of file
diff --git a/frontend/src/pages/GalleryPage.jsx b/frontend/src/pages/GalleryPage.jsx
new file mode 100644
index 0000000000..f350f71385
--- /dev/null
+++ b/frontend/src/pages/GalleryPage.jsx
@@ -0,0 +1,135 @@
+// src/pages/GalleryPage.jsx
+import styled from "styled-components";
+import { useParams, Link } from "react-router-dom";
+import { useEffect, useState } from "react";
+import ScrollToTop from "../components/ScrollToTop";
+import { theme } from "../styles/theme";
+
+// Glob-import
+const galleries = {
+ fryshuset: {
+ title: "Fryshuset 24.05.25",
+ credit: "Photo © Marielle Tengström @ Tritone",
+ images: Object.values(
+ import.meta.glob("/src/assets/images/fryshuset/*.{jpg,JPG,png}", {
+ eager: true,
+ import: "default",
+ })
+ ),
+ },
+ fredagsmangel: {
+ title: "Fredagsmangel 15.11.24",
+ credit: "Photo © Per Lenner",
+ images: Object.values(
+ import.meta.glob("/src/assets/images/fredagsmangel/*.{jpg,JPG,png}", {
+ eager: true,
+ import: "default",
+ })
+ ),
+ },
+ olearys: {
+ title: "O'learys 26.10.24",
+ credit: "Photo © Christoffer Wiklundh",
+ images: Object.values(
+ import.meta.glob("/src/assets/images/olearys/*.{jpg,JPG,png}", {
+ eager: true,
+ import: "default",
+ })
+ ),
+ },
+};
+
+const Wrapper = styled.section`
+ padding: 0 1rem 2rem;
+ background: ${theme.colors.pageBg};
+ color: ${theme.colors.primaryText};
+ min-height: 100vh;
+ text-align: center;
+`;
+
+
+const Title = styled.h1`
+ font-size: 2rem;
+ margin-bottom: 0.5rem;
+
+ @media (min-width: 768px) {
+ font-size: 2.8rem;
+ }
+`;
+
+const Credit = styled.p`
+ font-size: 1rem;
+ color: ${theme.colors.primaryText};
+ margin-bottom: 2rem;
+
+ @media (min-width: 768px) {
+ font-size: 1.3rem;
+ }
+`;
+
+const ImageGrid = styled.div`
+ display: grid;
+ gap: 0.75rem;
+ grid-template-columns: 1fr; /* 📱 default: en kolumn */
+
+ @media (min-width: 748px) {
+ grid-template-columns: repeat(
+ 3,
+ 1fr
+ ); /* 💻 three columns on larger screens */
+ }
+
+ img {
+ width: 100%;
+ height: auto;
+ object-fit: cover;
+ }
+`;
+
+export const GalleryPage = () => {
+ const { slug } = useParams();
+ const gallery = galleries[slug];
+ const [sortedImages, setSortedImages] = useState([]);
+
+ useEffect(() => {
+ if (!gallery) return;
+
+ Promise.all(
+ gallery.images.map((src) => {
+ return new Promise((resolve) => {
+ const img = new Image();
+ img.src = src;
+ img.onload = () => {
+ resolve({ src, isLandscape: img.width >= img.height });
+ };
+ });
+ })
+ ).then((results) => {
+ const landscape = results.filter((img) => img.isLandscape);
+ const portrait = results.filter((img) => !img.isLandscape);
+ setSortedImages([...portrait, ...landscape]);
+ });
+ }, [gallery]);
+
+ if (!gallery) {
+ return (
+
+ 404 – Gallery Not Found
+
+ );
+ }
+
+ return (
+
+ {gallery.title}
+ {gallery.credit}
+
+
+ {sortedImages.map((img, i) => (
+
+ ))}
+
+
+
+ );
+};
diff --git a/frontend/src/pages/Gigs.jsx b/frontend/src/pages/Gigs.jsx
new file mode 100644
index 0000000000..508a564060
--- /dev/null
+++ b/frontend/src/pages/Gigs.jsx
@@ -0,0 +1,234 @@
+import styled from "styled-components";
+import { MdLocationOn, MdAccessTime } from "react-icons/md";
+import ScrollToTop from "../components/ScrollToTop";
+import { Container } from "../components/shared/LayoutComponents";
+import { theme } from "../styles/theme";
+
+
+const Header = styled.div`
+ text-align: center;
+ margin-bottom: 3rem;
+`;
+
+const Title = styled.h1`
+ color: ${theme.colors.primaryText};
+ font-size: 2.5rem;
+ margin-bottom: 1rem;
+ text-transform: uppercase;
+ letter-spacing: 2px;
+
+ @media (min-width: 768px) {
+ font-size: 3.5rem;
+ }
+`;
+
+const Subtitle = styled.p`
+ color: ${theme.colors.secondaryText};
+ font-size: 1.1rem;
+ max-width: 600px;
+ margin: 0 auto;
+`;
+
+const GigsList = styled.div`
+ display: flex;
+ flex-direction: column;
+ gap: 2rem;
+`;
+
+const GigCard = styled.div`
+ background: ${theme.colors.sectionBg};
+ border: 1px solid ${theme.colors.charcoal};
+ padding: 2rem;
+ border-radius: 8px;
+ transition: border-color 0.3s ease;
+
+ &:hover {
+ border-color: ${theme.colors.buttonPrimary};
+ }
+`;
+
+const GigHeader = styled.div`
+ display: flex;
+ flex-direction: column;
+ gap: 1rem;
+ margin-bottom: 1.5rem;
+
+ @media (min-width: 768px) {
+ flex-direction: row;
+ justify-content: space-between;
+ align-items: flex-start;
+ }
+`;
+
+const VenueInfo = styled.div`
+ flex: 1;
+`;
+
+const VenueName = styled.h2`
+ color: ${theme.colors.primaryText};
+ font-size: 1.8rem;
+ margin-bottom: 0.5rem;
+ text-transform: uppercase;
+ letter-spacing: 1px;
+`;
+
+const VenueLocation = styled.div`
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+ color: ${theme.colors.secondaryText};
+ font-size: 1rem;
+ margin: 0;
+
+ svg {
+ color: #ff4444;
+ font-size: 1.1rem;
+ }
+`;
+
+const DateTimeInfo = styled.div`
+ text-align: left;
+
+ @media (min-width: 768px) {
+ text-align: right;
+ }
+`;
+
+const Date = styled.div`
+ color: #ff4444;
+ font-size: 1.5rem;
+ font-weight: bold;
+ margin-bottom: 0.5rem;
+`;
+
+const Time = styled.div`
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+ color: ${theme.colors.primaryText};
+ font-size: 1.1rem;
+
+ svg {
+ color: ${theme.colors.primaryText};
+ font-size: 1.1rem;
+ }
+`;
+
+const GigDetails = styled.div`
+ margin-bottom: 1.5rem;
+`;
+
+const Description = styled.p`
+ color: ${theme.colors.secondaryText};
+ line-height: 1.6;
+ margin-bottom: 1rem;
+`;
+
+const TicketLink = styled.a`
+ display: inline-block;
+ background: ${theme.colors.buttonPrimary};
+ color: ${theme.colors.primaryText};
+ text-decoration: none;
+ padding: 0.75rem 1.5rem;
+ border-radius: 4px;
+ font-weight: bold;
+ text-transform: uppercase;
+ letter-spacing: 1px;
+ transition: background-color 0.3s ease;
+
+ &:hover {
+ background: ${theme.colors.buttonPrimaryHover};
+ text-decoration: none;
+ color: ${theme.colors.primaryText};
+ }
+`;
+
+const TBANotice = styled.span`
+ color: ${theme.colors.secondaryText};
+ font-style: italic;
+ font-size: 0.9rem;
+`;
+
+export const Gigs = () => {
+ const upcomingGigs = [
+ {
+ id: 1,
+ venue: "Brother Tuck",
+ location: "Stockholm",
+ date: "13 September",
+ dayOfWeek: "Lör",
+ time: "20:00",
+ description: "Join us for an intimate evening of heavy music at Brother Tuck. Expect crushing riffs and atmospheric soundscapes.",
+ ticketLink: "https://zippertic.se/events/946",
+ hasTickets: true
+ },
+ {
+ id: 2,
+ venue: "The Node",
+ location: "Stockholm",
+ date: "7 November",
+ dayOfWeek: "Tor",
+ time: "TBA",
+ description: "More details coming soon for this highly anticipated show at The Node.",
+ ticketLink: null,
+ hasTickets: false
+ }
+ ];
+
+ return (
+
+
+ Upcoming Gigs
+
+ Join us live for an unforgettable metal experience.
+ Check back regularly for new dates and ticket information.
+
+
+
+
+ {upcomingGigs.map((gig) => (
+
+
+
+ {gig.venue}
+
+
+ {gig.location}
+
+
+
+ {gig.date}
+
+
+ {gig.time === "TBA" ? (
+ Time TBA
+ ) : (
+ gig.time
+ )}
+
+
+
+
+
+ {gig.description}
+
+ {gig.hasTickets && gig.ticketLink ? (
+
+ Get Tickets
+
+ ) : (
+ Tickets coming soon
+ )}
+
+
+ ))}
+
+
+
+
+ );
+};
\ No newline at end of file
diff --git a/frontend/src/pages/Home.jsx b/frontend/src/pages/Home.jsx
new file mode 100644
index 0000000000..be07dc6464
--- /dev/null
+++ b/frontend/src/pages/Home.jsx
@@ -0,0 +1,14 @@
+// src/pages/Home.jsx
+import { HeroSection } from "../components/HeroSection";
+import { InstagramFeed } from "../components/InstagramFeed";
+import ScrollToTop from "../components/ScrollToTop";
+
+export const Home = () => {
+ return (
+ <>
+
+
+
+ >
+ );
+};
diff --git a/frontend/src/pages/Login.jsx b/frontend/src/pages/Login.jsx
new file mode 100644
index 0000000000..d633495149
--- /dev/null
+++ b/frontend/src/pages/Login.jsx
@@ -0,0 +1,546 @@
+import { useState } from 'react';
+import { Link, useNavigate } from 'react-router-dom';
+import styled from 'styled-components';
+import { useAuth } from '../contexts/AuthContext';
+import { FaEye, FaEyeSlash } from 'react-icons/fa';
+import { Container } from "../components/shared/LayoutComponents";
+import { theme } from "../styles/theme";
+
+
+const LoginBox = styled.div`
+ max-width: 600px;
+ margin: 0 auto;
+ padding: 1rem;
+ width: 100%;
+ box-sizing: border-box;
+
+ @media (min-width: 768px) {
+ padding: 2rem;
+ }
+`;
+
+const Header = styled.div`
+ text-align: center;
+ margin-bottom: 2rem;
+
+ h1 {
+ color: ${theme.colors.primaryText};
+ font-size: 1.5rem;
+ font-weight: 800;
+ text-transform: uppercase;
+ letter-spacing: 1px;
+ margin-bottom: 0.5rem;
+
+ @media (min-width: 480px) {
+ font-size: 1.75rem;
+ letter-spacing: 1.5px;
+ }
+
+ @media (min-width: 768px) {
+ font-size: 2rem;
+ letter-spacing: 1.5px;
+ }
+ }
+
+ p {
+ color: ${theme.colors.secondaryText};
+ font-style: italic;
+ font-size: 0.9rem;
+ font-weight: 300;
+
+ @media (min-width: 480px) {
+ font-size: 1rem;
+ }
+
+ @media (min-width: 768px) {
+ font-size: 1.1rem;
+ }
+ }
+
+ @media (min-width: 768px) {
+ margin-bottom: 3rem;
+ }
+`;
+
+const FormSection = styled.div`
+ background: ${theme.colors.sectionBg};
+ border: 1px solid ${theme.colors.charcoal};
+ padding: 1rem;
+ margin-bottom: 1rem;
+
+ @media (min-width: 480px) {
+ padding: 1.5rem;
+ }
+
+ @media (min-width: 768px) {
+ padding: 2rem;
+ }
+`;
+
+
+const Form = styled.form`
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 1rem;
+
+ @media (min-width: 480px) {
+ gap: 1.25rem;
+ }
+`;
+
+const InputContainer = styled.div`
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ gap: 0.5rem;
+ width: 100%;
+`;
+
+const InputRow = styled.div`
+ display: flex;
+ flex-direction: column;
+ gap: 1.25rem;
+ width: 100%;
+
+ @media (min-width: 768px) {
+ flex-direction: row;
+ gap: 1rem;
+
+ > div {
+ flex: 1;
+ }
+ }
+`;
+
+const Label = styled.label`
+ color: ${theme.colors.primaryText};
+ font-weight: bold;
+ font-size: 0.85rem;
+ text-transform: uppercase;
+ letter-spacing: 0.5px;
+
+ @media (min-width: 480px) {
+ font-size: 0.9rem;
+ }
+`;
+
+const InputField = styled.div`
+ position: relative;
+`;
+
+const Input = styled.input`
+ padding: 0.75rem;
+ padding-right: ${props => props.$hasIcon ? '3rem' : '0.75rem'};
+ background: ${theme.colors.inputBg};
+ border: 2px solid #444444;
+ border-radius: 4px;
+ color: ${theme.colors.primaryText};
+ font-size: 1rem;
+ width: 100%;
+ box-sizing: border-box;
+ transition: all 0.3s ease;
+
+ &::placeholder {
+ color: ${theme.colors.mediumGray};
+ font-size: 0.875rem;
+
+ @media (max-width: 768px) {
+ font-size: 1rem;
+ }
+ }
+
+ &:focus {
+ outline: none;
+ border-color: ${theme.colors.buttonPrimary};
+ box-shadow: 0 0 10px rgba(220, 38, 38, 0.3);
+ }
+
+ @media (min-width: 480px) {
+ padding: 0.85rem;
+ padding-right: ${props => props.$hasIcon ? '3.2rem' : '0.85rem'};
+ }
+`;
+
+const PasswordToggleButton = styled.button`
+ position: absolute;
+ right: 0.5rem;
+ top: 50%;
+ transform: translateY(-50%);
+ background: none;
+ border: none;
+ cursor: pointer;
+ color: ${props => props.$isShowing ? theme.colors.buttonPrimary : theme.colors.mediumGray};
+ font-size: 1.1rem;
+ padding: 0.5rem;
+ min-width: 44px;
+ min-height: 44px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ transition: color 0.2s;
+ border-radius: 4px;
+
+ &:hover {
+ background: rgba(255, 255, 255, 0.05);
+ }
+
+ &:focus {
+ outline: 2px solid ${theme.colors.buttonPrimary};
+ outline-offset: 2px;
+ }
+
+ @media (min-width: 480px) {
+ right: 0.65rem;
+ }
+`;
+
+const Button = styled.button`
+ background-color: ${theme.colors.buttonPrimary};
+ color: ${theme.colors.primaryText};
+ border: none;
+ padding: 0.75rem 1.25rem;
+ font-size: 0.9rem;
+ font-weight: bold;
+ text-transform: uppercase;
+ letter-spacing: 0.5px;
+ cursor: pointer;
+ transition: background-color 0.2s;
+ width: auto;
+ margin: 0;
+
+ &:hover:not(:disabled) {
+ background-color: ${theme.colors.buttonPrimaryHover};
+ }
+
+ &:disabled {
+ background-color: ${theme.colors.buttonDisabled};
+ cursor: not-allowed;
+ }
+
+ @media (min-width: 480px) {
+ padding: 0.85rem 1.5rem;
+ font-size: 0.95rem;
+ }
+`;
+
+const ErrorMessage = styled.div`
+ color: ${theme.colors.primaryText};
+ background: ${theme.colors.buttonPrimary};
+ border: 1px solid #991b1b;
+ padding: 0.75rem;
+ border-radius: 4px;
+ text-align: center;
+ font-size: 0.9rem;
+ margin-bottom: 1rem;
+`;
+
+const RequiredStar = styled.span`
+ color: ${theme.colors.buttonPrimary};
+ margin-left: 4px;
+ font-weight: bold;
+`;
+
+const FieldError = styled.div`
+ color: ${theme.colors.buttonPrimary};
+ font-size: 0.8rem;
+ margin-top: 0.25rem;
+ margin-left: 0.25rem;
+`;
+
+const ToggleSection = styled.div`
+ display: flex;
+ justify-content: center;
+ margin: 0;
+ gap: 0;
+`;
+
+const ToggleButton = styled.button`
+ background-color: ${(props) => (props.$active ? theme.colors.buttonPrimary : theme.colors.charcoal)};
+ color: ${(props) => (props.$active ? theme.colors.primaryText : theme.colors.secondaryText)};
+ border: 1px solid ${(props) => (props.$active ? theme.colors.buttonPrimary : theme.colors.darkCharcoal)};
+ padding: 0.6rem 1.2rem;
+ font-size: 0.85rem;
+ font-weight: bold;
+ text-transform: uppercase;
+ letter-spacing: 0.5px;
+ cursor: pointer;
+ transition: background-color 0.2s;
+
+ &:first-child {
+ border-right: none;
+ }
+
+ &:last-child {
+ border-left: none;
+ }
+
+ &:hover {
+ background-color: ${(props) => (props.$active ? theme.colors.buttonPrimaryHover : "#444444")};
+ color: ${theme.colors.primaryText};
+ border-color: ${(props) => (props.$active ? theme.colors.buttonPrimary : theme.colors.mediumGray)};
+ }
+`;
+
+export const Login = () => {
+ const [isLogin, setIsLogin] = useState(true);
+ const [name, setName] = useState('');
+ const [email, setEmail] = useState('');
+ const [password, setPassword] = useState('');
+ const [confirmPassword, setConfirmPassword] = useState('');
+ const [showPassword, setShowPassword] = useState(false);
+ const [showConfirmPassword, setShowConfirmPassword] = useState(false);
+ const [loading, setLoading] = useState(false);
+ const [error, setError] = useState('');
+ const [fieldErrors, setFieldErrors] = useState({});
+
+ const { login, register } = useAuth();
+ const navigate = useNavigate();
+
+ const validateFields = () => {
+ const errors = {};
+
+ if (isLogin) {
+ if (!email.trim()) errors.email = 'Email is required';
+ if (!password.trim()) errors.password = 'Password is required';
+ } else {
+ if (!name.trim()) errors.name = 'Full name is required';
+ if (!email.trim()) errors.email = 'Email is required';
+ if (!password.trim()) errors.password = 'Password is required';
+ if (!confirmPassword.trim()) errors.confirmPassword = 'Please confirm your password';
+
+ if (password && password.length < 6) {
+ errors.password = 'Password must be at least 6 characters long';
+ }
+
+ if (password && confirmPassword && password !== confirmPassword) {
+ errors.confirmPassword = 'Passwords do not match';
+ }
+ }
+
+ setFieldErrors(errors);
+ return Object.keys(errors).length === 0;
+ };
+
+ const handleSubmit = async (e) => {
+ e.preventDefault();
+ setError('');
+ setFieldErrors({});
+
+ if (!validateFields()) {
+ return;
+ }
+
+ setLoading(true);
+
+ if (isLogin) {
+ const result = await login(email, password);
+ if (result.success) {
+ navigate('/merch');
+ } else {
+ setError(result.error);
+ }
+ } else {
+ const result = await register(name, email, password);
+ if (result.success) {
+ navigate('/merch');
+ } else {
+ setError(result.error);
+ }
+ }
+
+ setLoading(false);
+ };
+
+ const toggleMode = () => {
+ setIsLogin(!isLogin);
+ setError('');
+ setFieldErrors({});
+ // Clear form fields when switching
+ setName('');
+ setEmail('');
+ setPassword('');
+ setConfirmPassword('');
+ setShowPassword(false);
+ setShowConfirmPassword(false);
+ };
+
+ return (
+
+
+
+
+
+
+ !isLogin && toggleMode()}
+ >
+ Login
+
+ isLogin && toggleMode()}
+ >
+ Register
+
+
+
+
+
+ {error && {error} }
+
+
+
+
+
+ );
+};
\ No newline at end of file
diff --git a/frontend/src/pages/Media.jsx b/frontend/src/pages/Media.jsx
new file mode 100644
index 0000000000..2c043d363b
--- /dev/null
+++ b/frontend/src/pages/Media.jsx
@@ -0,0 +1,147 @@
+// src/pages/Media.jsx
+import styled from "styled-components";
+import { Link } from "react-router-dom";
+import ScrollToTop from "../components/ScrollToTop";
+import { theme } from "../styles/theme";
+
+// Import glob-bilder
+const fryshusetImages = import.meta.glob(
+ "/src/assets/images/fryshuset/*.{jpg,png,JPG}",
+ { eager: true, import: "default" }
+);
+const fredagsmangelImages = import.meta.glob(
+ "/src/assets/images/fredagsmangel/*.{jpg,png,JPG}",
+ { eager: true, import: "default" }
+);
+const olearysImages = import.meta.glob(
+ "/src/assets/images/olearys/*.{jpg,png,JPG}",
+ { eager: true, import: "default" }
+);
+
+const galleries = [
+ {
+ id: "fryshuset",
+ title: "Stockholm - Fryshuset 24.05.25",
+ credit: "Photo © Marielle Tengström @ Tritone",
+ images: Object.values(fryshusetImages),
+ },
+ {
+ id: "fredagsmangel",
+ title: "Jakobsberg - Fredagsmangel 15.11.24",
+ credit: "Photo © Per Lenner",
+ images: Object.values(fredagsmangelImages),
+ },
+ {
+ id: "olearys",
+ title: "Vällingby - O'learys - 26.10.24",
+ credit: "Photo © Christoffer Wiklundh",
+ images: Object.values(olearysImages),
+ },
+];
+
+const Wrapper = styled.section`
+ padding: 120px 2rem 2rem;
+ background: ${theme.colors.pageBg};
+ color: ${theme.colors.primaryText};
+ min-height: 100vh;
+`;
+
+const Grid = styled.div`
+ display: grid;
+ gap: 2rem;
+
+ @media (min-width: 768px) {
+ grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
+ }
+`;
+
+const Card = styled(Link)`
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ text-decoration: none;
+ color: inherit;
+ border-radius: 4px;
+ overflow: hidden;
+
+ img {
+ width: 100%;
+ aspect-ratio: 4 / 3;
+ object-fit: cover;
+ }
+
+ &:hover .overlay {
+ opacity: 1;
+ }
+`;
+
+const Overlay = styled.div`
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background: rgba(0, 0, 0, 0.85);
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ opacity: 0;
+ transition: opacity 0.3s ease;
+ border-radius: 4px;
+
+ @media (max-width: 767px) {
+ opacity: 1;
+ background: rgba(0, 0, 0, 0.85);
+ justify-content: flex-end;
+ padding: 1rem;
+ }
+`;
+
+const Title = styled.h2`
+ font-size: 1.2rem;
+ text-align: center;
+ margin-bottom: 1rem;
+
+ @media (max-width: 767px) {
+ font-size: 1rem;
+ margin-bottom: 0.5rem;
+ background: rgba(0, 0, 0, 0.9);
+ padding: 0.75rem;
+ border-radius: 4px;
+ font-weight: bold;
+ }
+`;
+
+const Button = styled.span`
+ background: ${theme.colors.red};
+ color: ${theme.colors.primaryText};
+ padding: 0.5rem 1rem;
+ font-weight: bold;
+ text-transform: uppercase;
+ border-radius: 4px;
+
+ @media (max-width: 767px) {
+ font-size: 0.9rem;
+ padding: 0.4rem 0.8rem;
+ }
+`;
+
+export const Media = () => {
+ return (
+
+
+ {galleries.map((gallery) => (
+
+
+
+ {gallery.title}
+ View More
+
+
+ ))}
+
+
+
+ );
+};
diff --git a/frontend/src/pages/Merch.jsx b/frontend/src/pages/Merch.jsx
new file mode 100644
index 0000000000..04f29e32a4
--- /dev/null
+++ b/frontend/src/pages/Merch.jsx
@@ -0,0 +1,358 @@
+import { useState, useEffect } from "react";
+import styled from "styled-components";
+import { useMerch } from "../hooks/useMerch";
+import MerchCard from "../components/MerchCard";
+import { LoadingSpinner } from "../components/LoadingSpinner";
+import ScrollToTop from "../components/ScrollToTop";
+import { theme } from "../styles/theme";
+
+const HeaderSection = styled.div`
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 1rem;
+
+ @media (max-width: 768px) {
+ flex-direction: column;
+ align-items: flex-start;
+ gap: 1rem;
+ }
+`;
+
+const FilterSection = styled.div`
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.5rem;
+`;
+
+const FilterButton = styled.button`
+ background: ${props => props.$active ? theme.colors.buttonPrimary : theme.colors.primaryText};
+ color: ${props => props.$active ? theme.colors.primaryText : theme.colors.pageBg};
+ border: none;
+ padding: 0.5rem 1rem;
+ border-radius: 4px;
+ font-size: 0.85rem;
+ cursor: pointer;
+ transition: all 0.2s ease;
+ font-weight: ${props => props.$active ? 'bold' : 'normal'};
+
+ &:hover {
+ background: ${props => props.$active ? theme.colors.buttonPrimaryHover : '#f5f5f5'};
+ }
+`;
+
+const PaginationContainer = styled.div`
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 0.75rem;
+ margin-top: 2rem;
+ padding: 1rem;
+`;
+
+const PaginationButtons = styled.div`
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+`;
+
+const PageButton = styled.button`
+ background: ${props => props.$active ? theme.colors.buttonPrimary : theme.colors.primaryText};
+ color: ${props => props.$active ? theme.colors.primaryText : theme.colors.pageBg};
+ border: none;
+ padding: 0.5rem 0.75rem;
+ border-radius: 4px;
+ font-size: 0.875rem;
+ cursor: pointer;
+ transition: all 0.2s ease;
+ min-width: 40px;
+ font-weight: ${props => props.$active ? 'bold' : 'normal'};
+
+ &:hover:not(:disabled) {
+ background: ${props => props.$active ? theme.colors.buttonPrimaryHover : '#f5f5f5'};
+ }
+
+ &:disabled {
+ opacity: 0.5;
+ cursor: not-allowed;
+ }
+`;
+
+const PageInfo = styled.span`
+ font-size: 0.875rem;
+ color: ${theme.colors.mediumGray};
+ text-align: center;
+
+ @media (min-width: 768px) {
+ margin: 0 1rem;
+ }
+`;
+
+const MerchGrid = styled.div`
+ display: grid;
+ grid-template-columns: repeat(2, 1fr);
+ gap: 1rem;
+
+ @media (min-width: 768px) {
+ grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
+ }
+`;
+
+const Container = styled.div`
+ padding: 1rem;
+ max-width: 1280px;
+ margin: 0 auto;
+`;
+
+const HeaderTitleWrapper = styled.div``;
+
+const HeaderTitle = styled.h1`
+ font-size: 1.5rem;
+ font-weight: bold;
+ margin: 0;
+`;
+
+const HeaderSubtitle = styled.h2`
+ font-size: 1rem;
+ font-weight: normal;
+ margin: 0.25rem 0 0 0;
+ opacity: 0.8;
+`;
+
+const SearchContainer = styled.div`
+ display: flex;
+ gap: 0.5rem;
+ margin-bottom: 1rem;
+ position: relative;
+`;
+
+const SearchInput = styled.input`
+ border: 1px solid #ccc;
+ padding: 0.5rem 0.75rem;
+ border-radius: 0.25rem;
+ width: 100%;
+ max-width: 28rem;
+`;
+
+const SearchingIndicator = styled.span`
+ position: absolute;
+ right: 10px;
+ top: 50%;
+ transform: translateY(-50%);
+ font-size: 0.875rem;
+ color: ${theme.colors.mediumGray};
+`;
+
+const ProductCount = styled.p`
+ font-size: 0.875rem;
+ opacity: 0.7;
+ margin-bottom: 0.5rem;
+`;
+
+const PaginationEllipsis = styled.span`
+ margin: 0 0.5rem;
+`;
+
+export const Merch = () => {
+ const [searchInput, setSearchInput] = useState("");
+ const [q, setQ] = useState("");
+ const [selectedFilter, setSelectedFilter] = useState("all");
+ const [currentPage, setCurrentPage] = useState(1);
+ const itemsPerPage = 10;
+
+ const { items, loading, error } = useMerch({
+ q,
+ limit: 100, // Get more items to paginate client-side
+ offset: 0
+ });
+
+ // Debounce search input
+ useEffect(() => {
+ const delayDebounceFn = setTimeout(() => {
+ setQ(searchInput);
+ setCurrentPage(1); // Reset to first page on new search
+ }, 300); // 300ms delay
+
+ return () => clearTimeout(delayDebounceFn);
+ }, [searchInput]);
+
+ // Reset page when filter changes
+ useEffect(() => {
+ setCurrentPage(1);
+ }, [selectedFilter]);
+
+ const filterOptions = [
+ { key: "all", label: "All" },
+ { key: "mens", label: "Men" },
+ { key: "womens", label: "Women" },
+ { key: "other", label: "Other" }
+ ];
+
+ const getFilteredItems = () => {
+ if (selectedFilter === "all") return items;
+
+ return items.filter(item => {
+ const productName = (item.productTypeName || item.name || "").toLowerCase();
+
+ switch (selectedFilter) {
+ case "mens":
+ return productName.includes("men") && !productName.includes("women");
+ case "womens":
+ return productName.includes("women") || productName.includes("ladies");
+ case "other":
+ return !productName.includes("men") && !productName.includes("women");
+ default:
+ return true;
+ }
+ });
+ };
+
+ const filteredItems = getFilteredItems();
+
+ // Calculate pagination
+ const totalPages = Math.ceil(filteredItems.length / itemsPerPage);
+ const startIndex = (currentPage - 1) * itemsPerPage;
+ const endIndex = startIndex + itemsPerPage;
+ const currentItems = filteredItems.slice(startIndex, endIndex);
+
+ // Generate page numbers to display
+ const getPageNumbers = () => {
+ const pages = [];
+ const maxPagesToShow = 5;
+
+ if (totalPages <= maxPagesToShow) {
+ for (let i = 1; i <= totalPages; i++) {
+ pages.push(i);
+ }
+ } else {
+ if (currentPage <= 3) {
+ for (let i = 1; i <= 4; i++) {
+ pages.push(i);
+ }
+ pages.push('...');
+ pages.push(totalPages);
+ } else if (currentPage >= totalPages - 2) {
+ pages.push(1);
+ pages.push('...');
+ for (let i = totalPages - 3; i <= totalPages; i++) {
+ pages.push(i);
+ }
+ } else {
+ pages.push(1);
+ pages.push('...');
+ pages.push(currentPage - 1);
+ pages.push(currentPage);
+ pages.push(currentPage + 1);
+ pages.push('...');
+ pages.push(totalPages);
+ }
+ }
+
+ return pages;
+ };
+
+ const handlePageChange = (page) => {
+ setCurrentPage(page);
+ window.scrollTo({ top: 0, behavior: 'smooth' });
+ };
+
+ return (
+
+ {/* Header with title and filters */}
+
+
+ Merch
+
+
+
+ {filterOptions.map(filter => (
+ setSelectedFilter(filter.key)}
+ >
+ {filter.label}
+
+ ))}
+
+
+
+ {/* Search input */}
+
+ setSearchInput(e.target.value)}
+ />
+ {searchInput && loading && (
+
+ Searching...
+
+ )}
+
+
+ {loading && }
+ {error && (
+
+ We're sorry, but we couldn't load the merch right now. Please try
+ again later or visit: https://morbid-gene.myspreadshop.se/
+
+ )}
+ {!loading && !error && (
+ <>
+
+ Showing {startIndex + 1}-{Math.min(endIndex, filteredItems.length)} of {filteredItems.length} products
+
+
+ {currentItems.map((it) => (
+
+ ))}
+
+
+ {/* Pagination */}
+ {totalPages > 1 && (
+
+
+ handlePageChange(currentPage - 1)}
+ disabled={currentPage === 1}
+ aria-label="Previous page"
+ >
+ ←
+
+
+ {getPageNumbers().map((page, index) => (
+ page === '...' ? (
+ ...
+ ) : (
+ handlePageChange(page)}
+ >
+ {page}
+
+ )
+ ))}
+
+ handlePageChange(currentPage + 1)}
+ disabled={currentPage === totalPages}
+ aria-label="Next page"
+ >
+ →
+
+
+
+
+ Page {currentPage} of {totalPages}
+
+
+ )}
+ >
+ )}
+
+
+
+ );
+}
diff --git a/frontend/src/pages/Privacy.jsx b/frontend/src/pages/Privacy.jsx
new file mode 100644
index 0000000000..008e3e65ff
--- /dev/null
+++ b/frontend/src/pages/Privacy.jsx
@@ -0,0 +1,195 @@
+import { useNavigate } from "react-router-dom";
+import ScrollToTop from "../components/ScrollToTop";
+import { Container } from "../components/shared/LayoutComponents";
+import { Title, SectionTitle } from "../components/shared/TypographyComponents";
+import { LinkButton } from "../components/shared/ButtonComponents";
+import { LastUpdated, Section, SubSection, Paragraph, List, InfoBox } from "../components/shared/LegalPageComponents";
+
+export const Privacy = () => {
+ const navigate = useNavigate();
+
+ const handleGoBack = () => {
+ navigate(-1);
+ };
+
+ return (
+
+ ← Go Back
+
+ Privacy Policy
+ Last updated: August 28, 2025
+
+
+ 1. Introduction
+
+ Morbid Gene ("we," "our," or "us") is committed to protecting your privacy. This Privacy Policy explains how we collect, use, and share information about you when you use our website and services.
+
+
+
+
+ 2. Information We Collect
+
+ Information You Provide
+
+ Contact information (email, name) when you use our contact form
+ Information provided when making purchases through Spreadshirt
+ Communications you send to us
+
+
+ Information Automatically Collected
+
+ Browser type and version
+ IP address
+ Pages you visit on our site
+ Time and date of your visit
+
+
+
+
+ 3. How We Use Your Information
+
+ We use the information we collect to:
+
+
+ Process and fulfill merchandise orders through Spreadshirt
+ Respond to your inquiries and support requests
+ Send you updates about your orders
+ Improve our website and services
+ Comply with legal obligations
+
+
+
+
+ 4. Information Sharing
+
+ We do not sell, trade, or rent your personal information to third parties. We may share your information with:
+
+
+ Spreadshirt: To process merchandise orders and handle shipping. When you make a purchase, your information is shared with Spreadshirt according to their privacy policy.
+ Service Providers: Who help us operate our website and business
+ Legal Requirements: When required by law or to protect our rights
+
+
+ For merchandise purchases, please also review:
+
+
+
+
+ Spreadshirt General Terms and Conditions (Swedish)
+
+
+
+
+ Spreadshirt Customer Terms and Conditions (Swedish)
+
+
+
+
+
+
+ 5. Cookies and Local Storage
+
+ Our website uses cookies and local storage to enhance your browsing experience and provide essential functionality.
+
+
+ Essential Cookies
+
+ These cookies are necessary for the website to function properly:
+
+
+ Authentication Cookie: Keeps you logged in securely using httpOnly cookies that cannot be accessed by JavaScript
+ Shopping Cart: Stores items in your cart locally on your device
+ Cookie Consent: Remembers your cookie preferences
+
+
+ Data Storage
+
+ We use the following storage methods:
+
+
+ HttpOnly Cookies: For secure authentication (cannot be accessed by scripts)
+ Local Storage: For shopping cart items and user preferences
+ Session Storage: For temporary data during your browsing session
+
+
+
+ You can control cookies through your browser settings. However, disabling essential cookies will prevent you from logging in or using the shopping cart.
+
+
+
+
+ 6. Data Security
+
+ We implement appropriate technical and organizational measures to protect your personal information against unauthorized access, alteration, disclosure, or destruction.
+
+
+ All payment processing is handled by Spreadshirt's secure payment system. We do not store credit card information on our servers.
+
+
+
+
+ 7. Data Retention
+
+ We retain your personal information only as long as necessary to fulfill the purposes outlined in this policy.
+
+
+ Account Data Retention
+
+ For user accounts created on our website:
+
+
+ Active Accounts: We retain your account data as long as your account remains active
+ Inactive Accounts: Accounts that have been inactive (no login) for 30 months will be automatically deleted from our system
+ Account Recovery: After deletion, you may create a new account using the same email address if desired
+ Data Deletion: When an account is deleted, all associated personal data (name, email, preferences, favorites) is permanently removed from our database
+
+
+
+ This automated cleanup process helps us comply with data minimization principles and ensures we only retain data that serves a legitimate purpose.
+
+
+
+
+ 8. Your Rights
+
+ Depending on your location, you may have certain rights regarding your personal information, including:
+
+
+ The right to access your personal information
+ The right to correct inaccurate information
+ The right to request deletion of your information
+ The right to object to processing of your information
+
+
+
+
+ 9. Children's Privacy
+
+ Our website is not intended for children under 13 years of age. We do not knowingly collect personal information from children under 13.
+
+
+
+
+ 10. Changes to This Policy
+
+ We may update this Privacy Policy from time to time. We will notify you of any changes by posting the new Privacy Policy on this page and updating the "Last updated" date.
+
+
+
+
+
+ Contact Us
+
+
+ If you have any questions about this Privacy Policy, please contact us at:
+
+
+ Email: morbidgenemusic@gmail.com
+ Website: morbidgeneofficial.com
+
+
+
+
+
+ );
+};
\ No newline at end of file
diff --git a/frontend/src/pages/ProductPage.jsx b/frontend/src/pages/ProductPage.jsx
new file mode 100644
index 0000000000..68d6e15ba3
--- /dev/null
+++ b/frontend/src/pages/ProductPage.jsx
@@ -0,0 +1,372 @@
+import { useState, useContext } from "react";
+import { useParams, useNavigate } from "react-router-dom";
+import styled from "styled-components";
+import { theme } from "../styles/theme";
+import { useProduct } from "../hooks/useProduct";
+import { useProductImages } from "../hooks/useProductImages";
+import { formatPrice } from "../utils/formatPrice";
+import { translateSize, translateColor, translateProductType } from "../utils/translations";
+import { CartContext } from "../contexts/CartProvider";
+import { ProductImageGallery } from "../components/ProductImageGallery";
+import { LoadingSpinner, InlineLoadingSpinner } from "../components/LoadingSpinner";
+import ScrollToTop from "../components/ScrollToTop";
+import FavoriteButton from "../components/FavoriteButton";
+import {
+ SelectionContainer,
+ Select,
+} from "../components/shared/FormComponents";
+import {
+ PageTitle,
+ Description,
+ Price,
+} from "../components/shared/TypographyComponents";
+import { AddToCartButton } from "../components/shared/ButtonComponents";
+import { LoadingMessage, ErrorMessage } from "../components/shared/StatusComponents";
+import { Container } from "../components/shared/LayoutComponents";
+
+// ProductPage-specific styled components
+
+const ImageSection = styled.div``;
+
+const DetailsSection = styled.div``;
+
+const BackButton = styled.button`
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+ background: none;
+ border: none;
+ color: ${theme.colors.secondaryText};
+ font-size: ${theme.typography.sizes.sm};
+ cursor: pointer;
+ padding: 0.5rem 0;
+ margin-bottom: ${theme.spacing.base};
+ transition: color 0.2s ease;
+
+ &:hover {
+ color: ${theme.colors.primaryText};
+ }
+
+ @media (min-width: ${theme.breakpoints.mobile}) {
+ font-size: ${theme.typography.sizes.base};
+ }
+`;
+
+const Tooltip = styled.div`
+ position: fixed;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ background: #333;
+ color: white;
+ padding: 12px 20px;
+ border-radius: 6px;
+ font-size: 14px;
+ font-weight: 500;
+ z-index: 10000;
+ opacity: ${props => props.$show ? 1 : 0};
+ transition: opacity 0.3s ease;
+ pointer-events: none;
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
+`;
+
+const ProductInfo = styled.div`
+ margin-top: ${theme.spacing['2xl']};
+ padding: ${theme.spacing.xl};
+ background-color: ${theme.colors.sectionBg};
+ font-size: ${theme.typography.sizes.sm};
+ color: ${theme.colors.secondaryText};
+ line-height: 1.5;
+
+ @media (min-width: ${theme.breakpoints.mobile}) {
+ padding: ${theme.spacing['2xl']};
+ font-size: ${theme.typography.sizes.base};
+ }
+
+ strong {
+ color: ${theme.colors.primaryText};
+ font-weight: ${theme.typography.weights.bold};
+ }
+
+ > strong:first-child {
+ font-size: ${theme.typography.sizes.base};
+ color: ${theme.colors.primaryText};
+ display: block;
+ margin-bottom: ${theme.spacing.base};
+
+ @media (min-width: ${theme.breakpoints.mobile}) {
+ font-size: ${theme.typography.sizes.lg};
+ }
+ }
+`;
+
+// Function to strip HTML tags
+const stripHtml = (html) => {
+ if (!html) return '';
+ return html.replace(/<[^>]*>/g, '').trim();
+};
+
+export const ProductPage = () => {
+ const { productId } = useParams();
+ const { product, productType, loading, error } = useProduct(productId);
+ const { addToCart } = useContext(CartContext);
+ const navigate = useNavigate();
+
+ // States
+ const [selectedColor, setSelectedColor] = useState(null);
+ const [selectedSize, setSelectedSize] = useState(null);
+ const [quantity, setQuantity] = useState(1);
+ const [showTooltip, setShowTooltip] = useState(false);
+ const [isAddingToCart, setIsAddingToCart] = useState(false);
+
+ // Get images for selected color (only if product is loaded)
+ const { images } = useProductImages(
+ product?.sellableId,
+ product ? selectedColor || product.defaultAppearanceId : null,
+ product?.ideaId
+ );
+
+ // Function to check availability
+ const isAvailable = (sizeId, appearanceId) => {
+ return productType?.stockStates?.some(
+ (stock) =>
+ stock.size.id === sizeId &&
+ stock.appearance.id === appearanceId &&
+ stock.available === true
+ );
+ };
+
+ // Filter sizes based on selected color
+ const getAvailableSizes = () => {
+ if (!selectedColor || !productType) return productType?.sizes || [];
+
+ return productType.sizes.map((size) => ({
+ ...size,
+ available: isAvailable(size.id, selectedColor),
+ }));
+ };
+
+ // Add to cart button and logic
+ const handleAddToCart = async () => {
+
+ // Validation
+ if (!selectedColor || !selectedSize) {
+ alert("Please select both color and size");
+ return;
+ }
+
+ if (product) {
+ setIsAddingToCart(true);
+
+ try {
+ // Find color and size names
+ const colorName = productType.appearances.find(
+ (a) => a.id === selectedColor
+ )?.name;
+ const sizeName = productType.sizes.find(
+ (s) => s.id === selectedSize
+ )?.name;
+
+ // Get correct image for selected color
+ const selectedImage =
+ images && images.length > 0 ? images[0].url : product.previewImage?.url;
+
+
+ // Add the specified quantity directly with the correct price
+ await addToCart(product, sizeName, selectedImage, colorName, quantity, product.price);
+
+ // Show tooltip for 2 seconds
+ setShowTooltip(true);
+ setTimeout(() => setShowTooltip(false), 2000);
+ } finally {
+ setIsAddingToCart(false);
+ }
+ }
+ };
+
+ if (loading) return ;
+ if (error) return Error: {error} ;
+ if (!product) return ;
+
+ const isButtonEnabled = selectedColor && selectedSize;
+
+ return (
+
+ {/* Product image gallery */}
+
+
+
+
+ {/* Product details */}
+
+ navigate('/merch')}>
+ ← Back to Merch
+
+
+
+
+ {translateProductType(productType?.name || product.productTypeName || product.name)}
+
+
+
+
+
+
+ {/* Show shortDescription from productType instead */}
+ {productType?.shortDescription && (
+ {stripHtml(productType.shortDescription)}
+ )}
+
+
+ {formatPrice(product.price?.amount, product.price?.currencyId)}
+
+
+ {/* Color, Size and Quantity Selection */}
+
+ {/* Color Selection */}
+ {
+ setSelectedColor(e.target.value);
+ setSelectedSize(null); // Reset size when color changes
+ }}
+ aria-label="Choose Color"
+ >
+
+ Choose Color
+
+ {productType?.appearances?.map((appearance) => (
+
+ {translateColor(appearance.name)}
+
+ ))}
+
+
+ {/* Size Selection */}
+ setSelectedSize(e.target.value)}
+ disabled={!selectedColor}
+ aria-label="Choose Size"
+ >
+
+ Choose Size
+
+ {getAvailableSizes().map((size) => (
+
+ {translateSize(size.name)} {selectedColor && !size.available && "(Out of stock)"}
+
+ ))}
+
+
+ {/* Quantity Selection */}
+ setQuantity(parseInt(e.target.value))}
+ aria-label="Quantity"
+ >
+ {[1,2,3,4,5,6,7,8,9,10].map(num => (
+
+ {num === 1 ? 'Qty: 1' : num}
+
+ ))}
+
+
+
+ {/* Add to Cart Button */}
+
+ {isAddingToCart ? (
+ <>
+
+ Adding to Cart...
+ >
+ ) : (
+ 'Add to Cart'
+ )}
+
+
+ {/* Product Information */}
+ {productType && (
+
+ Product Details
+
+ {/* Print - alltid Morbid Gene */}
+
+ Print: Morbid Gene
+
+
+ {/* Material - alltid visa med fallback */}
+
+ Material: {
+ productType.description && productType.description.includes('Material:')
+ ? stripHtml(productType.description.match(/Material:([^<]*)/)?.[1] || '').trim()
+ : 'N/A'
+ }
+
+
+ {/* Weight - more flexible search */}
+
+ Weight: {
+ (() => {
+ // Testa olika format i description
+ if (productType.description) {
+ const weightMatch = productType.description.match(/(\d+(?:g\/m²|g\/m2|gsm|\s*g\/m²))/i);
+ if (weightMatch) {
+ return stripHtml(weightMatch[1]).trim();
+ }
+
+ // Test only numbers followed by g
+ const simpleWeight = productType.description.match(/(\d+)\s*g/i);
+ if (simpleWeight) {
+ return `${simpleWeight[1]}g/m²`;
+ }
+ }
+
+ return 'N/A';
+ })()
+ }
+
+
+ {/* Fit - alltid visa med fallback */}
+
+ Fit: {productType.sizeFitHint || 'N/A'}
+
+
+ {/* Clothing Brand - from API */}
+
+ Clothing Brand: {productType.brand || 'N/A'}
+
+
+ {/* Care Instructions - alltid visa med fallback */}
+
+ Care Instructions: {
+ productType.washingInstructions && productType.washingInstructions.length > 0
+ ? productType.washingInstructions.join(', ')
+ : 'N/A'
+ }
+
+
+ )}
+
+
+ {/* Success Tooltip */}
+
+ Product added
+
+
+
+
+ );
+};
\ No newline at end of file
diff --git a/frontend/src/pages/Terms.jsx b/frontend/src/pages/Terms.jsx
new file mode 100644
index 0000000000..c96a251ad7
--- /dev/null
+++ b/frontend/src/pages/Terms.jsx
@@ -0,0 +1,106 @@
+import { useNavigate } from "react-router-dom";
+import ScrollToTop from "../components/ScrollToTop";
+import { Container } from "../components/shared/LayoutComponents";
+import { Title, SectionTitle } from "../components/shared/TypographyComponents";
+import { LinkButton } from "../components/shared/ButtonComponents";
+import { LastUpdated, Section, Paragraph, List, InfoBox } from "../components/shared/LegalPageComponents";
+
+export const Terms = () => {
+ const navigate = useNavigate();
+
+ const handleGoBack = () => {
+ navigate(-1);
+ };
+
+ return (
+
+ ← Go Back
+
+ Terms of Use
+ Last updated: August 28, 2025
+
+
+ 1. Acceptance of Terms
+
+ By accessing and using this website, you accept and agree to be bound by the terms and provision of this agreement.
+
+
+
+
+ 2. Use License
+
+ Permission is granted to temporarily download one copy of the materials on Morbid Gene's website for personal, non-commercial transitory viewing only.
+
+
+ This is the grant of a license, not a transfer of title
+ This license shall automatically terminate if you violate any of these restrictions
+ Morbid Gene may terminate this license at any time
+
+
+
+
+ 3. Product Purchases and Third-Party Terms
+
+ All merchandise purchases are processed through our partner Spreadshirt. When making a purchase, you will be redirected to Spreadshirt's secure checkout system.
+
+
+ By purchasing products through our store, you also agree to Spreadshirt's terms and conditions:
+
+
+
+
+ Spreadshirt General Terms and Conditions
+
+
+
+
+ Spreadshirt Customer Terms and Conditions
+
+
+
+
+ Note: These are Spreadshirt's terms and conditions in Swedish. They govern all aspects of product ordering, payment, shipping, returns, and customer service for merchandise purchases.
+
+
+
+
+ 4. User Accounts and Data Retention
+
+ If you create an account on our website, the following terms apply:
+
+
+ Account Security: You are responsible for maintaining the confidentiality of your account and password
+ Inactive Account Deletion: Accounts that remain inactive (no login) for 30 months will be automatically deleted from our system
+ Data Deletion: When an account is deleted, all associated personal data is permanently removed from our database
+ Account Recreation: After deletion, you may create a new account using the same email address if desired
+ No Recovery: Once deleted, inactive account data cannot be recovered
+
+
+ This policy ensures compliance with data protection regulations and helps maintain system security and performance.
+
+
+
+
+ 5. Privacy
+
+ Your use of our website is also governed by our Privacy Policy. Please review our Privacy Policy, which also governs the Site and informs users of our data collection practices.
+
+
+
+
+
+ Contact Us
+
+
+ If you have any questions about these Terms of Use, please contact us at:
+
+
+ Email: morbidgenemusic@gmail.com
+ Website: morbidgeneofficial.com
+
+
+
+
+
+ );
+};
\ No newline at end of file
diff --git a/frontend/src/styles/GlobalStyles.jsx b/frontend/src/styles/GlobalStyles.jsx
new file mode 100644
index 0000000000..c450084ecc
--- /dev/null
+++ b/frontend/src/styles/GlobalStyles.jsx
@@ -0,0 +1,47 @@
+import { createGlobalStyle } from "styled-components";
+import { theme } from './theme';
+
+export const GlobalStyles = createGlobalStyle`
+ *, *::before, *::after {
+ box-sizing: border-box;
+ margin: 0;
+ padding: 0;
+ }
+
+ html, body, #root {
+ height: 100%;
+ margin: 0;
+ padding: 0;
+ }
+
+ #root {
+ display: flex;
+ flex-direction: column;
+ min-height: 100vh;
+ }
+
+ body {
+ font-family: ${theme.typography.fontFamily};
+ color: ${theme.colors.primaryText};
+ line-height: 1.5;
+ overflow-x: hidden;
+ background-color: ${theme.colors.black};
+ min-height: 100vh;
+ }
+
+ a {
+ color: inherit;
+ text-decoration: none;
+ }
+
+ h1, h2, h3 {
+ font-family: ${theme.typography.headingFamily};
+ letter-spacing: ${theme.typography.letterSpacing.uppercase};
+ }
+
+ img {
+ max-width: 100%;
+ height: auto;
+ display: block;
+ }
+`;
diff --git a/frontend/src/styles/theme.js b/frontend/src/styles/theme.js
new file mode 100644
index 0000000000..499922a63b
--- /dev/null
+++ b/frontend/src/styles/theme.js
@@ -0,0 +1,162 @@
+// Morbid Gene Theme Configuration
+// Consistent colors and styling rules for the entire project
+
+export const theme = {
+ colors: {
+ // Primary colors
+ black: '#000000',
+ darkGray: '#1a1a1a',
+ white: '#ffffff',
+
+ // Red palette
+ red: '#cc0000',
+ darkRed: '#990000',
+ lightRed: '#991b1b',
+
+ // Gray palette
+ lightGray: '#cccccc',
+ mediumGray: '#666666',
+ charcoal: '#333333',
+ darkCharcoal: '#555555',
+
+ // Text colors
+ primaryText: '#ffffff',
+ secondaryText: '#cccccc',
+ mutedText: '#666666',
+
+ // Border colors
+ borderLight: '#333333',
+ borderDark: '#555555',
+ borderRed: '#cc0000',
+
+ // Background colors
+ pageBg: '#000000', // Main page background (black)
+ sectionBg: '#1a1a1a', // Section/card backgrounds (dark gray)
+ inputBg: '#222222', // Input field backgrounds
+
+ // Button states
+ buttonPrimary: '#cc0000',
+ buttonPrimaryHover: '#990000',
+ buttonSecondary: '#333333',
+ buttonSecondaryHover: '#555555',
+ buttonDisabled: '#666666',
+ },
+
+ // Typography
+ typography: {
+ fontFamily: "'Orbitron', sans-serif",
+ headingFamily: "'Bebas Neue', sans-serif",
+
+ // Font sizes
+ sizes: {
+ xs: '0.75rem', // 12px
+ sm: '0.875rem', // 14px
+ base: '1rem', // 16px
+ lg: '1.125rem', // 18px
+ xl: '1.25rem', // 20px
+ '2xl': '1.5rem', // 24px
+ '3xl': '1.875rem', // 30px
+ '4xl': '2.25rem', // 36px
+ },
+
+ // Font weights
+ weights: {
+ normal: 400,
+ medium: 500,
+ semibold: 600,
+ bold: 700,
+ extrabold: 800,
+ },
+
+ // Letter spacing
+ letterSpacing: {
+ tight: '-0.025em',
+ normal: '0',
+ wide: '0.025em',
+ wider: '0.05em',
+ widest: '0.1em',
+ uppercase: '1px',
+ }
+ },
+
+ // Spacing
+ spacing: {
+ xs: '0.25rem', // 4px
+ sm: '0.5rem', // 8px
+ md: '0.75rem', // 12px
+ base: '1rem', // 16px
+ lg: '1.25rem', // 20px
+ xl: '1.5rem', // 24px
+ '2xl': '2rem', // 32px
+ '3xl': '2.5rem', // 40px
+ '4xl': '3rem', // 48px
+ },
+
+ // Transitions
+ transitions: {
+ fast: '0.15s ease',
+ normal: '0.2s ease',
+ slow: '0.3s ease',
+ },
+
+ // Breakpoints
+ breakpoints: {
+ mobile: '480px',
+ tablet: '768px',
+ desktop: '1024px',
+ wide: '1280px',
+ }
+};
+
+// Helper function to get consistent button styles
+export const getButtonStyles = (variant = 'primary') => {
+ const styles = {
+ primary: `
+ background-color: ${theme.colors.buttonPrimary};
+ color: ${theme.colors.white};
+ border: none;
+ font-weight: ${theme.typography.weights.bold};
+ text-transform: uppercase;
+ letter-spacing: ${theme.typography.letterSpacing.uppercase};
+ cursor: pointer;
+ transition: background-color ${theme.transitions.normal};
+
+ &:hover:not(:disabled) {
+ background-color: ${theme.colors.buttonPrimaryHover};
+ }
+
+ &:disabled {
+ background-color: ${theme.colors.buttonDisabled};
+ cursor: not-allowed;
+ }
+ `,
+ secondary: `
+ background-color: ${theme.colors.buttonSecondary};
+ color: ${theme.colors.white};
+ border: none;
+ font-weight: ${theme.typography.weights.bold};
+ cursor: pointer;
+ transition: background-color ${theme.transitions.normal};
+
+ &:hover {
+ background-color: ${theme.colors.buttonSecondaryHover};
+ }
+ `,
+ danger: `
+ background-color: ${theme.colors.red};
+ color: ${theme.colors.white};
+ border: none;
+ font-weight: ${theme.typography.weights.bold};
+ text-transform: uppercase;
+ letter-spacing: ${theme.typography.letterSpacing.uppercase};
+ cursor: pointer;
+ transition: background-color ${theme.transitions.normal};
+
+ &:hover {
+ background-color: ${theme.colors.darkRed};
+ }
+ `
+ };
+
+ return styles[variant] || styles.primary;
+};
\ No newline at end of file
diff --git a/frontend/src/utils/basketUtils.js b/frontend/src/utils/basketUtils.js
new file mode 100644
index 0000000000..41c2d6bee2
--- /dev/null
+++ b/frontend/src/utils/basketUtils.js
@@ -0,0 +1,100 @@
+export const convertSpreadshirtItem = (basketItem, selectedImage = null, productPrice = null) => {
+ const properties = basketItem.element.properties;
+ const sizeLabel = properties.find((p) => p.key === "sizeLabel")?.value;
+ const appearanceLabel = properties.find(
+ (p) => p.key === "appearanceLabel"
+ )?.value;
+ const sellableId = properties.find((p) => p.key === "sellable")?.value;
+ const appearanceId = properties.find((p) => p.key === "appearance")?.value;
+
+ // Use product price if available, otherwise use Spreadshirt price
+ let priceToUse;
+
+ if (productPrice && productPrice.amount) {
+ // Use price from product page
+ priceToUse = {
+ amount: productPrice.amount,
+ currencyId: productPrice.currencyId || "EUR",
+ display: productPrice.amount,
+ vatIncluded: productPrice.amount,
+ vatExcluded: productPrice.amount
+ };
+ } else {
+ // Convert from cents to euros for Spreadshirt price
+ priceToUse = {
+ ...basketItem.priceItem,
+ display: basketItem.priceItem.display / 100,
+ vatIncluded: basketItem.priceItem.vatIncluded / 100,
+ vatExcluded: basketItem.priceItem.vatExcluded / 100,
+ };
+ }
+
+ // Hantera bilder och pris
+ let imageToUse = selectedImage;
+
+ if (selectedImage) {
+ // Save new image to localStorage with multiple keys for robustness
+ const primaryKey = `cart-image-${sellableId}-${sizeLabel}-${appearanceId}`;
+ const fallbackKey = `cart-image-${sellableId}-${sizeLabel}`;
+
+ try {
+ localStorage.setItem(primaryKey, selectedImage);
+ localStorage.setItem(fallbackKey, selectedImage); // Backup key
+ } catch (error) {
+ }
+ }
+
+ // Save or retrieve price from localStorage
+ const priceKey = `cart-price-${sellableId}-${sizeLabel}`;
+
+ if (productPrice && productPrice.amount) {
+ // Save new price
+ try {
+ localStorage.setItem(priceKey, JSON.stringify(priceToUse));
+ } catch (error) {
+ }
+ } else if (!productPrice) {
+ // Try to retrieve saved price
+ try {
+ const savedPrice = localStorage.getItem(priceKey);
+ if (savedPrice) {
+ priceToUse = JSON.parse(savedPrice);
+ }
+ } catch (error) {
+ }
+ }
+
+ if (!selectedImage) {
+ // Try to read existing image from localStorage with multiple possible keys
+ const possibleKeys = [
+ `cart-image-${sellableId}-${sizeLabel}-${appearanceId}`,
+ `cart-image-${sellableId}-${sizeLabel}`,
+ `cart-image-${sellableId}-${sizeLabel}-default`,
+ `cart-image-${sellableId}-${sizeLabel}-undefined`
+ ];
+
+ for (const imageKey of possibleKeys) {
+ try {
+ const savedImage = localStorage.getItem(imageKey);
+ if (savedImage && savedImage.startsWith('http')) {
+ imageToUse = savedImage;
+ break;
+ }
+ } catch (error) {
+ }
+ }
+
+ }
+
+ return {
+ sellableId: sellableId,
+ name: basketItem.description,
+ price: priceToUse,
+ originalPrice: priceToUse, // Spara originalpriset för framtida användning
+ size: sizeLabel,
+ color: appearanceLabel,
+ appearanceId: appearanceId,
+ quantity: basketItem.quantity,
+ selectedImage: imageToUse, // Använd den hämtade eller nya bilden
+ };
+};
diff --git a/frontend/src/utils/formatPrice.js b/frontend/src/utils/formatPrice.js
new file mode 100644
index 0000000000..bc829909e0
--- /dev/null
+++ b/frontend/src/utils/formatPrice.js
@@ -0,0 +1,24 @@
+export const formatPrice = (priceData) => {
+ // Handle different price formats from Spreadshirt API
+ let amount;
+
+ if (typeof priceData === "object" && priceData !== null) {
+ // Spreadshirt price object - use display value
+ amount = priceData.display || priceData.vatIncluded || priceData.amount;
+ } else {
+ // Simple number
+ amount = priceData;
+ }
+
+ if (!amount || amount === 0) return "0,00 €";
+
+ // If amount is over 100, it's likely in cents
+ // Convert from cents to euros by dividing by 100
+ const euroAmount = amount > 100 ? amount / 100 : amount;
+
+ return new Intl.NumberFormat("sv-SE", {
+ style: "currency",
+ currency: "EUR",
+ minimumFractionDigits: 2,
+ }).format(euroAmount);
+};
diff --git a/frontend/src/utils/translations.js b/frontend/src/utils/translations.js
new file mode 100644
index 0000000000..b73b6d38f4
--- /dev/null
+++ b/frontend/src/utils/translations.js
@@ -0,0 +1,116 @@
+// Translation mappings for product-related Swedish terms from the API
+// Using lowercase keys for case-insensitive lookup
+const translations = {
+ // Size translations
+ 'liten': 'Small',
+ 'mellan': 'Medium',
+ 'stor': 'Large',
+ 'extra stor': 'Extra Large',
+ 'extra extra stor': '2X Large',
+
+ // Color translations
+ 'svart': 'Black',
+ 'vit': 'White',
+ 'grå': 'Gray',
+ 'röd': 'Red',
+ 'blå': 'Blue',
+ 'grön': 'Green',
+ 'gul': 'Yellow',
+ 'orange': 'Orange',
+ 'rosa': 'Pink',
+ 'lila': 'Purple',
+ 'brun': 'Brown',
+ 'marinblå': 'Navy',
+ 'mörkgrå': 'Dark Gray',
+ 'ljusgrå': 'Light Gray',
+ 'heather grey': 'Heather Gray',
+ 'charcoal': 'Charcoal',
+ 'burgundy': 'Burgundy',
+ 'vinröd': 'Wine Red',
+ 'beige': 'Beige',
+ 'khaki': 'Khaki',
+
+ // Product type translations
+ 'huvtröja': 'Hoodie',
+ 'luvtröja': 'Hoodie',
+ 'tröja': 'Sweatshirt',
+ 't-shirt': 'T-shirt',
+ 't-tröja': 'T-shirt',
+ 'linne': 'Tank Top',
+ 'keps': 'Cap',
+ 'mugg': 'Mug',
+ 'väska': 'Bag',
+ 'tygväska': 'Tote Bag',
+ 'klistermärke': 'Sticker',
+ 'poster': 'Poster',
+ 'premiumluvtröja': 'Premium Hoodie',
+ 'premium luvtröja': 'Premium Hoodie',
+ 'premium t-shirt': 'Premium T-shirt',
+ 'herr': "Men's",
+ 'dam': "Women's",
+ 'unisex': 'Unisex',
+ 'barn': "Kids'",
+ 'premium': 'Premium',
+ 'ekologisk': 'Organic',
+ 'klassisk': 'Classic',
+};
+
+// Efficient case-insensitive translation function
+const translate = (text) => {
+ if (!text) return '';
+
+ // Convert to lowercase for case-insensitive lookup
+ const lowerText = text.toLowerCase();
+
+ // Try direct translation
+ if (translations[lowerText]) {
+ return translations[lowerText];
+ }
+
+ // Try partial matches for compound words
+ let translatedText = text;
+ Object.entries(translations).forEach(([swedish, english]) => {
+ const regex = new RegExp(`\\b${swedish}\\b`, 'gi');
+ translatedText = translatedText.replace(regex, english);
+ });
+
+ return translatedText;
+};
+
+// Translate size
+export const translateSize = (size) => {
+ if (!size) return '';
+
+ // Size codes like S, M, L, XL stay as they are
+ const sizeUpper = size.toUpperCase();
+ if (/^(XXS|XS|S|M|L|XL|XXL|3XL|4XL|5XL)$/.test(sizeUpper)) {
+ return sizeUpper;
+ }
+
+ return translate(size);
+};
+
+// Translate color
+export const translateColor = (color) => {
+ if (!color) return '';
+
+ const translated = translate(color);
+
+ // If no translation found, capitalize first letter
+ if (translated === color) {
+ return color.charAt(0).toUpperCase() + color.slice(1).toLowerCase();
+ }
+
+ return translated;
+};
+
+// Translate product type/name
+export const translateProductType = (type) => {
+ if (!type) return '';
+
+ // Split on spaces and dashes, translate each part
+ const parts = type.split(/[\s-]/);
+ const translatedParts = parts.map(part => translate(part) || part);
+
+ return translatedParts.join(' ').replace(/\s+/g, ' ').trim();
+};
\ No newline at end of file
diff --git a/frontend/vite.config.js b/frontend/vite.config.js
index 5a33944a9b..ad3102ea09 100644
--- a/frontend/vite.config.js
+++ b/frontend/vite.config.js
@@ -1,7 +1,22 @@
-import { defineConfig } from 'vite'
-import react from '@vitejs/plugin-react'
+import { defineConfig } from "vite";
+import react from "@vitejs/plugin-react";
-// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
-})
+ server: {
+ proxy: {
+ "/api": {
+ target: "http://localhost:8080", // Porten din backend kör på
+ changeOrigin: true,
+ },
+ "/auth": {
+ target: "http://localhost:8080",
+ changeOrigin: true,
+ },
+ "/user": {
+ target: "http://localhost:8080",
+ changeOrigin: true,
+ },
+ },
+ },
+});
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 0000000000..4bbf9fab92
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,733 @@
+{
+ "name": "morbid-gene-2.0",
+ "version": "1.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "morbid-gene-2.0",
+ "version": "1.0.0",
+ "dependencies": {
+ "react-icons": "^5.5.0"
+ },
+ "devDependencies": {
+ "concurrently": "^9.2.0",
+ "nodemon": "^3.1.10"
+ }
+ },
+ "node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/anymatch": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
+ "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "normalize-path": "^3.0.0",
+ "picomatch": "^2.0.4"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/binary-extensions": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
+ "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/brace-expansion": {
+ "version": "1.1.12",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
+ "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/braces": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
+ "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fill-range": "^7.1.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/chalk/node_modules/supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/chokidar": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
+ "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "anymatch": "~3.1.2",
+ "braces": "~3.0.2",
+ "glob-parent": "~5.1.2",
+ "is-binary-path": "~2.1.0",
+ "is-glob": "~4.0.1",
+ "normalize-path": "~3.0.0",
+ "readdirp": "~3.6.0"
+ },
+ "engines": {
+ "node": ">= 8.10.0"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/cliui": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
+ "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "string-width": "^4.2.0",
+ "strip-ansi": "^6.0.1",
+ "wrap-ansi": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/concurrently": {
+ "version": "9.2.0",
+ "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.2.0.tgz",
+ "integrity": "sha512-IsB/fiXTupmagMW4MNp2lx2cdSN2FfZq78vF90LBB+zZHArbIQZjQtzXCiXnvTxCZSvXanTqFLWBjw2UkLx1SQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "chalk": "^4.1.2",
+ "lodash": "^4.17.21",
+ "rxjs": "^7.8.1",
+ "shell-quote": "^1.8.1",
+ "supports-color": "^8.1.1",
+ "tree-kill": "^1.2.2",
+ "yargs": "^17.7.2"
+ },
+ "bin": {
+ "conc": "dist/bin/concurrently.js",
+ "concurrently": "dist/bin/concurrently.js"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/open-cli-tools/concurrently?sponsor=1"
+ }
+ },
+ "node_modules/debug": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
+ "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/escalade": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/fill-range": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
+ "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "to-regex-range": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/get-caller-file": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
+ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": "6.* || 8.* || >= 10.*"
+ }
+ },
+ "node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/ignore-by-default": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz",
+ "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/is-binary-path": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
+ "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "binary-extensions": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-glob": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-extglob": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.12.0"
+ }
+ },
+ "node_modules/lodash": {
+ "version": "4.17.21",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
+ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/nodemon": {
+ "version": "3.1.10",
+ "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.10.tgz",
+ "integrity": "sha512-WDjw3pJ0/0jMFmyNDp3gvY2YizjLmmOUQo6DEBY+JgdvW/yQ9mEeSw6H5ythl5Ny2ytb7f9C2nIbjSxMNzbJXw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "chokidar": "^3.5.2",
+ "debug": "^4",
+ "ignore-by-default": "^1.0.1",
+ "minimatch": "^3.1.2",
+ "pstree.remy": "^1.1.8",
+ "semver": "^7.5.3",
+ "simple-update-notifier": "^2.0.0",
+ "supports-color": "^5.5.0",
+ "touch": "^3.1.0",
+ "undefsafe": "^2.0.5"
+ },
+ "bin": {
+ "nodemon": "bin/nodemon.js"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/nodemon"
+ }
+ },
+ "node_modules/nodemon/node_modules/has-flag": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
+ "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/nodemon/node_modules/supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-flag": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/normalize-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/picomatch": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/pstree.remy": {
+ "version": "1.1.8",
+ "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz",
+ "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/react": {
+ "version": "19.1.1",
+ "resolved": "https://registry.npmjs.org/react/-/react-19.1.1.tgz",
+ "integrity": "sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==",
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-icons": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz",
+ "integrity": "sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": "*"
+ }
+ },
+ "node_modules/readdirp": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
+ "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "picomatch": "^2.2.1"
+ },
+ "engines": {
+ "node": ">=8.10.0"
+ }
+ },
+ "node_modules/require-directory": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
+ "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/rxjs": {
+ "version": "7.8.2",
+ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz",
+ "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "tslib": "^2.1.0"
+ }
+ },
+ "node_modules/semver": {
+ "version": "7.7.2",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
+ "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/shell-quote": {
+ "version": "1.8.3",
+ "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz",
+ "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/simple-update-notifier": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz",
+ "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "semver": "^7.5.3"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/supports-color": {
+ "version": "8.1.1",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
+ "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/supports-color?sponsor=1"
+ }
+ },
+ "node_modules/to-regex-range": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-number": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=8.0"
+ }
+ },
+ "node_modules/touch": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz",
+ "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "nodetouch": "bin/nodetouch.js"
+ }
+ },
+ "node_modules/tree-kill": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz",
+ "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "tree-kill": "cli.js"
+ }
+ },
+ "node_modules/tslib": {
+ "version": "2.8.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
+ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
+ "dev": true,
+ "license": "0BSD"
+ },
+ "node_modules/undefsafe": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz",
+ "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/wrap-ansi": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+ "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
+ "node_modules/y18n": {
+ "version": "5.0.8",
+ "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
+ "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/yargs": {
+ "version": "17.7.2",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
+ "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cliui": "^8.0.1",
+ "escalade": "^3.1.1",
+ "get-caller-file": "^2.0.5",
+ "require-directory": "^2.1.1",
+ "string-width": "^4.2.3",
+ "y18n": "^5.0.5",
+ "yargs-parser": "^21.1.1"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/yargs-parser": {
+ "version": "21.1.1",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
+ "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ }
+ }
+}
diff --git a/package.json b/package.json
index 680d190772..d1d24a5998 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,18 @@
{
- "name": "project-final-parent",
+ "name": "morbid-gene-2.0",
"version": "1.0.0",
+ "private": true,
"scripts": {
- "postinstall": "npm install --prefix backend"
+ "server": "nodemon backend/server.js",
+ "frontend": "npm run dev --prefix frontend",
+ "dev": "concurrently \"npm run server\" \"npm run frontend\"",
+ "start": "node backend/server.js"
+ },
+ "devDependencies": {
+ "concurrently": "^9.2.0",
+ "nodemon": "^3.1.10"
+ },
+ "dependencies": {
+ "react-icons": "^5.5.0"
}
-}
\ No newline at end of file
+}