diff --git a/[core]/es_extended/server/classes/player.lua b/[core]/es_extended/server/classes/player.lua
index 367c98381..67a7d7456 100644
--- a/[core]/es_extended/server/classes/player.lua
+++ b/[core]/es_extended/server/classes/player.lua
@@ -433,27 +433,34 @@ function CreateExtendedPlayer(playerId, identifier, ssn, group, accounts, invent
reason = reason or "Unknown"
if not tonumber(money) then
error(("Tried To Set Account ^5%s^1 For Player ^5%s^1 To An Invalid Number -> ^5%s^1"):format(accountName, self.playerId, money))
- return
+ return false
+ end
+ if money <= 0 then
+ error(("Tried To Set Account ^5%s^1 For Player ^5%s^1 To An Invalid Number -> ^5%s^1"):format(accountName, self.playerId, money))
+ return false
+ end
+ local account = self.getAccount(accountName)
+ if not account then
+ error(("Tried To Set Add To Invalid Account ^5%s^1 For Player ^5%s^1!"):format(accountName, self.playerId))
+ return false
+ end
+ money = account.round and ESX.Math.Round(money) or money
+ if self.accounts[account.index].money - money > self.accounts[account.index].money then
+ error(("Tried To Underflow Account ^5%s^1 For Player ^5%s^1!"):format(accountName, self.playerId))
+ return false
end
- if money > 0 then
- local account = self.getAccount(accountName)
-
- if account then
- money = account.round and ESX.Math.Round(money) or money
- if self.accounts[account.index].money - money > self.accounts[account.index].money then
- error(("Tried To Underflow Account ^5%s^1 For Player ^5%s^1!"):format(accountName, self.playerId))
- return
- end
- self.accounts[account.index].money = self.accounts[account.index].money - money
- self.triggerEvent("esx:setAccountMoney", account)
- TriggerEvent("esx:removeAccountMoney", self.source, accountName, money, reason)
- else
- error(("Tried To Set Add To Invalid Account ^5%s^1 For Player ^5%s^1!"):format(accountName, self.playerId))
+ if Config.CustomInventory == "ox" and Inventory and Inventory.accounts and Inventory.accounts[accountName] then
+ local removed = Inventory.RemoveItem(self.source, accountName, money)
+ if not removed then
+ return false, 0
end
- else
- error(("Tried To Set Account ^5%s^1 For Player ^5%s^1 To An Invalid Number -> ^5%s^1"):format(accountName, self.playerId, money))
end
+
+ self.accounts[account.index].money = self.accounts[account.index].money - money
+ self.triggerEvent("esx:setAccountMoney", account)
+ TriggerEvent("esx:removeAccountMoney", self.source, accountName, money, reason)
+ return true, money
end
function self.getInventoryItem(itemName)
diff --git a/[core]/esx_whitelist/.gitignore b/[core]/esx_whitelist/.gitignore
new file mode 100644
index 000000000..f706d8927
--- /dev/null
+++ b/[core]/esx_whitelist/.gitignore
@@ -0,0 +1,11 @@
+# Node modules
+web/node_modules/
+
+# Logs
+*.log
+
+# Config de entorno
+.env
+web/dist
+web/.next/
+whitelist_config.json
diff --git a/[core]/esx_whitelist/README.md b/[core]/esx_whitelist/README.md
new file mode 100644
index 000000000..2eee60338
--- /dev/null
+++ b/[core]/esx_whitelist/README.md
@@ -0,0 +1,74 @@
+
ESX Whitelist System
+Discord - Website - Documentation
+
+Dynamic whitelist with automated rules and Discord integration
+
+
+
+## ✨ Features
+
+- 🎯 Dynamic whitelist control without restarts
+- ⏱️ Grace period system before kick
+- 🎨 Modern tablet-style UI panel
+- 🔍 Auto-detect identifiers (License, Steam, Discord, XBL, FiveM)
+- 🤖 Automated rules (player count, admin presence, scheduled)
+- 🔔 Discord webhook logs + role verification
+- ⚡ Optimized performance (6x faster queries)
+- 🔐 Secure with rate limiting & validation
+
+---
+
+## 📦 Installation
+
+1. Extract to `resources/esx_whitelist`
+2. Add to `server.cfg`: `ensure esx_whitelist`
+3. Configure Discord token in `server/cfg_discord.lua` (optional)
+4. Restart server
+
+**Requirements:** ESX Legacy, oxmysql, ox_lib
+
+---
+
+## 🎮 Commands
+
+**In-Game (Admin)**
+```
+/whitelist - Open panel
+/wl_add [id] - Add player
+/wl_check [id] - Check status
+```
+
+**Console**
+```
+wl_add [identifier] - Add by identifier
+wl_remove [identifier] - Remove player
+wl_on / wl_off - Toggle whitelist
+```
+
+---
+
+## ⚙️ Configuration
+
+Edit `config.lua`:
+```lua
+Config.Locale = 'en'
+Config.UICommand = 'whitelist'
+Config.AdminGroups = { 'admin', 'mod' }
+```
+
+Discord token in `server/cfg_discord.lua`:
+```lua
+Config.DiscordBotToken = "YOUR_BOT_TOKEN"
+```
+
+---
+
+## 💬 Support
+
+- [ESX Discord](https://discord.esx-framework.org/)
+- [ESX Documentation](https://docs.esx-framework.org/en)
+
+---
+
+Developed by ESX TEAM - Alx
+Made with ❤️ for the ESX Community
\ No newline at end of file
diff --git a/[core]/esx_whitelist/client/main.lua b/[core]/esx_whitelist/client/main.lua
new file mode 100644
index 000000000..55728de28
--- /dev/null
+++ b/[core]/esx_whitelist/client/main.lua
@@ -0,0 +1,143 @@
+local isWhitelistEnabled = false
+local isGracePeriodActive = false
+local gracePeriodEndTime = 0
+local isUIVisible = false
+local translations = {}
+
+local function loadLocaleFile()
+ local localeFile = LoadResourceFile(GetCurrentResourceName(), 'locales/' .. Config.Locale .. '.json')
+ if localeFile then
+ translations = json.decode(localeFile)
+ end
+end
+
+local function T(key, ...)
+ local str = translations[key] or key
+ if not str then return key end
+ if ... then
+ local success, result = pcall(string.format, str, ...)
+ return success and result or str
+ end
+ return str
+end
+
+RegisterNetEvent('esx_whitelist:stateChanged', function(enabled)
+ isWhitelistEnabled = enabled
+end)
+
+RegisterNetEvent('esx_whitelist:startGracePeriod', function(seconds)
+ isGracePeriodActive = true
+ gracePeriodEndTime = GetGameTimer() + (seconds * 1000)
+
+ CreateThread(function()
+ while isGracePeriodActive and GetGameTimer() < gracePeriodEndTime do
+ Wait(1000)
+ local remainingSeconds = math.ceil((gracePeriodEndTime - GetGameTimer()) / 1000)
+ if remainingSeconds > 0 then
+ ESX.ShowNotification(string.format('~r~%s~s~\n%s', T('whitelist_active'), T('remaining_time', remainingSeconds)))
+ end
+ end
+ isGracePeriodActive = false
+ end)
+end)
+
+RegisterNetEvent('esx_whitelist:cancelGracePeriod', function()
+ isGracePeriodActive = false
+ gracePeriodEndTime = 0
+ ESX.ShowNotification('~g~' .. T('grace_cancelled'))
+end)
+
+local function toggleUI(visible)
+ isUIVisible = visible
+ SetNuiFocus(visible, visible)
+
+ if not visible then
+ SendNUIMessage({
+ action = 'closeUI'
+ })
+ end
+end
+
+RegisterCommand(Config.UICommand, function()
+ if isUIVisible then return end
+
+ ESX.TriggerServerCallback('esx_whitelist:getConfig', function(serverConfig)
+ if not serverConfig then
+ ESX.ShowNotification('~r~' .. T('no_permission'))
+ return
+ end
+
+ toggleUI(true)
+ SendNUIMessage({
+ action = 'openUI',
+ data = serverConfig
+ })
+ end)
+end)
+
+RegisterNUICallback('closeUI', function(data, cb)
+ toggleUI(false)
+ cb('ok')
+end)
+
+RegisterNUICallback('updateConfig', function(configData, cb)
+ ESX.TriggerServerCallback('esx_whitelist:updateConfig', function(success)
+ cb(success)
+ end, configData)
+end)
+
+RegisterNUICallback('testWebhook', function(data, cb)
+ ESX.TriggerServerCallback('esx_whitelist:testWebhook', function(success)
+ if success then
+ ESX.ShowNotification('~g~' .. T('webhook_sent'))
+ end
+ cb(success)
+ end)
+end)
+
+RegisterNUICallback('getWhitelistEntries', function(data, cb)
+ ESX.TriggerServerCallback('esx_whitelist:getWhitelistEntries', function(entries)
+ cb(entries or {})
+ end)
+end)
+
+RegisterNUICallback('getConfig', function(data, cb)
+ ESX.TriggerServerCallback('esx_whitelist:getConfig', function(serverConfig)
+ cb(serverConfig or {})
+ end)
+end)
+
+RegisterNUICallback('managePlayer', function(data, cb)
+ ESX.TriggerServerCallback('esx_whitelist:managePlayer', function(success, message)
+ cb({success = success, message = message})
+ end, data)
+end)
+
+RegisterNUICallback('toggleWhitelistStatus', function(data, cb)
+ ESX.TriggerServerCallback('esx_whitelist:toggleWhitelistStatus', function(success)
+ cb(success)
+ end, data)
+end)
+
+local function DisableControls()
+ while isUIVisible do
+ Wait(0)
+ DisableControlAction(0, 1, true)
+ DisableControlAction(0, 2, true)
+ DisableControlAction(0, 142, true)
+ DisableControlAction(0, 18, true)
+ DisableControlAction(0, 322, true)
+ DisableControlAction(0, 106, true)
+ end
+end
+
+CreateThread(function()
+ loadLocaleFile()
+
+ while true do
+ Wait(100)
+ if isUIVisible then
+ DisableControls()
+ end
+ end
+end)
\ No newline at end of file
diff --git a/[core]/esx_whitelist/config.lua b/[core]/esx_whitelist/config.lua
new file mode 100644
index 000000000..e4415dfe8
--- /dev/null
+++ b/[core]/esx_whitelist/config.lua
@@ -0,0 +1,14 @@
+Config = {}
+
+Config.Locale = 'en'
+Config.Debug = false
+
+Config.UICommand = 'whitelist'
+
+Config.AdminGroups = {
+ 'admin',
+ 'mod'
+}
+
+Config.ConsoleCommands = true
+Config.InGameCommands = true
\ No newline at end of file
diff --git a/[core]/esx_whitelist/fxmanifest.lua b/[core]/esx_whitelist/fxmanifest.lua
new file mode 100644
index 000000000..36711d1c9
--- /dev/null
+++ b/[core]/esx_whitelist/fxmanifest.lua
@@ -0,0 +1,35 @@
+fx_version 'cerulean'
+
+game 'gta5'
+
+description 'ESX Dynamic Whitelist System'
+
+version '1.0.0'
+
+lua54 'yes'
+
+shared_scripts {
+ '@es_extended/imports.lua',
+ '@ox_lib/init.lua',
+ 'config.lua'
+}
+
+server_scripts {
+ '@oxmysql/lib/MySQL.lua',
+ '@es_extended/locale.lua',
+ 'server/main.lua',
+ 'server/cfg_discord.lua',
+ 'server/commands.lua'
+}
+
+client_scripts {
+ '@es_extended/locale.lua',
+ 'client/main.lua'
+}
+
+ui_page 'web/dist/index.html'
+
+files {
+ 'web/dist/**/*',
+ 'locales/*.json'
+}
\ No newline at end of file
diff --git a/[core]/esx_whitelist/install.sql b/[core]/esx_whitelist/install.sql
new file mode 100644
index 000000000..197716749
--- /dev/null
+++ b/[core]/esx_whitelist/install.sql
@@ -0,0 +1,25 @@
+-- ESX Whitelist System Database Tables
+-- Note: These tables are created automatically by the script
+-- This file is provided for manual setup or debugging purposes only
+
+CREATE TABLE IF NOT EXISTS `whitelist` (
+ `id` INT AUTO_INCREMENT PRIMARY KEY,
+ `player_name` VARCHAR(255) COLLATE utf8mb4_unicode_ci,
+ `whitelisted` TINYINT(1) NOT NULL DEFAULT 0,
+ `added_by` VARCHAR(255) COLLATE utf8mb4_unicode_ci,
+ `added_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+
+CREATE TABLE IF NOT EXISTS `whitelist_identifiers` (
+ `id` INT AUTO_INCREMENT PRIMARY KEY,
+ `whitelist_id` INT NOT NULL,
+ `type` ENUM('steam', 'license', 'discord', 'xbl', 'live', 'fivem') NOT NULL,
+ `identifier` VARCHAR(255) NOT NULL COLLATE utf8mb4_bin,
+ FOREIGN KEY (`whitelist_id`) REFERENCES `whitelist`(`id`) ON DELETE CASCADE,
+ UNIQUE KEY `unique_identifier` (`type`, `identifier`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+
+-- Indexes for better performance
+CREATE INDEX `idx_whitelisted` ON `whitelist`(`whitelisted`);
+CREATE INDEX `idx_whitelist_id` ON `whitelist_identifiers`(`whitelist_id`);
+CREATE INDEX `idx_identifier` ON `whitelist_identifiers`(`identifier`);
\ No newline at end of file
diff --git a/[core]/esx_whitelist/locales/de.json b/[core]/esx_whitelist/locales/de.json
new file mode 100644
index 000000000..a2681dfa1
--- /dev/null
+++ b/[core]/esx_whitelist/locales/de.json
@@ -0,0 +1,101 @@
+{
+ "ui_title": "Whitelist Panel",
+ "ui_subtitle": "Server-Zugriffsverwaltungssystem",
+ "current_status": "Aktueller Status",
+ "whitelist_enabled": "Whitelist Aktiviert",
+ "whitelist_disabled": "Whitelist Deaktiviert",
+ "grace_period": "Schonfrist",
+ "grace_period_desc": "Zeit vor dem Ausschluss nicht autorisierter Spieler",
+ "grace_period_seconds": "Schonfrist Sekunden",
+ "kick_connected_label": "Verbundene kicken",
+ "discord_notifications": "Discord-Benachrichtigungen",
+ "discord_notifications_desc": "Ereignisse auf Discord protokollieren",
+ "webhook_url": "Webhook-URL",
+ "webhook_configured": "Webhook bereits konfiguriert",
+ "test_webhook": "Webhook Testen",
+ "discord_verification": "Discord-Verifizierung",
+ "discord_verification_desc": "Discord-Rollen bei Verbindung überprüfen",
+ "discord_token_status": "Bot-Token Status",
+ "discord_token_configured": "Token korrekt konfiguriert",
+ "discord_token_missing": "Token nicht konfiguriert",
+ "discord_token_warning": "Ein gültiger Bot-Token ist in cfg_discord.lua für die Discord-Verifizierung erforderlich",
+ "guild_id": "Server-ID",
+ "role_id": "Rollen-ID",
+ "manage_whitelist": "Manuelle Verwaltung",
+ "manage_whitelist_desc": "Spieler manuell hinzufügen oder entfernen",
+ "view_list": "Liste Anzeigen",
+ "action": "Aktion",
+ "add_to_whitelist": "Hinzufügen",
+ "remove_from_whitelist": "Entfernen",
+ "identifier_type": "Typ",
+ "identifier_value": "Identifikator",
+ "apply": "Anwenden",
+ "whitelist_rules": "Automatische Regeln",
+ "whitelist_rules_desc": "Whitelist automatisch aktivieren/deaktivieren",
+ "add_admin": "Admin",
+ "add_players": "Spieler",
+ "add_schedule": "Zeitplan",
+ "admin_presence": "Admin-Anwesenheit",
+ "player_count": "Spielerzähler",
+ "scheduled_time": "Geplante Zeit",
+ "priority": "Priorität",
+ "condition": "Bedingung",
+ "less_than": "Kleiner als (<)",
+ "greater_than": "Größer als (>)",
+ "greater_equal": "Größer oder gleich (>=)",
+ "equal_to": "Gleich (==)",
+ "admin_count": "Anzahl Admins",
+ "player_count_label": "Anzahl Spieler",
+ "enable_whitelist": "Aktivieren",
+ "disable_whitelist": "Deaktivieren",
+ "start_time": "Startzeit",
+ "end_time": "Endzeit",
+ "save_configuration": "Speichern",
+ "reset": "Zurücksetzen",
+ "whitelist_list": "Whitelist-Liste",
+ "search_players": "Spieler suchen...",
+ "total_entries": "Gesamteinträge",
+ "no_results": "Keine Ergebnisse gefunden",
+ "no_entries": "Keine Einträge",
+ "view_identifiers": "Identifikatoren anzeigen",
+ "hide_identifiers": "Identifikatoren verbergen",
+ "whitelisted": "Auf Whitelist",
+ "not_whitelisted": "Nicht auf Whitelist",
+ "checking_whitelist": "Whitelist wird überprüft...",
+ "kick_message": "Sie sind nicht auf der Whitelist",
+ "no_perms": "Sie haben keine Berechtigung",
+ "usage_wl_add": "Verwendung: /wl_add [Spieler-ID]",
+ "player_not_found": "Spieler nicht gefunden",
+ "player_already_wl": "Spieler ist bereits auf der Whitelist",
+ "player_added": "Spieler zur Whitelist hinzugefügt",
+ "added_to_whitelist": "Sie wurden zur Whitelist hinzugefügt",
+ "usage_wl_check": "Verwendung: /wl_check [Spieler-ID]",
+ "player_has_wl": "%s ist auf der Whitelist",
+ "player_no_wl": "%s ist NICHT auf der Whitelist",
+ "player_is_admin": "Dieser Spieler ist Administrator",
+ "config_saved": "Konfiguration gespeichert",
+ "webhook_invalid": "Ungültige Webhook-URL",
+ "webhook_updated": "Webhook aktualisiert",
+ "webhook_test": "Webhook-Test - Angefordert von %s",
+ "discord_no_token": "Discord aktiviert, aber kein Token konfiguriert",
+ "discord_missing_config": "Guild-ID oder Rollen-ID fehlt",
+ "whitelist_enabled_manual": "Whitelist manuell aktiviert von %s",
+ "whitelist_disabled_manual": "Whitelist manuell deaktiviert von %s",
+ "whitelist_enabled_auto": "Whitelist automatisch durch Regeln aktiviert",
+ "whitelist_disabled_auto": "Whitelist automatisch durch Regeln deaktiviert",
+ "discord_player_added": "✅ %s hat hinzugefügt: %s",
+ "discord_player_removed": "❌ %s hat entfernt: %s",
+ "invalid_identifier": "Ungültiger Identifikator",
+ "player_added_ui": "Spieler erfolgreich hinzugefügt",
+ "player_removed_ui": "Spieler erfolgreich entfernt",
+ "player_not_in_wl": "Spieler ist nicht auf der Whitelist",
+ "discord_title": "🛡️ ESX Whitelist",
+ "webhook_sent": "Test-Webhook gesendet",
+ "whitelist_active": "Whitelist Aktiv",
+ "remaining_time": "Verbleibende Zeit: %d Sekunden",
+ "grace_cancelled": "Schonfrist abgebrochen",
+ "no_permission": "Sie haben keine Berechtigung für diesen Befehl",
+ "invalid_identifier_format": "Ungültiges Identifikatorformat",
+ "ip_not_allowed": "IP-Identifikatoren sind nicht erlaubt",
+ "auto_detected": "Automatisch erkannt"
+}
\ No newline at end of file
diff --git a/[core]/esx_whitelist/locales/en.json b/[core]/esx_whitelist/locales/en.json
new file mode 100644
index 000000000..ea2d0efe3
--- /dev/null
+++ b/[core]/esx_whitelist/locales/en.json
@@ -0,0 +1,101 @@
+{
+ "checking_whitelist": "Checking whitelist...",
+ "no_permission": "You do not have permission to access the whitelist panel",
+ "config_saved": "Configuration saved",
+ "webhook_sent": "Webhook sent",
+ "kick_message": "The server is whitelisted. You do not have access.",
+ "whitelist_active": "Whitelist active",
+ "remaining_time": "You have %s seconds to be added",
+ "whitelist_enabled_auto": "Whitelist automatically enabled",
+ "whitelist_disabled_auto": "Whitelist automatically disabled",
+ "whitelist_enabled_manual": "Whitelist manually enabled by %s",
+ "whitelist_disabled_manual": "Whitelist manually disabled by %s",
+ "webhook_test": "Webhook test performed by %s",
+ "discord_title": "Whitelist System",
+ "ui_title": "Whitelist Control",
+ "ui_subtitle": "Manage your server's dynamic whitelist system",
+ "current_status": "Current Status",
+ "whitelist_enabled": "Whitelist Enabled",
+ "whitelist_disabled": "Whitelist Disabled",
+ "grace_period": "Grace Period",
+ "grace_period_desc": "Time before kicking players",
+ "grace_period_seconds": "Grace Period (seconds)",
+ "kick_connected_label": "Kick Connected Players",
+ "discord_notifications": "Discord Notifications",
+ "discord_notifications_desc": "Receive status change notifications",
+ "webhook_url": "Webhook URL",
+ "test_webhook": "Test Webhook",
+ "discord_verification": "Discord Verification",
+ "discord_verification_desc": "Verify players using Discord roles",
+ "guild_id": "Guild ID",
+ "role_id": "Role ID",
+ "bot_token": "Bot Token",
+ "bot_token_configured": "Token configured. Enter a new one to change it.",
+ "manage_whitelist": "Manual Whitelist Management",
+ "manage_whitelist_desc": "Add or remove players directly by identifier",
+ "add_to_whitelist": "Add to Whitelist",
+ "remove_from_whitelist": "Remove from Whitelist",
+ "identifier_type": "Identifier Type",
+ "identifier_value": "Identifier Value",
+ "apply": "Apply",
+ "view_list": "View List",
+ "whitelist_list": "Whitelist Entries",
+ "total_entries": "Total Entries",
+ "no_entries": "No entries in the whitelist",
+ "no_results": "No results found",
+ "search_players": "Search players...",
+ "whitelisted": "Whitelisted",
+ "not_whitelisted": "Not Whitelisted",
+ "view_identifiers": "View identifiers",
+ "hide_identifiers": "Hide",
+ "whitelist_rules": "Whitelist Rules",
+ "whitelist_rules_desc": "Configure dynamic whitelist conditions",
+ "add_admin": "Admin",
+ "add_players": "Players",
+ "add_schedule": "Schedule",
+ "admin_presence": "Admin Presence",
+ "player_count": "Player Count",
+ "scheduled_time": "Scheduled Time",
+ "priority": "Priority",
+ "condition": "Condition",
+ "less_than": "Less than",
+ "greater_than": "Greater than",
+ "greater_equal": "Greater or equal",
+ "equal_to": "Equal to",
+ "admin_count": "Admin Count",
+ "player_count_label": "Player Count",
+ "action": "Action",
+ "enable_whitelist": "Enable Whitelist",
+ "disable_whitelist": "Disable Whitelist",
+ "start_time": "Start Time",
+ "end_time": "End Time",
+ "reset": "Reset",
+ "save_configuration": "Save Configuration",
+ "webhook_configured": "Webhook configured. Enter a new one to change it.",
+ "no_perms": "No permissions",
+ "player_added": "Player added to whitelist",
+ "player_removed": "Player removed from whitelist",
+ "player_not_found": "Player not found",
+ "player_has_wl": "%s HAS whitelist",
+ "player_no_wl": "%s does NOT have whitelist",
+ "player_is_admin": "Is administrator",
+ "usage_wl_add": "Usage: /wl_add [id]",
+ "usage_wl_check": "Usage: /wl_check [id]",
+ "webhook_updated": "Webhook updated",
+ "webhook_invalid": "Invalid webhook URL",
+ "grace_cancelled": "Grace period cancelled",
+ "added_to_whitelist": "You have been added to the whitelist",
+ "invalid_identifier": "Invalid identifier",
+ "player_added_ui": "Player added to whitelist",
+ "player_removed_ui": "Player removed from whitelist",
+ "player_already_wl": "Player already has whitelist",
+ "player_not_in_wl": "Player not in whitelist",
+ "discord_player_added": "%s added %s to whitelist",
+ "discord_player_removed": "%s removed %s from whitelist",
+ "discord_no_token": "Discord Bot Token is not configured or has invalid format",
+ "discord_missing_config": "Discord configuration incomplete. Guild ID and Role ID are required.",
+ "discord_token_status": "Bot Token Status",
+ "discord_token_configured": "Valid Token Configured",
+ "discord_token_missing": "No Valid Token",
+ "discord_token_warning": "Discord verification requires a valid Bot Token configured in server/cfg_discord.lua"
+}
\ No newline at end of file
diff --git a/[core]/esx_whitelist/locales/es.json b/[core]/esx_whitelist/locales/es.json
new file mode 100644
index 000000000..89db7f8ba
--- /dev/null
+++ b/[core]/esx_whitelist/locales/es.json
@@ -0,0 +1,101 @@
+{
+ "ui_title": "Panel de Whitelist",
+ "ui_subtitle": "Sistema de gestión de acceso al servidor",
+ "current_status": "Estado Actual",
+ "whitelist_enabled": "Whitelist Activada",
+ "whitelist_disabled": "Whitelist Desactivada",
+ "grace_period": "Período de Gracia",
+ "grace_period_desc": "Tiempo antes de expulsar a jugadores no autorizados",
+ "grace_period_seconds": "Segundos de gracia",
+ "kick_connected_label": "Expulsar conectados",
+ "discord_notifications": "Notificaciones Discord",
+ "discord_notifications_desc": "Registra eventos en Discord",
+ "webhook_url": "URL del Webhook",
+ "webhook_configured": "Webhook ya configurado",
+ "test_webhook": "Probar Webhook",
+ "discord_verification": "Verificación Discord",
+ "discord_verification_desc": "Verificar roles de Discord al conectar",
+ "discord_token_status": "Estado del Bot Token",
+ "discord_token_configured": "Token configurado correctamente",
+ "discord_token_missing": "Token no configurado",
+ "discord_token_warning": "Se requiere un bot token válido en cfg_discord.lua para la verificación de Discord",
+ "guild_id": "ID del Servidor",
+ "role_id": "ID del Rol",
+ "manage_whitelist": "Gestión Manual",
+ "manage_whitelist_desc": "Añadir o eliminar jugadores manualmente",
+ "view_list": "Ver Lista",
+ "action": "Acción",
+ "add_to_whitelist": "Añadir",
+ "remove_from_whitelist": "Eliminar",
+ "identifier_type": "Tipo",
+ "identifier_value": "Identificador",
+ "apply": "Aplicar",
+ "whitelist_rules": "Reglas Automáticas",
+ "whitelist_rules_desc": "Activa/desactiva la whitelist automáticamente",
+ "add_admin": "Admin",
+ "add_players": "Jugadores",
+ "add_schedule": "Horario",
+ "admin_presence": "Presencia Admin",
+ "player_count": "Contador Jugadores",
+ "scheduled_time": "Horario Programado",
+ "priority": "Prioridad",
+ "condition": "Condición",
+ "less_than": "Menor que (<)",
+ "greater_than": "Mayor que (>)",
+ "greater_equal": "Mayor o igual (>=)",
+ "equal_to": "Igual a (==)",
+ "admin_count": "Cantidad de admins",
+ "player_count_label": "Cantidad de jugadores",
+ "enable_whitelist": "Activar",
+ "disable_whitelist": "Desactivar",
+ "start_time": "Hora inicio",
+ "end_time": "Hora fin",
+ "save_configuration": "Guardar",
+ "reset": "Reiniciar",
+ "whitelist_list": "Lista de Whitelist",
+ "search_players": "Buscar jugadores...",
+ "total_entries": "Entradas totales",
+ "no_results": "No se encontraron resultados",
+ "no_entries": "No hay entradas",
+ "view_identifiers": "Ver identificadores",
+ "hide_identifiers": "Ocultar identificadores",
+ "whitelisted": "En whitelist",
+ "not_whitelisted": "No en whitelist",
+ "checking_whitelist": "Verificando whitelist...",
+ "kick_message": "No estás en la whitelist",
+ "no_perms": "No tienes permisos",
+ "usage_wl_add": "Uso: /wl_add [ID del jugador]",
+ "player_not_found": "Jugador no encontrado",
+ "player_already_wl": "El jugador ya está en la whitelist",
+ "player_added": "Jugador añadido a la whitelist",
+ "added_to_whitelist": "Has sido añadido a la whitelist",
+ "usage_wl_check": "Uso: /wl_check [ID del jugador]",
+ "player_has_wl": "%s está en la whitelist",
+ "player_no_wl": "%s NO está en la whitelist",
+ "player_is_admin": "Este jugador es administrador",
+ "config_saved": "Configuración guardada",
+ "webhook_invalid": "URL del webhook inválida",
+ "webhook_updated": "Webhook actualizado",
+ "webhook_test": "Test de webhook - Solicitado por %s",
+ "discord_no_token": "Discord activado pero no hay token configurado",
+ "discord_missing_config": "Falta configurar Guild ID o Role ID",
+ "whitelist_enabled_manual": "Whitelist activada manualmente por %s",
+ "whitelist_disabled_manual": "Whitelist desactivada manualmente por %s",
+ "whitelist_enabled_auto": "Whitelist activada automáticamente por reglas",
+ "whitelist_disabled_auto": "Whitelist desactivada automáticamente por reglas",
+ "discord_player_added": "✅ %s añadió a: %s",
+ "discord_player_removed": "❌ %s eliminó a: %s",
+ "invalid_identifier": "Identificador inválido",
+ "player_added_ui": "Jugador añadido correctamente",
+ "player_removed_ui": "Jugador eliminado correctamente",
+ "player_not_in_wl": "El jugador no está en la whitelist",
+ "discord_title": "🛡️ ESX Whitelist",
+ "webhook_sent": "Webhook de prueba enviado",
+ "whitelist_active": "Whitelist Activa",
+ "remaining_time": "Tiempo restante: %d segundos",
+ "grace_cancelled": "Período de gracia cancelado",
+ "no_permission": "No tienes permiso para usar este comando",
+ "invalid_identifier_format": "Formato de identificador inválido",
+ "ip_not_allowed": "No se permiten identificadores IP",
+ "auto_detected": "Detectado automáticamente"
+}
\ No newline at end of file
diff --git a/[core]/esx_whitelist/locales/fr.json b/[core]/esx_whitelist/locales/fr.json
new file mode 100644
index 000000000..ad24b6129
--- /dev/null
+++ b/[core]/esx_whitelist/locales/fr.json
@@ -0,0 +1,101 @@
+{
+ "ui_title": "Panneau Whitelist",
+ "ui_subtitle": "Système de gestion d'accès au serveur",
+ "current_status": "État Actuel",
+ "whitelist_enabled": "Whitelist Activée",
+ "whitelist_disabled": "Whitelist Désactivée",
+ "grace_period": "Période de Grâce",
+ "grace_period_desc": "Temps avant d'expulser les joueurs non autorisés",
+ "grace_period_seconds": "Secondes de grâce",
+ "kick_connected_label": "Expulser les connectés",
+ "discord_notifications": "Notifications Discord",
+ "discord_notifications_desc": "Enregistrer les événements sur Discord",
+ "webhook_url": "URL du Webhook",
+ "webhook_configured": "Webhook déjà configuré",
+ "test_webhook": "Tester le Webhook",
+ "discord_verification": "Vérification Discord",
+ "discord_verification_desc": "Vérifier les rôles Discord à la connexion",
+ "discord_token_status": "État du Token Bot",
+ "discord_token_configured": "Token configuré correctement",
+ "discord_token_missing": "Token non configuré",
+ "discord_token_warning": "Un token bot valide est requis dans cfg_discord.lua pour la vérification Discord",
+ "guild_id": "ID du Serveur",
+ "role_id": "ID du Rôle",
+ "manage_whitelist": "Gestion Manuelle",
+ "manage_whitelist_desc": "Ajouter ou retirer des joueurs manuellement",
+ "view_list": "Voir la Liste",
+ "action": "Action",
+ "add_to_whitelist": "Ajouter",
+ "remove_from_whitelist": "Retirer",
+ "identifier_type": "Type",
+ "identifier_value": "Identifiant",
+ "apply": "Appliquer",
+ "whitelist_rules": "Règles Automatiques",
+ "whitelist_rules_desc": "Active/désactive automatiquement la whitelist",
+ "add_admin": "Admin",
+ "add_players": "Joueurs",
+ "add_schedule": "Horaire",
+ "admin_presence": "Présence Admin",
+ "player_count": "Compteur Joueurs",
+ "scheduled_time": "Horaire Programmé",
+ "priority": "Priorité",
+ "condition": "Condition",
+ "less_than": "Inférieur à (<)",
+ "greater_than": "Supérieur à (>)",
+ "greater_equal": "Supérieur ou égal (>=)",
+ "equal_to": "Égal à (==)",
+ "admin_count": "Nombre d'admins",
+ "player_count_label": "Nombre de joueurs",
+ "enable_whitelist": "Activer",
+ "disable_whitelist": "Désactiver",
+ "start_time": "Heure de début",
+ "end_time": "Heure de fin",
+ "save_configuration": "Enregistrer",
+ "reset": "Réinitialiser",
+ "whitelist_list": "Liste Whitelist",
+ "search_players": "Rechercher des joueurs...",
+ "total_entries": "Entrées totales",
+ "no_results": "Aucun résultat trouvé",
+ "no_entries": "Aucune entrée",
+ "view_identifiers": "Voir les identifiants",
+ "hide_identifiers": "Masquer les identifiants",
+ "whitelisted": "En whitelist",
+ "not_whitelisted": "Pas en whitelist",
+ "checking_whitelist": "Vérification de la whitelist...",
+ "kick_message": "Vous n'êtes pas sur la whitelist",
+ "no_perms": "Vous n'avez pas les permissions",
+ "usage_wl_add": "Usage: /wl_add [ID du joueur]",
+ "player_not_found": "Joueur introuvable",
+ "player_already_wl": "Le joueur est déjà sur la whitelist",
+ "player_added": "Joueur ajouté à la whitelist",
+ "added_to_whitelist": "Vous avez été ajouté à la whitelist",
+ "usage_wl_check": "Usage: /wl_check [ID du joueur]",
+ "player_has_wl": "%s est sur la whitelist",
+ "player_no_wl": "%s n'est PAS sur la whitelist",
+ "player_is_admin": "Ce joueur est administrateur",
+ "config_saved": "Configuration enregistrée",
+ "webhook_invalid": "URL du webhook invalide",
+ "webhook_updated": "Webhook mis à jour",
+ "webhook_test": "Test du webhook - Demandé par %s",
+ "discord_no_token": "Discord activé mais aucun token configuré",
+ "discord_missing_config": "Guild ID ou Role ID manquant",
+ "whitelist_enabled_manual": "Whitelist activée manuellement par %s",
+ "whitelist_disabled_manual": "Whitelist désactivée manuellement par %s",
+ "whitelist_enabled_auto": "Whitelist activée automatiquement par les règles",
+ "whitelist_disabled_auto": "Whitelist désactivée automatiquement par les règles",
+ "discord_player_added": "✅ %s a ajouté: %s",
+ "discord_player_removed": "❌ %s a retiré: %s",
+ "invalid_identifier": "Identifiant invalide",
+ "player_added_ui": "Joueur ajouté avec succès",
+ "player_removed_ui": "Joueur retiré avec succès",
+ "player_not_in_wl": "Le joueur n'est pas sur la whitelist",
+ "discord_title": "🛡️ ESX Whitelist",
+ "webhook_sent": "Webhook de test envoyé",
+ "whitelist_active": "Whitelist Active",
+ "remaining_time": "Temps restant: %d secondes",
+ "grace_cancelled": "Période de grâce annulée",
+ "no_permission": "Vous n'avez pas la permission d'utiliser cette commande",
+ "invalid_identifier_format": "Format d'identifiant invalide",
+ "ip_not_allowed": "Les identifiants IP ne sont pas autorisés",
+ "auto_detected": "Détecté automatiquement"
+}
\ No newline at end of file
diff --git a/[core]/esx_whitelist/locales/it.json b/[core]/esx_whitelist/locales/it.json
new file mode 100644
index 000000000..d4ccd2a60
--- /dev/null
+++ b/[core]/esx_whitelist/locales/it.json
@@ -0,0 +1,101 @@
+{
+ "ui_title": "Pannello Whitelist",
+ "ui_subtitle": "Sistema di gestione accesso al server",
+ "current_status": "Stato Attuale",
+ "whitelist_enabled": "Whitelist Attivata",
+ "whitelist_disabled": "Whitelist Disattivata",
+ "grace_period": "Periodo di Grazia",
+ "grace_period_desc": "Tempo prima di espellere i giocatori non autorizzati",
+ "grace_period_seconds": "Secondi di grazia",
+ "kick_connected_label": "Espelli connessi",
+ "discord_notifications": "Notifiche Discord",
+ "discord_notifications_desc": "Registra eventi su Discord",
+ "webhook_url": "URL del Webhook",
+ "webhook_configured": "Webhook già configurato",
+ "test_webhook": "Testa Webhook",
+ "discord_verification": "Verifica Discord",
+ "discord_verification_desc": "Verifica i ruoli Discord alla connessione",
+ "discord_token_status": "Stato Token Bot",
+ "discord_token_configured": "Token configurato correttamente",
+ "discord_token_missing": "Token non configurato",
+ "discord_token_warning": "È richiesto un token bot valido in cfg_discord.lua per la verifica Discord",
+ "guild_id": "ID del Server",
+ "role_id": "ID del Ruolo",
+ "manage_whitelist": "Gestione Manuale",
+ "manage_whitelist_desc": "Aggiungi o rimuovi giocatori manualmente",
+ "view_list": "Vedi Lista",
+ "action": "Azione",
+ "add_to_whitelist": "Aggiungi",
+ "remove_from_whitelist": "Rimuovi",
+ "identifier_type": "Tipo",
+ "identifier_value": "Identificatore",
+ "apply": "Applica",
+ "whitelist_rules": "Regole Automatiche",
+ "whitelist_rules_desc": "Attiva/disattiva automaticamente la whitelist",
+ "add_admin": "Admin",
+ "add_players": "Giocatori",
+ "add_schedule": "Orario",
+ "admin_presence": "Presenza Admin",
+ "player_count": "Contatore Giocatori",
+ "scheduled_time": "Orario Programmato",
+ "priority": "Priorità",
+ "condition": "Condizione",
+ "less_than": "Minore di (<)",
+ "greater_than": "Maggiore di (>)",
+ "greater_equal": "Maggiore o uguale (>=)",
+ "equal_to": "Uguale a (==)",
+ "admin_count": "Numero di admin",
+ "player_count_label": "Numero di giocatori",
+ "enable_whitelist": "Attiva",
+ "disable_whitelist": "Disattiva",
+ "start_time": "Ora inizio",
+ "end_time": "Ora fine",
+ "save_configuration": "Salva",
+ "reset": "Reimposta",
+ "whitelist_list": "Lista Whitelist",
+ "search_players": "Cerca giocatori...",
+ "total_entries": "Voci totali",
+ "no_results": "Nessun risultato trovato",
+ "no_entries": "Nessuna voce",
+ "view_identifiers": "Vedi identificatori",
+ "hide_identifiers": "Nascondi identificatori",
+ "whitelisted": "In whitelist",
+ "not_whitelisted": "Non in whitelist",
+ "checking_whitelist": "Verifica whitelist...",
+ "kick_message": "Non sei nella whitelist",
+ "no_perms": "Non hai i permessi",
+ "usage_wl_add": "Uso: /wl_add [ID giocatore]",
+ "player_not_found": "Giocatore non trovato",
+ "player_already_wl": "Il giocatore è già nella whitelist",
+ "player_added": "Giocatore aggiunto alla whitelist",
+ "added_to_whitelist": "Sei stato aggiunto alla whitelist",
+ "usage_wl_check": "Uso: /wl_check [ID giocatore]",
+ "player_has_wl": "%s è nella whitelist",
+ "player_no_wl": "%s NON è nella whitelist",
+ "player_is_admin": "Questo giocatore è amministratore",
+ "config_saved": "Configurazione salvata",
+ "webhook_invalid": "URL webhook non valido",
+ "webhook_updated": "Webhook aggiornato",
+ "webhook_test": "Test webhook - Richiesto da %s",
+ "discord_no_token": "Discord attivato ma nessun token configurato",
+ "discord_missing_config": "Manca la configurazione Guild ID o Role ID",
+ "whitelist_enabled_manual": "Whitelist attivata manualmente da %s",
+ "whitelist_disabled_manual": "Whitelist disattivata manualmente da %s",
+ "whitelist_enabled_auto": "Whitelist attivata automaticamente dalle regole",
+ "whitelist_disabled_auto": "Whitelist disattivata automaticamente dalle regole",
+ "discord_player_added": "✅ %s ha aggiunto: %s",
+ "discord_player_removed": "❌ %s ha rimosso: %s",
+ "invalid_identifier": "Identificatore non valido",
+ "player_added_ui": "Giocatore aggiunto con successo",
+ "player_removed_ui": "Giocatore rimosso con successo",
+ "player_not_in_wl": "Il giocatore non è nella whitelist",
+ "discord_title": "🛡️ ESX Whitelist",
+ "webhook_sent": "Webhook di test inviato",
+ "whitelist_active": "Whitelist Attiva",
+ "remaining_time": "Tempo rimanente: %d secondi",
+ "grace_cancelled": "Periodo di grazia annullato",
+ "no_permission": "Non hai il permesso per usare questo comando",
+ "invalid_identifier_format": "Formato identificatore non valido",
+ "ip_not_allowed": "Gli identificatori IP non sono consentiti",
+ "auto_detected": "Rilevato automaticamente"
+}
\ No newline at end of file
diff --git a/[core]/esx_whitelist/locales/pt.json b/[core]/esx_whitelist/locales/pt.json
new file mode 100644
index 000000000..9151ed9be
--- /dev/null
+++ b/[core]/esx_whitelist/locales/pt.json
@@ -0,0 +1,101 @@
+{
+ "ui_title": "Painel Whitelist",
+ "ui_subtitle": "Sistema de gestão de acesso ao servidor",
+ "current_status": "Status Atual",
+ "whitelist_enabled": "Whitelist Ativada",
+ "whitelist_disabled": "Whitelist Desativada",
+ "grace_period": "Período de Carência",
+ "grace_period_desc": "Tempo antes de expulsar jogadores não autorizados",
+ "grace_period_seconds": "Segundos de carência",
+ "kick_connected_label": "Expulsar conectados",
+ "discord_notifications": "Notificações Discord",
+ "discord_notifications_desc": "Registrar eventos no Discord",
+ "webhook_url": "URL do Webhook",
+ "webhook_configured": "Webhook já configurado",
+ "test_webhook": "Testar Webhook",
+ "discord_verification": "Verificação Discord",
+ "discord_verification_desc": "Verificar roles do Discord ao conectar",
+ "discord_token_status": "Status do Token Bot",
+ "discord_token_configured": "Token configurado corretamente",
+ "discord_token_missing": "Token não configurado",
+ "discord_token_warning": "É necessário um token bot válido em cfg_discord.lua para verificação do Discord",
+ "guild_id": "ID do Servidor",
+ "role_id": "ID do Cargo",
+ "manage_whitelist": "Gestão Manual",
+ "manage_whitelist_desc": "Adicionar ou remover jogadores manualmente",
+ "view_list": "Ver Lista",
+ "action": "Ação",
+ "add_to_whitelist": "Adicionar",
+ "remove_from_whitelist": "Remover",
+ "identifier_type": "Tipo",
+ "identifier_value": "Identificador",
+ "apply": "Aplicar",
+ "whitelist_rules": "Regras Automáticas",
+ "whitelist_rules_desc": "Ativa/desativa a whitelist automaticamente",
+ "add_admin": "Admin",
+ "add_players": "Jogadores",
+ "add_schedule": "Horário",
+ "admin_presence": "Presença Admin",
+ "player_count": "Contador Jogadores",
+ "scheduled_time": "Horário Programado",
+ "priority": "Prioridade",
+ "condition": "Condição",
+ "less_than": "Menor que (<)",
+ "greater_than": "Maior que (>)",
+ "greater_equal": "Maior ou igual (>=)",
+ "equal_to": "Igual a (==)",
+ "admin_count": "Quantidade de admins",
+ "player_count_label": "Quantidade de jogadores",
+ "enable_whitelist": "Ativar",
+ "disable_whitelist": "Desativar",
+ "start_time": "Hora início",
+ "end_time": "Hora fim",
+ "save_configuration": "Guardar",
+ "reset": "Reiniciar",
+ "whitelist_list": "Lista Whitelist",
+ "search_players": "Pesquisar jogadores...",
+ "total_entries": "Entradas totais",
+ "no_results": "Nenhum resultado encontrado",
+ "no_entries": "Sem entradas",
+ "view_identifiers": "Ver identificadores",
+ "hide_identifiers": "Ocultar identificadores",
+ "whitelisted": "Na whitelist",
+ "not_whitelisted": "Não na whitelist",
+ "checking_whitelist": "Verificando whitelist...",
+ "kick_message": "Você não está na whitelist",
+ "no_perms": "Você não tem permissões",
+ "usage_wl_add": "Uso: /wl_add [ID do jogador]",
+ "player_not_found": "Jogador não encontrado",
+ "player_already_wl": "O jogador já está na whitelist",
+ "player_added": "Jogador adicionado à whitelist",
+ "added_to_whitelist": "Você foi adicionado à whitelist",
+ "usage_wl_check": "Uso: /wl_check [ID do jogador]",
+ "player_has_wl": "%s está na whitelist",
+ "player_no_wl": "%s NÃO está na whitelist",
+ "player_is_admin": "Este jogador é administrador",
+ "config_saved": "Configuração guardada",
+ "webhook_invalid": "URL do webhook inválida",
+ "webhook_updated": "Webhook atualizado",
+ "webhook_test": "Teste de webhook - Solicitado por %s",
+ "discord_no_token": "Discord ativado mas sem token configurado",
+ "discord_missing_config": "Falta configurar Guild ID ou Role ID",
+ "whitelist_enabled_manual": "Whitelist ativada manualmente por %s",
+ "whitelist_disabled_manual": "Whitelist desativada manualmente por %s",
+ "whitelist_enabled_auto": "Whitelist ativada automaticamente pelas regras",
+ "whitelist_disabled_auto": "Whitelist desativada automaticamente pelas regras",
+ "discord_player_added": "✅ %s adicionou: %s",
+ "discord_player_removed": "❌ %s removeu: %s",
+ "invalid_identifier": "Identificador inválido",
+ "player_added_ui": "Jogador adicionado com sucesso",
+ "player_removed_ui": "Jogador removido com sucesso",
+ "player_not_in_wl": "O jogador não está na whitelist",
+ "discord_title": "🛡️ ESX Whitelist",
+ "webhook_sent": "Webhook de teste enviado",
+ "whitelist_active": "Whitelist Ativa",
+ "remaining_time": "Tempo restante: %d segundos",
+ "grace_cancelled": "Período de carência cancelado",
+ "no_permission": "Você não tem permissão para usar este comando",
+ "invalid_identifier_format": "Formato de identificador inválido",
+ "ip_not_allowed": "Identificadores IP não são permitidos",
+ "auto_detected": "Detectado automaticamente"
+}
\ No newline at end of file
diff --git a/[core]/esx_whitelist/server/cfg_discord.lua b/[core]/esx_whitelist/server/cfg_discord.lua
new file mode 100644
index 000000000..fc570c286
--- /dev/null
+++ b/[core]/esx_whitelist/server/cfg_discord.lua
@@ -0,0 +1,55 @@
+Config.DiscordBotToken = "YOUR_DISCORD_BOT_TOKEN_HERE"
+
+local function IsValidBotToken(token)
+ if not token or token == '' or #token < 50 or not string.match(token, '%.') then
+ return false
+ end
+
+ local invalid = {
+ 'TU_TOKEN_AQUI',
+ 'YOUR_TOKEN_HERE',
+ 'YOUR_BOT_TOKEN',
+ 'DISCORD_BOT_TOKEN',
+ 'YOUR_DISCORD_BOT_TOKEN_HERE'
+ }
+
+ for i = 1, #invalid do
+ if token == invalid[i] then
+ return false
+ end
+ end
+
+ return true
+end
+
+local function GetPlayerDiscord(src)
+ local discordId = GetPlayerIdentifierByType(src, 'discord')
+
+ if not discordId then
+ return 'Unknown'
+ end
+
+ discordId = discordId:gsub('discord:', '')
+
+ local promise = promise.new()
+ local name = nil
+
+ PerformHttpRequest("https://discordapp.com/api/users/" .. discordId, function(statusCode, data)
+ if statusCode == 200 then
+ local decoded = json.decode(data or "{}")
+ if decoded and decoded.global_name then
+ name = decoded.global_name
+ end
+ end
+ promise:resolve()
+ end, "GET", "", {
+ Authorization = "Bot " .. Config.DiscordBotToken
+ })
+
+ Citizen.Await(promise)
+
+ return name or 'Unknown'
+end
+
+_G.IsValidBotToken = IsValidBotToken
+_G.GetPlayerDiscord = GetPlayerDiscord
\ No newline at end of file
diff --git a/[core]/esx_whitelist/server/commands.lua b/[core]/esx_whitelist/server/commands.lua
new file mode 100644
index 000000000..71f02f222
--- /dev/null
+++ b/[core]/esx_whitelist/server/commands.lua
@@ -0,0 +1,288 @@
+---@diagnostic disable: undefined-global, need-check-nil, param-type-mismatch
+
+if Config.InGameCommands then
+ ESX.RegisterCommand('wl_add', 'admin', function(xPlayer, args)
+ if not args.id then
+ xPlayer.showNotification('~r~Usage: /wl_add [player id]')
+ return
+ end
+
+ local targetId = tonumber(args.id)
+ local targetPlayer = ESX.Player(targetId)
+
+ if not targetPlayer then
+ xPlayer.showNotification('~r~Player not found')
+ return
+ end
+
+ local targetIdentifiers = GetPlayerIdentifiersFiltered(targetId)
+
+ if #targetIdentifiers == 0 then
+ xPlayer.showNotification('~r~Player not found')
+ return
+ end
+
+ local conditions, params = BuildIdentifierQuery(targetIdentifiers)
+
+ MySQL.query('SELECT w.id, w.whitelisted FROM whitelist w JOIN whitelist_identifiers wi ON w.id = wi.whitelist_id WHERE ' .. conditions, params, function(result)
+ if result and #result > 0 then
+ if result[1].whitelisted == 1 then
+ xPlayer.showNotification('~r~Player already whitelisted')
+ return
+ end
+
+ local adminIdentifier = xPlayer.getIdentifier()
+
+ MySQL.update('UPDATE whitelist SET whitelisted = 1, added_by = ? WHERE id = ?', {
+ adminIdentifier, result[1].id
+ }, function()
+ for i = 1, #targetIdentifiers do
+ Whitelist.Cache[targetIdentifiers[i]] = result[1].id
+ end
+
+ xPlayer.showNotification('~g~Player added to whitelist')
+ targetPlayer.showNotification('~g~You have been added to the whitelist')
+
+ if Whitelist.GracePeriodPlayers[targetId] then
+ Whitelist.GracePeriodPlayers[targetId] = nil
+ TriggerClientEvent('esx_whitelist:cancelGracePeriod', targetId)
+ end
+ end)
+ else
+ xPlayer.showNotification('~r~Player not found')
+ end
+ end)
+ end, false, {help = 'Add player to whitelist', validate = true, arguments = {
+ {name = 'id', help = 'Player ID', type = 'number'}
+ }})
+
+ ESX.RegisterCommand('wl_check', 'admin', function(xPlayer, args)
+ if not args.id then
+ xPlayer.showNotification('~r~Usage: /wl_check [player id]')
+ return
+ end
+
+ local targetId = tonumber(args.id)
+ local targetPlayer = ESX.Player(targetId)
+
+ if not targetPlayer then
+ xPlayer.showNotification('~r~Player not found')
+ return
+ end
+
+ local targetIdentifiers = GetPlayerIdentifiersFiltered(targetId)
+
+ if #targetIdentifiers == 0 then
+ xPlayer.showNotification('~r~Player not found')
+ return
+ end
+
+ local conditions, params = BuildIdentifierQuery(targetIdentifiers)
+
+ MySQL.query('SELECT w.whitelisted FROM whitelist w JOIN whitelist_identifiers wi ON w.id = wi.whitelist_id WHERE ' .. conditions, params, function(result)
+ local hasWhitelist = result and #result > 0 and result[1].whitelisted == 1
+ local targetName = targetPlayer.getName()
+
+ if hasWhitelist then
+ xPlayer.showNotification('~g~' .. targetName .. ' is whitelisted')
+ else
+ xPlayer.showNotification('~r~' .. targetName .. ' is not whitelisted')
+ end
+
+ if IsPlayerAdmin(targetId) then
+ xPlayer.showNotification('~b~This player is an admin')
+ end
+ end)
+ end, false, {help = 'Check whitelist status', validate = true, arguments = {
+ {name = 'id', help = 'Player ID', type = 'number'}
+ }})
+end
+
+if Config.ConsoleCommands then
+ RegisterCommand('wl_add', function(source, args)
+ if source ~= 0 then return end
+
+ if not args[1] then
+ print('Usage: wl_add [identifier]')
+ return
+ end
+
+ local rawIdentifier = args[1]
+ local identifierType, identifierValue = NormalizeIdentifier(rawIdentifier)
+
+ if not identifierType or not identifierValue then
+ print('Invalid identifier format')
+ return
+ end
+
+ if identifierType == 'ip' then
+ print('IP identifiers are not allowed')
+ return
+ end
+
+ local fullIdentifier = identifierType .. ':' .. identifierValue
+
+ GetAllPlayerIdentifiers(fullIdentifier, function(allIdentifiers, existingPlayerName, isOnline)
+ local playerName = existingPlayerName or 'Pending...'
+
+ MySQL.query('SELECT w.id, w.whitelisted FROM whitelist w JOIN whitelist_identifiers wi ON w.id = wi.whitelist_id WHERE wi.identifier = ?', {fullIdentifier}, function(result)
+ if result and #result > 0 then
+ if result[1].whitelisted == 1 then
+ print('Player is already whitelisted')
+ return
+ end
+
+ MySQL.update('UPDATE whitelist SET whitelisted = 1, added_by = ?, player_name = ? WHERE id = ?', {
+ 'console', playerName, result[1].id
+ }, function()
+ local queries = {}
+ for i = 1, #allIdentifiers do
+ local idType = allIdentifiers[i]:match("^(%w+):")
+ if idType then
+ table.insert(queries, {
+ query = 'INSERT INTO whitelist_identifiers (whitelist_id, type, identifier) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE whitelist_id = VALUES(whitelist_id)',
+ values = {result[1].id, idType, allIdentifiers[i]}
+ })
+ end
+ end
+
+ if #queries > 0 then
+ MySQL.transaction(queries, function(success)
+ if success then
+ for i = 1, #allIdentifiers do
+ Whitelist.Cache[allIdentifiers[i]] = result[1].id
+ end
+ end
+ end)
+ end
+
+ print('Added to whitelist: ' .. playerName .. ' (' .. identifierType .. ')' .. (isOnline and ' [ONLINE]' or ''))
+
+ if isOnline then
+ NotifyOnlinePlayer(fullIdentifier)
+ end
+ end)
+ else
+ MySQL.insert('INSERT INTO whitelist (player_name, whitelisted, added_by) VALUES (?, ?, ?)', {
+ playerName, 1, 'console'
+ }, function(insertId)
+ if insertId then
+ local queries = {}
+ for i = 1, #allIdentifiers do
+ local idType = allIdentifiers[i]:match("^(%w+):")
+ if idType then
+ table.insert(queries, {
+ query = 'INSERT INTO whitelist_identifiers (whitelist_id, type, identifier) VALUES (?, ?, ?)',
+ values = {insertId, idType, allIdentifiers[i]}
+ })
+ end
+ end
+
+ if #queries > 0 then
+ MySQL.transaction(queries, function(success)
+ if success then
+ for i = 1, #allIdentifiers do
+ Whitelist.Cache[allIdentifiers[i]] = insertId
+ end
+ end
+ end)
+ end
+
+ print('Added to whitelist: ' .. playerName .. ' (' .. identifierType .. ')' .. (isOnline and ' [ONLINE]' or ''))
+
+ if isOnline then
+ NotifyOnlinePlayer(fullIdentifier)
+ end
+ end
+ end)
+ end
+ end)
+ end)
+ end, true)
+
+ RegisterCommand('wl_on', function(source)
+ if source ~= 0 then return end
+
+ if Whitelist.Enabled then
+ print('Whitelist is already enabled')
+ return
+ end
+
+ Whitelist.ManualOverride = false
+ Whitelist.Enabled = true
+ SaveConfig()
+
+ print('Whitelist has been enabled')
+ SendDiscordLog('Whitelist enabled from console', 15158332)
+
+ Wait(2000)
+ KickNonWhitelistedPlayers()
+
+ TriggerClientEvent('esx_whitelist:stateChanged', -1, Whitelist.Enabled)
+ end, true)
+
+ RegisterCommand('wl_off', function(source)
+ if source ~= 0 then return end
+
+ if not Whitelist.Enabled then
+ print('Whitelist is already disabled')
+ return
+ end
+
+ Whitelist.ManualOverride = true
+ Whitelist.Enabled = false
+ SaveConfig()
+
+ print('Whitelist has been disabled')
+ SendDiscordLog('Whitelist disabled from console', 3066993)
+
+ for playerSource in pairs(Whitelist.GracePeriodPlayers) do
+ Whitelist.GracePeriodPlayers[playerSource] = nil
+ TriggerClientEvent('esx_whitelist:cancelGracePeriod', playerSource)
+ end
+
+ TriggerClientEvent('esx_whitelist:stateChanged', -1, Whitelist.Enabled)
+ end, true)
+
+ RegisterCommand('wl_remove', function(source, args)
+ if source ~= 0 then return end
+
+ if not args[1] then
+ print('Usage: wl_remove [identifier]')
+ return
+ end
+
+ local rawIdentifier = args[1]
+ local identifierType, identifierValue = NormalizeIdentifier(rawIdentifier)
+
+ if not identifierType or not identifierValue then
+ print('Invalid identifier format')
+ return
+ end
+
+ if identifierType == 'ip' then
+ print('IP identifiers are not allowed')
+ return
+ end
+
+ local fullIdentifier = identifierType .. ':' .. identifierValue
+
+ MySQL.query('SELECT w.id, w.whitelisted, w.player_name FROM whitelist w JOIN whitelist_identifiers wi ON w.id = wi.whitelist_id WHERE wi.identifier = ?', {fullIdentifier}, function(result)
+ if not result or #result == 0 or result[1].whitelisted == 0 then
+ print('Player is not in whitelist')
+ return
+ end
+
+ MySQL.update('UPDATE whitelist SET whitelisted = 0 WHERE id = ?', {result[1].id}, function()
+ MySQL.query('SELECT identifier FROM whitelist_identifiers WHERE whitelist_id = ?', {result[1].id}, function(identifiers)
+ for i = 1, #identifiers do
+ Whitelist.Cache[identifiers[i].identifier] = nil
+ end
+
+ local playerName = result[1].player_name or 'Unknown'
+ print('Removed from whitelist: ' .. playerName .. ' (' .. identifierType .. ')')
+ end)
+ end)
+ end)
+ end, true)
+end
\ No newline at end of file
diff --git a/[core]/esx_whitelist/server/main.lua b/[core]/esx_whitelist/server/main.lua
new file mode 100644
index 000000000..2403c46b3
--- /dev/null
+++ b/[core]/esx_whitelist/server/main.lua
@@ -0,0 +1,975 @@
+Whitelist = {
+ Enabled = false,
+ GracePeriod = 60,
+ KickConnected = false,
+ DiscordWebhook = '',
+ DiscordEnabled = false,
+ DiscordGuildId = '',
+ DiscordRoleId = '',
+ DiscordBotToken = Config.DiscordBotToken or '',
+ Rules = {},
+ GracePeriodPlayers = {},
+ ManualOverride = false,
+ Cache = {}
+}
+
+local ruleChangeDebounce = false
+local configFile = 'whitelist_config.json'
+local webhookCooldown = {}
+
+local DefaultConfig = {
+ whitelistEnabled = false,
+ gracePeriod = 60,
+ kickConnected = false,
+ discordWebhook = '',
+ discordEnabled = false,
+ discordGuildId = '',
+ discordRoleId = '',
+ rules = {
+ {
+ id = '1',
+ type = 'admin-presence',
+ enabled = false,
+ priority = 1,
+ operator = '<',
+ value = 1,
+ action = 'enable'
+ },
+ {
+ id = '2',
+ type = 'player-count',
+ enabled = false,
+ priority = 2,
+ operator = '>',
+ value = 32,
+ action = 'enable'
+ },
+ {
+ id = '3',
+ type = 'scheduled',
+ enabled = false,
+ priority = 3,
+ startTime = '03:00',
+ endTime = '08:00'
+ }
+ }
+}
+
+local function IsPlayerAdmin(source)
+ if not source or source == 0 then return true end
+
+ local xPlayer = ESX.Player(source)
+ if not xPlayer then return false end
+
+ local group = xPlayer.getGroup()
+ for i = 1, #Config.AdminGroups do
+ if group == Config.AdminGroups[i] then
+ return true
+ end
+ end
+
+ return false
+end
+
+function GetPlayerIdentifiersFiltered(source)
+ local identifiers = {}
+ local license = GetPlayerIdentifierByType(source, 'license')
+ local license2 = GetPlayerIdentifierByType(source, 'license2')
+ local steam = GetPlayerIdentifierByType(source, 'steam')
+ local discord = GetPlayerIdentifierByType(source, 'discord')
+ local xbl = GetPlayerIdentifierByType(source, 'xbl')
+ local fivem = GetPlayerIdentifierByType(source, 'fivem')
+
+ if license then table.insert(identifiers, 'license:' .. license) end
+ if license2 then table.insert(identifiers, 'license2:' .. license2) end
+ if steam then table.insert(identifiers, 'steam:' .. steam) end
+ if discord then table.insert(identifiers, 'discord:' .. discord) end
+ if xbl then table.insert(identifiers, 'xbl:' .. xbl) end
+ if fivem then table.insert(identifiers, 'fivem:' .. fivem) end
+
+ return identifiers
+end
+
+function BuildIdentifierQuery(identifiers)
+ local conditions, params = {}, {}
+ for i = 1, #identifiers do
+ table.insert(conditions, 'wi.identifier = ?')
+ table.insert(params, identifiers[i])
+ end
+ return table.concat(conditions, ' OR '), params
+end
+
+local function SendDiscordLog(message, color)
+ if not Whitelist.DiscordWebhook or Whitelist.DiscordWebhook == '' or Whitelist.DiscordWebhook == '***CONFIGURED***' then return end
+ if not message or message == '' then return end
+
+ local currentTime = GetGameTimer()
+ if webhookCooldown[message] and currentTime - webhookCooldown[message] < 5000 then return end
+ webhookCooldown[message] = currentTime
+
+ PerformHttpRequest(Whitelist.DiscordWebhook, function() end, 'POST', json.encode({
+ embeds = {{
+ title = _U('discord_title'),
+ description = message,
+ color = color or 3447003,
+ timestamp = os.date('!%Y-%m-%dT%H:%M:%S')
+ }}
+ }), {['Content-Type'] = 'application/json'})
+end
+
+local function UpdateWhitelistCache()
+ MySQL.query('SELECT DISTINCT w.id, wi.identifier FROM whitelist w JOIN whitelist_identifiers wi ON w.id = wi.whitelist_id WHERE CAST(w.whitelisted AS UNSIGNED) = 1', {}, function(results)
+ Whitelist.Cache = {}
+ for i = 1, #results do
+ Whitelist.Cache[results[i].identifier] = results[i].id
+ end
+ end)
+end
+
+local function StartGracePeriod(source, playerName)
+ if not source or source == 0 then return end
+ if Whitelist.GracePeriod <= 0 then
+ DropPlayer(source, _U('kick_message'))
+ return
+ end
+
+ Whitelist.GracePeriodPlayers[source] = {
+ endTime = os.time() + Whitelist.GracePeriod,
+ playerName = playerName
+ }
+
+ TriggerClientEvent('esx_whitelist:startGracePeriod', source, Whitelist.GracePeriod)
+
+ CreateThread(function()
+ Wait(Whitelist.GracePeriod * 1000)
+ if Whitelist.GracePeriodPlayers[source] then
+ if IsPlayerAdmin(source) then
+ Whitelist.GracePeriodPlayers[source] = nil
+ return
+ end
+ DropPlayer(source, _U('kick_message'))
+ Whitelist.GracePeriodPlayers[source] = nil
+ end
+ end)
+end
+
+function KickNonWhitelistedPlayers()
+ local players = ESX.GetExtendedPlayers()
+ for i = 1, #players do
+ local xPlayer = players[i]
+ if xPlayer and xPlayer.source and not IsPlayerAdmin(xPlayer.source) then
+ local hasWhitelist = false
+ local identifiers = GetPlayerIdentifiersFiltered(xPlayer.source)
+
+ for j = 1, #identifiers do
+ if Whitelist.Cache[identifiers[j]] then
+ hasWhitelist = true
+ break
+ end
+ end
+
+ if not hasWhitelist then
+ if Whitelist.KickConnected then
+ DropPlayer(xPlayer.source, _U('kick_message'))
+ else
+ StartGracePeriod(xPlayer.source, xPlayer.getName())
+ end
+ end
+ end
+ end
+end
+
+function SaveConfig()
+ local data = {
+ whitelistEnabled = Whitelist.Enabled,
+ gracePeriod = Whitelist.GracePeriod,
+ kickConnected = Whitelist.KickConnected,
+ discordWebhook = Whitelist.DiscordWebhook,
+ discordEnabled = Whitelist.DiscordEnabled,
+ discordGuildId = Whitelist.DiscordGuildId,
+ discordRoleId = Whitelist.DiscordRoleId,
+ rules = Whitelist.Rules
+ }
+ SaveResourceFile(GetCurrentResourceName(), configFile, json.encode(data, {indent = true}), -1)
+end
+
+local function LoadConfig()
+ local configData = LoadResourceFile(GetCurrentResourceName(), configFile)
+
+ if not configData then
+ Whitelist.Enabled = DefaultConfig.whitelistEnabled
+ Whitelist.GracePeriod = DefaultConfig.gracePeriod
+ Whitelist.KickConnected = DefaultConfig.kickConnected
+ Whitelist.DiscordWebhook = DefaultConfig.discordWebhook
+ Whitelist.DiscordEnabled = DefaultConfig.discordEnabled
+ Whitelist.DiscordGuildId = DefaultConfig.discordGuildId
+ Whitelist.DiscordRoleId = DefaultConfig.discordRoleId
+ Whitelist.Rules = DefaultConfig.rules
+ SaveConfig()
+ return true
+ end
+
+ local success, data = pcall(json.decode, configData)
+
+ if not success or not data then
+ if Config.Debug then
+ print('Whitelist config file corrupted, regenerating with defaults')
+ end
+ Whitelist.Enabled = DefaultConfig.whitelistEnabled
+ Whitelist.GracePeriod = DefaultConfig.gracePeriod
+ Whitelist.KickConnected = DefaultConfig.kickConnected
+ Whitelist.DiscordWebhook = DefaultConfig.discordWebhook
+ Whitelist.DiscordEnabled = DefaultConfig.discordEnabled
+ Whitelist.DiscordGuildId = DefaultConfig.discordGuildId
+ Whitelist.DiscordRoleId = DefaultConfig.discordRoleId
+ Whitelist.Rules = DefaultConfig.rules
+ SaveConfig()
+ return true
+ end
+
+ Whitelist.Enabled = data.whitelistEnabled or DefaultConfig.whitelistEnabled
+ Whitelist.GracePeriod = data.gracePeriod or DefaultConfig.gracePeriod
+ Whitelist.KickConnected = data.kickConnected or DefaultConfig.kickConnected
+ Whitelist.DiscordWebhook = data.discordWebhook or DefaultConfig.discordWebhook
+ Whitelist.DiscordEnabled = data.discordEnabled or DefaultConfig.discordEnabled
+ Whitelist.DiscordGuildId = data.discordGuildId or DefaultConfig.discordGuildId
+ Whitelist.DiscordRoleId = data.discordRoleId or DefaultConfig.discordRoleId
+ Whitelist.Rules = data.rules or DefaultConfig.rules
+
+ return true
+end
+
+local function evaluateWhitelistRules()
+ local activeRules = {}
+ for i = 1, #Whitelist.Rules do
+ if Whitelist.Rules[i].enabled then
+ table.insert(activeRules, Whitelist.Rules[i])
+ end
+ end
+
+ table.sort(activeRules, function(a, b) return a.priority < b.priority end)
+
+ for i = 1, #activeRules do
+ local rule = activeRules[i]
+
+ if rule.type == 'admin-presence' then
+ local adminCount = 0
+ local players = ESX.GetExtendedPlayers()
+ for j = 1, #players do
+ if players[j] and players[j].source and IsPlayerAdmin(players[j].source) then
+ adminCount = adminCount + 1
+ end
+ end
+
+ local conditionMet = false
+ if rule.operator == '<' then conditionMet = adminCount < rule.value
+ elseif rule.operator == '>' then conditionMet = adminCount > rule.value
+ elseif rule.operator == '>=' then conditionMet = adminCount >= rule.value
+ elseif rule.operator == '==' then conditionMet = adminCount == rule.value
+ end
+
+ if conditionMet then return rule.action == 'enable' end
+
+ elseif rule.type == 'player-count' then
+ local playerCount = #ESX.GetExtendedPlayers()
+
+ local conditionMet = false
+ if rule.operator == '<' then conditionMet = playerCount < rule.value
+ elseif rule.operator == '>' then conditionMet = playerCount > rule.value
+ elseif rule.operator == '>=' then conditionMet = playerCount >= rule.value
+ elseif rule.operator == '==' then conditionMet = playerCount == rule.value
+ end
+
+ if conditionMet then return rule.action == 'enable' end
+
+ elseif rule.type == 'scheduled' then
+ local currentMinutes = tonumber(os.date('%H')) * 60 + tonumber(os.date('%M'))
+ local startHour, startMin = rule.startTime:match('(%d+):(%d+)')
+ local startMinutes = tonumber(startHour) * 60 + tonumber(startMin)
+ local endHour, endMin = rule.endTime:match('(%d+):(%d+)')
+ local endMinutes = tonumber(endHour) * 60 + tonumber(endMin)
+
+ local inRange
+ if startMinutes <= endMinutes then
+ inRange = currentMinutes >= startMinutes and currentMinutes <= endMinutes
+ else
+ inRange = currentMinutes >= startMinutes or currentMinutes <= endMinutes
+ end
+
+ return inRange
+ end
+ end
+
+ return Whitelist.Enabled
+end
+
+local function handleRuleChange()
+ if Whitelist.ManualOverride or ruleChangeDebounce then return end
+ ruleChangeDebounce = true
+
+ local newState = evaluateWhitelistRules()
+ if newState ~= Whitelist.Enabled then
+ Whitelist.Enabled = newState
+
+ if Whitelist.Enabled then
+ SendDiscordLog(_U('whitelist_enabled_auto'), 15158332)
+ KickNonWhitelistedPlayers()
+ else
+ SendDiscordLog(_U('whitelist_disabled_auto'), 3066993)
+ for playerSource in pairs(Whitelist.GracePeriodPlayers) do
+ if playerSource and playerSource ~= 0 then
+ Whitelist.GracePeriodPlayers[playerSource] = nil
+ TriggerClientEvent('esx_whitelist:cancelGracePeriod', playerSource)
+ end
+ end
+ end
+
+ TriggerClientEvent('esx_whitelist:stateChanged', -1, Whitelist.Enabled)
+ SaveConfig()
+ end
+
+ SetTimeout(5000, function()
+ ruleChangeDebounce = false
+ end)
+end
+
+local function checkDiscordRole(discordId, callback)
+ if not Whitelist.DiscordEnabled or Whitelist.DiscordGuildId == '' or Whitelist.DiscordRoleId == '' or not IsValidBotToken(Whitelist.DiscordBotToken) then
+ callback(false)
+ return
+ end
+
+ PerformHttpRequest(string.format('https://discord.com/api/v10/guilds/%s/members/%s', Whitelist.DiscordGuildId, discordId),
+ function(statusCode, data)
+ if statusCode == 200 then
+ local member = json.decode(data)
+ if member and member.roles then
+ for i = 1, #member.roles do
+ if member.roles[i] == Whitelist.DiscordRoleId then
+ callback(true)
+ return
+ end
+ end
+ end
+ end
+ callback(false)
+ end, 'GET', '', {
+ ['Authorization'] = 'Bot ' .. Whitelist.DiscordBotToken,
+ ['Content-Type'] = 'application/json'
+ })
+end
+
+local function DetectIdentifierType(value)
+ if not value or value == '' then return nil end
+
+ value = value:gsub("%s+", "")
+ value = value:gsub("^%w+:", "")
+
+ local len = #value
+
+ if value:find("^[0-9a-fA-F]+%-[0-9a-fA-F]+%-[0-9a-fA-F]+%-[0-9a-fA-F]+%-[0-9a-fA-F]+$") and len == 36 then
+ return "license2"
+ end
+
+ if value:find("^[0-9a-fA-F]+$") and len == 40 then
+ return "license"
+ end
+
+ if value:find("^%d+$") then
+ if len >= 17 and len <= 19 then
+ return "discord"
+ elseif len == 16 then
+ return "xbl"
+ elseif len >= 6 and len <= 8 then
+ return "fivem"
+ end
+ end
+
+ if value:find("^[0-9a-fA-F]+$") and len >= 15 and len <= 17 then
+ return "steam"
+ end
+
+ return nil
+end
+
+function NormalizeIdentifier(rawValue)
+ if not rawValue or rawValue == '' then return nil, nil end
+
+ local cleanValue = rawValue:gsub("%s+", "")
+ local providedPrefix, providedValue = cleanValue:match("^(%w+):(.+)$")
+ local valueToDetect = providedValue or cleanValue
+ local detectedType = DetectIdentifierType(valueToDetect)
+
+ if not detectedType then return nil, nil end
+
+ return detectedType, valueToDetect
+end
+
+function GetAllPlayerIdentifiers(singleIdentifier, callback)
+ local idType, idValue = NormalizeIdentifier(singleIdentifier)
+
+ if not idType or not idValue then
+ callback({}, nil, false)
+ return
+ end
+
+ local fullIdentifier = idType .. ':' .. idValue
+
+ local players = ESX.GetExtendedPlayers()
+ for i = 1, #players do
+ if players[i] and players[i].source then
+ local identifiers = GetPlayerIdentifiersFiltered(players[i].source)
+ for j = 1, #identifiers do
+ if identifiers[j] == fullIdentifier then
+ local playerName = GetPlayerName(players[i].source) or players[i].getName()
+ callback(identifiers, playerName, true)
+ return
+ end
+ end
+ end
+ end
+
+ MySQL.query([[
+ SELECT wi.identifier, w.player_name
+ FROM whitelist_identifiers wi
+ JOIN whitelist w ON w.id = wi.whitelist_id
+ WHERE wi.whitelist_id = (
+ SELECT whitelist_id FROM whitelist_identifiers WHERE identifier = ? LIMIT 1
+ )
+ ]], {fullIdentifier}, function(results)
+ if results and #results > 0 then
+ local allIdentifiers = {}
+ for i = 1, #results do
+ table.insert(allIdentifiers, results[i].identifier)
+ end
+ callback(allIdentifiers, results[1].player_name, false)
+ else
+ callback({fullIdentifier}, nil, false)
+ end
+ end)
+end
+
+function NotifyOnlinePlayer(fullIdentifier)
+ local players = ESX.GetExtendedPlayers()
+ for i = 1, #players do
+ if players[i] and players[i].source then
+ local onlineIdentifiers = GetPlayerIdentifiersFiltered(players[i].source)
+ for j = 1, #onlineIdentifiers do
+ if onlineIdentifiers[j] == fullIdentifier then
+ players[i].showNotification('~g~' .. _U('added_to_whitelist'))
+ if Whitelist.GracePeriodPlayers[players[i].source] then
+ Whitelist.GracePeriodPlayers[players[i].source] = nil
+ TriggerClientEvent('esx_whitelist:cancelGracePeriod', players[i].source)
+ end
+ return
+ end
+ end
+ end
+ end
+end
+
+AddEventHandler('playerConnecting', function(playerName, setKickReason, deferrals)
+ local source = source
+ if not source or source == 0 then return end
+
+ deferrals.defer()
+ Wait(0)
+
+ deferrals.update(_U('checking_whitelist'))
+
+ local realPlayerName = GetPlayerName(source) or playerName
+ local identifiers = GetPlayerIdentifiersFiltered(source)
+
+ if #identifiers == 0 then
+ deferrals.done(_U('kick_message'))
+ return
+ end
+
+ Wait(500)
+
+ local conditions, params = BuildIdentifierQuery(identifiers)
+ local query = 'SELECT DISTINCT w.id, w.player_name, CAST(w.whitelisted AS UNSIGNED) as whitelisted FROM whitelist w JOIN whitelist_identifiers wi ON w.id = wi.whitelist_id WHERE ' .. conditions
+
+ MySQL.query(query, params, function(result)
+ local whitelistId
+ local existsInDB = result and #result > 0
+
+ if existsInDB then
+ whitelistId = result[1].id
+
+ local queries = {}
+ for i = 1, #identifiers do
+ local idType = identifiers[i]:match("^(%w+):")
+ if idType then
+ table.insert(queries, {
+ query = 'INSERT INTO whitelist_identifiers (whitelist_id, type, identifier) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE whitelist_id = VALUES(whitelist_id)',
+ values = {whitelistId, idType, identifiers[i]}
+ })
+ end
+ end
+
+ if #queries > 0 then
+ MySQL.transaction(queries)
+ end
+
+ MySQL.update('UPDATE whitelist SET player_name = ? WHERE id = ?', {realPlayerName, whitelistId})
+ else
+ MySQL.insert('INSERT INTO whitelist (player_name, whitelisted) VALUES (?, ?)', {realPlayerName, 0}, function(insertId)
+ whitelistId = insertId
+ if whitelistId then
+ local queries = {}
+ for i = 1, #identifiers do
+ local idType = identifiers[i]:match("^(%w+):")
+ if idType then
+ table.insert(queries, {
+ query = 'INSERT INTO whitelist_identifiers (whitelist_id, type, identifier) VALUES (?, ?, ?)',
+ values = {whitelistId, idType, identifiers[i]}
+ })
+ end
+ end
+
+ if #queries > 0 then
+ MySQL.transaction(queries)
+ end
+ end
+ end)
+ Wait(500)
+ end
+
+ if not Whitelist.Enabled then
+ deferrals.done()
+ return
+ end
+
+ if Whitelist.DiscordEnabled then
+ local discordId = GetPlayerIdentifierByType(source, 'discord')
+
+ if not discordId then
+ deferrals.done(_U('kick_message'))
+ return
+ end
+
+ discordId = discordId:gsub('discord:', '')
+
+ checkDiscordRole(discordId, function(hasRole)
+ if hasRole then
+ MySQL.update('UPDATE whitelist SET whitelisted = 1 WHERE id = ?', {whitelistId}, function()
+ for i = 1, #identifiers do
+ Whitelist.Cache[identifiers[i]] = whitelistId
+ end
+ end)
+ deferrals.done()
+ else
+ MySQL.update('UPDATE whitelist SET whitelisted = 0 WHERE id = ?', {whitelistId}, function()
+ for i = 1, #identifiers do
+ Whitelist.Cache[identifiers[i]] = nil
+ end
+ end)
+ deferrals.done(_U('kick_message'))
+ end
+ end)
+ else
+ if existsInDB and tonumber(result[1].whitelisted) == 1 then
+ for i = 1, #identifiers do
+ Whitelist.Cache[identifiers[i]] = whitelistId
+ end
+ deferrals.done()
+ else
+ deferrals.done(_U('kick_message'))
+ end
+ end
+ end)
+end)
+
+AddEventHandler('esx:playerLoaded', function(playerId, xPlayer, isNew)
+ handleRuleChange()
+end)
+
+AddEventHandler('esx:playerDropped', function(playerId, reason)
+ if Whitelist.GracePeriodPlayers[playerId] then
+ Whitelist.GracePeriodPlayers[playerId] = nil
+ end
+ Wait(1000)
+ handleRuleChange()
+end)
+
+AddEventHandler('onResourceStart', function(resourceName)
+ if GetCurrentResourceName() ~= resourceName then return end
+
+ LoadConfig()
+
+ MySQL.query([[
+ CREATE TABLE IF NOT EXISTS whitelist (
+ id INT AUTO_INCREMENT PRIMARY KEY,
+ player_name VARCHAR(255) COLLATE utf8mb4_unicode_ci,
+ whitelisted TINYINT(1) NOT NULL DEFAULT 0,
+ added_by VARCHAR(255) COLLATE utf8mb4_unicode_ci,
+ added_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ INDEX idx_whitelisted (whitelisted)
+ )
+ ]])
+
+ MySQL.query([[
+ CREATE TABLE IF NOT EXISTS whitelist_identifiers (
+ id INT AUTO_INCREMENT PRIMARY KEY,
+ whitelist_id INT NOT NULL,
+ type ENUM('steam', 'license', 'license2', 'discord', 'xbl', 'fivem', 'ip') NOT NULL,
+ identifier VARCHAR(255) NOT NULL COLLATE utf8mb4_bin,
+ FOREIGN KEY (whitelist_id) REFERENCES whitelist(id) ON DELETE CASCADE,
+ UNIQUE (type, identifier),
+ INDEX idx_identifier (identifier)
+ )
+ ]])
+
+ Wait(1000)
+ UpdateWhitelistCache()
+
+ local status = Whitelist.Enabled and 'enabled' or 'disabled'
+ print('Whitelist system started - Status: ' .. status .. ' - Grace period: ' .. Whitelist.GracePeriod .. 's')
+
+ if Whitelist.DiscordEnabled and IsValidBotToken(Whitelist.DiscordBotToken) then
+ print('Discord role verification is active')
+ end
+
+ CreateThread(function()
+ while true do
+ Wait(30000)
+ handleRuleChange()
+ end
+ end)
+
+ if Whitelist.Enabled then
+ Wait(5000)
+ KickNonWhitelistedPlayers()
+ end
+end)
+
+ESX.RegisterServerCallback('esx_whitelist:getConfig', function(source, cb)
+ if not source or source == 0 or not IsPlayerAdmin(source) then
+ cb(nil)
+ return
+ end
+
+ local file = LoadResourceFile(GetCurrentResourceName(), 'locales/' .. Config.Locale .. '.json')
+ local localeTranslations = file and json.decode(file) or {}
+
+ cb({
+ whitelistEnabled = Whitelist.Enabled,
+ gracePeriod = Whitelist.GracePeriod,
+ kickConnected = Whitelist.KickConnected,
+ discordWebhook = Whitelist.DiscordWebhook ~= '' and '***CONFIGURED***' or '',
+ discordEnabled = Whitelist.DiscordEnabled,
+ discordGuildId = Whitelist.DiscordGuildId,
+ discordRoleId = Whitelist.DiscordRoleId,
+ rules = Whitelist.Rules,
+ locale = Config.Locale,
+ translations = localeTranslations,
+ hasBotToken = Whitelist.DiscordEnabled and IsValidBotToken(Whitelist.DiscordBotToken)
+ })
+end)
+
+ESX.RegisterServerCallback('esx_whitelist:getWhitelistEntries', function(source, cb)
+ if not source or source == 0 or not IsPlayerAdmin(source) then
+ cb({})
+ return
+ end
+
+ MySQL.query([[
+ SELECT w.id, w.player_name, CAST(w.whitelisted AS UNSIGNED) as whitelisted,
+ GROUP_CONCAT(wi.identifier SEPARATOR '|') as identifiers
+ FROM whitelist w
+ LEFT JOIN whitelist_identifiers wi ON w.id = wi.whitelist_id
+ GROUP BY w.id
+ ORDER BY w.whitelisted DESC, w.id ASC
+ ]], {}, function(results)
+ if not results then
+ cb({})
+ return
+ end
+
+ local entries = {}
+ for i = 1, #results do
+ local row = results[i]
+ local identifiers = {}
+ if row.identifiers then
+ for identifier in string.gmatch(row.identifiers, '[^|]+') do
+ table.insert(identifiers, identifier)
+ end
+ end
+
+ table.insert(entries, {
+ id = row.id,
+ identifiers = identifiers,
+ playerName = row.player_name or 'Unknown',
+ whitelisted = row.whitelisted
+ })
+ end
+
+ cb(entries)
+ end)
+end)
+
+ESX.RegisterServerCallback('esx_whitelist:updateConfig', function(source, cb, config)
+ local playerSource = source
+ if not playerSource or playerSource == 0 or not IsPlayerAdmin(playerSource) then
+ cb(false)
+ return
+ end
+
+ if config.discordEnabled and not IsValidBotToken(Whitelist.DiscordBotToken) then
+ TriggerClientEvent('esx:showNotification', playerSource, '~r~Discord bot token not configured')
+ cb(false)
+ return
+ end
+
+ if config.discordEnabled and (not config.discordGuildId or config.discordGuildId == '' or not config.discordRoleId or config.discordRoleId == '') then
+ TriggerClientEvent('esx:showNotification', playerSource, '~r~Discord configuration incomplete')
+ cb(false)
+ return
+ end
+
+ local oldState = Whitelist.Enabled
+ local xPlayer = ESX.Player(playerSource)
+ local playerName = xPlayer and xPlayer.getName() or 'Unknown'
+
+ if config.discordWebhook and config.discordWebhook ~= '' and config.discordWebhook ~= '***CONFIGURED***' then
+ if string.match(config.discordWebhook, '^https://discord.com/api/webhooks/') or
+ string.match(config.discordWebhook, '^https://discordapp.com/api/webhooks/') then
+ Whitelist.DiscordWebhook = config.discordWebhook
+ end
+ end
+
+ Whitelist.Enabled = config.whitelistEnabled
+ Whitelist.GracePeriod = config.gracePeriod
+ Whitelist.KickConnected = config.kickConnected
+ Whitelist.DiscordEnabled = config.discordEnabled
+ Whitelist.DiscordGuildId = config.discordGuildId
+ Whitelist.DiscordRoleId = config.discordRoleId
+ Whitelist.Rules = config.rules
+ Whitelist.ManualOverride = false
+
+ SaveConfig()
+
+ if Whitelist.Enabled and not oldState then
+ SendDiscordLog(string.format('Whitelist enabled by %s', playerName), 15158332)
+ Wait(2000)
+ KickNonWhitelistedPlayers()
+ elseif not Whitelist.Enabled and oldState then
+ SendDiscordLog(string.format('Whitelist disabled by %s', playerName), 3066993)
+ for src in pairs(Whitelist.GracePeriodPlayers) do
+ if src and src ~= 0 then
+ Whitelist.GracePeriodPlayers[src] = nil
+ TriggerClientEvent('esx_whitelist:cancelGracePeriod', src)
+ end
+ end
+ end
+
+ TriggerClientEvent('esx_whitelist:stateChanged', -1, Whitelist.Enabled)
+ TriggerClientEvent('esx:showNotification', playerSource, '~g~Configuration saved')
+ cb(true)
+end)
+
+ESX.RegisterServerCallback('esx_whitelist:testWebhook', function(source, cb)
+ local playerSource = source
+ if not playerSource or playerSource == 0 or not IsPlayerAdmin(playerSource) then
+ cb(false)
+ return
+ end
+
+ local xPlayer = ESX.Player(playerSource)
+ local playerName = xPlayer and xPlayer.getName() or 'Unknown'
+ SendDiscordLog(string.format('Webhook test by %s', playerName), 3447003)
+ cb(true)
+end)
+
+ESX.RegisterServerCallback('esx_whitelist:managePlayer', function(source, cb, data)
+ local playerSource = source
+ if not playerSource or playerSource == 0 then
+ cb(false, 'No permission')
+ return
+ end
+
+ if not IsPlayerAdmin(playerSource) then
+ cb(false, 'No permission')
+ return
+ end
+
+ local xPlayer = ESX.Player(playerSource)
+ if not xPlayer then
+ cb(false, 'Error getting player data')
+ return
+ end
+
+ local adminIdentifier = xPlayer.getIdentifier()
+ if not adminIdentifier then
+ cb(false, 'Error getting admin identifier')
+ return
+ end
+
+ local rawIdentifier = data.identifier
+ local action = data.action
+
+ if not rawIdentifier or rawIdentifier == '' then
+ cb(false, 'Invalid identifier')
+ return
+ end
+
+ local identifierType, identifierValue = NormalizeIdentifier(rawIdentifier)
+
+ if not identifierType or not identifierValue then
+ cb(false, 'Invalid identifier format')
+ return
+ end
+
+ if identifierType == 'ip' then
+ cb(false, 'IP identifiers not allowed')
+ return
+ end
+
+ local fullIdentifier = identifierType .. ':' .. identifierValue
+
+ if action == 'add' then
+ GetAllPlayerIdentifiers(fullIdentifier, function(allIdentifiers, existingPlayerName, isOnline)
+ local playerName = existingPlayerName or 'Pending...'
+
+ MySQL.query('SELECT w.id, w.whitelisted FROM whitelist w JOIN whitelist_identifiers wi ON w.id = wi.whitelist_id WHERE wi.identifier = ?', {fullIdentifier}, function(result)
+ if result and #result > 0 then
+ if result[1].whitelisted == 1 then
+ cb(false, 'Player already whitelisted')
+ return
+ end
+
+ MySQL.update('UPDATE whitelist SET whitelisted = 1, added_by = ?, player_name = ? WHERE id = ?', {
+ adminIdentifier, playerName, result[1].id
+ }, function(affectedRows)
+ local queries = {}
+ for i = 1, #allIdentifiers do
+ local idType = allIdentifiers[i]:match("^(%w+):")
+ if idType then
+ table.insert(queries, {
+ query = 'INSERT INTO whitelist_identifiers (whitelist_id, type, identifier) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE whitelist_id = VALUES(whitelist_id)',
+ values = {result[1].id, idType, allIdentifiers[i]}
+ })
+ end
+ end
+
+ if #queries > 0 then
+ MySQL.transaction(queries, function(success)
+ if success then
+ for i = 1, #allIdentifiers do
+ Whitelist.Cache[allIdentifiers[i]] = result[1].id
+ end
+ end
+ end)
+ end
+
+ SendDiscordLog(string.format('%s added %s to whitelist', xPlayer.getName(), playerName .. ' (' .. identifierType .. ')'), 3066993)
+
+ if isOnline then
+ NotifyOnlinePlayer(fullIdentifier)
+ end
+
+ cb(true, 'Player added successfully')
+ end)
+ else
+ MySQL.insert('INSERT INTO whitelist (player_name, whitelisted, added_by) VALUES (?, ?, ?)', {
+ playerName, 1, adminIdentifier
+ }, function(insertId)
+ if insertId then
+ local queries = {}
+ for i = 1, #allIdentifiers do
+ local idType = allIdentifiers[i]:match("^(%w+):")
+ if idType then
+ table.insert(queries, {
+ query = 'INSERT INTO whitelist_identifiers (whitelist_id, type, identifier) VALUES (?, ?, ?)',
+ values = {insertId, idType, allIdentifiers[i]}
+ })
+ end
+ end
+
+ if #queries > 0 then
+ MySQL.transaction(queries, function(success)
+ if success then
+ for i = 1, #allIdentifiers do
+ Whitelist.Cache[allIdentifiers[i]] = insertId
+ end
+ end
+ end)
+ end
+
+ SendDiscordLog(string.format('%s added %s to whitelist', xPlayer.getName(), playerName .. ' (' .. identifierType .. ')'), 3066993)
+
+ if isOnline then
+ NotifyOnlinePlayer(fullIdentifier)
+ end
+
+ cb(true, 'Player added successfully')
+ else
+ cb(false, 'Error inserting into database')
+ end
+ end)
+ end
+ end)
+ end)
+
+ elseif action == 'remove' then
+ MySQL.query('SELECT w.id, w.whitelisted FROM whitelist w JOIN whitelist_identifiers wi ON w.id = wi.whitelist_id WHERE wi.identifier = ?', {fullIdentifier}, function(result)
+ if not result or #result == 0 or result[1].whitelisted == 0 then
+ cb(false, 'Player not in whitelist')
+ return
+ end
+
+ MySQL.update('UPDATE whitelist SET whitelisted = 0 WHERE id = ?', {result[1].id}, function(affectedRows)
+ MySQL.query('SELECT identifier FROM whitelist_identifiers WHERE whitelist_id = ?', {result[1].id}, function(identifiers)
+ for i = 1, #identifiers do
+ Whitelist.Cache[identifiers[i].identifier] = nil
+ end
+
+ SendDiscordLog(string.format('%s removed %s from whitelist', xPlayer.getName(), fullIdentifier), 15158332)
+ cb(true, 'Player removed successfully')
+ end)
+ end)
+ end)
+ else
+ cb(false, 'Invalid action')
+ end
+end)
+
+
+ESX.RegisterServerCallback('esx_whitelist:toggleWhitelistStatus', function(source, cb, data)
+ local playerSource = source
+ if not playerSource or playerSource == 0 or not IsPlayerAdmin(playerSource) then
+ cb(false)
+ return
+ end
+
+ local xPlayer = ESX.Player(playerSource)
+ local playerName = xPlayer and xPlayer.getName() or 'Unknown'
+
+ MySQL.update('UPDATE whitelist SET whitelisted = ? WHERE id = ?', {data.status, data.id}, function(affectedRows)
+ MySQL.query('SELECT identifier FROM whitelist_identifiers WHERE whitelist_id = ?', {data.id}, function(result)
+ if result and #result > 0 then
+ for i = 1, #result do
+ if data.status == 1 then
+ Whitelist.Cache[result[i].identifier] = data.id
+ else
+ Whitelist.Cache[result[i].identifier] = nil
+ end
+ end
+ end
+
+ local action = data.status == 1 and 'added player to whitelist' or 'removed player from whitelist'
+ SendDiscordLog(string.format('%s %s (ID: %s)', playerName, action, data.id), data.status == 1 and 3066993 or 15158332)
+ cb(true)
+ end)
+ end)
+end)
+
+ESX.RegisterServerCallback('esx_whitelist:detectIdentifier', function(source, cb, rawValue)
+ local idType, idValue = NormalizeIdentifier(rawValue)
+ cb({
+ type = idType,
+ value = idValue,
+ valid = idType ~= nil and idValue ~= nil
+ })
+end)
\ No newline at end of file
diff --git a/[core]/esx_whitelist/web/app/globals.css b/[core]/esx_whitelist/web/app/globals.css
new file mode 100644
index 000000000..a7164816f
--- /dev/null
+++ b/[core]/esx_whitelist/web/app/globals.css
@@ -0,0 +1,169 @@
+@import "tailwindcss";
+@import "tw-animate-css";
+
+@import url("https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap");
+
+@custom-variant dark (&:is(.dark *));
+
+:root {
+ --background: transparent;
+ --foreground: #ffffff;
+
+ --card: #18181b;
+ --card-foreground: #ffffff;
+ --popover: #18181b;
+ --popover-foreground: #ffffff;
+ --border: #27272a;
+ --input: #27272a;
+ --ring: #3b82f6;
+
+ --primary: #3b82f6;
+ --primary-foreground: #ffffff;
+ --secondary: #27272a;
+ --secondary-foreground: #ffffff;
+ --muted: #27272a;
+ --muted-foreground: #a1a1aa;
+ --accent: #27272a;
+ --accent-foreground: #ffffff;
+ --destructive: #ef4444;
+ --destructive-foreground: #ffffff;
+
+ --chart-1: #3b82f6;
+ --chart-2: #8b5cf6;
+ --chart-3: #ec4899;
+ --chart-4: #f59e0b;
+ --chart-5: #10b981;
+
+ --sidebar: #18181b;
+ --sidebar-foreground: #ffffff;
+ --sidebar-primary: #3b82f6;
+ --sidebar-primary-foreground: #ffffff;
+ --sidebar-accent: #27272a;
+ --sidebar-accent-foreground: #ffffff;
+ --sidebar-border: #27272a;
+ --sidebar-ring: #3b82f6;
+
+ --font-sans: "Poppins", sans-serif;
+ --font-mono: "Geist Mono", "Geist Mono Fallback";
+ --font-serif: "Source Serif 4", "Source Serif 4 Fallback";
+
+ --radius: 0.5rem;
+
+ --shadow-x: 0px;
+ --shadow-y: 1px;
+ --shadow-blur: 2px;
+ --shadow-spread: 0px;
+ --shadow-opacity: 0.05;
+ --shadow-color: #000000;
+}
+
+@theme inline {
+ --font-sans: "Poppins", sans-serif;
+ --font-mono: "Geist Mono", "Geist Mono Fallback";
+ --font-serif: "Source Serif 4", "Source Serif 4 Fallback";
+
+ --color-background: var(--background);
+ --color-foreground: var(--foreground);
+
+ --color-card: var(--card);
+ --color-card-foreground: var(--card-foreground);
+ --color-popover: var(--popover);
+ --color-popover-foreground: var(--popover-foreground);
+ --color-border: var(--border);
+ --color-input: var(--input);
+ --color-ring: var(--ring);
+
+ --color-primary: var(--primary);
+ --color-primary-foreground: var(--primary-foreground);
+ --color-secondary: var(--secondary);
+ --color-secondary-foreground: var(--secondary-foreground);
+ --color-muted: var(--muted);
+ --color-muted-foreground: var(--muted-foreground);
+ --color-accent: var(--accent);
+ --color-accent-foreground: var(--accent-foreground);
+ --color-destructive: var(--destructive);
+ --color-destructive-foreground: var(--destructive-foreground);
+
+ --color-chart-1: var(--chart-1);
+ --color-chart-2: var(--chart-2);
+ --color-chart-3: var(--chart-3);
+ --color-chart-4: var(--chart-4);
+ --color-chart-5: var(--chart-5);
+
+ --color-sidebar: var(--sidebar);
+ --color-sidebar-foreground: var(--sidebar-foreground);
+ --color-sidebar-primary: var(--sidebar-primary);
+ --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
+ --color-sidebar-accent: var(--sidebar-accent);
+ --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
+ --color-sidebar-border: var(--sidebar-border);
+ --color-sidebar-ring: var(--sidebar-ring);
+
+ --radius-sm: calc(var(--radius) - 4px);
+ --radius-md: calc(var(--radius) - 2px);
+ --radius-lg: var(--radius);
+ --radius-xl: calc(var(--radius) + 4px);
+
+ --shadow-2xs: var(--shadow-x) var(--shadow-y)
+ color-mix(in srgb, var(--shadow-color) calc(var(--shadow-opacity) * 100%), transparent);
+ --shadow-xs: var(--shadow-x) var(--shadow-y) var(--shadow-blur) var(--shadow-spread)
+ color-mix(in srgb, var(--shadow-color) calc(var(--shadow-opacity) * 100%), transparent);
+ --shadow-sm: var(--shadow-x) var(--shadow-y) calc(var(--shadow-blur) * 1.5) var(--shadow-spread)
+ color-mix(in srgb, var(--shadow-color) calc(var(--shadow-opacity) * 100%), transparent);
+ --shadow-md: calc(var(--shadow-x) * 2) calc(var(--shadow-y) * 2) calc(var(--shadow-blur) * 3)
+ calc(var(--shadow-spread) - 1px)
+ color-mix(in srgb, var(--shadow-color) calc(var(--shadow-opacity) * 100%), transparent);
+ --shadow-lg: calc(var(--shadow-x) * 4) calc(var(--shadow-y) * 4) calc(var(--shadow-blur) * 5)
+ calc(var(--shadow-spread) - 3px)
+ color-mix(in srgb, var(--shadow-color) calc(var(--shadow-opacity) * 100%), transparent);
+ --shadow-xl: calc(var(--shadow-x) * 6) calc(var(--shadow-y) * 6) calc(var(--shadow-blur) * 7)
+ calc(var(--shadow-spread) - 5px)
+ color-mix(in srgb, var(--shadow-color) calc(var(--shadow-opacity) * 100%), transparent);
+ --shadow-2xl: calc(var(--shadow-x) * 8) calc(var(--shadow-y) * 8) calc(max(0px, var(--shadow-blur) * 9 - 8px))
+ calc(var(--shadow-spread) - 12px)
+ color-mix(in srgb, var(--shadow-color) calc(var(--shadow-opacity) * 250%), transparent);
+ --shadow: var(--shadow-md);
+
+ --color-shadow-color: var(--shadow-color);
+ --shadow-opacity: var(--shadow-opacity);
+ --shadow-spread: var(--shadow-spread);
+ --shadow-blur: var(--shadow-blur);
+ --shadow-y: var(--shadow-y);
+ --shadow-x: var(--shadow-x);
+}
+
+@layer base {
+ * {
+ @apply border-border outline-ring/50;
+ }
+ body {
+ @apply bg-background text-foreground;
+ }
+
+ .tablet-container::-webkit-scrollbar {
+ display: none;
+ }
+
+ @keyframes pulse {
+ 0%,
+ 100% {
+ opacity: 1;
+ }
+ 50% {
+ opacity: 0.7;
+ }
+ }
+
+ button,
+ [role="switch"],
+ [role="combobox"] {
+ cursor: pointer;
+ }
+
+ input[type="number"],
+ input[type="time"],
+ input[type="url"],
+ input[type="text"] {
+ cursor: text;
+ }
+}
diff --git a/[core]/esx_whitelist/web/app/layout.tsx b/[core]/esx_whitelist/web/app/layout.tsx
new file mode 100644
index 000000000..67940fa8d
--- /dev/null
+++ b/[core]/esx_whitelist/web/app/layout.tsx
@@ -0,0 +1,20 @@
+import type { Metadata } from 'next'
+import './globals.css'
+
+export const metadata: Metadata = {
+ title: 'esx_whitelist',
+}
+
+export default function RootLayout({
+ children,
+}: Readonly<{
+ children: React.ReactNode
+}>) {
+ return (
+
+
+ {children}
+
+
+ )
+}
\ No newline at end of file
diff --git a/[core]/esx_whitelist/web/app/page.tsx b/[core]/esx_whitelist/web/app/page.tsx
new file mode 100644
index 000000000..88261f7cf
--- /dev/null
+++ b/[core]/esx_whitelist/web/app/page.tsx
@@ -0,0 +1,1552 @@
+"use client"
+
+import { useState, useEffect, useRef } from "react"
+import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
+import { Button } from "@/components/ui/button"
+import { Input } from "@/components/ui/input"
+import { Label } from "@/components/ui/label"
+import { Badge } from "@/components/ui/badge"
+import { Separator } from "@/components/ui/separator"
+import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
+import { Clock, Users, Bell, Settings, AlertTriangle, UserCheck, Hash, List, X, User, UserPlus, UserMinus, Search, CheckCircle2, Copy, Check, Eye, EyeOff } from "lucide-react"
+
+type RuleType = "admin-presence" | "player-count" | "scheduled"
+type Operator = "<" | ">" | ">=" | "=="
+type Action = "enable" | "disable"
+type IdentifierType = "license" | "license2" | "steam" | "discord" | "xbl" | "fivem"
+type WhitelistAction = "add" | "remove"
+
+interface Rule {
+ id: string
+ type: RuleType
+ enabled: boolean
+ priority: number
+ operator?: Operator
+ value?: number
+ action?: Action
+ startTime?: string
+ endTime?: string
+}
+
+interface WhitelistEntry {
+ id: number
+ identifiers: string[]
+ playerName: string
+ whitelisted: number
+}
+
+interface WhitelistConfig {
+ whitelistEnabled: boolean
+ gracePeriod: number
+ kickConnected: boolean
+ discordWebhook: string
+ discordEnabled: boolean
+ discordGuildId: string
+ discordRoleId: string
+ rules: Rule[]
+ locale: string
+ translations: Record
+ hasBotToken: boolean
+}
+
+interface CustomSwitchProps {
+ checked: boolean
+ onCheckedChange: (checked: boolean) => void
+ disabled?: boolean
+ id?: string
+}
+
+const CustomSwitch = ({ checked, onCheckedChange, disabled = false, id }: CustomSwitchProps) => {
+ return (
+ !disabled && onCheckedChange(!checked)}
+ className="relative inline-flex h-6 w-11 items-center rounded-full transition-all duration-300 cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed"
+ style={{
+ background: checked
+ ? "linear-gradient(135deg, #FB9B04 0%, #ff8800 100%)"
+ : "rgba(60, 60, 60, 0.8)",
+ boxShadow: checked
+ ? "0 0 12px rgba(251, 155, 4, 0.4), inset 0 1px 2px rgba(0, 0, 0, 0.2)"
+ : "inset 0 2px 4px rgba(0, 0, 0, 0.3)",
+ }}
+ >
+
+
+ )
+}
+
+const useAudioSystem = () => {
+ const audioRefs = useRef<{ [key: string]: HTMLAudioElement }>({})
+
+ useEffect(() => {
+ audioRefs.current = {
+ click: new Audio("https://hebbkx1anhila5yf.public.blob.vercel-storage.com/buttonpress-ZhD2rrsS446feqLbA4Gk4ypddIry6X.wav"),
+ toggleOn: new Audio("https://hebbkx1anhila5yf.public.blob.vercel-storage.com/turnon-fSgd3bC1WSTe73HcQxjrKTc9muEW19.wav"),
+ toggleOff: new Audio("https://hebbkx1anhila5yf.public.blob.vercel-storage.com/turnoff-iDZ9CFS8GzYfuOVWECqbJbwoAQYNfD.wav"),
+ close: new Audio("https://r2.fivemanage.com/ezG2KbZP8dV8OUQgkQ0i6/close.mp3"),
+ }
+
+ Object.values(audioRefs.current).forEach((audio) => {
+ audio.load()
+ })
+ }, [])
+
+ const playSound = (type: "click" | "toggleOn" | "toggleOff" | "close") => {
+ const audio = audioRefs.current[type]
+ if (audio) {
+ audio.currentTime = 0
+ audio.play().catch(() => {})
+ }
+ }
+
+ return { playSound }
+}
+
+const rateLimitMap = new Map()
+
+async function fetchNui(eventName: string, data?: any) {
+ const now = Date.now()
+ const lastCall = rateLimitMap.get(eventName) || 0
+
+ if (now - lastCall < 1000) {
+ return {}
+ }
+
+ rateLimitMap.set(eventName, now)
+
+ const options = {
+ method: 'post',
+ headers: {
+ 'Content-Type': 'application/json; charset=UTF-8',
+ },
+ body: JSON.stringify(data),
+ }
+
+ const resourceName = (window as any).GetParentResourceName
+ ? (window as any).GetParentResourceName()
+ : 'esx_whitelist'
+
+ try {
+ const resp = await fetch(`https://${resourceName}/${eventName}`, options)
+ const text = await resp.text()
+ return text ? JSON.parse(text) : {}
+ } catch (error) {
+ return {}
+ }
+}
+
+export default function WhitelistControlPanel() {
+ const [visible, setVisible] = useState(false)
+ const [whitelistEnabled, setWhitelistEnabled] = useState(false)
+ const [gracePeriod, setGracePeriod] = useState(60)
+ const [kickConnected, setKickConnected] = useState(false)
+ const [discordWebhook, setDiscordWebhook] = useState("")
+ const [discordEnabled, setDiscordEnabled] = useState(false)
+ const [discordGuildId, setDiscordGuildId] = useState("")
+ const [discordRoleId, setDiscordRoleId] = useState("")
+ const [hasBotToken, setHasBotToken] = useState(false)
+ const [translations, setTranslations] = useState>({})
+ const [playerIdentifier, setPlayerIdentifier] = useState("")
+ const [detectedType, setDetectedType] = useState(null)
+ const [identifierType, setIdentifierType] = useState("license")
+ const [whitelistAction, setWhitelistAction] = useState("add")
+ const [showWhitelistView, setShowWhitelistView] = useState(false)
+ const [whitelistEntries, setWhitelistEntries] = useState([])
+ const [filteredEntries, setFilteredEntries] = useState([])
+ const [searchQuery, setSearchQuery] = useState("")
+ const [rules, setRules] = useState([])
+ const [expandedEntries, setExpandedEntries] = useState>(new Set())
+ const [isSaving, setIsSaving] = useState(false)
+ const [copiedId, setCopiedId] = useState(null)
+ const [validationError, setValidationError] = useState("")
+
+ const { playSound } = useAudioSystem()
+
+ const t = (key: string): string => {
+ return translations[key] || key
+ }
+
+ const detectIdentifierType = (value: string): IdentifierType | null => {
+ const cleanValue = value.trim().replace(/\s+/g, '').replace(/^\w+:/, '')
+ const len = cleanValue.length
+
+ if (/^[0-9a-fA-F]+-[0-9a-fA-F]+-[0-9a-fA-F]+-[0-9a-fA-F]+-[0-9a-fA-F]+$/.test(cleanValue) && len === 36) {
+ return "license2"
+ }
+
+ if (/^[0-9a-fA-F]+$/.test(cleanValue) && len === 40) {
+ return "license"
+ }
+
+ if (/^\d+$/.test(cleanValue)) {
+ if (len >= 17 && len <= 19) return "discord"
+ if (len === 16) return "xbl"
+ if (len >= 6 && len <= 8) return "fivem"
+ }
+
+ if (/^[0-9a-fA-F]+$/.test(cleanValue) && len >= 15 && len <= 17) {
+ return "steam"
+ }
+
+ return null
+ }
+
+ const copyToClipboard = (text: string, id: string) => {
+ const textArea = document.createElement('textarea')
+ textArea.value = text
+ textArea.style.position = 'fixed'
+ textArea.style.left = '-999999px'
+ textArea.style.top = '-999999px'
+ document.body.appendChild(textArea)
+ textArea.focus()
+ textArea.select()
+
+ try {
+ document.execCommand('copy')
+ setCopiedId(id)
+ setTimeout(() => setCopiedId(null), 2000)
+ } catch (err) {
+ console.error('Failed to copy:', err)
+ } finally {
+ textArea.remove()
+ }
+ }
+
+ const getIdentifierColor = (type: string) => {
+ const colorMap: Record = {
+ steam: "#66c0f4",
+ license: "#3b82f6",
+ license2: "#8b5cf6",
+ discord: "#5865f2",
+ xbl: "#107c10",
+ fivem: "#f97316"
+ }
+ return colorMap[type] || "#6b7280"
+ }
+
+ const validateIdentifier = (value: string): boolean => {
+ const cleaned = value.trim()
+
+ if (cleaned.length < 6 || cleaned.length > 50) {
+ setValidationError(t("identifier_length_error") || "Identifier must be between 6-50 characters")
+ return false
+ }
+
+ if (!/^[a-zA-Z0-9:-]+$/.test(cleaned)) {
+ setValidationError(t("identifier_chars_error") || "Invalid characters in identifier")
+ return false
+ }
+
+ setValidationError("")
+ return true
+ }
+
+ const validateWebhook = (webhook: string): boolean => {
+ if (!webhook || webhook === '***CONFIGURED***') return true
+
+ if (!webhook.startsWith('https://discord.com/api/webhooks/') &&
+ !webhook.startsWith('https://discordapp.com/api/webhooks/')) {
+ setValidationError(t("webhook_invalid") || "Invalid Discord webhook URL")
+ return false
+ }
+
+ setValidationError("")
+ return true
+ }
+
+ useEffect(() => {
+ const handleMessage = (event: MessageEvent) => {
+ const { action, data } = event.data
+
+ if (action === 'openUI') {
+ setVisible(true)
+ setWhitelistEnabled(data.whitelistEnabled)
+ setGracePeriod(data.gracePeriod)
+ setKickConnected(data.kickConnected)
+ setDiscordWebhook(data.discordWebhook)
+ setDiscordEnabled(data.discordEnabled)
+ setDiscordGuildId(data.discordGuildId)
+ setDiscordRoleId(data.discordRoleId)
+ setHasBotToken(data.hasBotToken)
+ setRules(data.rules)
+ setTranslations(data.translations || {})
+ } else if (action === 'closeUI') {
+ setVisible(false)
+ }
+ }
+
+ window.addEventListener('message', handleMessage)
+ return () => window.removeEventListener('message', handleMessage)
+ }, [])
+
+ useEffect(() => {
+ const handleEscape = (e: KeyboardEvent) => {
+ if (e.key === 'Escape' && visible) {
+ e.preventDefault()
+ playSound("close")
+ if (showWhitelistView) {
+ setShowWhitelistView(false)
+ setSearchQuery("")
+ setExpandedEntries(new Set())
+ } else {
+ handleClose()
+ }
+ }
+ }
+
+ window.addEventListener('keydown', handleEscape)
+ return () => window.removeEventListener('keydown', handleEscape)
+ }, [visible, showWhitelistView])
+
+ useEffect(() => {
+ if (searchQuery.trim() === "") {
+ setFilteredEntries(whitelistEntries)
+ } else {
+ const query = searchQuery.toLowerCase()
+ setFilteredEntries(
+ whitelistEntries.filter(entry =>
+ entry.playerName?.toLowerCase().includes(query) ||
+ entry.identifiers?.some(id => id.toLowerCase().includes(query))
+ )
+ )
+ }
+ }, [searchQuery, whitelistEntries])
+
+ useEffect(() => {
+ if (playerIdentifier.trim()) {
+ const detected = detectIdentifierType(playerIdentifier)
+ setDetectedType(detected)
+ if (detected && detected !== identifierType) {
+ setIdentifierType(detected)
+ }
+ } else {
+ setDetectedType(null)
+ }
+ }, [playerIdentifier])
+
+ const toggleExpanded = (id: number) => {
+ playSound("click")
+ const newExpanded = new Set(expandedEntries)
+ if (newExpanded.has(id)) {
+ newExpanded.delete(id)
+ } else {
+ newExpanded.add(id)
+ }
+ setExpandedEntries(newExpanded)
+ }
+
+ const handleClose = () => {
+ playSound("close")
+ setVisible(false)
+ setValidationError("")
+ fetchNui('closeUI')
+ }
+
+ const handleWhitelistToggle = (checked: boolean) => {
+ playSound(checked ? "toggleOn" : "toggleOff")
+ setWhitelistEnabled(checked)
+ }
+
+ const handleKickConnectedToggle = (checked: boolean) => {
+ playSound(checked ? "toggleOn" : "toggleOff")
+ setKickConnected(checked)
+ }
+
+ const handleDiscordToggle = (checked: boolean) => {
+ playSound(checked ? "toggleOn" : "toggleOff")
+ setDiscordEnabled(checked)
+ }
+
+ const handleAddRule = (type: RuleType) => {
+ playSound("click")
+ const newRule: Rule = {
+ id: Date.now().toString(),
+ type,
+ enabled: true,
+ priority: rules.length + 1,
+ ...(type === "admin-presence" && { operator: "<" as Operator, value: 1, action: "enable" as Action }),
+ ...(type === "player-count" && { operator: ">" as Operator, value: 50, action: "enable" as Action }),
+ ...(type === "scheduled" && { startTime: "00:00", endTime: "00:00" }),
+ }
+ setRules([...rules, newRule])
+ }
+
+ const handleRemoveRule = (id: string) => {
+ playSound("click")
+ setRules(rules.filter((rule) => rule.id !== id))
+ }
+
+ const handleRuleToggle = (id: string, checked: boolean) => {
+ playSound(checked ? "toggleOn" : "toggleOff")
+ setRules(rules.map((rule) => (rule.id === id ? { ...rule, enabled: checked } : rule)))
+ }
+
+ const updateRule = (id: string, updates: Partial) => {
+ setRules(rules.map((rule) => (rule.id === id ? { ...rule, ...updates } : rule)))
+ }
+
+ const handleTestWebhook = () => {
+ playSound("click")
+
+ if (!validateWebhook(discordWebhook)) {
+ return
+ }
+
+ fetchNui("testWebhook")
+ }
+
+ const handleManagePlayer = () => {
+ playSound("click")
+
+ if (!playerIdentifier.trim()) {
+ setValidationError(t("identifier_required") || "Identifier is required")
+ return
+ }
+
+ if (!validateIdentifier(playerIdentifier)) {
+ return
+ }
+
+ fetchNui("managePlayer", {
+ identifier: playerIdentifier.trim(),
+ type: identifierType,
+ action: whitelistAction
+ })
+
+ setPlayerIdentifier("")
+ setDetectedType(null)
+ setValidationError("")
+ }
+
+ const handleViewWhitelist = () => {
+ playSound("click")
+ fetchNui("getWhitelistEntries").then((entries: WhitelistEntry[]) => {
+ setWhitelistEntries(entries || [])
+ setFilteredEntries(entries || [])
+ setShowWhitelistView(true)
+ setExpandedEntries(new Set())
+ })
+ }
+
+ const handleToggleWhitelistStatus = (entry: WhitelistEntry) => {
+ playSound("click")
+ const newStatus = entry.whitelisted === 1 ? 0 : 1
+
+ fetchNui("toggleWhitelistStatus", {
+ id: entry.id,
+ status: newStatus
+ }).then(() => {
+ setWhitelistEntries(whitelistEntries.map(e =>
+ e.id === entry.id ? { ...e, whitelisted: newStatus } : e
+ ))
+ })
+ }
+
+ const handleSave = async () => {
+ if (isSaving) return
+
+ if (!validateWebhook(discordWebhook)) {
+ return
+ }
+
+ playSound("click")
+ setIsSaving(true)
+
+ try {
+ const configData = {
+ whitelistEnabled,
+ gracePeriod,
+ kickConnected,
+ discordWebhook: discordWebhook !== '***CONFIGURED***' ? discordWebhook : '',
+ discordEnabled,
+ discordGuildId,
+ discordRoleId,
+ rules,
+ }
+
+ await fetchNui("updateConfig", configData)
+ setValidationError("")
+ } finally {
+ setTimeout(() => setIsSaving(false), 1000)
+ }
+ }
+
+ const handleReset = () => {
+ playSound("click")
+ setValidationError("")
+ fetchNui("getConfig").then((config: WhitelistConfig) => {
+ setWhitelistEnabled(config.whitelistEnabled)
+ setGracePeriod(config.gracePeriod)
+ setKickConnected(config.kickConnected)
+ setDiscordWebhook(config.discordWebhook)
+ setDiscordEnabled(config.discordEnabled)
+ setDiscordGuildId(config.discordGuildId)
+ setDiscordRoleId(config.discordRoleId)
+ setHasBotToken(config.hasBotToken)
+ setRules(config.rules)
+ })
+ }
+
+ const getRuleIcon = (type: RuleType) => {
+ switch (type) {
+ case "admin-presence":
+ return
+ case "player-count":
+ return
+ case "scheduled":
+ return
+ }
+ }
+
+ const getRuleTitle = (type: RuleType) => {
+ switch (type) {
+ case "admin-presence":
+ return t("admin_presence")
+ case "player-count":
+ return t("player_count")
+ case "scheduled":
+ return t("scheduled_time")
+ }
+ }
+
+ if (!visible) return null
+
+ if (showWhitelistView) {
+ return (
+
+
+
+
+
+
+ {t("whitelist_list")}
+
+
+
{
+ playSound("close")
+ setShowWhitelistView(false)
+ setSearchQuery("")
+ setExpandedEntries(new Set())
+ }}
+ className="transition-all duration-200 hover:scale-110 active:scale-90 cursor-pointer"
+ style={{ color: "#FB9B04" }}
+ >
+
+
+
+
+
+
+
+
+ setSearchQuery(e.target.value)}
+ className="flex-1 cursor-text transition-all duration-200"
+ style={{
+ background: "rgba(25, 25, 25, 0.95)",
+ borderColor: "rgba(251, 155, 4, 0.25)",
+ color: "#F2F2F2",
+ fontSize: "14px",
+ }}
+ />
+
+
+
+
+
+
+
+
+
+ {t("total_entries")}: {filteredEntries.length}
+
+
+
+
+
+ {filteredEntries.length === 0 ? (
+
+ {searchQuery ? t("no_results") || "No results found" : t("no_entries")}
+
+ ) : (
+ filteredEntries.map((entry, index) => {
+ const isExpanded = expandedEntries.has(entry.id)
+
+ const groupedIdentifiers = entry.identifiers.reduce((acc, id) => {
+ const [type, value] = id.split(':')
+ if (!acc[type]) acc[type] = []
+ acc[type].push({ full: id, value })
+ return acc
+ }, {} as Record
>)
+
+ return (
+
+
+
+
+
+
+
+
+
+ {entry.playerName}
+
+
+ ID: {entry.id}
+
+
+
+
toggleExpanded(entry.id)}
+ className="flex items-center gap-1.5 transition-all duration-200 hover:opacity-80 group"
+ style={{
+ background: "none",
+ border: "none",
+ padding: 0,
+ cursor: "pointer"
+ }}
+ >
+ {isExpanded ? (
+
+ ) : (
+
+ )}
+
+ ({Object.keys(groupedIdentifiers).length})
+
+
+
+ {isExpanded && (
+
+ {Object.entries(groupedIdentifiers).map(([type, identifiers]) => (
+
+
+ {identifiers.map((id, idx) => (
+
+
+ {type}
+
+
+ {id.value}
+
+ copyToClipboard(id.full, id.full)}
+ className="flex-shrink-0 p-0.5 rounded transition-all duration-200 hover:bg-[rgba(251,155,4,0.2)] cursor-pointer"
+ style={{
+ border: "1px solid rgba(251, 155, 4, 0.3)",
+ background: copiedId === id.full ? "rgba(16, 185, 129, 0.2)" : "rgba(251, 155, 4, 0.1)"
+ }}
+ >
+ {copiedId === id.full ? (
+
+ ) : (
+
+ )}
+
+
+ ))}
+
+
+ ))}
+
+ )}
+
+
+ {entry.whitelisted === 1 ? t("whitelisted") || "Whitelisted" : t("not_whitelisted") || "Not Whitelisted"}
+
+
+
handleToggleWhitelistStatus(entry)}
+ className="transition-all duration-200 hover:scale-110 active:scale-90 cursor-pointer flex-shrink-0 ml-2"
+ style={{ color: entry.whitelisted === 1 ? "#ef4444" : "#10b981" }}
+ >
+ {entry.whitelisted === 1 ? : }
+
+
+
+ )
+ })
+ )}
+
+
+
+
+
+ )
+ }
+
+ return (
+
+
+
+
+
+
+ {t("ui_title")}
+
+
+
{t("ui_subtitle")}
+
+
+ {validationError && (
+
+
+
+
+
+ )}
+
+
+
+
+
+ {t("current_status")}
+
+
+
+
+
+
+
+
+ {whitelistEnabled ? t("whitelist_enabled") : t("whitelist_disabled")}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {t("grace_period")}
+
+
+ {t("grace_period_desc")}
+
+
+
+
+
+ {t("grace_period_seconds")}
+
+ {
+ const value = Number.parseInt(e.target.value)
+ setGracePeriod(isNaN(value) ? 0 : value)
+ }}
+ className="border cursor-text transition-all duration-200"
+ style={{
+ background: "rgba(25, 25, 25, 0.95)",
+ borderColor: "rgba(251, 155, 4, 0.25)",
+ color: "#F2F2F2",
+ fontSize: "14px",
+ }}
+ min="0"
+ />
+
+
+
+
+ {t("kick_connected_label")}
+
+
+
+
+
+
+
+
+
+
+ {t("discord_notifications")}
+
+
+ {t("discord_notifications_desc")}
+
+
+
+
+
+ {t("webhook_url")}
+
+
setDiscordWebhook(e.target.value)}
+ className="cursor-text transition-all duration-200"
+ style={{
+ background: "rgba(25, 25, 25, 0.95)",
+ borderColor: "rgba(251, 155, 4, 0.25)",
+ color: "#F2F2F2",
+ fontSize: "14px",
+ }}
+ />
+ {discordWebhook === '***CONFIGURED***' && (
+
+ {t("webhook_configured")}
+
+ )}
+
+
+ {t("test_webhook")}
+
+
+
+
+
+
+
+
+
+
+
+ {t("discord_verification")}
+
+
+ {t("discord_verification_desc")}
+
+
+
+
+
+
+ {discordEnabled && (
+ <>
+
+
+
+
+ {t("discord_token_status")}
+
+
+ {hasBotToken ? t("discord_token_configured") : t("discord_token_missing")}
+
+
+
+
+ {!hasBotToken && (
+
+
+
+ {t("discord_token_warning")}
+
+
+ )}
+ >
+ )}
+
+
+
+
+ {t("guild_id")}
+
+ setDiscordGuildId(e.target.value)}
+ disabled={!discordEnabled}
+ className="cursor-text transition-all duration-200"
+ style={{
+ background: "rgba(25, 25, 25, 0.95)",
+ borderColor: "rgba(251, 155, 4, 0.25)",
+ color: "#F2F2F2",
+ fontSize: "14px",
+ opacity: discordEnabled ? 1 : 0.5,
+ }}
+ />
+
+
+
+ {t("role_id")}
+
+ setDiscordRoleId(e.target.value)}
+ disabled={!discordEnabled}
+ className="cursor-text transition-all duration-200"
+ style={{
+ background: "rgba(25, 25, 25, 0.95)",
+ borderColor: "rgba(251, 155, 4, 0.25)",
+ color: "#F2F2F2",
+ fontSize: "14px",
+ opacity: discordEnabled ? 1 : 0.5,
+ }}
+ />
+
+
+
+
+
+ {!discordEnabled && (
+
+
+
+
+
+
+ {t("manage_whitelist")}
+
+
+ {t("manage_whitelist_desc")}
+
+
+
+
+ {t("view_list")}
+
+
+
+
+
+
+ {t("action")}
+ setWhitelistAction(value as WhitelistAction)}>
+
+
+
+
+ {t("add_to_whitelist")}
+ {t("remove_from_whitelist")}
+
+
+
+
+
+
+ {t("identifier_type")}
+ {detectedType && (
+
+
+ {detectedType.toUpperCase()}
+
+ )}
+
+
setIdentifierType(value as IdentifierType)} disabled={!!detectedType}>
+
+
+
+
+ License
+ License2
+ Steam
+ Discord
+ Xbox Live
+ FiveM
+
+
+
+
+
+ {t("identifier_value")}
+ setPlayerIdentifier(e.target.value)}
+ className="cursor-text transition-all duration-200"
+ style={{
+ background: "rgba(25, 25, 25, 0.95)",
+ borderColor: detectedType ? "rgba(16, 185, 129, 0.4)" : "rgba(251, 155, 4, 0.25)",
+ color: "#F2F2F2",
+ fontSize: "14px",
+ }}
+ />
+
+
+
+
+ {t("apply")}
+
+
+
+
+
+ )}
+
+
+
+
+
+
+
+ {t("whitelist_rules")}
+
+
+ {t("whitelist_rules_desc")}
+
+
+
+ handleAddRule("admin-presence")}
+ size="sm"
+ className="font-medium transition-all duration-200 hover:scale-105 active:scale-95 cursor-pointer"
+ style={{
+ background: "linear-gradient(135deg, #FB9B04 0%, #ff8800 100%)",
+ color: "#161616",
+ fontSize: "13px",
+ boxShadow: "0 2px 8px rgba(251, 155, 4, 0.3)",
+ }}
+ >
+
+ {t("add_admin")}
+
+ handleAddRule("player-count")}
+ size="sm"
+ className="font-medium transition-all duration-200 hover:scale-105 active:scale-95 cursor-pointer"
+ style={{
+ background: "linear-gradient(135deg, #FB9B04 0%, #ff8800 100%)",
+ color: "#161616",
+ fontSize: "13px",
+ boxShadow: "0 2px 8px rgba(251, 155, 4, 0.3)",
+ }}
+ >
+
+ {t("add_players")}
+
+ handleAddRule("scheduled")}
+ size="sm"
+ className="font-medium transition-all duration-200 hover:scale-105 active:scale-95 cursor-pointer"
+ style={{
+ background: "linear-gradient(135deg, #FB9B04 0%, #ff8800 100%)",
+ color: "#161616",
+ fontSize: "13px",
+ boxShadow: "0 2px 8px rgba(251, 155, 4, 0.3)",
+ }}
+ >
+
+ {t("add_schedule")}
+
+
+
+
+
+ {rules.map((rule, index) => (
+
+
+
+
handleRuleToggle(rule.id, checked)} />
+
+ {getRuleIcon(rule.type)}
+
+ {getRuleTitle(rule.type)}
+
+
+
+ {t("priority")} {rule.priority}
+
+
+
handleRemoveRule(rule.id)}
+ className="transition-all duration-200 hover:scale-110 active:scale-90 cursor-pointer"
+ style={{ color: "#ef4444" }}
+ >
+
+
+
+
+ {(rule.type === "admin-presence" || rule.type === "player-count") && (
+
+
+ {t("condition")}
+ updateRule(rule.id, { operator: value as Operator })}
+ >
+
+
+
+
+
+ {t("less_than")}
+
+
+ {t("greater_than")}
+
+
+ {t("greater_equal")}
+
+
+ {t("equal_to")}
+
+
+
+
+
+
+ {rule.type === "admin-presence" ? t("admin_count") : t("player_count_label")}
+
+ updateRule(rule.id, { value: Number.parseInt(e.target.value) })}
+ className="cursor-text transition-all duration-200"
+ style={{
+ background: "rgba(25, 25, 25, 0.95)",
+ borderColor: "rgba(251, 155, 4, 0.25)",
+ color: "#F2F2F2",
+ fontSize: "14px",
+ }}
+ min="0"
+ />
+
+
+ {t("action")}
+ updateRule(rule.id, { action: value as Action })}
+ >
+
+
+
+
+
+ {t("enable_whitelist")}
+
+
+ {t("disable_whitelist")}
+
+
+
+
+
+ {t("priority")}
+ updateRule(rule.id, { priority: Number.parseInt(e.target.value) })}
+ className="cursor-text transition-all duration-200"
+ style={{
+ background: "rgba(25, 25, 25, 0.95)",
+ borderColor: "rgba(251, 155, 4, 0.25)",
+ color: "#F2F2F2",
+ fontSize: "14px",
+ }}
+ min="1"
+ />
+
+
+ )}
+
+ {rule.type === "scheduled" && (
+
+
+ {t("start_time")}
+ updateRule(rule.id, { startTime: e.target.value })}
+ className="cursor-text transition-all duration-200"
+ style={{
+ background: "rgba(25, 25, 25, 0.95)",
+ borderColor: "rgba(251, 155, 4, 0.25)",
+ color: "#F2F2F2",
+ fontSize: "14px",
+ }}
+ />
+
+
+ {t("end_time")}
+ updateRule(rule.id, { endTime: e.target.value })}
+ className="cursor-text transition-all duration-200"
+ style={{
+ background: "rgba(25, 25, 25, 0.95)",
+ borderColor: "rgba(251, 155, 4, 0.25)",
+ color: "#F2F2F2",
+ fontSize: "14px",
+ }}
+ />
+
+
+ {t("priority")}
+ updateRule(rule.id, { priority: Number.parseInt(e.target.value) })}
+ className="cursor-text transition-all duration-200"
+ style={{
+ background: "rgba(25, 25, 25, 0.95)",
+ borderColor: "rgba(251, 155, 4, 0.25)",
+ color: "#F2F2F2",
+ fontSize: "14px",
+ }}
+ min="1"
+ />
+
+
+ )}
+
+ ))}
+
+
+
+
+
+ {t("reset")}
+
+
+ {t("save_configuration")}
+
+
+
+
+ )
+}
\ No newline at end of file
diff --git a/[core]/esx_whitelist/web/components.json b/[core]/esx_whitelist/web/components.json
new file mode 100644
index 000000000..4ee62ee10
--- /dev/null
+++ b/[core]/esx_whitelist/web/components.json
@@ -0,0 +1,21 @@
+{
+ "$schema": "https://ui.shadcn.com/schema.json",
+ "style": "new-york",
+ "rsc": true,
+ "tsx": true,
+ "tailwind": {
+ "config": "",
+ "css": "app/globals.css",
+ "baseColor": "neutral",
+ "cssVariables": true,
+ "prefix": ""
+ },
+ "aliases": {
+ "components": "@/components",
+ "utils": "@/lib/utils",
+ "ui": "@/components/ui",
+ "lib": "@/lib",
+ "hooks": "@/hooks"
+ },
+ "iconLibrary": "lucide"
+}
diff --git a/[core]/esx_whitelist/web/components/theme-provider.tsx b/[core]/esx_whitelist/web/components/theme-provider.tsx
new file mode 100644
index 000000000..55c2f6eb6
--- /dev/null
+++ b/[core]/esx_whitelist/web/components/theme-provider.tsx
@@ -0,0 +1,11 @@
+'use client'
+
+import * as React from 'react'
+import {
+ ThemeProvider as NextThemesProvider,
+ type ThemeProviderProps,
+} from 'next-themes'
+
+export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
+ return {children}
+}
diff --git a/[core]/esx_whitelist/web/components/ui/badge.tsx b/[core]/esx_whitelist/web/components/ui/badge.tsx
new file mode 100644
index 000000000..fc4126b7a
--- /dev/null
+++ b/[core]/esx_whitelist/web/components/ui/badge.tsx
@@ -0,0 +1,46 @@
+import * as React from 'react'
+import { Slot } from '@radix-ui/react-slot'
+import { cva, type VariantProps } from 'class-variance-authority'
+
+import { cn } from '@/lib/utils'
+
+const badgeVariants = cva(
+ 'inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden',
+ {
+ variants: {
+ variant: {
+ default:
+ 'border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90',
+ secondary:
+ 'border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90',
+ destructive:
+ 'border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60',
+ outline:
+ 'text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground',
+ },
+ },
+ defaultVariants: {
+ variant: 'default',
+ },
+ },
+)
+
+function Badge({
+ className,
+ variant,
+ asChild = false,
+ ...props
+}: React.ComponentProps<'span'> &
+ VariantProps & { asChild?: boolean }) {
+ const Comp = asChild ? Slot : 'span'
+
+ return (
+
+ )
+}
+
+export { Badge, badgeVariants }
diff --git a/[core]/esx_whitelist/web/components/ui/button.tsx b/[core]/esx_whitelist/web/components/ui/button.tsx
new file mode 100644
index 000000000..f64632d15
--- /dev/null
+++ b/[core]/esx_whitelist/web/components/ui/button.tsx
@@ -0,0 +1,60 @@
+import * as React from 'react'
+import { Slot } from '@radix-ui/react-slot'
+import { cva, type VariantProps } from 'class-variance-authority'
+
+import { cn } from '@/lib/utils'
+
+const buttonVariants = cva(
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
+ {
+ variants: {
+ variant: {
+ default: 'bg-primary text-primary-foreground hover:bg-primary/90',
+ destructive:
+ 'bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60',
+ outline:
+ 'border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50',
+ secondary:
+ 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
+ ghost:
+ 'hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50',
+ link: 'text-primary underline-offset-4 hover:underline',
+ },
+ size: {
+ default: 'h-9 px-4 py-2 has-[>svg]:px-3',
+ sm: 'h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5',
+ lg: 'h-10 rounded-md px-6 has-[>svg]:px-4',
+ icon: 'size-9',
+ 'icon-sm': 'size-8',
+ 'icon-lg': 'size-10',
+ },
+ },
+ defaultVariants: {
+ variant: 'default',
+ size: 'default',
+ },
+ },
+)
+
+function Button({
+ className,
+ variant,
+ size,
+ asChild = false,
+ ...props
+}: React.ComponentProps<'button'> &
+ VariantProps & {
+ asChild?: boolean
+ }) {
+ const Comp = asChild ? Slot : 'button'
+
+ return (
+
+ )
+}
+
+export { Button, buttonVariants }
diff --git a/[core]/esx_whitelist/web/components/ui/card.tsx b/[core]/esx_whitelist/web/components/ui/card.tsx
new file mode 100644
index 000000000..db7dd3cc9
--- /dev/null
+++ b/[core]/esx_whitelist/web/components/ui/card.tsx
@@ -0,0 +1,92 @@
+import * as React from 'react'
+
+import { cn } from '@/lib/utils'
+
+function Card({ className, ...props }: React.ComponentProps<'div'>) {
+ return (
+
+ )
+}
+
+function CardHeader({ className, ...props }: React.ComponentProps<'div'>) {
+ return (
+
+ )
+}
+
+function CardTitle({ className, ...props }: React.ComponentProps<'div'>) {
+ return (
+
+ )
+}
+
+function CardDescription({ className, ...props }: React.ComponentProps<'div'>) {
+ return (
+
+ )
+}
+
+function CardAction({ className, ...props }: React.ComponentProps<'div'>) {
+ return (
+
+ )
+}
+
+function CardContent({ className, ...props }: React.ComponentProps<'div'>) {
+ return (
+
+ )
+}
+
+function CardFooter({ className, ...props }: React.ComponentProps<'div'>) {
+ return (
+
+ )
+}
+
+export {
+ Card,
+ CardHeader,
+ CardFooter,
+ CardTitle,
+ CardAction,
+ CardDescription,
+ CardContent,
+}
diff --git a/[core]/esx_whitelist/web/components/ui/input.tsx b/[core]/esx_whitelist/web/components/ui/input.tsx
new file mode 100644
index 000000000..c7780e847
--- /dev/null
+++ b/[core]/esx_whitelist/web/components/ui/input.tsx
@@ -0,0 +1,25 @@
+import * as React from 'react'
+
+import { cn } from '@/lib/utils'
+
+function Input({ className, type, value, ...props }: React.ComponentProps<'input'>) {
+ // Convert NaN values to empty string for controlled inputs
+ const safeValue = typeof value === 'number' && isNaN(value) ? '' : value
+
+ return (
+
+ )
+}
+
+export { Input }
diff --git a/[core]/esx_whitelist/web/components/ui/label.tsx b/[core]/esx_whitelist/web/components/ui/label.tsx
new file mode 100644
index 000000000..5d66da2c6
--- /dev/null
+++ b/[core]/esx_whitelist/web/components/ui/label.tsx
@@ -0,0 +1,24 @@
+'use client'
+
+import * as React from 'react'
+import * as LabelPrimitive from '@radix-ui/react-label'
+
+import { cn } from '@/lib/utils'
+
+function Label({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+export { Label }
diff --git a/[core]/esx_whitelist/web/components/ui/select.tsx b/[core]/esx_whitelist/web/components/ui/select.tsx
new file mode 100644
index 000000000..5db0bcac0
--- /dev/null
+++ b/[core]/esx_whitelist/web/components/ui/select.tsx
@@ -0,0 +1,185 @@
+'use client'
+
+import * as React from 'react'
+import * as SelectPrimitive from '@radix-ui/react-select'
+import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from 'lucide-react'
+
+import { cn } from '@/lib/utils'
+
+function Select({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function SelectGroup({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function SelectValue({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function SelectTrigger({
+ className,
+ size = 'default',
+ children,
+ ...props
+}: React.ComponentProps & {
+ size?: 'sm' | 'default'
+}) {
+ return (
+
+ {children}
+
+
+
+
+ )
+}
+
+function SelectContent({
+ className,
+ children,
+ position = 'popper',
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+
+ {children}
+
+
+
+
+ )
+}
+
+function SelectLabel({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function SelectItem({
+ className,
+ children,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+
+
+
+ {children}
+
+ )
+}
+
+function SelectSeparator({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function SelectScrollUpButton({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+ )
+}
+
+function SelectScrollDownButton({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+ )
+}
+
+export {
+ Select,
+ SelectContent,
+ SelectGroup,
+ SelectItem,
+ SelectLabel,
+ SelectScrollDownButton,
+ SelectScrollUpButton,
+ SelectSeparator,
+ SelectTrigger,
+ SelectValue,
+}
diff --git a/[core]/esx_whitelist/web/components/ui/separator.tsx b/[core]/esx_whitelist/web/components/ui/separator.tsx
new file mode 100644
index 000000000..f43aaf551
--- /dev/null
+++ b/[core]/esx_whitelist/web/components/ui/separator.tsx
@@ -0,0 +1,28 @@
+'use client'
+
+import * as React from 'react'
+import * as SeparatorPrimitive from '@radix-ui/react-separator'
+
+import { cn } from '@/lib/utils'
+
+function Separator({
+ className,
+ orientation = 'horizontal',
+ decorative = true,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+export { Separator }
diff --git a/[core]/esx_whitelist/web/components/ui/switch.tsx b/[core]/esx_whitelist/web/components/ui/switch.tsx
new file mode 100644
index 000000000..3c4cfa325
--- /dev/null
+++ b/[core]/esx_whitelist/web/components/ui/switch.tsx
@@ -0,0 +1,31 @@
+'use client'
+
+import * as React from 'react'
+import * as SwitchPrimitive from '@radix-ui/react-switch'
+
+import { cn } from '@/lib/utils'
+
+function Switch({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+ )
+}
+
+export { Switch }
diff --git a/[core]/esx_whitelist/web/components/ui/use-mobile.tsx b/[core]/esx_whitelist/web/components/ui/use-mobile.tsx
new file mode 100644
index 000000000..4331d5c56
--- /dev/null
+++ b/[core]/esx_whitelist/web/components/ui/use-mobile.tsx
@@ -0,0 +1,19 @@
+import * as React from 'react'
+
+const MOBILE_BREAKPOINT = 768
+
+export function useIsMobile() {
+ const [isMobile, setIsMobile] = React.useState(undefined)
+
+ React.useEffect(() => {
+ const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`)
+ const onChange = () => {
+ setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
+ }
+ mql.addEventListener('change', onChange)
+ setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
+ return () => mql.removeEventListener('change', onChange)
+ }, [])
+
+ return !!isMobile
+}
diff --git a/[core]/esx_whitelist/web/hooks/useKeyPress.ts b/[core]/esx_whitelist/web/hooks/useKeyPress.ts
new file mode 100644
index 000000000..fa97ce953
--- /dev/null
+++ b/[core]/esx_whitelist/web/hooks/useKeyPress.ts
@@ -0,0 +1,31 @@
+import { useEffect, useState } from 'react'
+
+export const useKeyPress = (targetKey: string) => {
+ const [keyPressed, setKeyPressed] = useState(false)
+
+ useEffect(() => {
+ const downHandler = (e: KeyboardEvent) => {
+ if (e.key === targetKey) {
+ e.preventDefault()
+ setKeyPressed(true)
+ }
+ }
+
+ const upHandler = (e: KeyboardEvent) => {
+ if (e.key === targetKey) {
+ e.preventDefault()
+ setKeyPressed(false)
+ }
+ }
+
+ window.addEventListener('keydown', downHandler)
+ window.addEventListener('keyup', upHandler)
+
+ return () => {
+ window.removeEventListener('keydown', downHandler)
+ window.removeEventListener('keyup', upHandler)
+ }
+ }, [targetKey])
+
+ return keyPressed
+}
\ No newline at end of file
diff --git a/[core]/esx_whitelist/web/lib/utils.ts b/[core]/esx_whitelist/web/lib/utils.ts
new file mode 100644
index 000000000..fed2fe91e
--- /dev/null
+++ b/[core]/esx_whitelist/web/lib/utils.ts
@@ -0,0 +1,6 @@
+import { clsx, type ClassValue } from 'clsx'
+import { twMerge } from 'tailwind-merge'
+
+export function cn(...inputs: ClassValue[]) {
+ return twMerge(clsx(inputs))
+}
diff --git a/[core]/esx_whitelist/web/next-env.d.ts b/[core]/esx_whitelist/web/next-env.d.ts
new file mode 100644
index 000000000..1b3be0840
--- /dev/null
+++ b/[core]/esx_whitelist/web/next-env.d.ts
@@ -0,0 +1,5 @@
+///
+///
+
+// NOTE: This file should not be edited
+// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
diff --git a/[core]/esx_whitelist/web/next.config.mjs b/[core]/esx_whitelist/web/next.config.mjs
new file mode 100644
index 000000000..8731c96e7
--- /dev/null
+++ b/[core]/esx_whitelist/web/next.config.mjs
@@ -0,0 +1,12 @@
+/** @type {import('next').NextConfig} */
+const nextConfig = {
+ output: 'export',
+ distDir: 'dist',
+ images: {
+ unoptimized: true,
+ },
+ trailingSlash: true,
+ assetPrefix: './',
+}
+
+export default nextConfig
\ No newline at end of file
diff --git a/[core]/esx_whitelist/web/package-lock.json b/[core]/esx_whitelist/web/package-lock.json
new file mode 100644
index 000000000..6949ef01f
--- /dev/null
+++ b/[core]/esx_whitelist/web/package-lock.json
@@ -0,0 +1,3997 @@
+{
+ "name": "esx_whitelist",
+ "version": "0.1.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "esx_whitelist",
+ "version": "0.1.0",
+ "dependencies": {
+ "@hookform/resolvers": "^3.10.0",
+ "@radix-ui/react-accordion": "1.2.2",
+ "@radix-ui/react-alert-dialog": "1.1.4",
+ "@radix-ui/react-aspect-ratio": "1.1.1",
+ "@radix-ui/react-avatar": "1.1.2",
+ "@radix-ui/react-checkbox": "1.1.3",
+ "@radix-ui/react-collapsible": "1.1.2",
+ "@radix-ui/react-context-menu": "2.2.4",
+ "@radix-ui/react-dialog": "1.1.4",
+ "@radix-ui/react-dropdown-menu": "2.1.4",
+ "@radix-ui/react-hover-card": "1.1.4",
+ "@radix-ui/react-label": "2.1.1",
+ "@radix-ui/react-menubar": "1.1.4",
+ "@radix-ui/react-navigation-menu": "1.2.3",
+ "@radix-ui/react-popover": "1.1.4",
+ "@radix-ui/react-progress": "1.1.1",
+ "@radix-ui/react-radio-group": "1.2.2",
+ "@radix-ui/react-scroll-area": "1.2.2",
+ "@radix-ui/react-select": "2.1.4",
+ "@radix-ui/react-separator": "1.1.1",
+ "@radix-ui/react-slider": "1.2.2",
+ "@radix-ui/react-slot": "1.1.1",
+ "@radix-ui/react-switch": "1.1.2",
+ "@radix-ui/react-tabs": "1.1.2",
+ "@radix-ui/react-toast": "1.2.4",
+ "@radix-ui/react-toggle": "1.1.1",
+ "@radix-ui/react-toggle-group": "1.1.1",
+ "@radix-ui/react-tooltip": "1.1.6",
+ "@vercel/analytics": "latest",
+ "autoprefixer": "^10.4.20",
+ "class-variance-authority": "^0.7.1",
+ "clsx": "^2.1.1",
+ "cmdk": "1.0.4",
+ "date-fns": "4.1.0",
+ "embla-carousel-react": "8.5.1",
+ "geist": "latest",
+ "input-otp": "1.4.1",
+ "lucide-react": "^0.454.0",
+ "next": "15.2.4",
+ "next-themes": "^0.4.6",
+ "react": "^19",
+ "react-day-picker": "9.8.0",
+ "react-dom": "^19",
+ "react-hook-form": "^7.60.0",
+ "react-resizable-panels": "^2.1.7",
+ "recharts": "2.15.4",
+ "sonner": "^1.7.4",
+ "tailwind-merge": "^2.5.5",
+ "tailwindcss-animate": "^1.0.7",
+ "vaul": "^1.1.1",
+ "zod": "3.25.67"
+ },
+ "devDependencies": {
+ "@tailwindcss/postcss": "^4.1.9",
+ "@types/node": "^22",
+ "@types/react": "^19",
+ "@types/react-dom": "^19",
+ "postcss": "^8.5",
+ "tailwindcss": "^4.1.9",
+ "tw-animate-css": "1.3.3",
+ "typescript": "^5"
+ }
+ },
+ "node_modules/@alloc/quick-lru": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
+ "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@babel/runtime": {
+ "version": "7.28.4",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz",
+ "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@date-fns/tz": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@date-fns/tz/-/tz-1.2.0.tgz",
+ "integrity": "sha512-LBrd7MiJZ9McsOgxqWX7AaxrDjcFVjWH/tIKJd7pnR7McaslGYOP1QmmiBXdJH/H/yLCT+rcQ7FaPBUxRGUtrg==",
+ "license": "MIT"
+ },
+ "node_modules/@emnapi/runtime": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.6.0.tgz",
+ "integrity": "sha512-obtUmAHTMjll499P+D9A3axeJFlhdjOWdKUNs/U6QIGT7V5RjcUW1xToAzjvmgTSQhDbYn/NwfTRoJcQ2rNBxA==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@floating-ui/core": {
+ "version": "1.7.3",
+ "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.3.tgz",
+ "integrity": "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/utils": "^0.2.10"
+ }
+ },
+ "node_modules/@floating-ui/dom": {
+ "version": "1.7.4",
+ "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.4.tgz",
+ "integrity": "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/core": "^1.7.3",
+ "@floating-ui/utils": "^0.2.10"
+ }
+ },
+ "node_modules/@floating-ui/react-dom": {
+ "version": "2.1.6",
+ "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.6.tgz",
+ "integrity": "sha512-4JX6rEatQEvlmgU80wZyq9RT96HZJa88q8hp0pBd+LrczeDI4o6uA2M+uvxngVHo4Ihr8uibXxH6+70zhAFrVw==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/dom": "^1.7.4"
+ },
+ "peerDependencies": {
+ "react": ">=16.8.0",
+ "react-dom": ">=16.8.0"
+ }
+ },
+ "node_modules/@floating-ui/utils": {
+ "version": "0.2.10",
+ "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz",
+ "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==",
+ "license": "MIT"
+ },
+ "node_modules/@hookform/resolvers": {
+ "version": "3.10.0",
+ "resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-3.10.0.tgz",
+ "integrity": "sha512-79Dv+3mDF7i+2ajj7SkypSKHhl1cbln1OGavqrsF7p6mbUv11xpqpacPsGDCTRvCSjEEIez2ef1NveSVL3b0Ag==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react-hook-form": "^7.0.0"
+ }
+ },
+ "node_modules/@img/sharp-darwin-arm64": {
+ "version": "0.33.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz",
+ "integrity": "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-darwin-arm64": "1.0.4"
+ }
+ },
+ "node_modules/@img/sharp-darwin-x64": {
+ "version": "0.33.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz",
+ "integrity": "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-darwin-x64": "1.0.4"
+ }
+ },
+ "node_modules/@img/sharp-libvips-darwin-arm64": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz",
+ "integrity": "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-darwin-x64": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz",
+ "integrity": "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-arm": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz",
+ "integrity": "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-arm64": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz",
+ "integrity": "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-s390x": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.4.tgz",
+ "integrity": "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==",
+ "cpu": [
+ "s390x"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-x64": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz",
+ "integrity": "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linuxmusl-arm64": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz",
+ "integrity": "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linuxmusl-x64": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz",
+ "integrity": "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-linux-arm": {
+ "version": "0.33.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz",
+ "integrity": "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-arm": "1.0.5"
+ }
+ },
+ "node_modules/@img/sharp-linux-arm64": {
+ "version": "0.33.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz",
+ "integrity": "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-arm64": "1.0.4"
+ }
+ },
+ "node_modules/@img/sharp-linux-s390x": {
+ "version": "0.33.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.5.tgz",
+ "integrity": "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==",
+ "cpu": [
+ "s390x"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-s390x": "1.0.4"
+ }
+ },
+ "node_modules/@img/sharp-linux-x64": {
+ "version": "0.33.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz",
+ "integrity": "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-x64": "1.0.4"
+ }
+ },
+ "node_modules/@img/sharp-linuxmusl-arm64": {
+ "version": "0.33.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz",
+ "integrity": "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linuxmusl-arm64": "1.0.4"
+ }
+ },
+ "node_modules/@img/sharp-linuxmusl-x64": {
+ "version": "0.33.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz",
+ "integrity": "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linuxmusl-x64": "1.0.4"
+ }
+ },
+ "node_modules/@img/sharp-wasm32": {
+ "version": "0.33.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.5.tgz",
+ "integrity": "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==",
+ "cpu": [
+ "wasm32"
+ ],
+ "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/runtime": "^1.2.0"
+ },
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-win32-ia32": {
+ "version": "0.33.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.5.tgz",
+ "integrity": "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==",
+ "cpu": [
+ "ia32"
+ ],
+ "license": "Apache-2.0 AND LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-win32-x64": {
+ "version": "0.33.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz",
+ "integrity": "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "Apache-2.0 AND LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@jridgewell/gen-mapping": {
+ "version": "0.3.13",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
+ "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.0",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/remapping": {
+ "version": "2.3.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
+ "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@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.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
+ "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.31",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
+ "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.1.0",
+ "@jridgewell/sourcemap-codec": "^1.4.14"
+ }
+ },
+ "node_modules/@next/env": {
+ "version": "15.2.4",
+ "resolved": "https://registry.npmjs.org/@next/env/-/env-15.2.4.tgz",
+ "integrity": "sha512-+SFtMgoiYP3WoSswuNmxJOCwi06TdWE733D+WPjpXIe4LXGULwEaofiiAy6kbS0+XjM5xF5n3lKuBwN2SnqD9g==",
+ "license": "MIT"
+ },
+ "node_modules/@next/swc-darwin-arm64": {
+ "version": "15.2.4",
+ "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.2.4.tgz",
+ "integrity": "sha512-1AnMfs655ipJEDC/FHkSr0r3lXBgpqKo4K1kiwfUf3iE68rDFXZ1TtHdMvf7D0hMItgDZ7Vuq3JgNMbt/+3bYw==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-darwin-x64": {
+ "version": "15.2.4",
+ "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.2.4.tgz",
+ "integrity": "sha512-3qK2zb5EwCwxnO2HeO+TRqCubeI/NgCe+kL5dTJlPldV/uwCnUgC7VbEzgmxbfrkbjehL4H9BPztWOEtsoMwew==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-linux-arm64-gnu": {
+ "version": "15.2.4",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.2.4.tgz",
+ "integrity": "sha512-HFN6GKUcrTWvem8AZN7tT95zPb0GUGv9v0d0iyuTb303vbXkkbHDp/DxufB04jNVD+IN9yHy7y/6Mqq0h0YVaQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-linux-arm64-musl": {
+ "version": "15.2.4",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.2.4.tgz",
+ "integrity": "sha512-Oioa0SORWLwi35/kVB8aCk5Uq+5/ZIumMK1kJV+jSdazFm2NzPDztsefzdmzzpx5oGCJ6FkUC7vkaUseNTStNA==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-linux-x64-gnu": {
+ "version": "15.2.4",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.2.4.tgz",
+ "integrity": "sha512-yb5WTRaHdkgOqFOZiu6rHV1fAEK0flVpaIN2HB6kxHVSy/dIajWbThS7qON3W9/SNOH2JWkVCyulgGYekMePuw==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-linux-x64-musl": {
+ "version": "15.2.4",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.2.4.tgz",
+ "integrity": "sha512-Dcdv/ix6srhkM25fgXiyOieFUkz+fOYkHlydWCtB0xMST6X9XYI3yPDKBZt1xuhOytONsIFJFB08xXYsxUwJLw==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-win32-arm64-msvc": {
+ "version": "15.2.4",
+ "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.2.4.tgz",
+ "integrity": "sha512-dW0i7eukvDxtIhCYkMrZNQfNicPDExt2jPb9AZPpL7cfyUo7QSNl1DjsHjmmKp6qNAqUESyT8YFl/Aw91cNJJg==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-win32-x64-msvc": {
+ "version": "15.2.4",
+ "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.2.4.tgz",
+ "integrity": "sha512-SbnWkJmkS7Xl3kre8SdMF6F/XDh1DTFEhp0jRTj/uB8iPKoU2bb2NDfcu+iifv1+mxQEd1g2vvSxcZbXSKyWiQ==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@radix-ui/number": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.0.tgz",
+ "integrity": "sha512-V3gRzhVNU1ldS5XhAPTom1fOIo4ccrjjJgmE+LI2h/WaFpHmx0MQApT+KZHnx8abG6Avtfcz4WoEciMnpFT3HQ==",
+ "license": "MIT"
+ },
+ "node_modules/@radix-ui/primitive": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.1.tgz",
+ "integrity": "sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA==",
+ "license": "MIT"
+ },
+ "node_modules/@radix-ui/react-accordion": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-accordion/-/react-accordion-1.2.2.tgz",
+ "integrity": "sha512-b1oh54x4DMCdGsB4/7ahiSrViXxaBwRPotiZNnYXjLha9vfuURSAZErki6qjDoSIV0eXx5v57XnTGVtGwnfp2g==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.1",
+ "@radix-ui/react-collapsible": "1.1.2",
+ "@radix-ui/react-collection": "1.1.1",
+ "@radix-ui/react-compose-refs": "1.1.1",
+ "@radix-ui/react-context": "1.1.1",
+ "@radix-ui/react-direction": "1.1.0",
+ "@radix-ui/react-id": "1.1.0",
+ "@radix-ui/react-primitive": "2.0.1",
+ "@radix-ui/react-use-controllable-state": "1.1.0"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-alert-dialog": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-alert-dialog/-/react-alert-dialog-1.1.4.tgz",
+ "integrity": "sha512-A6Kh23qZDLy3PSU4bh2UJZznOrUdHImIXqF8YtUa6CN73f8EOO9XlXSCd9IHyPvIquTaa/kwaSWzZTtUvgXVGw==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.1",
+ "@radix-ui/react-compose-refs": "1.1.1",
+ "@radix-ui/react-context": "1.1.1",
+ "@radix-ui/react-dialog": "1.1.4",
+ "@radix-ui/react-primitive": "2.0.1",
+ "@radix-ui/react-slot": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-arrow": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.1.tgz",
+ "integrity": "sha512-NaVpZfmv8SKeZbn4ijN2V3jlHA9ngBG16VnIIm22nUR0Yk8KUALyBxT3KYEUnNuch9sTE8UTsS3whzBgKOL30w==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-primitive": "2.0.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-aspect-ratio": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-aspect-ratio/-/react-aspect-ratio-1.1.1.tgz",
+ "integrity": "sha512-kNU4FIpcFMBLkOUcgeIteH06/8JLBcYY6Le1iKenDGCYNYFX3TQqCZjzkOsz37h7r94/99GTb7YhEr98ZBJibw==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-primitive": "2.0.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-avatar": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-avatar/-/react-avatar-1.1.2.tgz",
+ "integrity": "sha512-GaC7bXQZ5VgZvVvsJ5mu/AEbjYLnhhkoidOboC50Z6FFlLA03wG2ianUoH+zgDQ31/9gCF59bE4+2bBgTyMiig==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-context": "1.1.1",
+ "@radix-ui/react-primitive": "2.0.1",
+ "@radix-ui/react-use-callback-ref": "1.1.0",
+ "@radix-ui/react-use-layout-effect": "1.1.0"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-checkbox": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.1.3.tgz",
+ "integrity": "sha512-HD7/ocp8f1B3e6OHygH0n7ZKjONkhciy1Nh0yuBgObqThc3oyx+vuMfFHKAknXRHHWVE9XvXStxJFyjUmB8PIw==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.1",
+ "@radix-ui/react-compose-refs": "1.1.1",
+ "@radix-ui/react-context": "1.1.1",
+ "@radix-ui/react-presence": "1.1.2",
+ "@radix-ui/react-primitive": "2.0.1",
+ "@radix-ui/react-use-controllable-state": "1.1.0",
+ "@radix-ui/react-use-previous": "1.1.0",
+ "@radix-ui/react-use-size": "1.1.0"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-collapsible": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-collapsible/-/react-collapsible-1.1.2.tgz",
+ "integrity": "sha512-PliMB63vxz7vggcyq0IxNYk8vGDrLXVWw4+W4B8YnwI1s18x7YZYqlG9PLX7XxAJUi0g2DxP4XKJMFHh/iVh9A==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.1",
+ "@radix-ui/react-compose-refs": "1.1.1",
+ "@radix-ui/react-context": "1.1.1",
+ "@radix-ui/react-id": "1.1.0",
+ "@radix-ui/react-presence": "1.1.2",
+ "@radix-ui/react-primitive": "2.0.1",
+ "@radix-ui/react-use-controllable-state": "1.1.0",
+ "@radix-ui/react-use-layout-effect": "1.1.0"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-collection": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.1.tgz",
+ "integrity": "sha512-LwT3pSho9Dljg+wY2KN2mrrh6y3qELfftINERIzBUO9e0N+t0oMTyn3k9iv+ZqgrwGkRnLpNJrsMv9BZlt2yuA==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.1",
+ "@radix-ui/react-context": "1.1.1",
+ "@radix-ui/react-primitive": "2.0.1",
+ "@radix-ui/react-slot": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-compose-refs": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.1.tgz",
+ "integrity": "sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-context": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.1.tgz",
+ "integrity": "sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-context-menu": {
+ "version": "2.2.4",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-context-menu/-/react-context-menu-2.2.4.tgz",
+ "integrity": "sha512-ap4wdGwK52rJxGkwukU1NrnEodsUFQIooANKu+ey7d6raQ2biTcEf8za1zr0mgFHieevRTB2nK4dJeN8pTAZGQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.1",
+ "@radix-ui/react-context": "1.1.1",
+ "@radix-ui/react-menu": "2.1.4",
+ "@radix-ui/react-primitive": "2.0.1",
+ "@radix-ui/react-use-callback-ref": "1.1.0",
+ "@radix-ui/react-use-controllable-state": "1.1.0"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-dialog": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.4.tgz",
+ "integrity": "sha512-Ur7EV1IwQGCyaAuyDRiOLA5JIUZxELJljF+MbM/2NC0BYwfuRrbpS30BiQBJrVruscgUkieKkqXYDOoByaxIoA==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.1",
+ "@radix-ui/react-compose-refs": "1.1.1",
+ "@radix-ui/react-context": "1.1.1",
+ "@radix-ui/react-dismissable-layer": "1.1.3",
+ "@radix-ui/react-focus-guards": "1.1.1",
+ "@radix-ui/react-focus-scope": "1.1.1",
+ "@radix-ui/react-id": "1.1.0",
+ "@radix-ui/react-portal": "1.1.3",
+ "@radix-ui/react-presence": "1.1.2",
+ "@radix-ui/react-primitive": "2.0.1",
+ "@radix-ui/react-slot": "1.1.1",
+ "@radix-ui/react-use-controllable-state": "1.1.0",
+ "aria-hidden": "^1.1.1",
+ "react-remove-scroll": "^2.6.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-direction": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.0.tgz",
+ "integrity": "sha512-BUuBvgThEiAXh2DWu93XsT+a3aWrGqolGlqqw5VU1kG7p/ZH2cuDlM1sRLNnY3QcBS69UIz2mcKhMxDsdewhjg==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-dismissable-layer": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.3.tgz",
+ "integrity": "sha512-onrWn/72lQoEucDmJnr8uczSNTujT0vJnA/X5+3AkChVPowr8n1yvIKIabhWyMQeMvvmdpsvcyDqx3X1LEXCPg==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.1",
+ "@radix-ui/react-compose-refs": "1.1.1",
+ "@radix-ui/react-primitive": "2.0.1",
+ "@radix-ui/react-use-callback-ref": "1.1.0",
+ "@radix-ui/react-use-escape-keydown": "1.1.0"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-dropdown-menu": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.4.tgz",
+ "integrity": "sha512-iXU1Ab5ecM+yEepGAWK8ZhMyKX4ubFdCNtol4sT9D0OVErG9PNElfx3TQhjw7n7BC5nFVz68/5//clWy+8TXzA==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.1",
+ "@radix-ui/react-compose-refs": "1.1.1",
+ "@radix-ui/react-context": "1.1.1",
+ "@radix-ui/react-id": "1.1.0",
+ "@radix-ui/react-menu": "2.1.4",
+ "@radix-ui/react-primitive": "2.0.1",
+ "@radix-ui/react-use-controllable-state": "1.1.0"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-focus-guards": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.1.tgz",
+ "integrity": "sha512-pSIwfrT1a6sIoDASCSpFwOasEwKTZWDw/iBdtnqKO7v6FeOzYJ7U53cPzYFVR3geGGXgVHaH+CdngrrAzqUGxg==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-focus-scope": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.1.tgz",
+ "integrity": "sha512-01omzJAYRxXdG2/he/+xy+c8a8gCydoQ1yOxnWNcRhrrBW5W+RQJ22EK1SaO8tb3WoUsuEw7mJjBozPzihDFjA==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.1",
+ "@radix-ui/react-primitive": "2.0.1",
+ "@radix-ui/react-use-callback-ref": "1.1.0"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-hover-card": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-hover-card/-/react-hover-card-1.1.4.tgz",
+ "integrity": "sha512-QSUUnRA3PQ2UhvoCv3eYvMnCAgGQW+sTu86QPuNb+ZMi+ZENd6UWpiXbcWDQ4AEaKF9KKpCHBeaJz9Rw6lRlaQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.1",
+ "@radix-ui/react-compose-refs": "1.1.1",
+ "@radix-ui/react-context": "1.1.1",
+ "@radix-ui/react-dismissable-layer": "1.1.3",
+ "@radix-ui/react-popper": "1.2.1",
+ "@radix-ui/react-portal": "1.1.3",
+ "@radix-ui/react-presence": "1.1.2",
+ "@radix-ui/react-primitive": "2.0.1",
+ "@radix-ui/react-use-controllable-state": "1.1.0"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-id": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.0.tgz",
+ "integrity": "sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-use-layout-effect": "1.1.0"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-label": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.1.tgz",
+ "integrity": "sha512-UUw5E4e/2+4kFMH7+YxORXGWggtY6sM8WIwh5RZchhLuUg2H1hc98Py+pr8HMz6rdaYrK2t296ZEjYLOCO5uUw==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-primitive": "2.0.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-menu": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.4.tgz",
+ "integrity": "sha512-BnOgVoL6YYdHAG6DtXONaR29Eq4nvbi8rutrV/xlr3RQCMMb3yqP85Qiw/3NReozrSW+4dfLkK+rc1hb4wPU/A==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.1",
+ "@radix-ui/react-collection": "1.1.1",
+ "@radix-ui/react-compose-refs": "1.1.1",
+ "@radix-ui/react-context": "1.1.1",
+ "@radix-ui/react-direction": "1.1.0",
+ "@radix-ui/react-dismissable-layer": "1.1.3",
+ "@radix-ui/react-focus-guards": "1.1.1",
+ "@radix-ui/react-focus-scope": "1.1.1",
+ "@radix-ui/react-id": "1.1.0",
+ "@radix-ui/react-popper": "1.2.1",
+ "@radix-ui/react-portal": "1.1.3",
+ "@radix-ui/react-presence": "1.1.2",
+ "@radix-ui/react-primitive": "2.0.1",
+ "@radix-ui/react-roving-focus": "1.1.1",
+ "@radix-ui/react-slot": "1.1.1",
+ "@radix-ui/react-use-callback-ref": "1.1.0",
+ "aria-hidden": "^1.1.1",
+ "react-remove-scroll": "^2.6.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-menubar": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-menubar/-/react-menubar-1.1.4.tgz",
+ "integrity": "sha512-+KMpi7VAZuB46+1LD7a30zb5IxyzLgC8m8j42gk3N4TUCcViNQdX8FhoH1HDvYiA8quuqcek4R4bYpPn/SY1GA==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.1",
+ "@radix-ui/react-collection": "1.1.1",
+ "@radix-ui/react-compose-refs": "1.1.1",
+ "@radix-ui/react-context": "1.1.1",
+ "@radix-ui/react-direction": "1.1.0",
+ "@radix-ui/react-id": "1.1.0",
+ "@radix-ui/react-menu": "2.1.4",
+ "@radix-ui/react-primitive": "2.0.1",
+ "@radix-ui/react-roving-focus": "1.1.1",
+ "@radix-ui/react-use-controllable-state": "1.1.0"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-navigation-menu": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-navigation-menu/-/react-navigation-menu-1.2.3.tgz",
+ "integrity": "sha512-IQWAsQ7dsLIYDrn0WqPU+cdM7MONTv9nqrLVYoie3BPiabSfUVDe6Fr+oEt0Cofsr9ONDcDe9xhmJbL1Uq1yKg==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.1",
+ "@radix-ui/react-collection": "1.1.1",
+ "@radix-ui/react-compose-refs": "1.1.1",
+ "@radix-ui/react-context": "1.1.1",
+ "@radix-ui/react-direction": "1.1.0",
+ "@radix-ui/react-dismissable-layer": "1.1.3",
+ "@radix-ui/react-id": "1.1.0",
+ "@radix-ui/react-presence": "1.1.2",
+ "@radix-ui/react-primitive": "2.0.1",
+ "@radix-ui/react-use-callback-ref": "1.1.0",
+ "@radix-ui/react-use-controllable-state": "1.1.0",
+ "@radix-ui/react-use-layout-effect": "1.1.0",
+ "@radix-ui/react-use-previous": "1.1.0",
+ "@radix-ui/react-visually-hidden": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-popover": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.4.tgz",
+ "integrity": "sha512-aUACAkXx8LaFymDma+HQVji7WhvEhpFJ7+qPz17Nf4lLZqtreGOFRiNQWQmhzp7kEWg9cOyyQJpdIMUMPc/CPw==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.1",
+ "@radix-ui/react-compose-refs": "1.1.1",
+ "@radix-ui/react-context": "1.1.1",
+ "@radix-ui/react-dismissable-layer": "1.1.3",
+ "@radix-ui/react-focus-guards": "1.1.1",
+ "@radix-ui/react-focus-scope": "1.1.1",
+ "@radix-ui/react-id": "1.1.0",
+ "@radix-ui/react-popper": "1.2.1",
+ "@radix-ui/react-portal": "1.1.3",
+ "@radix-ui/react-presence": "1.1.2",
+ "@radix-ui/react-primitive": "2.0.1",
+ "@radix-ui/react-slot": "1.1.1",
+ "@radix-ui/react-use-controllable-state": "1.1.0",
+ "aria-hidden": "^1.1.1",
+ "react-remove-scroll": "^2.6.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-popper": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.1.tgz",
+ "integrity": "sha512-3kn5Me69L+jv82EKRuQCXdYyf1DqHwD2U/sxoNgBGCB7K9TRc3bQamQ+5EPM9EvyPdli0W41sROd+ZU1dTCztw==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/react-dom": "^2.0.0",
+ "@radix-ui/react-arrow": "1.1.1",
+ "@radix-ui/react-compose-refs": "1.1.1",
+ "@radix-ui/react-context": "1.1.1",
+ "@radix-ui/react-primitive": "2.0.1",
+ "@radix-ui/react-use-callback-ref": "1.1.0",
+ "@radix-ui/react-use-layout-effect": "1.1.0",
+ "@radix-ui/react-use-rect": "1.1.0",
+ "@radix-ui/react-use-size": "1.1.0",
+ "@radix-ui/rect": "1.1.0"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-portal": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.3.tgz",
+ "integrity": "sha512-NciRqhXnGojhT93RPyDaMPfLH3ZSl4jjIFbZQ1b/vxvZEdHsBZ49wP9w8L3HzUQwep01LcWtkUvm0OVB5JAHTw==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-primitive": "2.0.1",
+ "@radix-ui/react-use-layout-effect": "1.1.0"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-presence": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.2.tgz",
+ "integrity": "sha512-18TFr80t5EVgL9x1SwF/YGtfG+l0BS0PRAlCWBDoBEiDQjeKgnNZRVJp/oVBl24sr3Gbfwc/Qpj4OcWTQMsAEg==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.1",
+ "@radix-ui/react-use-layout-effect": "1.1.0"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-primitive": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.1.tgz",
+ "integrity": "sha512-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-slot": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-progress": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-progress/-/react-progress-1.1.1.tgz",
+ "integrity": "sha512-6diOawA84f/eMxFHcWut0aE1C2kyE9dOyCTQOMRR2C/qPiXz/X0SaiA/RLbapQaXUCmy0/hLMf9meSccD1N0pA==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-context": "1.1.1",
+ "@radix-ui/react-primitive": "2.0.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-radio-group": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-radio-group/-/react-radio-group-1.2.2.tgz",
+ "integrity": "sha512-E0MLLGfOP0l8P/NxgVzfXJ8w3Ch8cdO6UDzJfDChu4EJDy+/WdO5LqpdY8PYnCErkmZH3gZhDL1K7kQ41fAHuQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.1",
+ "@radix-ui/react-compose-refs": "1.1.1",
+ "@radix-ui/react-context": "1.1.1",
+ "@radix-ui/react-direction": "1.1.0",
+ "@radix-ui/react-presence": "1.1.2",
+ "@radix-ui/react-primitive": "2.0.1",
+ "@radix-ui/react-roving-focus": "1.1.1",
+ "@radix-ui/react-use-controllable-state": "1.1.0",
+ "@radix-ui/react-use-previous": "1.1.0",
+ "@radix-ui/react-use-size": "1.1.0"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-roving-focus": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.1.tgz",
+ "integrity": "sha512-QE1RoxPGJ/Nm8Qmk0PxP8ojmoaS67i0s7hVssS7KuI2FQoc/uzVlZsqKfQvxPE6D8hICCPHJ4D88zNhT3OOmkw==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.1",
+ "@radix-ui/react-collection": "1.1.1",
+ "@radix-ui/react-compose-refs": "1.1.1",
+ "@radix-ui/react-context": "1.1.1",
+ "@radix-ui/react-direction": "1.1.0",
+ "@radix-ui/react-id": "1.1.0",
+ "@radix-ui/react-primitive": "2.0.1",
+ "@radix-ui/react-use-callback-ref": "1.1.0",
+ "@radix-ui/react-use-controllable-state": "1.1.0"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-scroll-area": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-scroll-area/-/react-scroll-area-1.2.2.tgz",
+ "integrity": "sha512-EFI1N/S3YxZEW/lJ/H1jY3njlvTd8tBmgKEn4GHi51+aMm94i6NmAJstsm5cu3yJwYqYc93gpCPm21FeAbFk6g==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/number": "1.1.0",
+ "@radix-ui/primitive": "1.1.1",
+ "@radix-ui/react-compose-refs": "1.1.1",
+ "@radix-ui/react-context": "1.1.1",
+ "@radix-ui/react-direction": "1.1.0",
+ "@radix-ui/react-presence": "1.1.2",
+ "@radix-ui/react-primitive": "2.0.1",
+ "@radix-ui/react-use-callback-ref": "1.1.0",
+ "@radix-ui/react-use-layout-effect": "1.1.0"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-select": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.1.4.tgz",
+ "integrity": "sha512-pOkb2u8KgO47j/h7AylCj7dJsm69BXcjkrvTqMptFqsE2i0p8lHkfgneXKjAgPzBMivnoMyt8o4KiV4wYzDdyQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/number": "1.1.0",
+ "@radix-ui/primitive": "1.1.1",
+ "@radix-ui/react-collection": "1.1.1",
+ "@radix-ui/react-compose-refs": "1.1.1",
+ "@radix-ui/react-context": "1.1.1",
+ "@radix-ui/react-direction": "1.1.0",
+ "@radix-ui/react-dismissable-layer": "1.1.3",
+ "@radix-ui/react-focus-guards": "1.1.1",
+ "@radix-ui/react-focus-scope": "1.1.1",
+ "@radix-ui/react-id": "1.1.0",
+ "@radix-ui/react-popper": "1.2.1",
+ "@radix-ui/react-portal": "1.1.3",
+ "@radix-ui/react-primitive": "2.0.1",
+ "@radix-ui/react-slot": "1.1.1",
+ "@radix-ui/react-use-callback-ref": "1.1.0",
+ "@radix-ui/react-use-controllable-state": "1.1.0",
+ "@radix-ui/react-use-layout-effect": "1.1.0",
+ "@radix-ui/react-use-previous": "1.1.0",
+ "@radix-ui/react-visually-hidden": "1.1.1",
+ "aria-hidden": "^1.1.1",
+ "react-remove-scroll": "^2.6.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-separator": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.1.tgz",
+ "integrity": "sha512-RRiNRSrD8iUiXriq/Y5n4/3iE8HzqgLHsusUSg5jVpU2+3tqcUFPJXHDymwEypunc2sWxDUS3UC+rkZRlHedsw==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-primitive": "2.0.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-slider": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-slider/-/react-slider-1.2.2.tgz",
+ "integrity": "sha512-sNlU06ii1/ZcbHf8I9En54ZPW0Vil/yPVg4vQMcFNjrIx51jsHbFl1HYHQvCIWJSr1q0ZmA+iIs/ZTv8h7HHSA==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/number": "1.1.0",
+ "@radix-ui/primitive": "1.1.1",
+ "@radix-ui/react-collection": "1.1.1",
+ "@radix-ui/react-compose-refs": "1.1.1",
+ "@radix-ui/react-context": "1.1.1",
+ "@radix-ui/react-direction": "1.1.0",
+ "@radix-ui/react-primitive": "2.0.1",
+ "@radix-ui/react-use-controllable-state": "1.1.0",
+ "@radix-ui/react-use-layout-effect": "1.1.0",
+ "@radix-ui/react-use-previous": "1.1.0",
+ "@radix-ui/react-use-size": "1.1.0"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-slot": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.1.tgz",
+ "integrity": "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-switch": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-switch/-/react-switch-1.1.2.tgz",
+ "integrity": "sha512-zGukiWHjEdBCRyXvKR6iXAQG6qXm2esuAD6kDOi9Cn+1X6ev3ASo4+CsYaD6Fov9r/AQFekqnD/7+V0Cs6/98g==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.1",
+ "@radix-ui/react-compose-refs": "1.1.1",
+ "@radix-ui/react-context": "1.1.1",
+ "@radix-ui/react-primitive": "2.0.1",
+ "@radix-ui/react-use-controllable-state": "1.1.0",
+ "@radix-ui/react-use-previous": "1.1.0",
+ "@radix-ui/react-use-size": "1.1.0"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-tabs": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.2.tgz",
+ "integrity": "sha512-9u/tQJMcC2aGq7KXpGivMm1mgq7oRJKXphDwdypPd/j21j/2znamPU8WkXgnhUaTrSFNIt8XhOyCAupg8/GbwQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.1",
+ "@radix-ui/react-context": "1.1.1",
+ "@radix-ui/react-direction": "1.1.0",
+ "@radix-ui/react-id": "1.1.0",
+ "@radix-ui/react-presence": "1.1.2",
+ "@radix-ui/react-primitive": "2.0.1",
+ "@radix-ui/react-roving-focus": "1.1.1",
+ "@radix-ui/react-use-controllable-state": "1.1.0"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-toast": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-toast/-/react-toast-1.2.4.tgz",
+ "integrity": "sha512-Sch9idFJHJTMH9YNpxxESqABcAFweJG4tKv+0zo0m5XBvUSL8FM5xKcJLFLXononpePs8IclyX1KieL5SDUNgA==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.1",
+ "@radix-ui/react-collection": "1.1.1",
+ "@radix-ui/react-compose-refs": "1.1.1",
+ "@radix-ui/react-context": "1.1.1",
+ "@radix-ui/react-dismissable-layer": "1.1.3",
+ "@radix-ui/react-portal": "1.1.3",
+ "@radix-ui/react-presence": "1.1.2",
+ "@radix-ui/react-primitive": "2.0.1",
+ "@radix-ui/react-use-callback-ref": "1.1.0",
+ "@radix-ui/react-use-controllable-state": "1.1.0",
+ "@radix-ui/react-use-layout-effect": "1.1.0",
+ "@radix-ui/react-visually-hidden": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-toggle": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle/-/react-toggle-1.1.1.tgz",
+ "integrity": "sha512-i77tcgObYr743IonC1hrsnnPmszDRn8p+EGUsUt+5a/JFn28fxaM88Py6V2mc8J5kELMWishI0rLnuGLFD/nnQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.1",
+ "@radix-ui/react-primitive": "2.0.1",
+ "@radix-ui/react-use-controllable-state": "1.1.0"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-toggle-group": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle-group/-/react-toggle-group-1.1.1.tgz",
+ "integrity": "sha512-OgDLZEA30Ylyz8YSXvnGqIHtERqnUt1KUYTKdw/y8u7Ci6zGiJfXc02jahmcSNK3YcErqioj/9flWC9S1ihfwg==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.1",
+ "@radix-ui/react-context": "1.1.1",
+ "@radix-ui/react-direction": "1.1.0",
+ "@radix-ui/react-primitive": "2.0.1",
+ "@radix-ui/react-roving-focus": "1.1.1",
+ "@radix-ui/react-toggle": "1.1.1",
+ "@radix-ui/react-use-controllable-state": "1.1.0"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-tooltip": {
+ "version": "1.1.6",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.1.6.tgz",
+ "integrity": "sha512-TLB5D8QLExS1uDn7+wH/bjEmRurNMTzNrtq7IjaS4kjion9NtzsTGkvR5+i7yc9q01Pi2KMM2cN3f8UG4IvvXA==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.1",
+ "@radix-ui/react-compose-refs": "1.1.1",
+ "@radix-ui/react-context": "1.1.1",
+ "@radix-ui/react-dismissable-layer": "1.1.3",
+ "@radix-ui/react-id": "1.1.0",
+ "@radix-ui/react-popper": "1.2.1",
+ "@radix-ui/react-portal": "1.1.3",
+ "@radix-ui/react-presence": "1.1.2",
+ "@radix-ui/react-primitive": "2.0.1",
+ "@radix-ui/react-slot": "1.1.1",
+ "@radix-ui/react-use-controllable-state": "1.1.0",
+ "@radix-ui/react-visually-hidden": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-callback-ref": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.0.tgz",
+ "integrity": "sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-controllable-state": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.1.0.tgz",
+ "integrity": "sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-use-callback-ref": "1.1.0"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-escape-keydown": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.0.tgz",
+ "integrity": "sha512-L7vwWlR1kTTQ3oh7g1O0CBF3YCyyTj8NmhLR+phShpyA50HCfBFKVJTpshm9PzLiKmehsrQzTYTpX9HvmC9rhw==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-use-callback-ref": "1.1.0"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-layout-effect": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.0.tgz",
+ "integrity": "sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-previous": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.0.tgz",
+ "integrity": "sha512-Z/e78qg2YFnnXcW88A4JmTtm4ADckLno6F7OXotmkQfeuCVaKuYzqAATPhVzl3delXE7CxIV8shofPn3jPc5Og==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-rect": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.0.tgz",
+ "integrity": "sha512-0Fmkebhr6PiseyZlYAOtLS+nb7jLmpqTrJyv61Pe68MKYW6OWdRE2kI70TaYY27u7H0lajqM3hSMMLFq18Z7nQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/rect": "1.1.0"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-size": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.0.tgz",
+ "integrity": "sha512-XW3/vWuIXHa+2Uwcc2ABSfcCledmXhhQPlGbfcRXbiUQI5Icjcg19BGCZVKKInYbvUCut/ufbbLLPFC5cbb1hw==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-use-layout-effect": "1.1.0"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-visually-hidden": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.1.1.tgz",
+ "integrity": "sha512-vVfA2IZ9q/J+gEamvj761Oq1FpWgCDaNOOIfbPVp2MVPLEomUr5+Vf7kJGwQ24YxZSlQVar7Bes8kyTo5Dshpg==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-primitive": "2.0.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/rect": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.0.tgz",
+ "integrity": "sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==",
+ "license": "MIT"
+ },
+ "node_modules/@swc/counter": {
+ "version": "0.1.3",
+ "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz",
+ "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/@swc/helpers": {
+ "version": "0.5.15",
+ "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz",
+ "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "tslib": "^2.8.0"
+ }
+ },
+ "node_modules/@tailwindcss/node": {
+ "version": "4.1.15",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.15.tgz",
+ "integrity": "sha512-HF4+7QxATZWY3Jr8OlZrBSXmwT3Watj0OogeDvdUY/ByXJHQ+LBtqA2brDb3sBxYslIFx6UP94BJ4X6a4L9Bmw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/remapping": "^2.3.4",
+ "enhanced-resolve": "^5.18.3",
+ "jiti": "^2.6.0",
+ "lightningcss": "1.30.2",
+ "magic-string": "^0.30.19",
+ "source-map-js": "^1.2.1",
+ "tailwindcss": "4.1.15"
+ }
+ },
+ "node_modules/@tailwindcss/oxide": {
+ "version": "4.1.15",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.15.tgz",
+ "integrity": "sha512-krhX+UOOgnsUuks2SR7hFafXmLQrKxB4YyRTERuCE59JlYL+FawgaAlSkOYmDRJdf1Q+IFNDMl9iRnBW7QBDfQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 10"
+ },
+ "optionalDependencies": {
+ "@tailwindcss/oxide-android-arm64": "4.1.15",
+ "@tailwindcss/oxide-darwin-arm64": "4.1.15",
+ "@tailwindcss/oxide-darwin-x64": "4.1.15",
+ "@tailwindcss/oxide-freebsd-x64": "4.1.15",
+ "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.15",
+ "@tailwindcss/oxide-linux-arm64-gnu": "4.1.15",
+ "@tailwindcss/oxide-linux-arm64-musl": "4.1.15",
+ "@tailwindcss/oxide-linux-x64-gnu": "4.1.15",
+ "@tailwindcss/oxide-linux-x64-musl": "4.1.15",
+ "@tailwindcss/oxide-wasm32-wasi": "4.1.15",
+ "@tailwindcss/oxide-win32-arm64-msvc": "4.1.15",
+ "@tailwindcss/oxide-win32-x64-msvc": "4.1.15"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-android-arm64": {
+ "version": "4.1.15",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.15.tgz",
+ "integrity": "sha512-TkUkUgAw8At4cBjCeVCRMc/guVLKOU1D+sBPrHt5uVcGhlbVKxrCaCW9OKUIBv1oWkjh4GbunD/u/Mf0ql6kEA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-darwin-arm64": {
+ "version": "4.1.15",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.15.tgz",
+ "integrity": "sha512-xt5XEJpn2piMSfvd1UFN6jrWXyaKCwikP4Pidcf+yfHTSzSpYhG3dcMktjNkQO3JiLCp+0bG0HoWGvz97K162w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-darwin-x64": {
+ "version": "4.1.15",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.15.tgz",
+ "integrity": "sha512-TnWaxP6Bx2CojZEXAV2M01Yl13nYPpp0EtGpUrY+LMciKfIXiLL2r/SiSRpagE5Fp2gX+rflp/Os1VJDAyqymg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-freebsd-x64": {
+ "version": "4.1.15",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.15.tgz",
+ "integrity": "sha512-quISQDWqiB6Cqhjc3iWptXVZHNVENsWoI77L1qgGEHNIdLDLFnw3/AfY7DidAiiCIkGX/MjIdB3bbBZR/G2aJg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": {
+ "version": "4.1.15",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.15.tgz",
+ "integrity": "sha512-ObG76+vPlab65xzVUQbExmDU9FIeYLQ5k2LrQdR2Ud6hboR+ZobXpDoKEYXf/uOezOfIYmy2Ta3w0ejkTg9yxg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-arm64-gnu": {
+ "version": "4.1.15",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.15.tgz",
+ "integrity": "sha512-4WbBacRmk43pkb8/xts3wnOZMDKsPFyEH/oisCm2q3aLZND25ufvJKcDUpAu0cS+CBOL05dYa8D4U5OWECuH/Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-arm64-musl": {
+ "version": "4.1.15",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.15.tgz",
+ "integrity": "sha512-AbvmEiteEj1nf42nE8skdHv73NoR+EwXVSgPY6l39X12Ex8pzOwwfi3Kc8GAmjsnsaDEbk+aj9NyL3UeyHcTLg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-x64-gnu": {
+ "version": "4.1.15",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.15.tgz",
+ "integrity": "sha512-+rzMVlvVgrXtFiS+ES78yWgKqpThgV19ISKD58Ck+YO5pO5KjyxLt7AWKsWMbY0R9yBDC82w6QVGz837AKQcHg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-x64-musl": {
+ "version": "4.1.15",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.15.tgz",
+ "integrity": "sha512-fPdEy7a8eQN9qOIK3Em9D3TO1z41JScJn8yxl/76mp4sAXFDfV4YXxsiptJcOwy6bGR+70ZSwFIZhTXzQeqwQg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-wasm32-wasi": {
+ "version": "4.1.15",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.15.tgz",
+ "integrity": "sha512-sJ4yd6iXXdlgIMfIBXuVGp/NvmviEoMVWMOAGxtxhzLPp9LOj5k0pMEMZdjeMCl4C6Up+RM8T3Zgk+BMQ0bGcQ==",
+ "bundleDependencies": [
+ "@napi-rs/wasm-runtime",
+ "@emnapi/core",
+ "@emnapi/runtime",
+ "@tybys/wasm-util",
+ "@emnapi/wasi-threads",
+ "tslib"
+ ],
+ "cpu": [
+ "wasm32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/core": "^1.5.0",
+ "@emnapi/runtime": "^1.5.0",
+ "@emnapi/wasi-threads": "^1.1.0",
+ "@napi-rs/wasm-runtime": "^1.0.7",
+ "@tybys/wasm-util": "^0.10.1",
+ "tslib": "^2.4.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
+ "version": "4.1.15",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.15.tgz",
+ "integrity": "sha512-sJGE5faXnNQ1iXeqmRin7Ds/ru2fgCiaQZQQz3ZGIDtvbkeV85rAZ0QJFMDg0FrqsffZG96H1U9AQlNBRLsHVg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-win32-x64-msvc": {
+ "version": "4.1.15",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.15.tgz",
+ "integrity": "sha512-NLeHE7jUV6HcFKS504bpOohyi01zPXi2PXmjFfkzTph8xRxDdxkRsXm/xDO5uV5K3brrE1cCwbUYmFUSHR3u1w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/postcss": {
+ "version": "4.1.15",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.1.15.tgz",
+ "integrity": "sha512-IZh8IT76KujRz6d15wZw4eoeViT4TqmzVWNNfpuNCTKiaZUwgr5vtPqO4HjuYDyx3MgGR5qgPt1HMzTeLJyA3g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@alloc/quick-lru": "^5.2.0",
+ "@tailwindcss/node": "4.1.15",
+ "@tailwindcss/oxide": "4.1.15",
+ "postcss": "^8.4.41",
+ "tailwindcss": "4.1.15"
+ }
+ },
+ "node_modules/@types/d3-array": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz",
+ "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==",
+ "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/node": {
+ "version": "22.18.12",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-22.18.12.tgz",
+ "integrity": "sha512-BICHQ67iqxQGFSzfCFTT7MRQ5XcBjG5aeKh5Ok38UBbPe5fxTyE+aHFxwVrGyr8GNlqFMLKD1D3P2K/1ks8tog==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~6.21.0"
+ }
+ },
+ "node_modules/@types/react": {
+ "version": "19.2.2",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.2.tgz",
+ "integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "csstype": "^3.0.2"
+ }
+ },
+ "node_modules/@types/react-dom": {
+ "version": "19.2.2",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.2.tgz",
+ "integrity": "sha512-9KQPoO6mZCi7jcIStSnlOWn2nEF3mNmyr3rIAsGnAbQKYbRLyqmeSc39EVgtxXVia+LMT8j3knZLAZAh+xLmrw==",
+ "devOptional": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "^19.2.0"
+ }
+ },
+ "node_modules/@vercel/analytics": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/@vercel/analytics/-/analytics-1.5.0.tgz",
+ "integrity": "sha512-MYsBzfPki4gthY5HnYN7jgInhAZ7Ac1cYDoRWFomwGHWEX7odTEzbtg9kf/QSo7XEsEAqlQugA6gJ2WS2DEa3g==",
+ "license": "MPL-2.0",
+ "peerDependencies": {
+ "@remix-run/react": "^2",
+ "@sveltejs/kit": "^1 || ^2",
+ "next": ">= 13",
+ "react": "^18 || ^19 || ^19.0.0-rc",
+ "svelte": ">= 4",
+ "vue": "^3",
+ "vue-router": "^4"
+ },
+ "peerDependenciesMeta": {
+ "@remix-run/react": {
+ "optional": true
+ },
+ "@sveltejs/kit": {
+ "optional": true
+ },
+ "next": {
+ "optional": true
+ },
+ "react": {
+ "optional": true
+ },
+ "svelte": {
+ "optional": true
+ },
+ "vue": {
+ "optional": true
+ },
+ "vue-router": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/aria-hidden": {
+ "version": "1.2.6",
+ "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz",
+ "integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==",
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/autoprefixer": {
+ "version": "10.4.21",
+ "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz",
+ "integrity": "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/autoprefixer"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "browserslist": "^4.24.4",
+ "caniuse-lite": "^1.0.30001702",
+ "fraction.js": "^4.3.7",
+ "normalize-range": "^0.1.2",
+ "picocolors": "^1.1.1",
+ "postcss-value-parser": "^4.2.0"
+ },
+ "bin": {
+ "autoprefixer": "bin/autoprefixer"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ },
+ "peerDependencies": {
+ "postcss": "^8.1.0"
+ }
+ },
+ "node_modules/baseline-browser-mapping": {
+ "version": "2.8.18",
+ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.18.tgz",
+ "integrity": "sha512-UYmTpOBwgPScZpS4A+YbapwWuBwasxvO/2IOHArSsAhL/+ZdmATBXTex3t+l2hXwLVYK382ibr/nKoY9GKe86w==",
+ "license": "Apache-2.0",
+ "bin": {
+ "baseline-browser-mapping": "dist/cli.js"
+ }
+ },
+ "node_modules/browserslist": {
+ "version": "4.26.3",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.26.3.tgz",
+ "integrity": "sha512-lAUU+02RFBuCKQPj/P6NgjlbCnLBMp4UtgTx7vNHd3XSIJF87s9a5rA3aH2yw3GS9DqZAUbOtZdCCiZeVRqt0w==",
+ "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": {
+ "baseline-browser-mapping": "^2.8.9",
+ "caniuse-lite": "^1.0.30001746",
+ "electron-to-chromium": "^1.5.227",
+ "node-releases": "^2.0.21",
+ "update-browserslist-db": "^1.1.3"
+ },
+ "bin": {
+ "browserslist": "cli.js"
+ },
+ "engines": {
+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+ }
+ },
+ "node_modules/busboy": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
+ "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==",
+ "dependencies": {
+ "streamsearch": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=10.16.0"
+ }
+ },
+ "node_modules/caniuse-lite": {
+ "version": "1.0.30001751",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001751.tgz",
+ "integrity": "sha512-A0QJhug0Ly64Ii3eIqHu5X51ebln3k4yTUkY1j8drqpWHVreg/VLijN48cZ1bYPiqOQuqpkIKnzr/Ul8V+p6Cw==",
+ "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/class-variance-authority": {
+ "version": "0.7.1",
+ "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz",
+ "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "clsx": "^2.1.1"
+ },
+ "funding": {
+ "url": "https://polar.sh/cva"
+ }
+ },
+ "node_modules/client-only": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
+ "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==",
+ "license": "MIT"
+ },
+ "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/cmdk": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/cmdk/-/cmdk-1.0.4.tgz",
+ "integrity": "sha512-AnsjfHyHpQ/EFeAnG216WY7A5LiYCoZzCSygiLvfXC3H3LFGCprErteUcszaVluGOhuOTbJS3jWHrSDYPBBygg==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-dialog": "^1.1.2",
+ "@radix-ui/react-id": "^1.1.0",
+ "@radix-ui/react-primitive": "^2.0.0",
+ "use-sync-external-store": "^1.2.2"
+ },
+ "peerDependencies": {
+ "react": "^18 || ^19 || ^19.0.0-rc",
+ "react-dom": "^18 || ^19 || ^19.0.0-rc"
+ }
+ },
+ "node_modules/color": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
+ "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "color-convert": "^2.0.1",
+ "color-string": "^1.9.0"
+ },
+ "engines": {
+ "node": ">=12.5.0"
+ }
+ },
+ "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==",
+ "license": "MIT",
+ "optional": true,
+ "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==",
+ "license": "MIT",
+ "optional": true
+ },
+ "node_modules/color-string": {
+ "version": "1.9.1",
+ "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz",
+ "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "color-name": "^1.0.0",
+ "simple-swizzle": "^0.2.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/date-fns": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz",
+ "integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/kossnocorp"
+ }
+ },
+ "node_modules/date-fns-jalali": {
+ "version": "4.1.0-0",
+ "resolved": "https://registry.npmjs.org/date-fns-jalali/-/date-fns-jalali-4.1.0-0.tgz",
+ "integrity": "sha512-hTIP/z+t+qKwBDcmmsnmjWTduxCg+5KfdqWQvb2X/8C9+knYY6epN/pfxdDuyVlSVeFz0sM5eEfwIUQ70U4ckg==",
+ "license": "MIT"
+ },
+ "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/detect-libc": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
+ "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
+ "devOptional": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/detect-node-es": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz",
+ "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==",
+ "license": "MIT"
+ },
+ "node_modules/dom-helpers": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz",
+ "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.8.7",
+ "csstype": "^3.0.2"
+ }
+ },
+ "node_modules/electron-to-chromium": {
+ "version": "1.5.237",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.237.tgz",
+ "integrity": "sha512-icUt1NvfhGLar5lSWH3tHNzablaA5js3HVHacQimfP8ViEBOQv+L7DKEuHdbTZ0SKCO1ogTJTIL1Gwk9S6Qvcg==",
+ "license": "ISC"
+ },
+ "node_modules/embla-carousel": {
+ "version": "8.5.1",
+ "resolved": "https://registry.npmjs.org/embla-carousel/-/embla-carousel-8.5.1.tgz",
+ "integrity": "sha512-JUb5+FOHobSiWQ2EJNaueCNT/cQU9L6XWBbWmorWPQT9bkbk+fhsuLr8wWrzXKagO3oWszBO7MSx+GfaRk4E6A==",
+ "license": "MIT"
+ },
+ "node_modules/embla-carousel-react": {
+ "version": "8.5.1",
+ "resolved": "https://registry.npmjs.org/embla-carousel-react/-/embla-carousel-react-8.5.1.tgz",
+ "integrity": "sha512-z9Y0K84BJvhChXgqn2CFYbfEi6AwEr+FFVVKm/MqbTQ2zIzO1VQri6w67LcfpVF0AjbhwVMywDZqY4alYkjW5w==",
+ "license": "MIT",
+ "dependencies": {
+ "embla-carousel": "8.5.1",
+ "embla-carousel-reactive-utils": "8.5.1"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.1 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
+ }
+ },
+ "node_modules/embla-carousel-reactive-utils": {
+ "version": "8.5.1",
+ "resolved": "https://registry.npmjs.org/embla-carousel-reactive-utils/-/embla-carousel-reactive-utils-8.5.1.tgz",
+ "integrity": "sha512-n7VSoGIiiDIc4MfXF3ZRTO59KDp820QDuyBDGlt5/65+lumPHxX2JLz0EZ23hZ4eg4vZGUXwMkYv02fw2JVo/A==",
+ "license": "MIT",
+ "peerDependencies": {
+ "embla-carousel": "8.5.1"
+ }
+ },
+ "node_modules/enhanced-resolve": {
+ "version": "5.18.3",
+ "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz",
+ "integrity": "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "graceful-fs": "^4.2.4",
+ "tapable": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "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/eventemitter3": {
+ "version": "4.0.7",
+ "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
+ "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==",
+ "license": "MIT"
+ },
+ "node_modules/fast-equals": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.3.2.tgz",
+ "integrity": "sha512-6rxyATwPCkaFIL3JLqw8qXqMpIZ942pTX/tbQFkRsDGblS8tNGtlUauA/+mt6RUfqn/4MoEr+WDkYoIQbibWuQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/fraction.js": {
+ "version": "4.3.7",
+ "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz",
+ "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==",
+ "license": "MIT",
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "type": "patreon",
+ "url": "https://github.com/sponsors/rawify"
+ }
+ },
+ "node_modules/geist": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/geist/-/geist-1.5.1.tgz",
+ "integrity": "sha512-mAHZxIsL2o3ZITFaBVFBnwyDOw+zNLYum6A6nIjpzCGIO8QtC3V76XF2RnZTyLx1wlDTmMDy8jg3Ib52MIjGvQ==",
+ "license": "SIL OPEN FONT LICENSE",
+ "peerDependencies": {
+ "next": ">=13.2.0"
+ }
+ },
+ "node_modules/get-nonce": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz",
+ "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/graceful-fs": {
+ "version": "4.2.11",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
+ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/input-otp": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/input-otp/-/input-otp-1.4.1.tgz",
+ "integrity": "sha512-+yvpmKYKHi9jIGngxagY9oWiiblPB7+nEO75F2l2o4vs+6vpPZZmUl4tBNYuTCvQjhvEIbdNeJu70bhfYP2nbw==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc"
+ }
+ },
+ "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-arrayish": {
+ "version": "0.3.4",
+ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.4.tgz",
+ "integrity": "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==",
+ "license": "MIT",
+ "optional": true
+ },
+ "node_modules/jiti": {
+ "version": "2.6.1",
+ "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz",
+ "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "jiti": "lib/jiti-cli.mjs"
+ }
+ },
+ "node_modules/js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+ "license": "MIT"
+ },
+ "node_modules/lightningcss": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.2.tgz",
+ "integrity": "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==",
+ "dev": true,
+ "license": "MPL-2.0",
+ "dependencies": {
+ "detect-libc": "^2.0.3"
+ },
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ },
+ "optionalDependencies": {
+ "lightningcss-android-arm64": "1.30.2",
+ "lightningcss-darwin-arm64": "1.30.2",
+ "lightningcss-darwin-x64": "1.30.2",
+ "lightningcss-freebsd-x64": "1.30.2",
+ "lightningcss-linux-arm-gnueabihf": "1.30.2",
+ "lightningcss-linux-arm64-gnu": "1.30.2",
+ "lightningcss-linux-arm64-musl": "1.30.2",
+ "lightningcss-linux-x64-gnu": "1.30.2",
+ "lightningcss-linux-x64-musl": "1.30.2",
+ "lightningcss-win32-arm64-msvc": "1.30.2",
+ "lightningcss-win32-x64-msvc": "1.30.2"
+ }
+ },
+ "node_modules/lightningcss-android-arm64": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.30.2.tgz",
+ "integrity": "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-darwin-arm64": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.2.tgz",
+ "integrity": "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-darwin-x64": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.2.tgz",
+ "integrity": "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-freebsd-x64": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.2.tgz",
+ "integrity": "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm-gnueabihf": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.2.tgz",
+ "integrity": "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm64-gnu": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.2.tgz",
+ "integrity": "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm64-musl": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.2.tgz",
+ "integrity": "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-x64-gnu": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.2.tgz",
+ "integrity": "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-x64-musl": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.2.tgz",
+ "integrity": "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-win32-arm64-msvc": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.2.tgz",
+ "integrity": "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-win32-x64-msvc": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.2.tgz",
+ "integrity": "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lodash": {
+ "version": "4.17.21",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
+ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
+ "license": "MIT"
+ },
+ "node_modules/loose-envify": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
+ "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
+ "license": "MIT",
+ "dependencies": {
+ "js-tokens": "^3.0.0 || ^4.0.0"
+ },
+ "bin": {
+ "loose-envify": "cli.js"
+ }
+ },
+ "node_modules/lucide-react": {
+ "version": "0.454.0",
+ "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.454.0.tgz",
+ "integrity": "sha512-hw7zMDwykCLnEzgncEEjHeA6+45aeEzRYuKHuyRSOPkhko+J3ySGjGIzu+mmMfDFG1vazHepMaYFYHbTFAZAAQ==",
+ "license": "ISC",
+ "peerDependencies": {
+ "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc"
+ }
+ },
+ "node_modules/magic-string": {
+ "version": "0.30.19",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.19.tgz",
+ "integrity": "sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.5"
+ }
+ },
+ "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/next": {
+ "version": "15.2.4",
+ "resolved": "https://registry.npmjs.org/next/-/next-15.2.4.tgz",
+ "integrity": "sha512-VwL+LAaPSxEkd3lU2xWbgEOtrM8oedmyhBqaVNmgKB+GvZlCy9rgaEc+y2on0wv+l0oSFqLtYD6dcC1eAedUaQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@next/env": "15.2.4",
+ "@swc/counter": "0.1.3",
+ "@swc/helpers": "0.5.15",
+ "busboy": "1.6.0",
+ "caniuse-lite": "^1.0.30001579",
+ "postcss": "8.4.31",
+ "styled-jsx": "5.1.6"
+ },
+ "bin": {
+ "next": "dist/bin/next"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^19.8.0 || >= 20.0.0"
+ },
+ "optionalDependencies": {
+ "@next/swc-darwin-arm64": "15.2.4",
+ "@next/swc-darwin-x64": "15.2.4",
+ "@next/swc-linux-arm64-gnu": "15.2.4",
+ "@next/swc-linux-arm64-musl": "15.2.4",
+ "@next/swc-linux-x64-gnu": "15.2.4",
+ "@next/swc-linux-x64-musl": "15.2.4",
+ "@next/swc-win32-arm64-msvc": "15.2.4",
+ "@next/swc-win32-x64-msvc": "15.2.4",
+ "sharp": "^0.33.5"
+ },
+ "peerDependencies": {
+ "@opentelemetry/api": "^1.1.0",
+ "@playwright/test": "^1.41.2",
+ "babel-plugin-react-compiler": "*",
+ "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0",
+ "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0",
+ "sass": "^1.3.0"
+ },
+ "peerDependenciesMeta": {
+ "@opentelemetry/api": {
+ "optional": true
+ },
+ "@playwright/test": {
+ "optional": true
+ },
+ "babel-plugin-react-compiler": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/next-themes": {
+ "version": "0.4.6",
+ "resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.4.6.tgz",
+ "integrity": "sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc"
+ }
+ },
+ "node_modules/next/node_modules/postcss": {
+ "version": "8.4.31",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
+ "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==",
+ "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.6",
+ "picocolors": "^1.0.0",
+ "source-map-js": "^1.0.2"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/node-releases": {
+ "version": "2.0.25",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.25.tgz",
+ "integrity": "sha512-4auku8B/vw5psvTiiN9j1dAOsXvMoGqJuKJcR+dTdqiXEK20mMTk1UEo3HS16LeGQsVG6+qKTPM9u/qQ2LqATA==",
+ "license": "MIT"
+ },
+ "node_modules/normalize-range": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz",
+ "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==",
+ "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/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/postcss": {
+ "version": "8.5.6",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
+ "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
+ "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/prop-types": {
+ "version": "15.8.1",
+ "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
+ "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.4.0",
+ "object-assign": "^4.1.1",
+ "react-is": "^16.13.1"
+ }
+ },
+ "node_modules/prop-types/node_modules/react-is": {
+ "version": "16.13.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
+ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
+ "license": "MIT"
+ },
+ "node_modules/react": {
+ "version": "19.2.0",
+ "resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz",
+ "integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-day-picker": {
+ "version": "9.8.0",
+ "resolved": "https://registry.npmjs.org/react-day-picker/-/react-day-picker-9.8.0.tgz",
+ "integrity": "sha512-E0yhhg7R+pdgbl/2toTb0xBhsEAtmAx1l7qjIWYfcxOy8w4rTSVfbtBoSzVVhPwKP/5E9iL38LivzoE3AQDhCQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@date-fns/tz": "1.2.0",
+ "date-fns": "4.1.0",
+ "date-fns-jalali": "4.1.0-0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "individual",
+ "url": "https://github.com/sponsors/gpbl"
+ },
+ "peerDependencies": {
+ "react": ">=16.8.0"
+ }
+ },
+ "node_modules/react-dom": {
+ "version": "19.2.0",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz",
+ "integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==",
+ "license": "MIT",
+ "dependencies": {
+ "scheduler": "^0.27.0"
+ },
+ "peerDependencies": {
+ "react": "^19.2.0"
+ }
+ },
+ "node_modules/react-hook-form": {
+ "version": "7.65.0",
+ "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.65.0.tgz",
+ "integrity": "sha512-xtOzDz063WcXvGWaHgLNrNzlsdFgtUWcb32E6WFaGTd7kPZG3EeDusjdZfUsPwKCKVXy1ZlntifaHZ4l8pAsmw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/react-hook-form"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17 || ^18 || ^19"
+ }
+ },
+ "node_modules/react-is": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
+ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
+ "license": "MIT"
+ },
+ "node_modules/react-remove-scroll": {
+ "version": "2.7.1",
+ "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.1.tgz",
+ "integrity": "sha512-HpMh8+oahmIdOuS5aFKKY6Pyog+FNaZV/XyJOq7b4YFwsFHe5yYfdbIalI4k3vU2nSDql7YskmUseHsRrJqIPA==",
+ "license": "MIT",
+ "dependencies": {
+ "react-remove-scroll-bar": "^2.3.7",
+ "react-style-singleton": "^2.2.3",
+ "tslib": "^2.1.0",
+ "use-callback-ref": "^1.3.3",
+ "use-sidecar": "^1.1.3"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/react-remove-scroll-bar": {
+ "version": "2.3.8",
+ "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz",
+ "integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==",
+ "license": "MIT",
+ "dependencies": {
+ "react-style-singleton": "^2.2.2",
+ "tslib": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/react-resizable-panels": {
+ "version": "2.1.9",
+ "resolved": "https://registry.npmjs.org/react-resizable-panels/-/react-resizable-panels-2.1.9.tgz",
+ "integrity": "sha512-z77+X08YDIrgAes4jl8xhnUu1LNIRp4+E7cv4xHmLOxxUPO/ML7PSrE813b90vj7xvQ1lcf7g2uA9GeMZonjhQ==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": "^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc",
+ "react-dom": "^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
+ }
+ },
+ "node_modules/react-smooth": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-4.0.4.tgz",
+ "integrity": "sha512-gnGKTpYwqL0Iii09gHobNolvX4Kiq4PKx6eWBCYYix+8cdw+cGo3do906l1NBPKkSWx1DghC1dlWG9L2uGd61Q==",
+ "license": "MIT",
+ "dependencies": {
+ "fast-equals": "^5.0.1",
+ "prop-types": "^15.8.1",
+ "react-transition-group": "^4.4.5"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
+ "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
+ "node_modules/react-style-singleton": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz",
+ "integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==",
+ "license": "MIT",
+ "dependencies": {
+ "get-nonce": "^1.0.0",
+ "tslib": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/react-transition-group": {
+ "version": "4.4.5",
+ "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz",
+ "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@babel/runtime": "^7.5.5",
+ "dom-helpers": "^5.0.1",
+ "loose-envify": "^1.4.0",
+ "prop-types": "^15.6.2"
+ },
+ "peerDependencies": {
+ "react": ">=16.6.0",
+ "react-dom": ">=16.6.0"
+ }
+ },
+ "node_modules/recharts": {
+ "version": "2.15.4",
+ "resolved": "https://registry.npmjs.org/recharts/-/recharts-2.15.4.tgz",
+ "integrity": "sha512-UT/q6fwS3c1dHbXv2uFgYJ9BMFHu3fwnd7AYZaEQhXuYQ4hgsxLvsUXzGdKeZrW5xopzDCvuA2N41WJ88I7zIw==",
+ "license": "MIT",
+ "dependencies": {
+ "clsx": "^2.0.0",
+ "eventemitter3": "^4.0.1",
+ "lodash": "^4.17.21",
+ "react-is": "^18.3.1",
+ "react-smooth": "^4.0.4",
+ "recharts-scale": "^0.4.4",
+ "tiny-invariant": "^1.3.1",
+ "victory-vendor": "^36.6.8"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "peerDependencies": {
+ "react": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
+ "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
+ "node_modules/recharts-scale": {
+ "version": "0.4.5",
+ "resolved": "https://registry.npmjs.org/recharts-scale/-/recharts-scale-0.4.5.tgz",
+ "integrity": "sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==",
+ "license": "MIT",
+ "dependencies": {
+ "decimal.js-light": "^2.4.1"
+ }
+ },
+ "node_modules/scheduler": {
+ "version": "0.27.0",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
+ "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
+ "license": "MIT"
+ },
+ "node_modules/semver": {
+ "version": "7.7.3",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz",
+ "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==",
+ "license": "ISC",
+ "optional": true,
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/sharp": {
+ "version": "0.33.5",
+ "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz",
+ "integrity": "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==",
+ "hasInstallScript": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "dependencies": {
+ "color": "^4.2.3",
+ "detect-libc": "^2.0.3",
+ "semver": "^7.6.3"
+ },
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-darwin-arm64": "0.33.5",
+ "@img/sharp-darwin-x64": "0.33.5",
+ "@img/sharp-libvips-darwin-arm64": "1.0.4",
+ "@img/sharp-libvips-darwin-x64": "1.0.4",
+ "@img/sharp-libvips-linux-arm": "1.0.5",
+ "@img/sharp-libvips-linux-arm64": "1.0.4",
+ "@img/sharp-libvips-linux-s390x": "1.0.4",
+ "@img/sharp-libvips-linux-x64": "1.0.4",
+ "@img/sharp-libvips-linuxmusl-arm64": "1.0.4",
+ "@img/sharp-libvips-linuxmusl-x64": "1.0.4",
+ "@img/sharp-linux-arm": "0.33.5",
+ "@img/sharp-linux-arm64": "0.33.5",
+ "@img/sharp-linux-s390x": "0.33.5",
+ "@img/sharp-linux-x64": "0.33.5",
+ "@img/sharp-linuxmusl-arm64": "0.33.5",
+ "@img/sharp-linuxmusl-x64": "0.33.5",
+ "@img/sharp-wasm32": "0.33.5",
+ "@img/sharp-win32-ia32": "0.33.5",
+ "@img/sharp-win32-x64": "0.33.5"
+ }
+ },
+ "node_modules/simple-swizzle": {
+ "version": "0.2.4",
+ "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.4.tgz",
+ "integrity": "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "is-arrayish": "^0.3.1"
+ }
+ },
+ "node_modules/sonner": {
+ "version": "1.7.4",
+ "resolved": "https://registry.npmjs.org/sonner/-/sonner-1.7.4.tgz",
+ "integrity": "sha512-DIS8z4PfJRbIyfVFDVnK9rO3eYDtse4Omcm6bt0oEr5/jtLgysmjuBl1frJ9E/EQZrFmKx2A8m/s5s9CRXIzhw==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc",
+ "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc"
+ }
+ },
+ "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/streamsearch": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",
+ "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==",
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
+ "node_modules/styled-jsx": {
+ "version": "5.1.6",
+ "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz",
+ "integrity": "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==",
+ "license": "MIT",
+ "dependencies": {
+ "client-only": "0.0.1"
+ },
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "peerDependencies": {
+ "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0"
+ },
+ "peerDependenciesMeta": {
+ "@babel/core": {
+ "optional": true
+ },
+ "babel-plugin-macros": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/tailwind-merge": {
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.6.0.tgz",
+ "integrity": "sha512-P+Vu1qXfzediirmHOC3xKGAYeZtPcV9g76X+xg2FD4tYgR71ewMA35Y3sCz3zhiN/dwefRpJX0yBcgwi1fXNQA==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/dcastil"
+ }
+ },
+ "node_modules/tailwindcss": {
+ "version": "4.1.15",
+ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.15.tgz",
+ "integrity": "sha512-k2WLnWkYFkdpRv+Oby3EBXIyQC8/s1HOFMBUViwtAh6Z5uAozeUSMQlIsn/c6Q2iJzqG6aJT3wdPaRNj70iYxQ==",
+ "license": "MIT"
+ },
+ "node_modules/tailwindcss-animate": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/tailwindcss-animate/-/tailwindcss-animate-1.0.7.tgz",
+ "integrity": "sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==",
+ "license": "MIT",
+ "peerDependencies": {
+ "tailwindcss": ">=3.0.0 || insiders"
+ }
+ },
+ "node_modules/tapable": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz",
+ "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ }
+ },
+ "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/tslib": {
+ "version": "2.8.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
+ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
+ "license": "0BSD"
+ },
+ "node_modules/tw-animate-css": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/tw-animate-css/-/tw-animate-css-1.3.3.tgz",
+ "integrity": "sha512-tXE2TRWrskc4TU3RDd7T8n8Np/wCfoeH9gz22c7PzYqNPQ9FBGFbWWzwL0JyHcFp+jHozmF76tbHfPAx22ua2Q==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/Wombosvideo"
+ }
+ },
+ "node_modules/typescript": {
+ "version": "5.9.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
+ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/undici-types": {
+ "version": "6.21.0",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
+ "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "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/use-callback-ref": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz",
+ "integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==",
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/use-sidecar": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz",
+ "integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==",
+ "license": "MIT",
+ "dependencies": {
+ "detect-node-es": "^1.1.0",
+ "tslib": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/use-sync-external-store": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz",
+ "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
+ "node_modules/vaul": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/vaul/-/vaul-1.1.2.tgz",
+ "integrity": "sha512-ZFkClGpWyI2WUQjdLJ/BaGuV6AVQiJ3uELGk3OYtP+B6yCO7Cmn9vPFXVJkRaGkOJu3m8bQMgtyzNHixULceQA==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-dialog": "^1.1.1"
+ },
+ "peerDependencies": {
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc"
+ }
+ },
+ "node_modules/victory-vendor": {
+ "version": "36.9.2",
+ "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-36.9.2.tgz",
+ "integrity": "sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==",
+ "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/zod": {
+ "version": "3.25.67",
+ "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.67.tgz",
+ "integrity": "sha512-idA2YXwpCdqUSKRCACDE6ItZD9TZzy3OZMtpfLoh6oPR47lipysRrJfjzMqFxQ3uJuUPyUeWe1r9vLH33xO/Qw==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/colinhacks"
+ }
+ }
+ }
+}
diff --git a/[core]/esx_whitelist/web/package.json b/[core]/esx_whitelist/web/package.json
new file mode 100644
index 000000000..023078321
--- /dev/null
+++ b/[core]/esx_whitelist/web/package.json
@@ -0,0 +1,74 @@
+{
+ "name": "esx_whitelist",
+ "version": "0.1.0",
+ "private": true,
+ "scripts": {
+ "build": "next build",
+ "dev": "next dev",
+ "lint": "next lint",
+ "start": "next start"
+ },
+ "dependencies": {
+ "@hookform/resolvers": "^3.10.0",
+ "@radix-ui/react-accordion": "1.2.2",
+ "@radix-ui/react-alert-dialog": "1.1.4",
+ "@radix-ui/react-aspect-ratio": "1.1.1",
+ "@radix-ui/react-avatar": "1.1.2",
+ "@radix-ui/react-checkbox": "1.1.3",
+ "@radix-ui/react-collapsible": "1.1.2",
+ "@radix-ui/react-context-menu": "2.2.4",
+ "@radix-ui/react-dialog": "1.1.4",
+ "@radix-ui/react-dropdown-menu": "2.1.4",
+ "@radix-ui/react-hover-card": "1.1.4",
+ "@radix-ui/react-label": "2.1.1",
+ "@radix-ui/react-menubar": "1.1.4",
+ "@radix-ui/react-navigation-menu": "1.2.3",
+ "@radix-ui/react-popover": "1.1.4",
+ "@radix-ui/react-progress": "1.1.1",
+ "@radix-ui/react-radio-group": "1.2.2",
+ "@radix-ui/react-scroll-area": "1.2.2",
+ "@radix-ui/react-select": "2.1.4",
+ "@radix-ui/react-separator": "1.1.1",
+ "@radix-ui/react-slider": "1.2.2",
+ "@radix-ui/react-slot": "1.1.1",
+ "@radix-ui/react-switch": "1.1.2",
+ "@radix-ui/react-tabs": "1.1.2",
+ "@radix-ui/react-toast": "1.2.4",
+ "@radix-ui/react-toggle": "1.1.1",
+ "@radix-ui/react-toggle-group": "1.1.1",
+ "@radix-ui/react-tooltip": "1.1.6",
+ "@vercel/analytics": "latest",
+ "autoprefixer": "^10.4.20",
+ "class-variance-authority": "^0.7.1",
+ "clsx": "^2.1.1",
+ "cmdk": "1.0.4",
+ "date-fns": "4.1.0",
+ "embla-carousel-react": "8.5.1",
+ "geist": "latest",
+ "input-otp": "1.4.1",
+ "lucide-react": "^0.454.0",
+ "next": "15.2.4",
+ "next-themes": "^0.4.6",
+ "react": "^19",
+ "react-day-picker": "9.8.0",
+ "react-dom": "^19",
+ "react-hook-form": "^7.60.0",
+ "react-resizable-panels": "^2.1.7",
+ "recharts": "2.15.4",
+ "sonner": "^1.7.4",
+ "tailwind-merge": "^2.5.5",
+ "tailwindcss-animate": "^1.0.7",
+ "vaul": "^1.1.1",
+ "zod": "3.25.67"
+ },
+ "devDependencies": {
+ "@tailwindcss/postcss": "^4.1.9",
+ "@types/node": "^22",
+ "@types/react": "^19",
+ "@types/react-dom": "^19",
+ "postcss": "^8.5",
+ "tailwindcss": "^4.1.9",
+ "tw-animate-css": "1.3.3",
+ "typescript": "^5"
+ }
+}
\ No newline at end of file
diff --git a/[core]/esx_whitelist/web/pnpm-lock.yaml b/[core]/esx_whitelist/web/pnpm-lock.yaml
new file mode 100644
index 000000000..ca98c4816
--- /dev/null
+++ b/[core]/esx_whitelist/web/pnpm-lock.yaml
@@ -0,0 +1,5 @@
+lockfileVersion: '9.0'
+
+settings:
+ autoInstallPeers: true
+ excludeLinksFromLockfile: false
\ No newline at end of file
diff --git a/[core]/esx_whitelist/web/postcss.config.mjs b/[core]/esx_whitelist/web/postcss.config.mjs
new file mode 100644
index 000000000..a869506ee
--- /dev/null
+++ b/[core]/esx_whitelist/web/postcss.config.mjs
@@ -0,0 +1,8 @@
+/** @type {import('postcss-load-config').Config} */
+const config = {
+ plugins: {
+ '@tailwindcss/postcss': {},
+ },
+}
+
+export default config
diff --git a/[core]/esx_whitelist/web/public/esx-logo.png b/[core]/esx_whitelist/web/public/esx-logo.png
new file mode 100644
index 000000000..9be59f999
Binary files /dev/null and b/[core]/esx_whitelist/web/public/esx-logo.png differ
diff --git a/[core]/esx_whitelist/web/styles/globals.css b/[core]/esx_whitelist/web/styles/globals.css
new file mode 100644
index 000000000..23cc7e3a9
--- /dev/null
+++ b/[core]/esx_whitelist/web/styles/globals.css
@@ -0,0 +1,125 @@
+@import 'tailwindcss';
+@import 'tw-animate-css';
+
+@custom-variant dark (&:is(.dark *));
+
+:root {
+ --background: oklch(1 0 0);
+ --foreground: oklch(0.145 0 0);
+ --card: oklch(1 0 0);
+ --card-foreground: oklch(0.145 0 0);
+ --popover: oklch(1 0 0);
+ --popover-foreground: oklch(0.145 0 0);
+ --primary: oklch(0.205 0 0);
+ --primary-foreground: oklch(0.985 0 0);
+ --secondary: oklch(0.97 0 0);
+ --secondary-foreground: oklch(0.205 0 0);
+ --muted: oklch(0.97 0 0);
+ --muted-foreground: oklch(0.556 0 0);
+ --accent: oklch(0.97 0 0);
+ --accent-foreground: oklch(0.205 0 0);
+ --destructive: oklch(0.577 0.245 27.325);
+ --destructive-foreground: oklch(0.577 0.245 27.325);
+ --border: oklch(0.922 0 0);
+ --input: oklch(0.922 0 0);
+ --ring: oklch(0.708 0 0);
+ --chart-1: oklch(0.646 0.222 41.116);
+ --chart-2: oklch(0.6 0.118 184.704);
+ --chart-3: oklch(0.398 0.07 227.392);
+ --chart-4: oklch(0.828 0.189 84.429);
+ --chart-5: oklch(0.769 0.188 70.08);
+ --radius: 0.625rem;
+ --sidebar: oklch(0.985 0 0);
+ --sidebar-foreground: oklch(0.145 0 0);
+ --sidebar-primary: oklch(0.205 0 0);
+ --sidebar-primary-foreground: oklch(0.985 0 0);
+ --sidebar-accent: oklch(0.97 0 0);
+ --sidebar-accent-foreground: oklch(0.205 0 0);
+ --sidebar-border: oklch(0.922 0 0);
+ --sidebar-ring: oklch(0.708 0 0);
+}
+
+.dark {
+ --background: oklch(0.145 0 0);
+ --foreground: oklch(0.985 0 0);
+ --card: oklch(0.145 0 0);
+ --card-foreground: oklch(0.985 0 0);
+ --popover: oklch(0.145 0 0);
+ --popover-foreground: oklch(0.985 0 0);
+ --primary: oklch(0.985 0 0);
+ --primary-foreground: oklch(0.205 0 0);
+ --secondary: oklch(0.269 0 0);
+ --secondary-foreground: oklch(0.985 0 0);
+ --muted: oklch(0.269 0 0);
+ --muted-foreground: oklch(0.708 0 0);
+ --accent: oklch(0.269 0 0);
+ --accent-foreground: oklch(0.985 0 0);
+ --destructive: oklch(0.396 0.141 25.723);
+ --destructive-foreground: oklch(0.637 0.237 25.331);
+ --border: oklch(0.269 0 0);
+ --input: oklch(0.269 0 0);
+ --ring: oklch(0.439 0 0);
+ --chart-1: oklch(0.488 0.243 264.376);
+ --chart-2: oklch(0.696 0.17 162.48);
+ --chart-3: oklch(0.769 0.188 70.08);
+ --chart-4: oklch(0.627 0.265 303.9);
+ --chart-5: oklch(0.645 0.246 16.439);
+ --sidebar: oklch(0.205 0 0);
+ --sidebar-foreground: oklch(0.985 0 0);
+ --sidebar-primary: oklch(0.488 0.243 264.376);
+ --sidebar-primary-foreground: oklch(0.985 0 0);
+ --sidebar-accent: oklch(0.269 0 0);
+ --sidebar-accent-foreground: oklch(0.985 0 0);
+ --sidebar-border: oklch(0.269 0 0);
+ --sidebar-ring: oklch(0.439 0 0);
+}
+
+@theme inline {
+ --font-sans: var(--font-geist-sans);
+ --font-mono: var(--font-geist-mono);
+ --color-background: var(--background);
+ --color-foreground: var(--foreground);
+ --color-card: var(--card);
+ --color-card-foreground: var(--card-foreground);
+ --color-popover: var(--popover);
+ --color-popover-foreground: var(--popover-foreground);
+ --color-primary: var(--primary);
+ --color-primary-foreground: var(--primary-foreground);
+ --color-secondary: var(--secondary);
+ --color-secondary-foreground: var(--secondary-foreground);
+ --color-muted: var(--muted);
+ --color-muted-foreground: var(--muted-foreground);
+ --color-accent: var(--accent);
+ --color-accent-foreground: var(--accent-foreground);
+ --color-destructive: var(--destructive);
+ --color-destructive-foreground: var(--destructive-foreground);
+ --color-border: var(--border);
+ --color-input: var(--input);
+ --color-ring: var(--ring);
+ --color-chart-1: var(--chart-1);
+ --color-chart-2: var(--chart-2);
+ --color-chart-3: var(--chart-3);
+ --color-chart-4: var(--chart-4);
+ --color-chart-5: var(--chart-5);
+ --radius-sm: calc(var(--radius) - 4px);
+ --radius-md: calc(var(--radius) - 2px);
+ --radius-lg: var(--radius);
+ --radius-xl: calc(var(--radius) + 4px);
+ --color-sidebar: var(--sidebar);
+ --color-sidebar-foreground: var(--sidebar-foreground);
+ --color-sidebar-primary: var(--sidebar-primary);
+ --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
+ --color-sidebar-accent: var(--sidebar-accent);
+ --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
+ --color-sidebar-border: var(--sidebar-border);
+ --color-sidebar-ring: var(--sidebar-ring);
+}
+
+@layer base {
+ * {
+ @apply border-border outline-ring/50;
+ }
+ body {
+ @apply bg-background text-foreground;
+ }
+}
diff --git a/[core]/esx_whitelist/web/tsconfig.json b/[core]/esx_whitelist/web/tsconfig.json
new file mode 100644
index 000000000..bfb5cd229
--- /dev/null
+++ b/[core]/esx_whitelist/web/tsconfig.json
@@ -0,0 +1,41 @@
+{
+ "compilerOptions": {
+ "lib": [
+ "dom",
+ "dom.iterable",
+ "esnext"
+ ],
+ "allowJs": true,
+ "target": "ES6",
+ "skipLibCheck": true,
+ "strict": true,
+ "noEmit": true,
+ "esModuleInterop": true,
+ "module": "esnext",
+ "moduleResolution": "bundler",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "jsx": "preserve",
+ "incremental": true,
+ "plugins": [
+ {
+ "name": "next"
+ }
+ ],
+ "paths": {
+ "@/*": [
+ "./*"
+ ]
+ }
+ },
+ "include": [
+ "**/*.ts",
+ "**/*.tsx",
+ ".next/types/**/*.ts",
+ "next-env.d.ts",
+ "dist/types/**/*.ts"
+ ],
+ "exclude": [
+ "node_modules"
+ ]
+}