diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..d856b2b5 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/\[esx_addons\]/esx_shops diff --git a/[esx_addons]/esx_halloween/README.md b/[esx_addons]/esx_halloween/README.md new file mode 100644 index 00000000..6a2a824f --- /dev/null +++ b/[esx_addons]/esx_halloween/README.md @@ -0,0 +1,206 @@ +# ESX Halloween Event + +Dead players don't have to stay dead. This FiveM/ESX resource adds a ghost respawn system that lets players come back as haunting specters with the ability to terrify the living. Built for performance, security, and fully configurable. + +## What This Does + +When a player dies, there's a chance they'll be offered a choice: respawn normally, or come back as a ghost. Choose the spectral path and you'll get increased speed, partial invisibility, and the ability to scare nearby players with jump scares. Ghosts can roam for up to 10 minutes before automatically respawning, or they can exit ghost mode whenever they want. + +The resource also includes a flexible notification system with Halloween theming, perfect for event-specific messages and alerts. + +## Ghost System Breakdown + +**The Basics** +Configure spawn chances, ghost duration, visibility range, and movement speed through a straightforward config file. Ghosts use a zombie ped model by default but this can be changed to any model you prefer. + +**Visibility Mechanics** +Ghosts aren't fully transparent. They appear at 30% opacity to players within 25 meters, but become completely invisible beyond that range. This creates an effective balance between spookiness and gameplay clarity. + +**Movement & Abilities** +Ghost players move 50% faster than normal and can trigger a scare ability using the E key (configurable). The scare has a 30-second cooldown and affects all players within 10 meters, triggering screen shake, sounds, and a brief jumpscare visual effect. When scared, players receive a notification showing which ghost haunted them. Ghosts cannot use weapons or vehicles. + +**Admin Controls** +Admins can force the ghost choice dialog on any player using `/ghost [player_id]`. This is useful for events or testing. Permission is tied to ESX groups defined in the config. + +## Installation + +Standard resource installation applies here: + +1. Drop the `esx_halloween` folder into your server's resources directory +2. Add `ensure esx_halloween` to your server.cfg +3. Edit `config.lua` to match your preferences +4. Restart the server + +## Configuration + +Everything meaningful can be adjusted in `config.lua`: + +```lua +Config = { + Ghost = { + enabled = true, + spawnChance = 20, -- 20% chance on death + pedModel = 's_m_y_zombie_01', -- Ped model for ghost + maxDuration = 600000, -- 10 minutes + + visibility = { + range = 25.0, -- Visibility range in meters + alpha = 77 -- Opacity (0-255) + }, + + movement = { + speedMultiplier = 1.5 -- 1.5x speed + }, + + abilities = { + scare = { + enabled = true, + cooldown = 30000, -- 30 seconds + range = 10.0, -- 10 meters + keybind = 'E', + effects = { + screenShake = true, + sound = true, + duration = 3000 -- 3 seconds + } + } + } + }, + + AdminGroups = { + 'admin', + 'superadmin' + } +} +``` + +The spawn chance determines how likely a player is to get the ghost option after death. Duration controls the maximum time as a ghost. Visibility settings affect how ghosts appear to other players. Movement multiplier adjusts ghost speed. + +## Commands + +**`/ghost [player_id]`** (Admin Only) +Triggers the ghost choice dialog. Without an ID, it targets yourself. With an ID, it targets that player. Requires admin or superadmin group membership. + +``` +/ghost # Show choice for yourself +/ghost 5 # Show choice for player ID 5 +``` + +## Using Notifications + +The notification system can be triggered from any Lua code: + +```lua +exports['esx_halloween']:showNotification({ + size = 'small', + position = 'top-right', + header = 'Item Received', + description = 'You found a Halloween candy!', + duration = 5000 +}) +``` + +Notifications support small and large sizes, multiple positioning options (top-left, top-right, top-center, bottom-center), and queue-based display so they don't overlap. Check `EXPORTS.md` for complete export documentation. + +## How Ghost Mode Works + +**Activation Sequence** +Player dies → system rolls spawn chance → if successful, choice dialog appears → player selects ghost or normal respawn → client requests ghost mode from server → server validates and approves/denies request → client enables ghost mode after server approval. + +This request-response pattern prevents exploits and ensures proper server-side validation of all ghost mode activations. + +**While Ghosting** +Movement speed increases by 50%. Opacity is set to 30% for nearby players. The scare ability becomes available on E key with a 30-second cooldown. Weapons and vehicles are disabled. A HUD displays remaining time and provides an exit button. + +When a ghost uses the scare ability, the target player sees a jumpscare effect and receives a notification 2 seconds later showing which ghost scared them. + +**Ending Ghost Mode** +Either the 10-minute timer expires and the player auto-respawns, or the player presses X (configurable) to exit ghost mode early. + +## Theme Integration + +This resource respects ESX UI theme convars, so it will automatically match your server's color scheme: + +- `esx:ui:primaryColor` +- `esx:ui:secondaryColor` +- `esx:ui:backgroundColor` +- `esx:ui:accentColor` +- `esx:ui:logoUrl` + +Colors apply automatically on resource start without requiring manual configuration. + +## Technical Notes + +**Requirements** +ESX Legacy framework, FiveM server with `use_fxv2_oal` support, Lua 5.4. + +**Performance Optimizations** +Event-driven architecture means no constant loops. Visibility checks use adaptive intervals: 500ms when ghosts are active, 2000ms when idle. Control disabling runs every frame (Wait(0)) as required by native functions. Model loading has a 10-second timeout. These performance values are hardcoded for optimal balance between responsiveness and resource usage. + +**Security Features** +- All ghost mode requests require server-side validation +- Request-response pattern with 5-second timeout prevents client-side exploits +- Cooldown system prevents spam (60s ghost request, 30s scare ability) +- Maximum concurrent ghost limit (configurable, default 10) +- Scare range validation on server-side +- Input sanitization prevents XSS attacks in notifications +- Parameter validation on all exports + +**ESX Compliance** +No async code in net events. `use_fxv2_oal 'yes'` is enabled. ESX theme convars are supported. Type definitions are included for better development experience. + +**File Structure** +``` +esx_halloween/ +├── fxmanifest.lua # Resource manifest +├── config.lua # Configuration +├── types.lua # Lua type definitions +├── README.md # This file +├── EXPORTS.md # Export documentation +├── shared/ +│ └── events.lua # Centralized event constants +├── client/ +│ ├── main.lua # Notification system +│ ├── ghost.lua # Ghost system logic +│ ├── respawn.lua # Respawn handling +│ └── convars.lua # ESX theme support +├── server/ +│ ├── main.lua # Server initialization +│ ├── ghost.lua # Ghost state sync & validation +│ ├── commands.lua # Admin commands +│ └── config_validator.lua # Config validation on start +└── web/ + ├── build/ # Production build + └── src/ # Svelte 5 source code +``` + +## Building the UI + +If you modify the Svelte source: + +```bash +cd web +npm install +npm run build +``` + +The build output goes to `web/build` and is automatically loaded by the resource. + +## Troubleshooting + +**Ghost choice not appearing after death** +Check that `Config.Ghost.enabled` is set to true and that spawn chance is above 0. Console errors will indicate if something else is wrong. + +**UI elements not rendering** +Verify the resource is actually started with `ensure esx_halloween` in your server.cfg. Check that the `web/build` folder exists and contains the UI files. Browser console (F8) will show any client-side errors. + +**Admin command rejected** +Confirm your ESX group is listed in `Config.AdminGroups`. Player IDs must be valid online players. Permission errors will show in chat. + +## License + +Open source and free to modify. Use it however you need. + +## Development Notes + +Built for ESX Legacy with modern best practices. Event-driven architecture keeps performance high. Optimized for production servers but easy to extend for custom features. diff --git a/[esx_addons]/esx_halloween/client/convars.lua b/[esx_addons]/esx_halloween/client/convars.lua new file mode 100644 index 00000000..60ddd44a --- /dev/null +++ b/[esx_addons]/esx_halloween/client/convars.lua @@ -0,0 +1,23 @@ +--- Retrieves ESX UI theme colors from server convars +--- Used to sync ESX Halloween UI with server's ESX color scheme +--- Falls back to default orange (#fb9b04) if convars not set +---@return ThemeColors Table with primary, secondary, background, accent, logoUrl fields +local function GetESXThemeColors() + return { + primary = GetConvar('esx:ui:primaryColor', '#fb9b04'), + secondary = GetConvar('esx:ui:secondaryColor', '#1a1a1a'), + background = GetConvar('esx:ui:backgroundColor', '#000000'), + accent = GetConvar('esx:ui:accentColor', '#fb9b04'), + logoUrl = GetConvar('esx:ui:logoUrl', '') + } +end + +--- NUI Callback for frontend to request theme colors +--- Prevents race conditions by letting frontend fetch when ready +RegisterNUICallback('ready', function(data, cb) + local colors = GetESXThemeColors() + cb({ + ok = true, + data = colors + }) +end) diff --git a/[esx_addons]/esx_halloween/client/ghost.lua b/[esx_addons]/esx_halloween/client/ghost.lua new file mode 100644 index 00000000..8f88b46b --- /dev/null +++ b/[esx_addons]/esx_halloween/client/ghost.lua @@ -0,0 +1,427 @@ +local KEYBIND_MAP = { + ['X'] = 73, + ['E'] = 38, + ['DELETE'] = 178, + ['BACKSPACE'] = 177 +} + +---@type GhostState +local ghostState = { + isGhost = false, + lastScareTime = 0, + activatedByDeath = false, + startTime = 0, + pendingRequest = false +} + +local otherGhostPlayers = {} +local pendingGhostFromDeath = false +local ghostRequestTimeout = nil + +--- Checks if the local player is currently in ghost mode +---@return boolean True if player is a ghost, false otherwise +local function IsGhost() + return ghostState.isGhost +end + +--- Requests ghost mode activation from server +--- Waits for server approval before enabling ghost mode +--- Includes timeout handling if server doesn't respond +---@param fromDeath boolean Whether ghost mode was activated from death +---@return nil +local function RequestGhostMode(fromDeath) + if ghostState.isGhost or ghostState.pendingRequest then return end + + ghostState.pendingRequest = true + ghostState.activatedByDeath = fromDeath or false + + -- Request ghost mode from server + TriggerServerEvent(Events.REQUEST_GHOST_MODE, 'ghost') + + -- Set timeout in case server doesn't respond + ghostRequestTimeout = ESX.SetTimeout(Config.Ghost.security.requestTimeout, function() + if ghostState.pendingRequest then + ghostState.pendingRequest = false + ghostState.activatedByDeath = false + print('^3[ESX Halloween] Ghost mode request timed out^7') + end + end) +end + +--- Enables ghost mode after server approval +--- Changes model to zombie, applies transparency (30% alpha) +--- Disables combat, increases speed by 1.5x, shows ghost HUD +--- Automatically exits after Config.Ghost.maxDuration or manual X key press +---@param fromDeath boolean Whether ghost mode was activated from death +---@return boolean success True if ghost mode was enabled successfully +local function EnableGhostMode(fromDeath) + if ghostState.isGhost then return false end + + -- Clear timeout if exists + if ghostRequestTimeout then + ESX.ClearTimeout(ghostRequestTimeout) + ghostRequestTimeout = nil + end + + ghostState.pendingRequest = false + ghostState.activatedByDeath = fromDeath or false + + if fromDeath then + TriggerEvent('esx_ambulancejob:revive') + Wait(500) + + local playerPed = PlayerPedId() + ClearPedTasks(playerPed) + FreezeEntityPosition(playerPed, false) + SetEntityInvincible(playerPed, false) + + Wait(100) + end + + local model = GetHashKey(Config.Ghost.pedModel) + + RequestModel(model) + local startTime = GetGameTimer() + while not HasModelLoaded(model) do + Wait(100) + if GetGameTimer() - startTime > 10000 then -- 10 second timeout + print('^1[ESX Halloween] Failed to load ghost model: ' .. Config.Ghost.pedModel .. '^7') + -- Rollback state + ghostState.activatedByDeath = false + return false + end + end + + SetPlayerModel(PlayerId(), model) + SetModelAsNoLongerNeeded(model) + + local playerPed = PlayerPedId() + ghostState.isGhost = true + ghostState.lastScareTime = 0 + ghostState.startTime = GetGameTimer() + + SetEntityAlpha(playerPed, Config.Ghost.visibility.alpha, false) + SetPedCanRagdoll(playerPed, false) + SetEntityInvincible(playerPed, true) + SetPlayerInvincible(PlayerId(), true) + + SetRunSprintMultiplierForPlayer(PlayerId(), Config.Ghost.movement.speedMultiplier) + + SendNUIMessage({ + type = 'showGhostHUD', + maxDuration = Config.Ghost.maxDuration, + exitKey = Config.Ghost.exitKeybind, + scareKey = Config.Ghost.abilities.scare.keybind + }) + + -- Start Ghost Control Thread (only runs while player is a ghost) + CreateThread(function() + while ghostState.isGhost do + Wait(0) -- Must be 0 for DisableControlAction to work + + -- GHOST CONTROL DISABLING (every frame) + DisableControlAction(0, 24, true) -- Attack + DisableControlAction(0, 25, true) -- Aim + DisableControlAction(0, 140, true) -- Melee light attack + DisableControlAction(0, 141, true) -- Melee heavy attack + DisableControlAction(0, 142, true) -- Melee alternate attack + + -- Check exit keybind + if IsControlJustPressed(0, exitKey) then + DisableGhostMode() + break + end + + -- Check scare keybind + if Config.Ghost.abilities.scare.enabled and IsControlJustPressed(0, scareKey) then + local currentTime = GetGameTimer() + if currentTime - ghostState.lastScareTime >= Config.Ghost.abilities.scare.cooldown then + local playerPed = PlayerPedId() + local playerCoords = GetEntityCoords(playerPed) + local closestPlayer, closestDistance = ESX.Game.GetClosestPlayer(playerCoords) + + if closestPlayer ~= -1 and closestDistance and closestDistance <= Config.Ghost.abilities.scare.range then + local targetId = GetPlayerServerId(closestPlayer) + TriggerServerEvent(Events.TRIGGER_SCARE, targetId) + ghostState.lastScareTime = currentTime + end + end + end + end + end) + + -- Start Ghost Duration Check Thread (only runs while player is a ghost) + CreateThread(function() + while ghostState.isGhost do + Wait(1000) + + if ghostState.startTime > 0 then + local elapsed = GetGameTimer() - ghostState.startTime + if elapsed >= Config.Ghost.maxDuration then + DisableGhostMode() + break + end + end + end + end) + + return true +end + +--- Disables ghost mode and restores player to normal state +--- Handles screen fade, skin restoration, and optional hospital respawn +--- Re-enables combat controls and resets speed multiplier to 1.0 +--- If activated by death, triggers hospital respawn via RespawnAtHospital() +--- Notifies server of ghost mode exit via event +---@return nil +local function DisableGhostMode() + if not ghostState.isGhost then return end + + local wasActivatedByDeath = ghostState.activatedByDeath + + -- Reset ghost state + ghostState.isGhost = false + ghostState.activatedByDeath = false + ghostState.startTime = 0 + + -- Reset player stats + SetRunSprintMultiplierForPlayer(PlayerId(), 1.0) + SetPlayerInvincible(PlayerId(), false) + + -- Fade to black for smooth transition + FadeScreenOut() + + -- Restore player model and skin + RestorePlayerSkin(function() + if wasActivatedByDeath then + -- Respawn at hospital with fade in + RespawnAtHospital() + else + -- Just fade back in at current location + FadeScreenIn() + end + end) + + -- Notify server and hide UI + TriggerServerEvent(Events.EXIT_GHOST_MODE) + SendNUIMessage({ + type = 'hideGhostHUD' + }) +end + +-- Death handler +AddEventHandler('esx:onPlayerDeath', function() + if not Config.Ghost.enabled then return end + + if math.random(100) <= Config.Ghost.spawnChance then + ESX.SetTimeout(Config.Ghost.security.deathCooldown, function() + pendingGhostFromDeath = true + SetNuiFocus(true, true) + SendNUIMessage({ + type = 'showGhostChoice' + }) + + -- Auto-clear focus after 30 seconds to prevent permanent input blocking + ESX.SetTimeout(30000, function() + if pendingGhostFromDeath then + SetNuiFocus(false, false) + pendingGhostFromDeath = false + SendNUIMessage({ type = 'hideGhostChoice' }) + end + end) + end) + end +end) + +-- Show ghost choice (admin command) +RegisterNetEvent(Events.SHOW_GHOST_CHOICE) +AddEventHandler(Events.SHOW_GHOST_CHOICE, function(forced) + -- Validate parameter type + if type(forced) ~= "boolean" then + forced = false + end + + pendingGhostFromDeath = false + SetNuiFocus(true, true) + SendNUIMessage({ + type = 'showGhostChoice', + forced = forced + }) + + -- Auto-clear focus after 30 seconds to prevent permanent input blocking + ESX.SetTimeout(30000, function() + SetNuiFocus(false, false) + SendNUIMessage({ type = 'hideGhostChoice' }) + end) +end) + +-- Server response to ghost mode request +RegisterNetEvent(Events.GHOST_MODE_RESPONSE) +AddEventHandler(Events.GHOST_MODE_RESPONSE, function(approved, reason) + -- Validate parameter types + if type(approved) ~= "boolean" then + print('^1[ESX Halloween] Invalid ghost mode response^7') + return + end + + if not ghostState.pendingRequest then return end + + if approved then + local success = EnableGhostMode(ghostState.activatedByDeath) + if not success then + print('^1[ESX Halloween] Failed to enable ghost mode^7') + end + else + -- Request denied + ghostState.pendingRequest = false + ghostState.activatedByDeath = false + + if ghostRequestTimeout then + ESX.ClearTimeout(ghostRequestTimeout) + ghostRequestTimeout = nil + end + + if type(reason) == "string" then + print('^3[ESX Halloween] ' .. reason .. '^7') + end + end +end) + +-- NUI Callbacks +RegisterNUICallback('ghostChoice', function(data, cb) + SetNuiFocus(false, false) + + -- Validate data + if type(data) ~= 'table' or not data.choice then + print('^1[ESX Halloween] Invalid ghost choice data^7') + cb('error') + return + end + + if data.choice == 'ghost' then + RequestGhostMode(pendingGhostFromDeath) + end + pendingGhostFromDeath = false + cb('ok') +end) + +-- Cache keybinds +local exitKey = KEYBIND_MAP[Config.Ghost.exitKeybind] or 73 +local scareKey = KEYBIND_MAP[Config.Ghost.abilities.scare.keybind] or 38 + +--- Ghost Visibility Thread +--- Updates alpha/visibility for other ghost players based on distance +--- Performance: Checks every 500ms, only when other ghosts exist +CreateThread(function() + local visibilityRange = Config.Ghost.visibility.range + local visibilityAlpha = Config.Ghost.visibility.alpha + + while true do + Wait(500) + + local hasGhosts = next(otherGhostPlayers) ~= nil + + if hasGhosts then + local playerPed = PlayerPedId() + local playerCoords = GetEntityCoords(playerPed) + + for playerId in pairs(otherGhostPlayers) do + local targetPlayer = GetPlayerFromServerId(playerId) + + if targetPlayer ~= -1 then + local targetPed = GetPlayerPed(targetPlayer) + if targetPed ~= 0 then + local targetCoords = GetEntityCoords(targetPed) + local distance = #(playerCoords - targetCoords) + + if distance <= visibilityRange then + SetEntityAlpha(targetPed, visibilityAlpha, false) + else + SetEntityAlpha(targetPed, 0, false) + end + end + end + end + end + end +end) + +-- Receive scare effect +RegisterNetEvent(Events.RECEIVE_SCARE) +AddEventHandler(Events.RECEIVE_SCARE, function(ghostName) + -- Validate parameter type + if type(ghostName) ~= "string" then + ghostName = "A ghost" + end + + SendNUIMessage({ + type = 'triggerJumpscare', + soundVolume = Config.Ghost.abilities.scare.effects.soundVolume + }) + + if Config.Ghost.abilities.scare.effects.screenShake then + ShakeGameplayCam('SMALL_EXPLOSION_SHAKE', 0.3) + end + + if Config.Ghost.abilities.scare.effects.sound then + PlaySoundFrontend(-1, 'CHECKPOINT_MISSED', 'HUD_MINI_GAME_SOUNDSET', true) + end + + -- Show notification AFTER jumpscare effect (2 second delay) + ESX.SetTimeout(2000, function() + SendNUIMessage({ + type = 'showNotification', + size = 'large', + position = 'bottom-center', + header = 'You were scared!', + description = ghostName .. ' has haunted you as a ghost!', + duration = 4000 + }) + end) +end) + +-- Sync ghost states from server +RegisterNetEvent(Events.SYNC_GHOST_STATE) +AddEventHandler(Events.SYNC_GHOST_STATE, function(playerId, isGhost) + -- Validate parameter types + if type(playerId) ~= "number" or type(isGhost) ~= "boolean" then + print('^1[ESX Halloween] Invalid sync ghost state data^7') + return + end + + if isGhost then + otherGhostPlayers[playerId] = true + else + otherGhostPlayers[playerId] = nil + end +end) + +--- Ghost visibility thread +--- Note: Ghost visibility is now handled by master thread in main_thread.lua + +-- Cleanup on resource stop +AddEventHandler('onResourceStop', function(resourceName) + if GetCurrentResourceName() ~= resourceName then return end + + SetNuiFocus(false, false) + + -- Reset all ghost players alpha + for playerId in pairs(otherGhostPlayers) do + local targetPlayer = GetPlayerFromServerId(playerId) + if targetPlayer ~= -1 then + local targetPed = GetPlayerPed(targetPlayer) + if targetPed ~= 0 then + SetEntityAlpha(targetPed, 255, false) + end + end + end + + if ghostState.isGhost then + DisableGhostMode() + end + + -- Clear timeouts + if ghostRequestTimeout then + ESX.ClearTimeout(ghostRequestTimeout) + end +end) diff --git a/[esx_addons]/esx_halloween/client/main.lua b/[esx_addons]/esx_halloween/client/main.lua new file mode 100644 index 00000000..4192e007 --- /dev/null +++ b/[esx_addons]/esx_halloween/client/main.lua @@ -0,0 +1,92 @@ +--- Sanitizes string input to prevent XSS/HTML injection +--- Replaces HTML special characters with safe equivalents +---@param input string Raw input string +---@return string sanitized Sanitized string safe for display +local function SanitizeString(input) + if type(input) ~= "string" then + return tostring(input) + end + + local sanitized = input + sanitized = string.gsub(sanitized, "<", "<") + sanitized = string.gsub(sanitized, ">", ">") + sanitized = string.gsub(sanitized, '"', """) + sanitized = string.gsub(sanitized, "'", "'") + sanitized = string.gsub(sanitized, "&", "&") + + -- Limit length to prevent spam + if #sanitized > 500 then + sanitized = string.sub(sanitized, 1, 500) .. "..." + end + + return sanitized +end + +--- Displays a notification to the player via NUI +--- Validates input data and sends formatted notification to web UI +--- Large notifications only support bottom-center position +--- Sanitizes header and description to prevent XSS attacks +---@param data NotificationData Notification configuration object +---@return nil +---@example +--- exports['esx_halloween']:showNotification({ +--- size = 'small', +--- position = 'top-right', +--- header = 'Achievement', +--- description = 'You found a secret!', +--- duration = 3000 +--- }) +local function ShowNotification(data) + if type(data) ~= "table" then + print("^1[ESX Halloween] Error: ShowNotification requires a table parameter^7") + return + end + + if not data.size or not data.position or not data.header or not data.description then + print("^1[ESX Halloween] Error: Missing required notification fields^7") + return + end + + -- Localize to avoid constant table indexing + local size = data.size + local position = data.position + + -- Validate size + if size ~= "small" and size ~= "large" then + print("^3[ESX Halloween] Warning: Invalid size '" .. tostring(size) .. "', defaulting to 'small'^7") + size = "small" + end + + -- Validate position + local validPositions = { ["top-left"] = true, ["top-right"] = true, ["top-center"] = true, ["bottom-center"] = true } + if not validPositions[position] then + print("^3[ESX Halloween] Warning: Invalid position '" .. tostring(position) .. "', defaulting to 'top-right'^7") + position = "top-right" + end + + if size == "large" and position ~= "bottom-center" then + print("^3[ESX Halloween] Warning: Large notifications only support bottom-center position^7") + position = "bottom-center" + end + + -- Sanitize strings + local header = SanitizeString(data.header) + local description = SanitizeString(data.description) + + -- Validate duration + local duration = tonumber(data.duration) or 5000 + if duration < 1000 or duration > 30000 then + duration = 5000 + end + + SendNUIMessage({ + type = 'showNotification', + size = size, + position = position, + header = header, + description = description, + duration = duration + }) +end + +exports('showNotification', ShowNotification) diff --git a/[esx_addons]/esx_halloween/client/respawn.lua b/[esx_addons]/esx_halloween/client/respawn.lua new file mode 100644 index 00000000..8a1d7ad5 --- /dev/null +++ b/[esx_addons]/esx_halloween/client/respawn.lua @@ -0,0 +1,115 @@ +--- Finds the closest hospital respawn point from Config.RespawnPoints +---@return table|nil Closest respawn point with coords and heading, or nil if no respawn points configured +---@example +--- local hospital = GetClosestRespawnPoint() +--- if hospital then +--- SetEntityCoords(ped, hospital.coords.x, hospital.coords.y, hospital.coords.z) +--- end +local function GetClosestRespawnPoint() + if not Config.RespawnPoints or #Config.RespawnPoints == 0 then + print('^1[ESX Halloween] Error: No respawn points configured in Config.RespawnPoints^7') + return nil + end + + local plyCoords = GetEntityCoords(PlayerPedId()) + local closestDist, closestHospital + + for i = 1, #Config.RespawnPoints do + local dist = #(plyCoords - Config.RespawnPoints[i].coords) + + if not closestDist or dist <= closestDist then + closestDist, closestHospital = dist, Config.RespawnPoints[i] + end + end + + return closestHospital +end + +--- Fades screen to black and waits for completion +--- Uses 800ms fade duration as standard +---@return nil +function FadeScreenOut() + DoScreenFadeOut(800) + while not IsScreenFadedOut() do + Wait(50) + end +end + +--- Fades screen in from black +--- Uses 800ms fade duration as standard +---@return nil +function FadeScreenIn() + DoScreenFadeIn(800) +end + +--- Restores player to freemode model and loads their saved skin +--- Determines correct model based on player's sex (male/female) +--- Loads skin via ESX skinchanger after model change +---@param callback function|nil Function to call after skin is restored +---@return nil +---@example +--- RestorePlayerSkin(function() +--- print('Skin restored') +--- end) +function RestorePlayerSkin(callback) + TriggerEvent('skinchanger:getSkin', function(skin) + if not skin then + print('^1[ESX Halloween] Error: Failed to get player skin from skinchanger^7') + if callback then callback() end + return + end + + local modelHash = skin.sex == 0 and GetHashKey('mp_m_freemode_01') or GetHashKey('mp_f_freemode_01') + + ESX.Streaming.RequestModel(modelHash, function() + SetPlayerModel(PlayerId(), modelHash) + SetModelAsNoLongerNeeded(modelHash) + + Wait(100) + + TriggerEvent('skinchanger:loadSkin', skin) + + Wait(100) + + local playerPed = PlayerPedId() + SetEntityInvincible(playerPed, false) + SetEntityAlpha(playerPed, 255, false) + SetPedCanRagdoll(playerPed, true) + + if callback then + callback() + end + end) + end) +end + +--- Respawns player at closest hospital from Config.RespawnPoints +--- Triggers ESX ambulancejob callback to remove items +--- Uses NetworkResurrectLocalPlayer for proper respawn +---@return nil +---@example +--- RespawnAtHospital() +function RespawnAtHospital() + + ESX.TriggerServerCallback('esx_ambulancejob:removeItemsAfterRPDeath', function() + local playerPed = PlayerPedId() + local closestHospital = GetClosestRespawnPoint() + + if closestHospital then + local coords = closestHospital.coords + local heading = closestHospital.heading or 0.0 + + SetEntityCoordsNoOffset(playerPed, coords.x, coords.y, coords.z, false, false, false) + NetworkResurrectLocalPlayer(coords.x, coords.y, coords.z, heading, true, false) + SetPlayerInvincible(playerPed, false) + ClearPedBloodDamage(playerPed) + + TriggerServerEvent('esx:onPlayerSpawn') + TriggerEvent('esx:onPlayerSpawn') + else + print('^1[ESX Halloween] Error: No respawn point found, respawning at current location^7') + end + + FadeScreenIn() + end) +end diff --git a/[esx_addons]/esx_halloween/client/trickortreat.lua b/[esx_addons]/esx_halloween/client/trickortreat.lua new file mode 100644 index 00000000..a6884d55 --- /dev/null +++ b/[esx_addons]/esx_halloween/client/trickortreat.lua @@ -0,0 +1,430 @@ +---Trick-or-Treat Client Logic +---Manages NPC spawning, blips, interactions, and UI updates + +---@type TrickOrTreatConfig +local Config + +---Store spawned house NPCs +---@type table houseNPCs - [houseIdx] = pedHandle +local houseNPCs = {} + +---Store active blips +---@type table blips - [houseIdx] = blipHandle +local blips = {} + +---Track current active houses +---@type table activeHouses - [houseIdx] = true +local activeHouses = {} + +---Round state tracking +---@type table roundState +local roundState = { + active = false, + totalHouses = 0, + currentHouses = 0, + timeRemaining = 0, +} + +---Player interaction state +local inRound = false + +---============================================================================= +--- NPC Management +---============================================================================= + +---Spawn NPC for a specific house location +---@param houseIdx number Index of house +---@param house table House data (coords, heading, pedModel) +local function SpawnHouseNPC(houseIdx, house) + -- Request model + local modelHash = GetHashKey(house.pedModel) + + RequestModel(modelHash) + local startTime = GetGameTimer() + while not HasModelLoaded(modelHash) do + Wait(50) + if GetGameTimer() - startTime > 5000 then + print('^1[ESX Halloween] Failed to load model: ' .. house.pedModel .. '^7') + return + end + end + + -- Get proper ground Z coordinate (search up to 50 units down) + local groundZ, groundFound = GetGroundZFor_3dCoord(house.coords.x, house.coords.y, house.coords.z + 50.0, false) + local spawnZ = groundFound and groundZ or house.coords.z + + -- Create ped at ground level + local ped = CreatePed(4, modelHash, house.coords.x, house.coords.y, spawnZ, house.heading, false, false) + + -- Ensure ped is placed properly on ground + PlaceObjectOnGroundProperly(ped) + + -- Configure ped + SetEntityAsMissionEntity(ped, true, true) + SetBlockingOfNonTemporaryEvents(ped, true) + FreezeEntityPosition(ped, true) + SetEntityInvincible(ped, true) + + -- Store reference + houseNPCs[houseIdx] = ped + + -- Release model + SetModelAsNoLongerNeeded(modelHash) +end + +---Spawn all NPCs for active houses +local function SpawnAllNPCs() + for idx, house in ipairs(Config.houses) do + if activeHouses[idx] then + SpawnHouseNPC(idx, house) + end + end + + print('^2[ESX Halloween] Spawned Trick-or-Treat NPCs^7') +end + +---Delete all spawned NPCs +local function DeleteAllNPCs() + for idx, ped in pairs(houseNPCs) do + if DoesEntityExist(ped) then + DeleteEntity(ped) + end + end + houseNPCs = {} +end + +---============================================================================= +--- Blip Management +---============================================================================= + +---Create or update blips for active houses +---@param activeHouseIds table Array of active house indices +local function UpdateActiveBlips(activeHouseIds) + -- Delete old blips + for _, blip in pairs(blips) do + if blip ~= 0 then + RemoveBlip(blip) + end + end + blips = {} + + -- Create new blips for active houses + for _, houseIdx in ipairs(activeHouseIds) do + local house = Config.houses[houseIdx] + if house then + local blip = AddBlipForCoord(house.coords.x, house.coords.y, house.coords.z) + SetBlipAsShortRange(blip, false) + SetBlipSprite(blip, Config.blips.sprite) + SetBlipColour(blip, Config.blips.activeColor) + SetBlipScale(blip, Config.blips.scale) + BeginTextCommandSetBlipName('STRING') + AddTextComponentString('Trick-or-Treat House') + EndTextCommandSetBlipName(blip) + + blips[houseIdx] = blip + end + end +end + +---Remove all blips +local function RemoveAllBlips() + for _, blip in pairs(blips) do + if blip ~= 0 then + RemoveBlip(blip) + end + end + blips = {} +end + +---============================================================================= +--- Interaction Handling (Performance-Optimized Multi-Level Loop) +---============================================================================= + +---Send house collection request to server +---@param houseIdx number Index of house to collect from +local function SendHouseCollectRequest(houseIdx) + TriggerServerEvent(Events.HOUSE_COLLECT_REQUEST, houseIdx) +end + +-- Track currently nearby house for fast rendering +local nearbyHouseIdx = nil + +---============================================================================= +--- NUI Communication +---============================================================================= + +---Handle round start from server +RegisterNetEvent(Events.ROUND_START) +AddEventHandler(Events.ROUND_START, function(data) + print('^3[DEBUG] ROUND_START event received^7') + print('^3[DEBUG] Data: ' .. json.encode(data) .. '^7') + + -- Validate data parameter + if type(data) ~= "table" then + print('^1[ESX Halloween] Invalid round start data^7') + return + end + + if type(data.totalHouses) ~= "number" or + type(data.duration) ~= "number" or + type(data.activeHouseIds) ~= "table" then + print('^1[ESX Halloween] Missing required round start fields^7') + return + end + + roundState.active = true + roundState.totalHouses = data.totalHouses + roundState.currentHouses = 0 + roundState.timeRemaining = data.duration + + -- Store active houses + activeHouses = {} + for _, houseIdx in ipairs(data.activeHouseIds) do + activeHouses[houseIdx] = true + end + + -- Spawn NPCs for this round + SpawnAllNPCs() + + -- Update blips + UpdateActiveBlips(data.activeHouseIds) + + -- Notify UI + SendNUIMessage({ + type = 'trickOrTreatRoundStart', + totalHouses = data.totalHouses, + timeRemaining = data.duration, + activeHouseIds = data.activeHouseIds, + }) + + print('^2[ESX Halloween] Trick-or-Treat round started (client)^7') + + -- Start Interaction Thread (only runs while round is active) + CreateThread(function() + while roundState.active do + local sleep = 1000 -- Default: check once per second when not near a house + + -- HELP TEXT RENDERING + KEY HANDLING (only when near house) + if Config and Config.enabled and nearbyHouseIdx and roundState.active then + sleep = 0 -- Only use Wait(0) when ACTUALLY near a house + + local playerPed = PlayerPedId() + local playerCoords = GetEntityCoords(playerPed) + local house = Config.houses[nearbyHouseIdx] + + if house then + local distance = #(playerCoords - house.coords) + + if distance <= Config.interaction.distance then + BeginTextCommandDisplayHelp('STRING') + AddTextComponentString('Press ~INPUT_CONTEXT~ to collect candy') + EndTextCommandDisplayHelp(0) + + -- Handle E-key press + if IsControlJustReleased(0, Config.interaction.key) then + SendHouseCollectRequest(nearbyHouseIdx) + end + end + end + end + + Wait(sleep) + end + end) + + -- Start Proximity Detection Thread (only runs while round is active) + CreateThread(function() + while roundState.active do + Wait(500) + + if Config and Config.enabled then + local playerPed = PlayerPedId() + local playerCoords = GetEntityCoords(playerPed) + local nearestHouseIdx = nil + local nearestDistance = Config.interaction.distance + + for houseIdx, house in ipairs(Config.houses) do + if activeHouses[houseIdx] then + local distance = #(playerCoords - house.coords) + + if distance < nearestDistance then + nearestDistance = distance + nearestHouseIdx = houseIdx + end + end + end + + nearbyHouseIdx = nearestHouseIdx + else + nearbyHouseIdx = nil + end + end + end) +end) + +---Handle round end from server +RegisterNetEvent(Events.ROUND_END) +AddEventHandler(Events.ROUND_END, function(data) + -- Validate data parameter + if type(data) ~= "table" then + print('^1[ESX Halloween] Invalid round end data^7') + return + end + + if type(data.totalCollected) ~= "number" or type(data.totalHouses) ~= "number" then + print('^1[ESX Halloween] Missing round end fields^7') + return + end + + roundState.active = false + + -- Delete NPCs + DeleteAllNPCs() + + -- Remove blips + RemoveAllBlips() + + -- Notify UI + SendNUIMessage({ + type = 'trickOrTreatRoundEnd', + totalCollected = data.totalCollected, + totalHouses = data.totalHouses, + }) + + print('^2[ESX Halloween] Trick-or-Treat round ended (client)^7') +end) + +---Handle house collection response from server +RegisterNetEvent(Events.HOUSE_COLLECT_RESPONSE) +AddEventHandler(Events.HOUSE_COLLECT_RESPONSE, function(data) + -- Validate data parameter + if type(data) ~= "table" then + print('^1[ESX Halloween] Invalid collect response data^7') + return + end + + if type(data.success) ~= "boolean" then + print('^1[ESX Halloween] Missing success field^7') + return + end + + if data.success then + -- Validate reward fields + if not data.rewardType or not data.rewardItem or type(data.rewardAmount) ~= "number" then + print('^1[ESX Halloween] Missing reward data^7') + return + end + + -- Show reward popup + SendNUIMessage({ + type = 'trickOrTreatCollect', + rewardType = data.rewardType, + rewardItem = data.rewardItem, + rewardAmount = data.rewardAmount, + remainingHouses = data.remainingHouses, + }) + + -- Play success sound + TriggerEvent('esx:showNotification', 'Collected from house!') + else + -- Show error notification + local errorMsg = "Unknown error" + if type(data.error) == "string" then + errorMsg = data.error + end + TriggerEvent('esx:showNotification', 'Failed: ' .. errorMsg) + end +end) + +---Handle house state synchronization from server +RegisterNetEvent(Events.HOUSE_STATE_SYNC) +AddEventHandler(Events.HOUSE_STATE_SYNC, function(data) + -- Validate data parameter + if type(data) ~= "table" then + print('^1[ESX Halloween] Invalid state sync data^7') + return + end + + if type(data.currentHouses) ~= "number" or + type(data.totalHouses) ~= "number" or + type(data.timeRemaining) ~= "number" then + print('^1[ESX Halloween] Missing state sync fields^7') + return + end + + if roundState.active then + roundState.currentHouses = data.currentHouses + roundState.totalHouses = data.totalHouses + roundState.timeRemaining = data.timeRemaining + + -- Update UI progress + SendNUIMessage({ + type = 'trickOrTreatProgress', + currentHouses = data.currentHouses, + totalHouses = data.totalHouses, + timeRemaining = data.timeRemaining, + }) + end +end) + +---Handle trick effect (trick reward) +RegisterNetEvent(Events.TRIGGER_TRICK) +AddEventHandler(Events.TRIGGER_TRICK, function() + -- Screen shake + ShakeGameplayCam('SMALL_EXPLOSION_SHAKE', 0.5) + + -- Show jumpscare UI + SendNUIMessage({ + type = 'trickOrTreatCollect', + rewardType = 'trick', + rewardItem = 'trick', + rewardAmount = 0, + }) + + TriggerEvent('esx:showNotification', '^1You got tricked!^7') +end) + +---============================================================================= +--- Cleanup +---============================================================================= + +---Cleanup on resource stop +AddEventHandler('onResourceStop', function(resourceName) + if GetCurrentResourceName() ~= resourceName then return end + + DeleteAllNPCs() + RemoveAllBlips() + roundState.active = false + + print('^2[ESX Halloween] Trick-or-Treat client cleaned up^7') +end) + +---============================================================================= +--- Initialization +---============================================================================= + +--- Initialize config after resource load +CreateThread(function() + print('^2[TrickOrTreat] Waiting for global config...^7') + + -- Wait for global Config to be defined + local maxWaits = 50 + local waits = 0 + while not _G.Config or not _G.Config.TrickOrTreat do + Wait(100) + waits = waits + 1 + if waits >= maxWaits then + print('^1[TrickOrTreat] Config timeout!^7') + return + end + end + + Config = _G.Config.TrickOrTreat + + if not Config or not Config.enabled then + print('^3[TrickOrTreat] Trick-or-Treat is disabled, skipping init^7') + return + end + + print('^2[TrickOrTreat] ✓ Client initialized^7') +end) diff --git a/[esx_addons]/esx_halloween/config.lua b/[esx_addons]/esx_halloween/config.lua new file mode 100644 index 00000000..cba57309 --- /dev/null +++ b/[esx_addons]/esx_halloween/config.lua @@ -0,0 +1,57 @@ +---@type GhostConfig +Config = { + Ghost = { + enabled = true, + spawnChance = 20, + pedModel = 'u_m_y_zombie_01', + maxDuration = 600000, -- 10 minutes in milliseconds + exitKeybind = 'X', + + visibility = { + range = 25.0, -- Distance in meters + alpha = 77 -- ~30% opacity (0-255) + }, + + movement = { + speedMultiplier = 1.5 -- 1.5x normal speed + }, + + abilities = { + scare = { + enabled = true, + cooldown = 30000, -- 30 seconds in milliseconds + range = 10.0, -- Distance in meters + keybind = 'E', + effects = { + screenShake = true, + sound = true, + soundVolume = 0.8, + duration = 3000 -- 3 seconds in milliseconds + } + } + }, + + -- Security & anti-abuse settings + security = { + ghostRequestCooldown = 60000, -- 60 seconds between ghost mode requests + maxConcurrentGhosts = 10, -- Maximum number of ghosts at once + deathCooldown = 2000, -- 2 seconds after death before showing ghost choice + requestTimeout = 5000 -- 5 seconds timeout for ghost mode request response + } + }, + + -- Trick-or-Treat event configuration (loaded via shared_scripts) + TrickOrTreat = TrickOrTreatConfig or {}, + + -- Admin permissions for /ghost command + AdminGroups = { + 'admin', + 'superadmin' + }, + + -- Hospital respawn points (used when exiting ghost mode after death) + RespawnPoints = { + {coords = vector3(341.0, -1397.3, 32.5), heading = 48.5}, -- Central Los Santos + {coords = vector3(1836.03, 3670.99, 34.28), heading = 296.06} -- Sandy Shores + } +} diff --git a/[esx_addons]/esx_halloween/configs/trickortreat.lua b/[esx_addons]/esx_halloween/configs/trickortreat.lua new file mode 100644 index 00000000..c5f7c3e8 --- /dev/null +++ b/[esx_addons]/esx_halloween/configs/trickortreat.lua @@ -0,0 +1,97 @@ +---Trick-or-Treat Configuration +---Defines all settings for the trick-or-treating event feature +---@class TrickOrTreatConfig +TrickOrTreatConfig = { + ---Enable/disable the trick-or-treat event feature + enabled = true, + + ---Frequency of trick-or-treat rounds in minutes + roundFrequencyMinutes = 5, + + ---Number of random houses selected per round + doorsPerRound = 12, + + ---Amount of candy collected per door per round + candyPerDoor = 3, + + ---Notification display settings + notifications = { + ---Duration in ms to show round start notification + roundStart = 10000, + ---Duration in ms to show house empty notification + houseEmpty = 5000, + }, + + ---Map blip/marker settings for active houses + blips = { + ---Blip color for active houses (27 = orange) + activeColor = 27, + ---Blip sprite for candy icon (280 = candy) + sprite = 280, + ---Blip scale/size + scale = 0.8, + }, + + ---Reward probability distribution (chances must sum to 100) + rewards = { + ---Common treat reward + candy = { + item = 'medikit', + amount = 3, + chance = 70, -- 70% chance + }, + ---Rare treat reward + rareCandy = { + item = 'diamond', + amount = 1, + chance = 15, -- 15% chance + }, + ---Trick/negative effect (no item, but effects) + trick = { + chance = 15, -- 15% chance + effects = { 'jumpscare', 'small_damage' }, + }, + }, + + ---Player interaction settings + interaction = { + ---Maximum distance to interact with house/NPC + distance = 2.0, + ---Keybind for interaction (38 = E key) + key = 38, + }, + + ---House locations for trick-or-treating (xyz coordinates and heading) + ---These are real Los Santos residential locations from across the map + houses = { + -- Vinewood Hills (affluent area) + { coords = vec3(-174.35, 502.23, 137.42), heading = 45.0, pedModel = 'a_f_m_bevhills_01' }, + { coords = vec3(-682.04, 592.09, 145.39), heading = 315.0, pedModel = 'a_m_m_business_01' }, + { coords = vec3(-902.27, 694.09, 151.43), heading = 135.0, pedModel = 'a_f_m_business_02' }, + -- Rockford Hills (mansion area) + { coords = vec3(-1288.84, 439.82, 97.69), heading = 270.0, pedModel = 'a_m_y_business_01' }, + { coords = vec3(-1405.81, 526.75, 123.83), heading = 90.0, pedModel = 'a_f_m_bevhills_02' }, + { coords = vec3(-1578.23, 764.94, 189.57), heading = 0.0, pedModel = 'a_m_y_business_02' }, + -- West Vinewood (residential) + { coords = vec3(-1922.32, 166.09, 84.66), heading = 180.0, pedModel = 'a_f_y_business_01' }, + { coords = vec3(-1965.46, 211.08, 86.80), heading = 45.0, pedModel = 'a_m_y_business_03' }, + -- Mirror Park (middle class) + { coords = vec3(1265.24, -647.03, 68.12), heading = 270.0, pedModel = 'a_f_m_eastsa_01' }, + { coords = vec3(1010.47, -423.08, 65.35), heading = 135.0, pedModel = 'a_m_y_clubcust_01' }, + -- Del Perro Heights (beachside) + { coords = vec3(-1467.83, -538.98, 55.62), heading = 45.0, pedModel = 'a_f_y_business_02' }, + { coords = vec3(-1529.51, -428.57, 35.60), heading = 315.0, pedModel = 'a_m_y_hipster_01' }, + -- Downtown (apartments) + { coords = vec3(-596.61, -282.36, 35.45), heading = 90.0, pedModel = 'a_f_y_hipster_01' }, + { coords = vec3(-273.00, -957.37, 31.22), heading = 270.0, pedModel = 'a_m_y_hipster_02' }, + -- Grove Street (residential) + { coords = vec3(127.94, -1930.14, 21.38), heading = 180.0, pedModel = 'a_f_y_hipster_02' }, + { coords = vec3(91.54, -1960.98, 20.75), heading = 0.0, pedModel = 'a_m_m_eastsa_01' }, + -- Sandy Shores (outer areas) + { coords = vec3(1661.15, 3819.82, 35.18), heading = 45.0, pedModel = 'a_f_y_hipster_03' }, + { coords = vec3(1702.75, 4819.56, 42.06), heading = 315.0, pedModel = 'a_m_y_hipster_03' }, + -- Paleto Bay (rural) + { coords = vec3(-146.86, 6341.49, 31.49), heading = 135.0, pedModel = 'a_f_y_clubcust_02' }, + { coords = vec3(-1291.01, -1439.87, 4.31), heading = 270.0, pedModel = 'a_m_y_clubcust_02' }, + }, +} diff --git a/[esx_addons]/esx_halloween/fxmanifest.lua b/[esx_addons]/esx_halloween/fxmanifest.lua new file mode 100644 index 00000000..535bf10a --- /dev/null +++ b/[esx_addons]/esx_halloween/fxmanifest.lua @@ -0,0 +1,38 @@ +fx_version 'cerulean' +game 'gta5' +lua54 'yes' +use_fxv2_oal 'yes' + +description 'ESX Halloween Event - Ghost Mode & Trick-or-Treat' +version '2.0.0' + +ui_page 'web/build/index.html' + +files { + 'web/build/**/*' +} + +shared_script '@es_extended/imports.lua' + +shared_scripts { + 'shared/events.lua', + 'types.lua', + 'configs/trickortreat.lua', + 'config.lua' +} + +client_scripts { + 'client/convars.lua', + 'client/respawn.lua', + 'client/ghost.lua', + 'client/trickortreat.lua', + 'client/main.lua' +} + +server_scripts { + 'server/config_validator.lua', + 'server/main.lua', + 'server/ghost.lua', + 'server/trickortreat.lua', + 'server/commands.lua' +} diff --git a/[esx_addons]/esx_halloween/server/commands.lua b/[esx_addons]/esx_halloween/server/commands.lua new file mode 100644 index 00000000..1ab61f94 --- /dev/null +++ b/[esx_addons]/esx_halloween/server/commands.lua @@ -0,0 +1,49 @@ +---@param source number +---@return boolean +local function HasAdminPermission(source) + local xPlayer = ESX.Player(source) + if not xPlayer then return false end + + local playerGroup = xPlayer.getGroup() + + for i = 1, #Config.AdminGroups do + if playerGroup == Config.AdminGroups[i] then + return true + end + end + + return false +end + +---@param source number +---@return boolean +local function IsPlayerValid(source) + return GetPlayerEndpoint(source) ~= nil +end + +RegisterCommand('ghost', function(source, args) + if not HasAdminPermission(source) then + TriggerClientEvent('chat:addMessage', source, { + color = {255, 0, 0}, + args = {'System', 'No permission'} + }) + return + end + + local targetId = tonumber(args[1]) or source + + if not IsPlayerValid(targetId) then + TriggerClientEvent('chat:addMessage', source, { + color = {255, 0, 0}, + args = {'System', 'Player not found'} + }) + return + end + + TriggerClientEvent(Events.SHOW_GHOST_CHOICE, targetId, true) + + local adminName = GetPlayerName(source) or 'Console' + local targetName = GetPlayerName(targetId) or 'Unknown' + + print(string.format('[ESX Halloween] %s triggered ghost choice for %s (ID: %d)', adminName, targetName, targetId)) +end, false) diff --git a/[esx_addons]/esx_halloween/server/config_validator.lua b/[esx_addons]/esx_halloween/server/config_validator.lua new file mode 100644 index 00000000..6158a283 --- /dev/null +++ b/[esx_addons]/esx_halloween/server/config_validator.lua @@ -0,0 +1,124 @@ +--- Validates all config values on resource start +--- Prints warnings for invalid values and applies safe defaults +---@return boolean True if config is valid (with corrections), false if critically broken +local function ValidateConfig() + local isValid = true + + -- Validate Ghost.enabled + if type(Config.Ghost.enabled) ~= 'boolean' then + print('^3[ESX Halloween] Config warning: Ghost.enabled must be boolean, defaulting to true^7') + Config.Ghost.enabled = true + end + + -- Validate Ghost.spawnChance + if type(Config.Ghost.spawnChance) ~= 'number' or Config.Ghost.spawnChance < 0 or Config.Ghost.spawnChance > 100 then + print('^3[ESX Halloween] Config warning: Ghost.spawnChance must be 0-100, defaulting to 20^7') + Config.Ghost.spawnChance = 20 + end + + -- Validate Ghost.maxDuration + if type(Config.Ghost.maxDuration) ~= 'number' or Config.Ghost.maxDuration < 1000 then + print('^3[ESX Halloween] Config warning: Ghost.maxDuration must be >= 1000ms, defaulting to 600000^7') + Config.Ghost.maxDuration = 600000 + end + + -- Validate Ghost.visibility.range + if type(Config.Ghost.visibility.range) ~= 'number' or Config.Ghost.visibility.range <= 0 then + print('^3[ESX Halloween] Config warning: Ghost.visibility.range must be > 0, defaulting to 25.0^7') + Config.Ghost.visibility.range = 25.0 + end + + -- Validate Ghost.visibility.alpha + if type(Config.Ghost.visibility.alpha) ~= 'number' or Config.Ghost.visibility.alpha < 0 or Config.Ghost.visibility.alpha > 255 then + print('^3[ESX Halloween] Config warning: Ghost.visibility.alpha must be 0-255, defaulting to 77^7') + Config.Ghost.visibility.alpha = 77 + end + + -- Validate Ghost.movement.speedMultiplier + if type(Config.Ghost.movement.speedMultiplier) ~= 'number' or Config.Ghost.movement.speedMultiplier < 0.1 or Config.Ghost.movement.speedMultiplier > 5.0 then + print('^3[ESX Halloween] Config warning: Ghost.movement.speedMultiplier must be 0.1-5.0, defaulting to 1.5^7') + Config.Ghost.movement.speedMultiplier = 1.5 + end + + -- Validate Ghost.abilities.scare.cooldown + if type(Config.Ghost.abilities.scare.cooldown) ~= 'number' or Config.Ghost.abilities.scare.cooldown < 1000 then + print('^3[ESX Halloween] Config warning: Ghost.abilities.scare.cooldown must be >= 1000ms, defaulting to 30000^7') + Config.Ghost.abilities.scare.cooldown = 30000 + end + + -- Validate Ghost.abilities.scare.range + if type(Config.Ghost.abilities.scare.range) ~= 'number' or Config.Ghost.abilities.scare.range <= 0 then + print('^3[ESX Halloween] Config warning: Ghost.abilities.scare.range must be > 0, defaulting to 10.0^7') + Config.Ghost.abilities.scare.range = 10.0 + end + + -- Validate Ghost.security.ghostRequestCooldown + if type(Config.Ghost.security.ghostRequestCooldown) ~= 'number' or Config.Ghost.security.ghostRequestCooldown < 0 then + print('^3[ESX Halloween] Config warning: Ghost.security.ghostRequestCooldown must be >= 0, defaulting to 60000^7') + Config.Ghost.security.ghostRequestCooldown = 60000 + end + + -- Validate Ghost.security.maxConcurrentGhosts + if type(Config.Ghost.security.maxConcurrentGhosts) ~= 'number' or Config.Ghost.security.maxConcurrentGhosts < 1 then + print('^3[ESX Halloween] Config warning: Ghost.security.maxConcurrentGhosts must be >= 1, defaulting to 10^7') + Config.Ghost.security.maxConcurrentGhosts = 10 + end + + -- Validate Ghost.security.deathCooldown + if type(Config.Ghost.security.deathCooldown) ~= 'number' or Config.Ghost.security.deathCooldown < 0 then + print('^3[ESX Halloween] Config warning: Ghost.security.deathCooldown must be >= 0, defaulting to 2000^7') + Config.Ghost.security.deathCooldown = 2000 + end + + -- Validate Ghost.security.requestTimeout + if type(Config.Ghost.security.requestTimeout) ~= 'number' or Config.Ghost.security.requestTimeout < 1000 then + print('^3[ESX Halloween] Config warning: Ghost.security.requestTimeout must be >= 1000ms, defaulting to 5000^7') + Config.Ghost.security.requestTimeout = 5000 + end + + -- Validate TrickOrTreat.enabled + if type(Config.TrickOrTreat.enabled) ~= 'boolean' then + print('^3[ESX Halloween] Config warning: TrickOrTreat.enabled must be boolean, defaulting to true^7') + Config.TrickOrTreat.enabled = true + end + + -- Validate TrickOrTreat.roundFrequencyMinutes + if type(Config.TrickOrTreat.roundFrequencyMinutes) ~= 'number' or Config.TrickOrTreat.roundFrequencyMinutes < 1 then + print('^3[ESX Halloween] Config warning: TrickOrTreat.roundFrequencyMinutes must be >= 1, defaulting to 5^7') + Config.TrickOrTreat.roundFrequencyMinutes = 5 + end + + -- Validate TrickOrTreat.doorsPerRound + if type(Config.TrickOrTreat.doorsPerRound) ~= 'number' or Config.TrickOrTreat.doorsPerRound < 1 then + print('^3[ESX Halloween] Config warning: TrickOrTreat.doorsPerRound must be >= 1, defaulting to 12^7') + Config.TrickOrTreat.doorsPerRound = 12 + end + + -- Validate TrickOrTreat.candyPerDoor + if type(Config.TrickOrTreat.candyPerDoor) ~= 'number' or Config.TrickOrTreat.candyPerDoor < 1 then + print('^3[ESX Halloween] Config warning: TrickOrTreat.candyPerDoor must be >= 1, defaulting to 3^7') + Config.TrickOrTreat.candyPerDoor = 3 + end + + -- Validate TrickOrTreat.houses table exists + if type(Config.TrickOrTreat.houses) ~= 'table' or #Config.TrickOrTreat.houses == 0 then + print('^3[ESX Halloween] Config warning: TrickOrTreat.houses must be non-empty table, skipping feature^7') + Config.TrickOrTreat.enabled = false + end + + -- Validate reward chances sum to 100 + local rewardTotal = (Config.TrickOrTreat.rewards.candy.chance or 0) + + (Config.TrickOrTreat.rewards.rareCandy.chance or 0) + + (Config.TrickOrTreat.rewards.trick.chance or 0) + if rewardTotal ~= 100 then + print('^3[ESX Halloween] Config warning: TrickOrTreat reward chances must sum to 100 (currently ' .. rewardTotal .. ')^7') + end + + print('^2[ESX Halloween] Config validation completed - All values are valid^7') + return isValid +end + +-- Run validation on resource start +CreateThread(function() + ValidateConfig() +end) diff --git a/[esx_addons]/esx_halloween/server/ghost.lua b/[esx_addons]/esx_halloween/server/ghost.lua new file mode 100644 index 00000000..0f0732f0 --- /dev/null +++ b/[esx_addons]/esx_halloween/server/ghost.lua @@ -0,0 +1,265 @@ +local ghostPlayers = {} +local lastGhostRequest = {} +local lastScareTime = {} + +---Checks if a player is currently in ghost mode +---@param source number Player server ID +---@return boolean isGhost True if player is a ghost +local function IsPlayerGhost(source) + return ghostPlayers[source] ~= nil +end + +---Validates if a player connection exists +---@param source number Player server ID +---@return boolean isValid True if player is online +local function IsPlayerValid(source) + return GetPlayerEndpoint(source) ~= nil +end + +---Gets the number of currently active ghosts +---@return number count Number of active ghosts +local function GetActiveGhostCount() + local count = 0 + for _ in pairs(ghostPlayers) do + count = count + 1 + end + return count +end + +---Sets or clears a player's ghost state and syncs to all clients +---@param source number Player server ID +---@param enabled boolean True to enable ghost mode, false to disable +local function SetGhostState(source, enabled) + if enabled then + ghostPlayers[source] = { + startTime = os.time(), + active = true + } + else + ghostPlayers[source] = nil + lastScareTime[source] = nil + end + + TriggerClientEvent(Events.SYNC_GHOST_STATE, -1, source, enabled) +end + +---Validates that the scare target is within allowed range +---Always performs server-side validation for security +---@param source number Ghost player server ID +---@param target number Target player server ID +---@return boolean inRange True if target is within scare range +local function ValidateScareRange(source, target) + -- Prevent ghost from scaring themselves + if source == target then + return false + end + + local sourcePed = GetPlayerPed(source) + local targetPed = GetPlayerPed(target) + + if sourcePed == 0 or targetPed == 0 then + return false + end + + local sourceCoords = GetEntityCoords(sourcePed) + local targetCoords = GetEntityCoords(targetPed) + + local distance = #(sourceCoords - targetCoords) + + return distance <= Config.Ghost.abilities.scare.range +end + +---Checks if a player can request ghost mode (respects cooldown) +---@param source number Player server ID +---@return boolean canRequest True if cooldown has passed +---@return string|nil reason Reason for denial if canRequest is false +local function CanRequestGhost(source) + local currentTime = os.time() * 1000 + local lastRequest = lastGhostRequest[source] or 0 + + if currentTime - lastRequest < Config.Ghost.security.ghostRequestCooldown then + local remainingMs = Config.Ghost.security.ghostRequestCooldown - (currentTime - lastRequest) + local remainingSec = math.ceil(remainingMs / 1000) + return false, string.format('Please wait %d seconds before requesting ghost mode again', remainingSec) + end + + return true, nil +end + +---Checks if a ghost can use scare ability (respects cooldown) +---@param source number Ghost player server ID +---@return boolean canScare True if cooldown has passed +---@return string|nil reason Reason for denial if canScare is false +local function CanUseScare(source) + local currentTime = os.time() * 1000 + local lastScare = lastScareTime[source] or 0 + + if currentTime - lastScare < Config.Ghost.abilities.scare.cooldown then + local remainingMs = Config.Ghost.abilities.scare.cooldown - (currentTime - lastScare) + local remainingSec = math.ceil(remainingMs / 1000) + return false, string.format('Scare ability on cooldown (%d seconds remaining)', remainingSec) + end + + return true, nil +end + +-- Player disconnected +AddEventHandler('playerDropped', function() + local source = source + + -- Cleanup ghost state if exists + if ghostPlayers[source] then + SetGhostState(source, false) + end + + -- Always cleanup tracking tables (even if player was never a ghost) + lastScareTime[source] = nil + lastGhostRequest[source] = nil +end) + +-- Client requests ghost mode +RegisterNetEvent(Events.REQUEST_GHOST_MODE) +AddEventHandler(Events.REQUEST_GHOST_MODE, function(choice) + local source = source + + if not IsPlayerValid(source) then + return + end + + if choice ~= 'ghost' then + TriggerClientEvent(Events.GHOST_MODE_RESPONSE, source, false, 'Invalid choice') + return + end + + -- Check if already a ghost + if IsPlayerGhost(source) then + TriggerClientEvent(Events.GHOST_MODE_RESPONSE, source, false, 'You are already a ghost') + return + end + + -- Check rate limiting + local canRequest, reason = CanRequestGhost(source) + if not canRequest then + TriggerClientEvent(Events.GHOST_MODE_RESPONSE, source, false, reason) + return + end + + -- Check concurrent ghost limit + if GetActiveGhostCount() >= Config.Ghost.security.maxConcurrentGhosts then + TriggerClientEvent(Events.GHOST_MODE_RESPONSE, source, false, 'Maximum number of ghosts reached') + return + end + + -- Approve request + lastGhostRequest[source] = os.time() * 1000 + SetGhostState(source, true) + TriggerClientEvent(Events.GHOST_MODE_RESPONSE, source, true) +end) + +-- Client exits ghost mode +RegisterNetEvent(Events.EXIT_GHOST_MODE) +AddEventHandler(Events.EXIT_GHOST_MODE, function() + local source = source + + if not IsPlayerValid(source) then + return + end + + if not IsPlayerGhost(source) then + return + end + + SetGhostState(source, false) +end) + +-- Scare ability triggered +RegisterNetEvent(Events.TRIGGER_SCARE) +AddEventHandler(Events.TRIGGER_SCARE, function(targetId) + local source = source + + -- Validate input type + if type(targetId) ~= 'number' then + print(string.format('^3[ESX Halloween] Player %d sent invalid targetId: %s^7', source, tostring(targetId))) + return + end + + -- Validate ghost status + if not IsPlayerGhost(source) then + print(string.format('^3[ESX Halloween] Player %d tried to scare without being a ghost^7', source)) + return + end + + -- Validate target exists + if not IsPlayerValid(targetId) then + return + end + + -- Validate scare cooldown + local canScare, reason = CanUseScare(source) + if not canScare then + -- Silent fail - client should handle cooldown UI + return + end + + -- Validate range + if not ValidateScareRange(source, targetId) then + -- Silent fail - client should handle range checking + return + end + + -- Update cooldown + lastScareTime[source] = os.time() * 1000 + + -- Get ghost player name + local ghostName = GetPlayerName(source) or 'A ghost' + + -- Trigger scare effect + TriggerClientEvent(Events.RECEIVE_SCARE, targetId, ghostName) +end) + +-- Cleanup on resource stop +AddEventHandler('onResourceStop', function(resourceName) + if GetCurrentResourceName() ~= resourceName then return end + + -- Notify all clients to disable all ghosts + for playerId in pairs(ghostPlayers) do + TriggerClientEvent(Events.SYNC_GHOST_STATE, -1, playerId, false) + end + + ghostPlayers = {} + lastScareTime = {} + lastGhostRequest = {} +end) + +-- Export for other resources with parameter validation +exports('IsPlayerGhost', function(source) + if type(source) ~= 'number' or source < 0 then + print('^3[ESX Halloween] Warning: IsPlayerGhost called with invalid source^7') + return false + end + + if not IsPlayerValid(source) then + return false + end + + return IsPlayerGhost(source) +end) + +exports('SetPlayerGhost', function(source, enabled) + if type(source) ~= 'number' or source < 0 then + print('^1[ESX Halloween] Error: SetPlayerGhost called with invalid source^7') + return + end + + if type(enabled) ~= 'boolean' then + print('^1[ESX Halloween] Error: SetPlayerGhost called with invalid enabled value^7') + return + end + + if not IsPlayerValid(source) then + print('^3[ESX Halloween] Warning: SetPlayerGhost called for offline player^7') + return + end + + SetGhostState(source, enabled) +end) diff --git a/[esx_addons]/esx_halloween/server/main.lua b/[esx_addons]/esx_halloween/server/main.lua new file mode 100644 index 00000000..ddb7e7b7 --- /dev/null +++ b/[esx_addons]/esx_halloween/server/main.lua @@ -0,0 +1,3 @@ +CreateThread(function() + print('[ESX Halloween] Resource started successfully') +end) diff --git a/[esx_addons]/esx_halloween/server/trickortreat.lua b/[esx_addons]/esx_halloween/server/trickortreat.lua new file mode 100644 index 00000000..ea9cc43c --- /dev/null +++ b/[esx_addons]/esx_halloween/server/trickortreat.lua @@ -0,0 +1,474 @@ +---Trick-or-Treat Server Logic +---Manages round scheduling, house state, rewards, and client communication + +---@type TrickOrTreatConfig +local TrickOrTreatConfig = Config.TrickOrTreat +local Config = Config -- Keep reference to global Config for AdminGroups + +---Track last collection time per house per round +---@type table houseCooldowns - [houseId] = lastCollectTime +local houseCooldowns = {} + +---Track current active houses in round +---@type table activeHouses - [houseId] = true if active +local activeHouses = {} + +---Track round state +---@type table roundState +local roundState = { + active = false, + startTime = 0, + duration = 0, + doorsPerRound = 0, + selectedHouses = {}, + collectedCount = 0, +} + +---Track player request cooldowns (anti-spam) +---@type table playerCooldowns - [source] = timestamp +local playerCooldowns = {} + +---============================================================================= +--- Round Management Functions +---============================================================================= + +---Selects random houses for the current round +---@param count number Number of houses to select +---@return table selectedIndices Array of randomly selected house indices +local function SelectRandomHouses(count) + local totalHouses = #TrickOrTreatConfig.houses + if count > totalHouses then count = totalHouses end + + local indices = {} + local used = {} + + while #indices < count do + local randomIdx = math.random(1, totalHouses) + if not used[randomIdx] then + table.insert(indices, randomIdx) + used[randomIdx] = true + end + end + + return indices +end + +---Starts a new trick-or-treat round +---Selects random houses and notifies all clients +local function StartTrickOrTreatRound() + if roundState.active then + print('^3[ESX Halloween] A round is already active, ignoring start request^7') + return + end + + roundState.active = true + roundState.startTime = GetGameTimer() + roundState.duration = TrickOrTreatConfig.roundFrequencyMinutes * 60000 + roundState.doorsPerRound = math.min(TrickOrTreatConfig.doorsPerRound, #TrickOrTreatConfig.houses) + roundState.selectedHouses = SelectRandomHouses(roundState.doorsPerRound) + roundState.collectedCount = 0 + + -- Initialize active houses + activeHouses = {} + for _, houseIdx in ipairs(roundState.selectedHouses) do + activeHouses[houseIdx] = true + end + + -- Notify all clients + TriggerClientEvent(Events.ROUND_START, -1, { + totalHouses = roundState.doorsPerRound, + duration = roundState.duration, + activeHouseIds = roundState.selectedHouses, + }) + + print( + '^2[ESX Halloween] Trick-or-Treat round started! ^3' .. + roundState.doorsPerRound .. + ' ^2houses available^7' + ) +end + +---Ends the current trick-or-treat round +---Resets state and notifies clients +local function EndTrickOrTreatRound() + if not roundState.active then return end + + local collected = roundState.collectedCount + local total = roundState.doorsPerRound + + TriggerClientEvent(Events.ROUND_END, -1, { + totalCollected = collected, + totalHouses = total, + }) + + roundState.active = false + roundState.selectedHouses = {} + activeHouses = {} + houseCooldowns = {} + + print('^2[ESX Halloween] Trick-or-Treat round ended! ' .. collected .. '/' .. total .. ' collected^7') +end + +---Checks if a house is currently active in the round +---@param houseIdx number Index of the house +---@return boolean True if house is active and not emptied +local function IsHouseActive(houseIdx) + return roundState.active and activeHouses[houseIdx] == true +end + +---============================================================================= +--- Reward Management Functions +---============================================================================= + +---Rolls a random reward based on configured probabilities +---@return table rewardData Contains: rewardType, item, amount +local function RollReward() + local roll = math.random(100) + local chance = 0 + + -- Check candy (70%) + chance = chance + TrickOrTreatConfig.rewards.candy.chance + if roll <= chance then + return { + rewardType = 'treat', + item = TrickOrTreatConfig.rewards.candy.item, + amount = TrickOrTreatConfig.candyPerDoor * TrickOrTreatConfig.rewards.candy.amount, + } + end + + -- Check rare candy (15%) + chance = chance + TrickOrTreatConfig.rewards.rareCandy.chance + if roll <= chance then + return { + rewardType = 'treat', + item = TrickOrTreatConfig.rewards.rareCandy.item, + amount = TrickOrTreatConfig.rewards.rareCandy.amount, + } + end + + -- Trick (15%) + return { + rewardType = 'trick', + item = 'trick', + amount = 0, + } +end + +---============================================================================= +--- Request Handlers +---============================================================================= + +---Handles player house collection request +---Server-side validation with security checks +---@param source number Player server ID +---@param houseIdx number Index of house to collect from +RegisterNetEvent(Events.HOUSE_COLLECT_REQUEST) +AddEventHandler(Events.HOUSE_COLLECT_REQUEST, function(houseIdx) + local source = source + + -- Validate player exists + if not GetPlayerEndpoint(source) then + TriggerClientEvent(Events.HOUSE_COLLECT_RESPONSE, source, { + success = false, + error = 'Player not found', + }) + return + end + + -- Validate round is active + if not roundState.active then + TriggerClientEvent(Events.HOUSE_COLLECT_RESPONSE, source, { + success = false, + error = 'No active round', + }) + return + end + + -- Validate house index + if type(houseIdx) ~= 'number' or houseIdx < 1 or houseIdx > #TrickOrTreatConfig.houses then + print( + '^1[ESX Halloween] Player ' .. + source .. ' sent invalid houseIdx: ' .. tostring(houseIdx) .. '^7' + ) + TriggerClientEvent(Events.HOUSE_COLLECT_RESPONSE, source, { + success = false, + error = 'Invalid house', + }) + return + end + + -- Validate house is active + if not IsHouseActive(houseIdx) then + TriggerClientEvent(Events.HOUSE_COLLECT_RESPONSE, source, { + success = false, + error = 'House not available', + }) + return + end + + -- Validate distance (server-side security check) + local xPlayer = ESX.Player(source) + if not xPlayer then + TriggerClientEvent(Events.HOUSE_COLLECT_RESPONSE, source, { + success = false, + error = 'Player data not found', + }) + return + end + + local playerPed = GetPlayerPed(source) + if playerPed == 0 then + TriggerClientEvent(Events.HOUSE_COLLECT_RESPONSE, source, { + success = false, + error = 'Ped not found', + }) + return + end + + local playerCoords = GetEntityCoords(playerPed) + local houseCoords = TrickOrTreatConfig.houses[houseIdx].coords + local distance = #(playerCoords - houseCoords) + + if distance > TrickOrTreatConfig.interaction.distance + 5.0 then + -- Allow 5m buffer for network latency + print( + '^1[ESX Halloween] Player ' .. + source .. + ' attempted collection from ' .. + distance .. + 'm away (max: ' .. + (TrickOrTreatConfig.interaction.distance + 5.0) .. + ')^7' + ) + TriggerClientEvent(Events.HOUSE_COLLECT_RESPONSE, source, { + success = false, + error = 'Too far away', + }) + return + end + + -- Prevent double-collection of same house + if houseCooldowns[houseIdx] and houseCooldowns[houseIdx] > GetGameTimer() - 500 then + TriggerClientEvent(Events.HOUSE_COLLECT_RESPONSE, source, { + success = false, + error = 'House already collected', + }) + return + end + + -- Roll reward + local reward = RollReward() + + -- Handle based on reward type + if reward.rewardType == 'treat' then + -- Try to add item to inventory + if xPlayer.canCarryItem(reward.item, reward.amount) then + xPlayer.addInventoryItem(reward.item, reward.amount) + else + -- Inventory full, send error response (client will show notification) + TriggerClientEvent(Events.HOUSE_COLLECT_RESPONSE, source, { + success = false, + error = 'Inventory full', + }) + return + end + elseif reward.rewardType == 'trick' then + -- Apply trick effect (jumpscare + damage) + TriggerClientEvent(Events.TRIGGER_TRICK, source) + xPlayer.addHealth(-10) -- Small damage (max health is usually 200) + end + + -- Mark house as collected this round + activeHouses[houseIdx] = false + houseCooldowns[houseIdx] = GetGameTimer() + roundState.collectedCount = roundState.collectedCount + 1 + + -- Send success response to client + TriggerClientEvent(Events.HOUSE_COLLECT_RESPONSE, source, { + success = true, + rewardType = reward.rewardType, + rewardItem = reward.item, + rewardAmount = reward.amount, + remainingHouses = roundState.doorsPerRound - roundState.collectedCount, + }) + + -- Broadcast progress update to all clients + TriggerClientEvent(Events.HOUSE_STATE_SYNC, -1, { + currentHouses = roundState.collectedCount, + totalHouses = roundState.doorsPerRound, + timeRemaining = roundState.duration - (GetGameTimer() - roundState.startTime), + }) + + print( + '^2[ESX Halloween] Player ' .. + xPlayer.getName() .. + ' collected from house ' .. houseIdx .. ' (' .. reward.item .. ')^7' + ) +end) + +---============================================================================= +--- Timers & Scheduling +---============================================================================= + +---Main round timer thread +---Manages round scheduling, duration, and auto-start +---Performance: Dynamic sleep - 10s when inactive (auto-start check), 1s when active (progress updates) +CreateThread(function() + local nextAutoStartTime = GetGameTimer() + (TrickOrTreatConfig.roundFrequencyMinutes * 60000) + local lastProgressUpdate = 0 + + print('^2[ESX Halloween] Timer thread started - next auto-start in ' .. TrickOrTreatConfig.roundFrequencyMinutes .. ' minutes^7') + + while true do + -- Dynamic sleep: 10s when inactive (idle), 1s when active (for progress updates) + local sleep = roundState.active and 1000 or 10000 + Wait(sleep) + + if not roundState.active then + -- Check if time for auto-start (every 10 seconds when idle) + if GetGameTimer() >= nextAutoStartTime then + print('^3[ESX Halloween] Auto-starting round...^7') + StartTrickOrTreatRound() + nextAutoStartTime = GetGameTimer() + (TrickOrTreatConfig.roundFrequencyMinutes * 60000) + end + else + -- Round is active - check if should end + local elapsedTime = GetGameTimer() - roundState.startTime + if elapsedTime >= roundState.duration then + EndTrickOrTreatRound() + nextAutoStartTime = GetGameTimer() + (TrickOrTreatConfig.roundFrequencyMinutes * 60000) + else + -- Send progress updates every 1 second + if GetGameTimer() - lastProgressUpdate >= 1000 then + lastProgressUpdate = GetGameTimer() + TriggerClientEvent(Events.HOUSE_STATE_SYNC, -1, { + currentHouses = roundState.collectedCount, + totalHouses = roundState.doorsPerRound, + timeRemaining = roundState.duration - elapsedTime, + }) + end + end + end + end +end) + +---Cleanup on player disconnect +AddEventHandler('playerDropped', function() + local source = source + playerCooldowns[source] = nil +end) + +---Cleanup on resource stop +AddEventHandler('onResourceStop', function(resourceName) + if GetCurrentResourceName() ~= resourceName then return end + + if roundState.active then + EndTrickOrTreatRound() + end + + houseCooldowns = {} + activeHouses = {} + playerCooldowns = {} + + print('^2[ESX Halloween] Trick-or-Treat cleaned up^7') +end) + +---============================================================================= +--- Admin Permission Check +---============================================================================= + +---Check if player has admin permission based on group +---@param source number Player ID +---@return boolean True if player is admin +local function HasAdminPermission(source) + if source == 0 then return true end -- Console always has permission + + local xPlayer = ESX.Player(source) + if not xPlayer then return false end + + local playerGroup = xPlayer.getGroup() + + for i = 1, #Config.AdminGroups do + if playerGroup == Config.AdminGroups[i] then + return true + end + end + + return false +end + +---============================================================================= +--- Admin Commands +---============================================================================= + +---Start a trick-or-treat round immediately +---@param source number Admin player ID +---@param args table Command arguments +TriggerEvent('chat:addSuggestion', '/startround', 'Start a trick-or-treat round immediately') +TriggerEvent('chat:addSuggestion', '/resetcooldowns', 'Reset all house cooldowns') + +RegisterCommand('startround', function(source, args, rawCommand) + if source == 0 then + -- Console can always execute + else + -- Check admin permission + if not HasAdminPermission(source) then + TriggerClientEvent( + 'chat:addMessage', + source, + { + args = { 'Halloween' }, + msg = '^1You do not have permission to use this command^7', + } + ) + return + end + end + + StartTrickOrTreatRound() + + if source ~= 0 then + TriggerClientEvent( + 'chat:addMessage', + source, + { args = { 'Halloween' }, msg = '^2Round started!^7' } + ) + end +end) + +RegisterCommand('resetcooldowns', function(source, args, rawCommand) + if source == 0 then + -- Console can always execute + else + -- Check admin permission + if not HasAdminPermission(source) then + TriggerClientEvent( + 'chat:addMessage', + source, + { + args = { 'Halloween' }, + msg = '^1You do not have permission to use this command^7', + } + ) + return + end + end + + houseCooldowns = {} + activeHouses = {} + + if roundState.active then + -- Re-initialize active houses + for _, houseIdx in ipairs(roundState.selectedHouses) do + activeHouses[houseIdx] = true + end + end + + if source ~= 0 then + TriggerClientEvent( + 'chat:addMessage', + source, + { args = { 'Halloween' }, msg = '^2Cooldowns reset!^7' } + ) + end +end) diff --git a/[esx_addons]/esx_halloween/shared/events.lua b/[esx_addons]/esx_halloween/shared/events.lua new file mode 100644 index 00000000..c90b05b9 --- /dev/null +++ b/[esx_addons]/esx_halloween/shared/events.lua @@ -0,0 +1,21 @@ +---Event name constants for client-server communication +---Centralizes all event names to prevent typos and improve maintainability +---@class Events +Events = { + -- Ghost Mode Events + REQUEST_GHOST_MODE = 'esx_halloween:requestGhostMode', + EXIT_GHOST_MODE = 'esx_halloween:exitGhostMode', + TRIGGER_SCARE = 'esx_halloween:triggerScare', + SHOW_GHOST_CHOICE = 'esx_halloween:showGhostChoice', + GHOST_MODE_RESPONSE = 'esx_halloween:ghostModeResponse', + SYNC_GHOST_STATE = 'esx_halloween:syncGhostState', + RECEIVE_SCARE = 'esx_halloween:receiveScare', + + -- Trick-or-Treat Events + ROUND_START = 'esx_halloween:trickOrTreatRoundStart', + ROUND_END = 'esx_halloween:trickOrTreatRoundEnd', + HOUSE_COLLECT_REQUEST = 'esx_halloween:houseCollectRequest', + HOUSE_COLLECT_RESPONSE = 'esx_halloween:houseCollectResponse', + HOUSE_STATE_SYNC = 'esx_halloween:houseStateSync', + TRIGGER_TRICK = 'esx_halloween:triggerTrick' +} diff --git a/[esx_addons]/esx_halloween/types.lua b/[esx_addons]/esx_halloween/types.lua new file mode 100644 index 00000000..dab89aed --- /dev/null +++ b/[esx_addons]/esx_halloween/types.lua @@ -0,0 +1,103 @@ +---Main ghost mode configuration +---@class GhostConfig +---@field enabled boolean Enable/disable ghost mode feature +---@field spawnChance number Percentage chance (0-100) to show ghost choice on death +---@field pedModel string Ped model hash for ghost appearance (e.g., 'u_m_y_zombie_01') +---@field maxDuration number Maximum ghost mode duration in milliseconds +---@field exitKeybind string Keybind to exit ghost mode manually +---@field visibility GhostVisibilityConfig Ghost visibility settings +---@field movement GhostMovementConfig Ghost movement settings +---@field abilities GhostAbilitiesConfig Ghost abilities configuration +---@field security GhostSecurityConfig Anti-abuse and security settings + +---Ghost visibility configuration +---@class GhostVisibilityConfig +---@field range number Distance in meters where ghosts are visible to other players +---@field alpha number Transparency level (0-255, where 255 is fully opaque) + +---Ghost movement configuration +---@class GhostMovementConfig +---@field speedMultiplier number Speed multiplier for ghost movement (1.0 = normal speed) + +---@class GhostAbilitiesConfig +---@field scare GhostScareConfig Scare ability configuration + +---@class GhostScareConfig +---@field enabled boolean Whether scare ability is enabled +---@field cooldown number Cooldown duration in milliseconds between scare uses +---@field range number Maximum distance in meters to scare targets +---@field keybind string Keybind to trigger scare ability +---@field effects GhostScareEffects Visual and audio effects configuration + +---@class GhostScareEffects +---@field screenShake boolean Enable screen shake effect on scare +---@field sound boolean Enable sound effect on scare +---@field soundVolume number Sound volume (0.0 - 1.0) +---@field duration number Effect duration in milliseconds + +---Security and anti-abuse configuration +---@class GhostSecurityConfig +---@field ghostRequestCooldown number Milliseconds cooldown between ghost mode requests per player +---@field maxConcurrentGhosts number Maximum number of concurrent ghosts allowed on server +---@field deathCooldown number Milliseconds delay after death before showing ghost choice +---@field requestTimeout number Milliseconds to wait for server response before timeout + +---Client-side ghost state tracking +---@class GhostState +---@field isGhost boolean Whether player is currently in ghost mode +---@field lastScareTime number Last time scare ability was used (GetGameTimer) +---@field activatedByDeath boolean Whether ghost mode was activated from death or admin command +---@field startTime number Ghost mode start time (GetGameTimer) +---@field pendingRequest boolean Whether a ghost mode request is awaiting server response + +---ESX player object structure +---@class ESXPlayer +---@field source number Player server ID +---@field identifier string Player unique identifier +---@field name string Player display name +---@field group string Player permission group (user, admin, superadmin, etc.) + +---ESX UI theme colors from server convars +---@class ThemeColors +---@field primary string Primary theme color (hex format) +---@field secondary string Secondary theme color (hex format) +---@field background string Background color (hex format) +---@field accent string Accent color (hex format) +---@field logoUrl string Server logo URL + +---Notification display configuration +---@class NotificationData +---@field size "small"|"large" Notification card size (large only supports bottom-center) +---@field position "top-left"|"top-right"|"top-center"|"bottom-center" Screen position for notification +---@field header string Notification title text (will be sanitized) +---@field description string Notification body text (will be sanitized) +---@field duration number|nil Duration in milliseconds (default: 5000, min: 1000, max: 30000) + +---Trick-or-Treat configuration +---@class TrickOrTreatConfig +---@field enabled boolean Enable/disable trick-or-treat feature +---@field roundFrequencyMinutes number Minutes between round starts +---@field doorsPerRound number Number of random houses per round +---@field candyPerDoor number Candy amount per door collection +---@field notifications table Notification duration settings +---@field blips table Blip color and sprite settings +---@field rewards table Reward probability and items +---@field interaction table Interaction distance and keybind +---@field houses table Array of house location data + +---Trick-or-Treat house location data +---@class HouseLocation +---@field coords vector3 House position coordinates +---@field heading number NPC heading/direction +---@field pedModel string Ped model for NPC at house + +---Trick-or-Treat state tracking (client) +---@class TrickOrTreatState +---@field hudVisible boolean Whether HUD is currently showing +---@field currentHouses number Houses collected in current round +---@field totalHouses number Total houses available in round +---@field timeRemaining number Time left in round (ms) +---@field rewardVisible boolean Whether reward popup is showing +---@field rewardType "treat"|"trick" Type of reward (treat or trick) +---@field rewardItem string Item name for reward +---@field rewardAmount number Item amount for reward diff --git a/[esx_addons]/esx_halloween/web/.gitignore b/[esx_addons]/esx_halloween/web/.gitignore new file mode 100644 index 00000000..a547bf36 --- /dev/null +++ b/[esx_addons]/esx_halloween/web/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/[esx_addons]/esx_halloween/web/.vscode/extensions.json b/[esx_addons]/esx_halloween/web/.vscode/extensions.json new file mode 100644 index 00000000..bdef8201 --- /dev/null +++ b/[esx_addons]/esx_halloween/web/.vscode/extensions.json @@ -0,0 +1,3 @@ +{ + "recommendations": ["svelte.svelte-vscode"] +} diff --git a/[esx_addons]/esx_halloween/web/build/assets/background.BL28lNcM.webp b/[esx_addons]/esx_halloween/web/build/assets/background.BL28lNcM.webp new file mode 100644 index 00000000..e17b5cfe Binary files /dev/null and b/[esx_addons]/esx_halloween/web/build/assets/background.BL28lNcM.webp differ diff --git a/[esx_addons]/esx_halloween/web/build/assets/background.webp b/[esx_addons]/esx_halloween/web/build/assets/background.webp new file mode 100644 index 00000000..e17b5cfe Binary files /dev/null and b/[esx_addons]/esx_halloween/web/build/assets/background.webp differ diff --git a/[esx_addons]/esx_halloween/web/build/assets/index.BW9W5J1Q.css b/[esx_addons]/esx_halloween/web/build/assets/index.BW9W5J1Q.css new file mode 100644 index 00000000..0d40fac7 --- /dev/null +++ b/[esx_addons]/esx_halloween/web/build/assets/index.BW9W5J1Q.css @@ -0,0 +1 @@ +.notification.svelte-1wg8nyl{position:relative;border-radius:var(--radius-lg);box-shadow:0 4px 12px #0006;display:flex;flex-direction:column;opacity:0;transition:all .4s ease-out;overflow:hidden}.notification--visible.svelte-1wg8nyl{opacity:1}.notification--small.svelte-1wg8nyl{width:21.25rem;min-height:7.5rem;padding:var(--space-md)}.notification--large.svelte-1wg8nyl{width:38.75rem;min-height:10.3125rem;padding:var(--space-lg)}.notification--top-left.svelte-1wg8nyl{transform:translate(-100%) scale(1)}.notification--top-left.notification--visible.svelte-1wg8nyl{transform:translate(0) scale(1)}.notification--top-right.svelte-1wg8nyl{transform:translate(100%) scale(1)}.notification--top-right.notification--visible.svelte-1wg8nyl{transform:translate(0) scale(1)}.notification--top-center.svelte-1wg8nyl{transform:translateY(-100%) scale(1)}.notification--top-center.notification--visible.svelte-1wg8nyl{transform:translateY(0) scale(1)}.notification--bottom-center.svelte-1wg8nyl{transform:translateY(100%) scale(1)}.notification--bottom-center.notification--visible.svelte-1wg8nyl{transform:translateY(0) scale(1)}.notification__background.svelte-1wg8nyl{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);width:120%;height:120%;object-fit:cover;filter:brightness(.7);z-index:1}.notification__pumpkin.svelte-1wg8nyl{position:absolute;right:0;top:50%;transform:translateY(-50%);z-index:2;pointer-events:none;animation:svelte-1wg8nyl-pumpkinGlow 2s ease-in-out infinite}.notification__pumpkin--small.svelte-1wg8nyl{height:90%;width:auto;right:.5rem}.notification__pumpkin--large.svelte-1wg8nyl{height:85%;width:auto;right:1rem}@keyframes svelte-1wg8nyl-pumpkinGlow{0%,to{filter:brightness(1.1) saturate(1.2)}50%{filter:brightness(1.4) saturate(1.6)}}.notification__content.svelte-1wg8nyl{position:relative;z-index:3;display:flex;flex-direction:column;gap:var(--space-sm);height:100%;justify-content:center;max-width:60%}.notification__header.svelte-1wg8nyl{margin:0;color:var(--color-brand);font-family:var(--font-family-halloween);font-size:1.5rem;font-weight:var(--font-weight-normal);line-height:1.2;letter-spacing:.02em}.notification__description.svelte-1wg8nyl{margin:0;color:var(--color-lightest);font-size:var(--font-size-body);font-weight:300;line-height:1.4}.notification--large.svelte-1wg8nyl .notification__header:where(.svelte-1wg8nyl){font-size:1.75rem}.notification--large.svelte-1wg8nyl .notification__content:where(.svelte-1wg8nyl){max-width:70%}@media(max-width:768px){.notification--small.svelte-1wg8nyl{width:90vw;max-width:21.25rem}.notification--large.svelte-1wg8nyl{width:95vw;max-width:38.75rem}.notification__content.svelte-1wg8nyl{max-width:55%}}.notification-container.svelte-v226t6{position:fixed;z-index:var(--z-notification);pointer-events:none}.notification-container.svelte-v226t6>*{pointer-events:auto}.notification-container--top-left.svelte-v226t6{top:8rem;left:var(--space-xl)}.notification-container--top-right.svelte-v226t6{top:8rem;right:var(--space-xl)}.notification-container--top-center.svelte-v226t6{top:var(--space-xl);left:50%;transform:translate(-50%)}.notification-container--bottom-center.svelte-v226t6{bottom:var(--space-xl);left:50%;transform:translate(-50%)}@media(max-width:768px){.notification-container--top-left.svelte-v226t6,.notification-container--top-right.svelte-v226t6,.notification-container--top-center.svelte-v226t6,.notification-container--bottom-center.svelte-v226t6{left:var(--space-md);right:var(--space-md)}.notification-container--top-center.svelte-v226t6,.notification-container--bottom-center.svelte-v226t6{left:50%;right:auto}}.ghost-choice-overlay.svelte-wua3zs{position:fixed;inset:0;display:flex;align-items:center;justify-content:center;background:#000c;z-index:1000;animation:svelte-wua3zs-fadeIn .3s ease}.ghost-choice-modal.svelte-wua3zs{background:#1a1a1af2;border:2px solid var(--primary-color, #fb9b04);border-radius:8px;padding:2rem;min-width:400px;text-align:center;position:relative;animation:svelte-wua3zs-slideUp .4s ease}.pumpkin-decoration.svelte-wua3zs{position:absolute;top:-40px;left:50%;transform:translate(-50%);width:80px;height:80px}.pumpkin-decoration.svelte-wua3zs img:where(.svelte-wua3zs){width:100%;height:100%;filter:drop-shadow(0 0 20px var(--primary-color, #fb9b04));animation:svelte-wua3zs-glow 2s ease-in-out infinite}h2.svelte-wua3zs{color:var(--primary-color, #fb9b04);font-size:2rem;margin:1rem 0;font-family:Creepster,cursive}p.svelte-wua3zs{color:#ffffffe6;margin-bottom:2rem}.buttons.svelte-wua3zs{display:flex;gap:1rem;justify-content:center}.btn.svelte-wua3zs{padding:.75rem 1.5rem;border:none;border-radius:4px;font-size:1rem;cursor:pointer;transition:all .2s ease;font-weight:600}.btn-ghost.svelte-wua3zs{background:var(--primary-color, #fb9b04);color:#000}.btn-ghost.svelte-wua3zs:hover{transform:scale(1.05);box-shadow:0 0 20px var(--primary-color, #fb9b04)}.btn-normal.svelte-wua3zs{background:#ffffff1a;color:#fff;border:1px solid rgba(255,255,255,.2)}.btn-normal.svelte-wua3zs:hover{background:#fff3}.countdown.svelte-wua3zs{margin-top:1rem;color:#ffffff80;font-size:.875rem}@keyframes svelte-wua3zs-fadeIn{0%{opacity:0}to{opacity:1}}@keyframes svelte-wua3zs-slideUp{0%{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}}@keyframes svelte-wua3zs-glow{0%,to{filter:drop-shadow(0 0 20px var(--primary-color, #fb9b04))}50%{filter:drop-shadow(0 0 30px var(--primary-color, #fb9b04))}}.ghost-hud.svelte-nnmop8{position:fixed;bottom:2rem;right:2rem;display:flex;flex-direction:column;gap:1rem;animation:svelte-nnmop8-slideIn .3s ease}.ghost-status.svelte-nnmop8{background:#1a1a1ae6;border:1px solid var(--primary-color, #fb9b04);border-radius:8px;padding:1rem;display:flex;align-items:center;gap:1rem;min-width:200px}.ghost-icon.svelte-nnmop8{font-size:2rem;filter:drop-shadow(0 0 10px var(--primary-color, #fb9b04))}.ghost-info.svelte-nnmop8{flex:1}.ghost-label.svelte-nnmop8{color:var(--primary-color, #fb9b04);font-size:.875rem;font-weight:600;margin-bottom:.25rem}.ghost-timer.svelte-nnmop8{color:#ffffffe6;font-size:1.25rem;font-weight:700;font-family:monospace}.ghost-abilities.svelte-nnmop8{background:#1a1a1ae6;border:1px solid rgba(251,155,4,.3);border-radius:8px;padding:.75rem}.ability.svelte-nnmop8{display:flex;align-items:center;gap:.75rem}.ability-key.svelte-nnmop8{background:var(--primary-color, #fb9b04);color:#000;width:32px;height:32px;display:flex;align-items:center;justify-content:center;border-radius:4px;font-weight:700;font-size:.875rem}.ability-label.svelte-nnmop8{color:#fffc;font-size:.875rem}.exit-info.svelte-nnmop8{background:#ff3b3b1a;border:1px solid rgba(255,59,59,.3);border-radius:4px;padding:.75rem;text-align:center;color:#ffffffb3;font-size:.875rem}.exit-info.svelte-nnmop8 .key:where(.svelte-nnmop8){display:inline-block;background:#ff3b3bcc;color:#fff;padding:.25rem .5rem;border-radius:3px;font-weight:700;margin:0 .25rem}@keyframes svelte-nnmop8-slideIn{0%{opacity:0;transform:translate(20px)}to{opacity:1;transform:translate(0)}}.jumpscare-overlay.svelte-1tyzjxl{position:fixed;inset:0;background:#000000f2;z-index:9999;display:flex;align-items:center;justify-content:center;animation:svelte-1tyzjxl-flashIn .1s ease,svelte-1tyzjxl-shake .5s ease}.jumpscare-content.svelte-1tyzjxl{text-align:center;animation:svelte-1tyzjxl-scaleUp .3s ease}.jumpscare-image.svelte-1tyzjxl{width:200px;height:200px;filter:drop-shadow(0 0 40px #ff0000) brightness(1.5);animation:svelte-1tyzjxl-pulse .2s ease infinite}.jumpscare-text.svelte-1tyzjxl{font-size:4rem;color:red;font-family:Creepster,cursive;margin-top:1rem;text-shadow:0 0 20px #ff0000;animation:svelte-1tyzjxl-glitch .3s ease infinite}@keyframes svelte-1tyzjxl-flashIn{0%{opacity:0}to{opacity:1}}@keyframes svelte-1tyzjxl-shake{0%,to{transform:translate(0)}10%,30%,50%,70%,90%{transform:translate(-10px)}20%,40%,60%,80%{transform:translate(10px)}}@keyframes svelte-1tyzjxl-scaleUp{0%{transform:scale(.5);opacity:0}to{transform:scale(1);opacity:1}}@keyframes svelte-1tyzjxl-pulse{0%,to{transform:scale(1)}50%{transform:scale(1.1)}}@keyframes svelte-1tyzjxl-glitch{0%{text-shadow:0 0 20px #ff0000}25%{text-shadow:-5px 0 20px #ff0000,5px 0 20px #00ff00}50%{text-shadow:5px 0 20px #0000ff,-5px 0 20px #ff0000}75%{text-shadow:0 5px 20px #ff0000,0 -5px 20px #00ff00}to{text-shadow:0 0 20px #ff0000}}.trick-or-treat-hud.svelte-1kqc1a7{position:fixed;top:var(--space-xl);left:var(--space-xl);z-index:var(--z-fixed);animation:svelte-1kqc1a7-slideIn .4s ease-out}.hud-card.svelte-1kqc1a7{position:relative;border-radius:var(--radius-lg);padding:var(--space-lg);min-width:18.75rem;overflow:hidden;box-shadow:0 4px 12px #0006}.hud-card__background.svelte-1kqc1a7{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);width:120%;height:120%;object-fit:cover;filter:brightness(.7);z-index:1;pointer-events:none}.hud-card__pumpkin.svelte-1kqc1a7{position:absolute;right:0;top:50%;transform:translateY(-50%);height:85%;z-index:2;pointer-events:none;animation:svelte-1kqc1a7-pumpkinGlow 2s ease-in-out infinite}.hud-card__content.svelte-1kqc1a7{position:relative;z-index:3;max-width:70%}.hud-header.svelte-1kqc1a7{margin-bottom:var(--space-lg)}.hud-title.svelte-1kqc1a7{color:var(--color-brand);font-size:var(--font-size-h3);font-family:var(--font-family-halloween);font-weight:var(--font-weight-bold);margin:0;letter-spacing:.02em;text-shadow:0 2px 8px rgba(0,0,0,.6)}.progress-section.svelte-1kqc1a7{margin-bottom:var(--space-md)}.progress-bar-container.svelte-1kqc1a7{background:#ffffff14;height:.625rem;border-radius:var(--radius-sm);overflow:hidden;margin-bottom:var(--space-sm);border:1px solid rgba(var(--color-brand-rgb),.2)}.progress-bar.svelte-1kqc1a7{width:100%;height:100%;position:relative}.progress-fill.svelte-1kqc1a7{background:linear-gradient(90deg,rgba(var(--color-brand-rgb),.6),var(--color-brand));height:100%;transition:width var(--transition-base) cubic-bezier(.34,1.56,.64,1);box-shadow:0 0 15px rgba(var(--color-brand-rgb),.8),inset 0 0 10px #fff3}.progress-text.svelte-1kqc1a7{color:var(--color-lightest);font-size:var(--font-size-small);font-weight:300}.timer-section.svelte-1kqc1a7{display:flex;align-items:center;gap:var(--space-sm);color:var(--color-brand);font-size:var(--font-size-small)}.timer-icon{color:var(--color-brand);animation:svelte-1kqc1a7-pulse 1s ease-in-out infinite}.timer-text.svelte-1kqc1a7{font-weight:var(--font-weight-medium);color:var(--color-lightest)}@keyframes svelte-1kqc1a7-slideIn{0%{opacity:0;transform:translate(-1.25rem)}to{opacity:1;transform:translate(0)}}@keyframes svelte-1kqc1a7-pumpkinGlow{0%,to{filter:brightness(1.1) saturate(1.2)}50%{filter:brightness(1.4) saturate(1.6)}}@keyframes svelte-1kqc1a7-pulse{0%,to{transform:scale(1)}50%{transform:scale(1.15)}}.reward-popup.svelte-1vu0z5b{position:fixed;top:0;left:0;width:100vw;height:100vh;display:flex;align-items:center;justify-content:center;z-index:var(--z-modal);pointer-events:none}.reward-popup.trick.svelte-1vu0z5b{background:radial-gradient(circle,rgba(var(--color-danger-rgb),.2) 0%,transparent 70%)}.glow-effect.svelte-1vu0z5b{position:absolute;width:25rem;height:25rem;border-radius:50%;background:radial-gradient(circle,rgba(var(--color-brand-rgb),.3) 0%,transparent 70%);animation:svelte-1vu0z5b-glowPulse 2s ease-in-out infinite;filter:blur(40px)}.reward-popup.trick.svelte-1vu0z5b .glow-effect:where(.svelte-1vu0z5b){background:radial-gradient(circle,rgba(var(--color-danger-rgb),.4) 0%,transparent 70%)}.reward-card.svelte-1vu0z5b{position:relative;border-radius:var(--radius-xl);padding:var(--space-3xl);z-index:calc(var(--z-modal) + 1);box-shadow:0 4px 12px #0006;min-width:28.125rem;overflow:hidden;animation:svelte-1vu0z5b-slideInFromTop .4s ease-out}.reward-card.trick.svelte-1vu0z5b{animation:svelte-1vu0z5b-slideInFromTop .4s ease-out,svelte-1vu0z5b-shake .5s ease .4s}.reward-card__background.svelte-1vu0z5b{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);width:120%;height:120%;object-fit:cover;filter:brightness(.7);z-index:1;pointer-events:none}.reward-card__pumpkin.svelte-1vu0z5b{position:absolute;right:0;top:50%;transform:translateY(-50%);height:90%;z-index:2;pointer-events:none;animation:svelte-1vu0z5b-pumpkinGlow 2s ease-in-out infinite}.reward-card__content.svelte-1vu0z5b{position:relative;z-index:3;max-width:65%}.reward-title-container.svelte-1vu0z5b{display:flex;align-items:center;gap:var(--space-md);margin-bottom:var(--space-lg)}.reward-title-icon{color:var(--color-brand)}.reward-card.trick.svelte-1vu0z5b .reward-title-icon{color:var(--color-danger)}.reward-title.svelte-1vu0z5b{color:var(--color-brand);font-size:var(--font-size-h1);font-family:var(--font-family-halloween);font-weight:var(--font-weight-bold);letter-spacing:.125rem;text-shadow:0 2px 8px rgba(0,0,0,.6)}.reward-card.trick.svelte-1vu0z5b .reward-title:where(.svelte-1vu0z5b){color:var(--color-danger)}.reward-message.svelte-1vu0z5b{color:var(--color-lightest);font-size:var(--font-size-h4);margin-bottom:var(--space-lg);font-weight:300;text-shadow:0 2px 4px rgba(0,0,0,.6)}.item-display.svelte-1vu0z5b{display:flex;flex-direction:column;align-items:flex-start;gap:var(--space-md);margin-top:var(--space-lg)}.item-icon{color:var(--color-brand);animation:svelte-1vu0z5b-bounce .6s cubic-bezier(.34,1.56,.64,1)}.item-amount.svelte-1vu0z5b{color:var(--color-brand);font-size:var(--font-size-h2);font-weight:var(--font-weight-bold);text-shadow:0 2px 8px rgba(0,0,0,.6);animation:svelte-1vu0z5b-slideUp .5s ease .2s both}.candy-particle.svelte-1vu0z5b{position:fixed;animation:svelte-1vu0z5b-candyFall 2s ease-in forwards;top:-1.25rem;z-index:calc(var(--z-modal) - 1);pointer-events:none}.candy-particle.svelte-1vu0z5b svg{color:var(--color-brand)}@keyframes svelte-1vu0z5b-slideInFromTop{0%{opacity:0;transform:translateY(-100%)}to{opacity:1;transform:translateY(0)}}@keyframes svelte-1vu0z5b-shake{0%,to{transform:translate(0) rotate(0)}10%{transform:translate(-.3125rem,-.3125rem) rotate(-1deg)}20%{transform:translate(.3125rem,.3125rem) rotate(1deg)}30%{transform:translate(-.3125rem,.3125rem) rotate(-1deg)}40%{transform:translate(.3125rem,-.3125rem) rotate(1deg)}50%{transform:translate(-.3125rem,-.3125rem) rotate(-1deg)}}@keyframes svelte-1vu0z5b-pumpkinGlow{0%,to{filter:brightness(1.1) saturate(1.2)}50%{filter:brightness(1.4) saturate(1.6)}}@keyframes svelte-1vu0z5b-bounce{0%,to{transform:translateY(0)}50%{transform:translateY(-1.25rem)}}@keyframes svelte-1vu0z5b-slideUp{0%{opacity:0;transform:translateY(1.25rem)}to{opacity:1;transform:translateY(0)}}@keyframes svelte-1vu0z5b-candyFall{0%{opacity:1;transform:translateY(0) rotate(0)}90%{opacity:1}to{opacity:0;transform:translateY(100vh) rotate(720deg)}}@keyframes svelte-1vu0z5b-glowPulse{0%,to{transform:scale(1);opacity:.6}50%{transform:scale(1.2);opacity:.9}}.particle-container.svelte-1qiz201{position:fixed;top:0;left:0;width:100vw;height:100vh;pointer-events:none;z-index:200;overflow:hidden}.particle.svelte-1qiz201{position:absolute;font-size:calc(24px * var(--hud-font-scale));display:flex;align-items:center;justify-content:center;will-change:transform,opacity;mix-blend-mode:screen}.candy-particle.svelte-1qiz201{filter:drop-shadow(0 0 4px rgba(255,149,0,.6))}.blood-particle.svelte-1qiz201{color:#8b0000;filter:drop-shadow(0 0 2px rgba(139,0,0,.4))}.spark-particle.svelte-1qiz201{color:#ff0;filter:drop-shadow(0 0 6px rgba(255,255,0,.7));text-shadow:0 0 10px rgba(255,255,0,.5)}@media(prefers-reduced-motion:reduce){.particle.svelte-1qiz201{animation:none}}:root{--primary-color: #fb9b04;--secondary-color: #1a1a1a;--background-color: #000000;--accent-color: #fb9b04;--logo-url: "";--color-brand: var(--primary-color);--color-brand-rgb: 251, 155, 4;--color-danger: #ff3b3b;--color-danger-rgb: 255, 59, 59;--color-darkest: #161616;--color-darkest-rgb: 22, 22, 22;--color-dark: #252525;--color-dark-rgb: 37, 37, 37;--color-mid: #383838;--color-mid-rgb: 56, 56, 56;--color-light: #969696;--color-light-rgb: 150, 150, 150;--color-lightest: #f2f2f2;--color-lightest-rgb: 242, 242, 242;--color-bg-primary: var(--color-darkest);--color-bg-secondary: var(--color-dark);--color-bg-tertiary: var(--color-mid);--color-text-primary: var(--color-lightest);--color-text-secondary: var(--color-light);--color-text-inverse: var(--color-darkest);--font-family: "Poppins", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;--font-family-halloween: "Creepster", cursive;--font-size-base: 1rem;--font-size-h1: 2rem;--font-size-h2: 1.5rem;--font-size-h3: 1.25rem;--font-size-h4: 1.125rem;--font-size-h5: 1rem;--font-size-h6: .875rem;--font-size-body: 1rem;--font-size-small: .875rem;--font-size-tiny: .75rem;--font-weight-normal: 400;--font-weight-medium: 500;--font-weight-semibold: 600;--font-weight-bold: 700;--line-height-tight: 1.2;--line-height-normal: 1.5;--line-height-relaxed: 1.75;--space-xs: .25rem;--space-sm: .5rem;--space-md: 1rem;--space-lg: 1.5rem;--space-xl: 2rem;--space-2xl: 3rem;--space-3xl: 4rem;--radius-sm: .25rem;--radius-md: .5rem;--radius-lg: .75rem;--radius-xl: 1rem;--radius-full: 9999px;--shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, .05);--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, .1), 0 2px 4px -1px rgba(0, 0, 0, .06);--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, .1), 0 4px 6px -2px rgba(0, 0, 0, .05);--shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, .1), 0 10px 10px -5px rgba(0, 0, 0, .04);--shadow-brand: 0 0 20px rgba(var(--color-brand-rgb), .3);--shadow-brand-strong: 0 0 30px rgba(var(--color-brand-rgb), .5);--shadow-danger: 0 0 30px rgba(var(--color-danger-rgb), .2);--shadow-danger-strong: 0 0 50px rgba(var(--color-danger-rgb), .6);--transition-fast: .15s ease-in-out;--transition-base: .25s ease-in-out;--transition-slow: .35s ease-in-out;--z-base: 1;--z-dropdown: 100;--z-sticky: 200;--z-fixed: 300;--z-modal-backdrop: 400;--z-modal: 500;--z-popover: 600;--z-tooltip: 700;--z-notification: 800}*,*:before,*:after{box-sizing:border-box}*{margin:0;padding:0}html{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}html,body{height:100%}body{line-height:1.5;text-rendering:optimizeSpeed}input,button,textarea,select{font:inherit}button{background:none;border:none;cursor:pointer;color:inherit}ul,ol{list-style:none}a{text-decoration:none;color:inherit}img,picture,video,canvas,svg{display:block;max-width:100%}@media(prefers-reduced-motion:reduce){*,*:before,*:after{animation-duration:.01ms!important;animation-iteration-count:1!important;transition-duration:.01ms!important;scroll-behavior:auto!important}}:focus{outline:none}:focus-visible{outline:2px solid var(--color-brand);outline-offset:2px}:disabled{cursor:not-allowed;opacity:.6}html{font-size:clamp(12px,1vw,20px)}body{font-family:var(--font-family);font-size:var(--font-size-base);font-weight:var(--font-weight-normal);line-height:var(--line-height-normal);color:var(--color-text-primary);background-color:transparent}h1{font-size:var(--font-size-h1);font-weight:var(--font-weight-bold);line-height:var(--line-height-tight)}h2{font-size:var(--font-size-h2);font-weight:var(--font-weight-bold);line-height:var(--line-height-tight)}h3{font-size:var(--font-size-h3);font-weight:var(--font-weight-semibold);line-height:var(--line-height-tight)}h4{font-size:var(--font-size-h4);font-weight:var(--font-weight-semibold);line-height:var(--line-height-tight)}h5{font-size:var(--font-size-h5);font-weight:var(--font-weight-medium);line-height:var(--line-height-normal)}h6{font-size:var(--font-size-h6);font-weight:var(--font-weight-medium);line-height:var(--line-height-normal)}p{font-size:var(--font-size-body);line-height:var(--line-height-normal)}.flex{display:flex}.flex-col{flex-direction:column}.flex-row{flex-direction:row}.items-center{align-items:center}.items-start{align-items:flex-start}.items-end{align-items:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.justify-start{justify-content:flex-start}.justify-end{justify-content:flex-end}.gap-xs{gap:var(--space-xs)}.gap-sm{gap:var(--space-sm)}.gap-md{gap:var(--space-md)}.gap-lg{gap:var(--space-lg)}.gap-xl{gap:var(--space-xl)}.p-xs{padding:var(--space-xs)}.p-sm{padding:var(--space-sm)}.p-md{padding:var(--space-md)}.p-lg{padding:var(--space-lg)}.p-xl{padding:var(--space-xl)}.m-xs{margin:var(--space-xs)}.m-sm{margin:var(--space-sm)}.m-md{margin:var(--space-md)}.m-lg{margin:var(--space-lg)}.m-xl{margin:var(--space-xl)}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.hidden{display:none}.block{display:block}.inline-block{display:inline-block}.w-full{width:100%}.h-full{height:100%}.container{width:100%;max-width:1280px;margin-left:auto;margin-right:auto;padding-left:var(--space-md);padding-right:var(--space-md)}@media(min-width:640px){.container{padding-left:var(--space-lg);padding-right:var(--space-lg)}}@media(min-width:1024px){.container{padding-left:var(--space-xl);padding-right:var(--space-xl)}}@keyframes candyFall{0%{opacity:1;transform:translateY(calc(-100px * var(--hud-scale))) rotate(0)}50%{opacity:.8}to{opacity:0;transform:translateY(calc(400px * var(--hud-scale))) rotate(360deg)}}@keyframes pumpkinFloat{0%,to{transform:translateY(0)}50%{transform:translateY(calc(-8px * var(--hud-scale)))}}@keyframes slideInFromLeft{0%{opacity:0;transform:translate(calc(-50px * var(--hud-scale)))}to{opacity:1;transform:translate(0)}}@keyframes rewardScaleIn{0%{opacity:0;transform:scale(.8)}to{opacity:1;transform:scale(1)}}@keyframes trickShake{0%,to{transform:translate(0)}25%{transform:translate(calc(-5px * var(--hud-scale)))}50%{transform:translate(calc(5px * var(--hud-scale)))}75%{transform:translate(calc(-5px * var(--hud-scale)))}}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}@keyframes fadeOut{0%{opacity:1}to{opacity:0}}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}@keyframes borderGlowPulse{0%,to{border-color:#ad06434d}50%{border-color:#ad064399}}@keyframes textGlowPulse{0%,to{text-shadow:0 0 10px rgba(173,6,67,.3)}50%{text-shadow:0 0 20px rgba(173,6,67,.6)}}.animation-fast{animation-duration:.3s;animation-timing-function:cubic-bezier(.34,1.56,.64,1)}.animation-medium{animation-duration:.6s;animation-timing-function:cubic-bezier(.25,.46,.45,.94)}.animation-slow{animation-duration:1s;animation-timing-function:ease-in-out}.animation-continuous{animation-iteration-count:infinite}@media(prefers-reduced-motion:reduce){*{animation-duration:.01ms!important;animation-iteration-count:1!important;transition-duration:.01ms!important}} diff --git a/[esx_addons]/esx_halloween/web/build/assets/index.Dph3xcxq.css b/[esx_addons]/esx_halloween/web/build/assets/index.Dph3xcxq.css new file mode 100644 index 00000000..3e9dd774 --- /dev/null +++ b/[esx_addons]/esx_halloween/web/build/assets/index.Dph3xcxq.css @@ -0,0 +1 @@ +.notification.svelte-1wg8nyl{position:relative;border-radius:var(--radius-lg);box-shadow:0 4px 12px #0006;display:flex;flex-direction:column;opacity:0;transition:all .4s ease-out;overflow:hidden}.notification--visible.svelte-1wg8nyl{opacity:1}.notification--small.svelte-1wg8nyl{width:21.25rem;min-height:7.5rem;padding:var(--space-md)}.notification--large.svelte-1wg8nyl{width:38.75rem;min-height:10.3125rem;padding:var(--space-lg)}.notification--top-left.svelte-1wg8nyl{transform:translate(-100%) scale(1)}.notification--top-left.notification--visible.svelte-1wg8nyl{transform:translate(0) scale(1)}.notification--top-right.svelte-1wg8nyl{transform:translate(100%) scale(1)}.notification--top-right.notification--visible.svelte-1wg8nyl{transform:translate(0) scale(1)}.notification--top-center.svelte-1wg8nyl{transform:translateY(-100%) scale(1)}.notification--top-center.notification--visible.svelte-1wg8nyl{transform:translateY(0) scale(1)}.notification--bottom-center.svelte-1wg8nyl{transform:translateY(100%) scale(1)}.notification--bottom-center.notification--visible.svelte-1wg8nyl{transform:translateY(0) scale(1)}.notification__background.svelte-1wg8nyl{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);width:120%;height:120%;object-fit:cover;filter:brightness(.7);z-index:1}.notification__pumpkin.svelte-1wg8nyl{position:absolute;right:0;top:50%;transform:translateY(-50%);z-index:2;pointer-events:none;animation:svelte-1wg8nyl-pumpkinGlow 2s ease-in-out infinite}.notification__pumpkin--small.svelte-1wg8nyl{height:90%;width:auto;right:.5rem}.notification__pumpkin--large.svelte-1wg8nyl{height:85%;width:auto;right:1rem}@keyframes svelte-1wg8nyl-pumpkinGlow{0%,to{filter:brightness(1.1) saturate(1.2)}50%{filter:brightness(1.4) saturate(1.6)}}.notification__content.svelte-1wg8nyl{position:relative;z-index:3;display:flex;flex-direction:column;gap:var(--space-sm);height:100%;justify-content:center;max-width:60%}.notification__header.svelte-1wg8nyl{margin:0;color:var(--color-brand);font-family:var(--font-family-halloween);font-size:1.5rem;font-weight:var(--font-weight-normal);line-height:1.2;letter-spacing:.02em}.notification__description.svelte-1wg8nyl{margin:0;color:var(--color-lightest);font-size:var(--font-size-body);font-weight:300;line-height:1.4}.notification--large.svelte-1wg8nyl .notification__header:where(.svelte-1wg8nyl){font-size:1.75rem}.notification--large.svelte-1wg8nyl .notification__content:where(.svelte-1wg8nyl){max-width:70%}@media(max-width:768px){.notification--small.svelte-1wg8nyl{width:90vw;max-width:21.25rem}.notification--large.svelte-1wg8nyl{width:95vw;max-width:38.75rem}.notification__content.svelte-1wg8nyl{max-width:55%}}.notification-container.svelte-v226t6{position:fixed;z-index:var(--z-notification);pointer-events:none}.notification-container.svelte-v226t6>*{pointer-events:auto}.notification-container--top-left.svelte-v226t6{top:8rem;left:var(--space-xl)}.notification-container--top-right.svelte-v226t6{top:8rem;right:var(--space-xl)}.notification-container--top-center.svelte-v226t6{top:var(--space-xl);left:50%;transform:translate(-50%)}.notification-container--bottom-center.svelte-v226t6{bottom:var(--space-xl);left:50%;transform:translate(-50%)}@media(max-width:768px){.notification-container--top-left.svelte-v226t6,.notification-container--top-right.svelte-v226t6,.notification-container--top-center.svelte-v226t6,.notification-container--bottom-center.svelte-v226t6{left:var(--space-md);right:var(--space-md)}.notification-container--top-center.svelte-v226t6,.notification-container--bottom-center.svelte-v226t6{left:50%;right:auto}}.ghost-choice-overlay.svelte-wua3zs{position:fixed;inset:0;display:flex;align-items:center;justify-content:center;background:#000c;z-index:1000;animation:svelte-wua3zs-fadeIn .3s ease}.ghost-choice-modal.svelte-wua3zs{background:#1a1a1af2;border:2px solid var(--primary-color, #fb9b04);border-radius:8px;padding:2rem;min-width:400px;text-align:center;position:relative;animation:svelte-wua3zs-slideUp .4s ease}.pumpkin-decoration.svelte-wua3zs{position:absolute;top:-40px;left:50%;transform:translate(-50%);width:80px;height:80px}.pumpkin-decoration.svelte-wua3zs img:where(.svelte-wua3zs){width:100%;height:100%;filter:drop-shadow(0 0 20px var(--primary-color, #fb9b04));animation:svelte-wua3zs-glow 2s ease-in-out infinite}h2.svelte-wua3zs{color:var(--primary-color, #fb9b04);font-size:2rem;margin:1rem 0;font-family:Creepster,cursive}p.svelte-wua3zs{color:#ffffffe6;margin-bottom:2rem}.buttons.svelte-wua3zs{display:flex;gap:1rem;justify-content:center}.btn.svelte-wua3zs{padding:.75rem 1.5rem;border:none;border-radius:4px;font-size:1rem;cursor:pointer;transition:all .2s ease;font-weight:600}.btn-ghost.svelte-wua3zs{background:var(--primary-color, #fb9b04);color:#000}.btn-ghost.svelte-wua3zs:hover{transform:scale(1.05);box-shadow:0 0 20px var(--primary-color, #fb9b04)}.btn-normal.svelte-wua3zs{background:#ffffff1a;color:#fff;border:1px solid rgba(255,255,255,.2)}.btn-normal.svelte-wua3zs:hover{background:#fff3}.countdown.svelte-wua3zs{margin-top:1rem;color:#ffffff80;font-size:.875rem}@keyframes svelte-wua3zs-fadeIn{0%{opacity:0}to{opacity:1}}@keyframes svelte-wua3zs-slideUp{0%{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}}@keyframes svelte-wua3zs-glow{0%,to{filter:drop-shadow(0 0 20px var(--primary-color, #fb9b04))}50%{filter:drop-shadow(0 0 30px var(--primary-color, #fb9b04))}}.ghost-hud.svelte-nnmop8{position:fixed;bottom:2rem;right:2rem;display:flex;flex-direction:column;gap:1rem;animation:svelte-nnmop8-slideIn .3s ease}.ghost-status.svelte-nnmop8{background:#1a1a1ae6;border:1px solid var(--primary-color, #fb9b04);border-radius:8px;padding:1rem;display:flex;align-items:center;gap:1rem;min-width:200px}.ghost-icon.svelte-nnmop8{font-size:2rem;filter:drop-shadow(0 0 10px var(--primary-color, #fb9b04))}.ghost-info.svelte-nnmop8{flex:1}.ghost-label.svelte-nnmop8{color:var(--primary-color, #fb9b04);font-size:.875rem;font-weight:600;margin-bottom:.25rem}.ghost-timer.svelte-nnmop8{color:#ffffffe6;font-size:1.25rem;font-weight:700;font-family:monospace}.ghost-abilities.svelte-nnmop8{background:#1a1a1ae6;border:1px solid rgba(251,155,4,.3);border-radius:8px;padding:.75rem}.ability.svelte-nnmop8{display:flex;align-items:center;gap:.75rem}.ability-key.svelte-nnmop8{background:var(--primary-color, #fb9b04);color:#000;width:32px;height:32px;display:flex;align-items:center;justify-content:center;border-radius:4px;font-weight:700;font-size:.875rem}.ability-label.svelte-nnmop8{color:#fffc;font-size:.875rem}.exit-info.svelte-nnmop8{background:#ff3b3b1a;border:1px solid rgba(255,59,59,.3);border-radius:4px;padding:.75rem;text-align:center;color:#ffffffb3;font-size:.875rem}.exit-info.svelte-nnmop8 .key:where(.svelte-nnmop8){display:inline-block;background:#ff3b3bcc;color:#fff;padding:.25rem .5rem;border-radius:3px;font-weight:700;margin:0 .25rem}@keyframes svelte-nnmop8-slideIn{0%{opacity:0;transform:translate(20px)}to{opacity:1;transform:translate(0)}}.jumpscare-overlay.svelte-1tyzjxl{position:fixed;inset:0;background:#000000f2;z-index:9999;display:flex;align-items:center;justify-content:center;animation:svelte-1tyzjxl-flashIn .1s ease,svelte-1tyzjxl-shake .5s ease}.jumpscare-content.svelte-1tyzjxl{text-align:center;animation:svelte-1tyzjxl-scaleUp .3s ease}.jumpscare-image.svelte-1tyzjxl{width:200px;height:200px;filter:drop-shadow(0 0 40px #ff0000) brightness(1.5);animation:svelte-1tyzjxl-pulse .2s ease infinite}.jumpscare-text.svelte-1tyzjxl{font-size:4rem;color:red;font-family:Creepster,cursive;margin-top:1rem;text-shadow:0 0 20px #ff0000;animation:svelte-1tyzjxl-glitch .3s ease infinite}@keyframes svelte-1tyzjxl-flashIn{0%{opacity:0}to{opacity:1}}@keyframes svelte-1tyzjxl-shake{0%,to{transform:translate(0)}10%,30%,50%,70%,90%{transform:translate(-10px)}20%,40%,60%,80%{transform:translate(10px)}}@keyframes svelte-1tyzjxl-scaleUp{0%{transform:scale(.5);opacity:0}to{transform:scale(1);opacity:1}}@keyframes svelte-1tyzjxl-pulse{0%,to{transform:scale(1)}50%{transform:scale(1.1)}}@keyframes svelte-1tyzjxl-glitch{0%{text-shadow:0 0 20px #ff0000}25%{text-shadow:-5px 0 20px #ff0000,5px 0 20px #00ff00}50%{text-shadow:5px 0 20px #0000ff,-5px 0 20px #ff0000}75%{text-shadow:0 5px 20px #ff0000,0 -5px 20px #00ff00}to{text-shadow:0 0 20px #ff0000}}:root{--primary-color: #fb9b04;--secondary-color: #1a1a1a;--background-color: #000000;--accent-color: #fb9b04;--logo-url: "";--color-brand: var(--primary-color);--color-brand-rgb: 251, 155, 4;--color-darkest: #161616;--color-darkest-rgb: 22, 22, 22;--color-dark: #252525;--color-dark-rgb: 37, 37, 37;--color-mid: #383838;--color-mid-rgb: 56, 56, 56;--color-light: #969696;--color-light-rgb: 150, 150, 150;--color-lightest: #f2f2f2;--color-lightest-rgb: 242, 242, 242;--color-bg-primary: var(--color-darkest);--color-bg-secondary: var(--color-dark);--color-bg-tertiary: var(--color-mid);--color-text-primary: var(--color-lightest);--color-text-secondary: var(--color-light);--color-text-inverse: var(--color-darkest);--font-family: "Poppins", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;--font-family-halloween: "Creepster", cursive;--font-size-base: 1rem;--font-size-h1: 2rem;--font-size-h2: 1.5rem;--font-size-h3: 1.25rem;--font-size-h4: 1.125rem;--font-size-h5: 1rem;--font-size-h6: .875rem;--font-size-body: 1rem;--font-size-small: .875rem;--font-size-tiny: .75rem;--font-weight-normal: 400;--font-weight-medium: 500;--font-weight-semibold: 600;--font-weight-bold: 700;--line-height-tight: 1.2;--line-height-normal: 1.5;--line-height-relaxed: 1.75;--space-xs: .25rem;--space-sm: .5rem;--space-md: 1rem;--space-lg: 1.5rem;--space-xl: 2rem;--space-2xl: 3rem;--space-3xl: 4rem;--radius-sm: .25rem;--radius-md: .5rem;--radius-lg: .75rem;--radius-xl: 1rem;--radius-full: 9999px;--shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, .05);--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, .1), 0 2px 4px -1px rgba(0, 0, 0, .06);--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, .1), 0 4px 6px -2px rgba(0, 0, 0, .05);--shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, .1), 0 10px 10px -5px rgba(0, 0, 0, .04);--shadow-brand: 0 0 20px rgba(var(--color-brand-rgb), .3);--shadow-brand-strong: 0 0 30px rgba(var(--color-brand-rgb), .5);--transition-fast: .15s ease-in-out;--transition-base: .25s ease-in-out;--transition-slow: .35s ease-in-out;--z-base: 1;--z-dropdown: 100;--z-sticky: 200;--z-fixed: 300;--z-modal-backdrop: 400;--z-modal: 500;--z-popover: 600;--z-tooltip: 700;--z-notification: 800}*,*:before,*:after{box-sizing:border-box}*{margin:0;padding:0}html{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}html,body{height:100%}body{line-height:1.5;text-rendering:optimizeSpeed}input,button,textarea,select{font:inherit}button{background:none;border:none;cursor:pointer;color:inherit}ul,ol{list-style:none}a{text-decoration:none;color:inherit}img,picture,video,canvas,svg{display:block;max-width:100%}@media(prefers-reduced-motion:reduce){*,*:before,*:after{animation-duration:.01ms!important;animation-iteration-count:1!important;transition-duration:.01ms!important;scroll-behavior:auto!important}}:focus{outline:none}:focus-visible{outline:2px solid var(--color-brand);outline-offset:2px}:disabled{cursor:not-allowed;opacity:.6}html{font-size:clamp(12px,1vw,20px)}body{font-family:var(--font-family);font-size:var(--font-size-base);font-weight:var(--font-weight-normal);line-height:var(--line-height-normal);color:var(--color-text-primary);background-color:transparent}h1{font-size:var(--font-size-h1);font-weight:var(--font-weight-bold);line-height:var(--line-height-tight)}h2{font-size:var(--font-size-h2);font-weight:var(--font-weight-bold);line-height:var(--line-height-tight)}h3{font-size:var(--font-size-h3);font-weight:var(--font-weight-semibold);line-height:var(--line-height-tight)}h4{font-size:var(--font-size-h4);font-weight:var(--font-weight-semibold);line-height:var(--line-height-tight)}h5{font-size:var(--font-size-h5);font-weight:var(--font-weight-medium);line-height:var(--line-height-normal)}h6{font-size:var(--font-size-h6);font-weight:var(--font-weight-medium);line-height:var(--line-height-normal)}p{font-size:var(--font-size-body);line-height:var(--line-height-normal)}.flex{display:flex}.flex-col{flex-direction:column}.flex-row{flex-direction:row}.items-center{align-items:center}.items-start{align-items:flex-start}.items-end{align-items:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.justify-start{justify-content:flex-start}.justify-end{justify-content:flex-end}.gap-xs{gap:var(--space-xs)}.gap-sm{gap:var(--space-sm)}.gap-md{gap:var(--space-md)}.gap-lg{gap:var(--space-lg)}.gap-xl{gap:var(--space-xl)}.p-xs{padding:var(--space-xs)}.p-sm{padding:var(--space-sm)}.p-md{padding:var(--space-md)}.p-lg{padding:var(--space-lg)}.p-xl{padding:var(--space-xl)}.m-xs{margin:var(--space-xs)}.m-sm{margin:var(--space-sm)}.m-md{margin:var(--space-md)}.m-lg{margin:var(--space-lg)}.m-xl{margin:var(--space-xl)}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.hidden{display:none}.block{display:block}.inline-block{display:inline-block}.w-full{width:100%}.h-full{height:100%}.container{width:100%;max-width:1280px;margin-left:auto;margin-right:auto;padding-left:var(--space-md);padding-right:var(--space-md)}@media(min-width:640px){.container{padding-left:var(--space-lg);padding-right:var(--space-lg)}}@media(min-width:1024px){.container{padding-left:var(--space-xl);padding-right:var(--space-xl)}} diff --git a/[esx_addons]/esx_halloween/web/build/assets/index.RJAuhYw9.js b/[esx_addons]/esx_halloween/web/build/assets/index.RJAuhYw9.js new file mode 100644 index 00000000..0758c4e7 --- /dev/null +++ b/[esx_addons]/esx_halloween/web/build/assets/index.RJAuhYw9.js @@ -0,0 +1,179 @@ +(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const i of document.querySelectorAll('link[rel="modulepreload"]'))n(i);new MutationObserver(i=>{for(const s of i)if(s.type==="childList")for(const a of s.addedNodes)a.tagName==="LINK"&&a.rel==="modulepreload"&&n(a)}).observe(document,{childList:!0,subtree:!0});function r(i){const s={};return i.integrity&&(s.integrity=i.integrity),i.referrerPolicy&&(s.referrerPolicy=i.referrerPolicy),i.crossOrigin==="use-credentials"?s.credentials="include":i.crossOrigin==="anonymous"?s.credentials="omit":s.credentials="same-origin",s}function n(i){if(i.ep)return;i.ep=!0;const s=r(i);fetch(i.href,s)}})();const Pr=!1;var Mt=Array.isArray,Rn=Array.prototype.indexOf,Jt=Array.from,jt=Object.defineProperty,xe=Object.getOwnPropertyDescriptor,Nr=Object.getOwnPropertyDescriptors,In=Object.prototype,$n=Array.prototype,Xt=Object.getPrototypeOf,vr=Object.isExtensible;function ot(e){return typeof e=="function"}function Vn(e){return e()}function qt(e){for(var t=0;t{e=n,t=i});return{promise:r,resolve:e,reject:t}}function Dn(e,t){if(Array.isArray(e))return e;if(!(Symbol.iterator in e))return Array.from(e);const r=[];for(const n of e)if(r.push(n),r.length===t)break;return r}const U=2,Qt=4,xt=8,ze=16,Se=32,je=64,Pt=128,se=256,Tt=512,F=1024,Z=2048,Oe=4096,Q=8192,be=16384,Zt=32768,pt=65536,hr=1<<17,Ln=1<<18,tt=1<<19,Cr=1<<20,Ft=1<<21,er=1<<22,Re=1<<23,de=Symbol("$state"),Rr=Symbol("legacy props"),zn=Symbol(""),Be=new class extends Error{name="StaleReactionError";message="The reaction that called `getAbortSignal()` was re-run or destroyed"};function Ir(e){throw new Error("https://svelte.dev/e/lifecycle_outside_component")}function jn(){throw new Error("https://svelte.dev/e/async_derived_orphan")}function qn(e){throw new Error("https://svelte.dev/e/effect_in_teardown")}function Fn(){throw new Error("https://svelte.dev/e/effect_in_unowned_derived")}function Hn(e){throw new Error("https://svelte.dev/e/effect_orphan")}function Un(){throw new Error("https://svelte.dev/e/effect_update_depth_exceeded")}function Kn(e){throw new Error("https://svelte.dev/e/props_invalid_value")}function Bn(){throw new Error("https://svelte.dev/e/state_descriptors_fixed")}function Wn(){throw new Error("https://svelte.dev/e/state_prototype_fixed")}function Gn(){throw new Error("https://svelte.dev/e/state_unsafe_mutation")}function Yn(){throw new Error("https://svelte.dev/e/svelte_boundary_reset_onerror")}const Nt=1,Ot=2,$r=4,Jn=8,Xn=16,Qn=1,Zn=2,Vr=4,ei=8,ti=16,ri=1,ni=2,L=Symbol(),ii="http://www.w3.org/1999/xhtml",si="http://www.w3.org/2000/svg",ai="@attach";function oi(){console.warn("https://svelte.dev/e/select_multiple_invalid_value")}function li(){console.warn("https://svelte.dev/e/svelte_boundary_reset_noop")}let ui=!1;function Dr(e){return e===this.v}function fi(e,t){return e!=e?t==t:e!==t||e!==null&&typeof e=="object"||typeof e=="function"}function Lr(e){return!fi(e,this.v)}let rt=!1,ci=!1;function di(){rt=!0}let R=null;function Je(e){R=e}function he(e,t=!1,r){R={p:R,c:null,e:null,s:e,x:null,l:rt&&!t?{s:null,u:null,$:[]}:null}}function _e(e){var t=R,r=t.e;if(r!==null){t.e=null;for(var n of r)Zr(n)}return e!==void 0&&(t.x=e),R=t.p,e??{}}function mt(){return!rt||R!==null&&R.l===null}let We=[];function vi(){var e=We;We=[],qt(e)}function qe(e){if(We.length===0){var t=We;queueMicrotask(()=>{t===We&&vi()})}We.push(e)}const hi=new WeakMap;function zr(e){var t=S;if(t===null)return M.f|=Re,e;if((t.f&Zt)===0){if((t.f&Pt)===0)throw!t.parent&&e instanceof Error&&jr(e),e;t.b.error(e)}else Xe(e,t)}function Xe(e,t){for(;t!==null;){if((t.f&Pt)!==0)try{t.b.error(e);return}catch(r){e=r}t=t.parent}throw e instanceof Error&&jr(e),e}function jr(e){const t=hi.get(e);t&&(jt(e,"message",{value:t.message}),jt(e,"stack",{value:t.stack}))}const Et=new Set;let I=null,le=null,Ht=new Set,Ae=[],tr=null,Ut=!1;class ye{committed=!1;current=new Map;#e=new Map;#t=new Set;#s=0;#r=0;#l=null;#o=[];#a=[];skipped_effects=new Set;process(t){Ae=[],this.apply();var r={parent:null,effect:null,effects:[],render_effects:[],block_effects:[]};for(const n of t)this.#n(n,r);this.#u(),this.#r>0?(this.#i(r.effects),this.#i(r.render_effects),this.#i(r.block_effects)):(I=null,_r(r.render_effects),_r(r.effects)),le=null}#n(t,r){t.f^=F;for(var n=t.first;n!==null;){var i=n.f,s=(i&(Se|je))!==0,a=s&&(i&F)!==0,o=a||(i&Q)!==0||this.skipped_effects.has(n);if((n.f&Pt)!==0&&n.b?.is_pending()&&(r={parent:r,effect:n,effects:[],render_effects:[],block_effects:[]}),!o&&n.fn!==null){s?n.f^=F:(i&Qt)!==0?r.effects.push(n):Ct(n)&&((n.f&ze)!==0&&r.block_effects.push(n),At(n));var l=n.first;if(l!==null){n=l;continue}}var f=n.parent;for(n=n.next;n===null&&f!==null;)f===r.effect&&(this.#i(r.effects),this.#i(r.render_effects),this.#i(r.block_effects),r=r.parent),n=f.next,f=f.parent}}#i(t){for(const r of t)((r.f&Z)!==0?this.#o:this.#a).push(r),H(r,F)}capture(t,r){this.#e.has(t)||this.#e.set(t,r),this.current.set(t,t.v),le?.set(t,t.v)}activate(){I=this}deactivate(){I=null,le=null}flush(){if(Ae.length>0){if(this.activate(),_i(),I!==null&&I!==this)return}else this.#u();this.deactivate();for(const t of Ht)if(Ht.delete(t),t(),I!==null)break}#u(){if(this.#r===0){for(const t of this.#t)t();this.#t.clear()}this.#s===0&&this.#f()}#f(){if(Et.size>1){this.#e.clear();var t=le,r=!0,n={parent:null,effect:null,effects:[],render_effects:[],block_effects:[]};for(const i of Et){if(i===this){r=!1;continue}const s=[];for(const[o,l]of this.current){if(i.current.has(o))if(r&&l!==i.current.get(o))i.current.set(o,l);else continue;s.push(o)}if(s.length===0)continue;const a=[...i.current.keys()].filter(o=>!this.current.has(o));if(a.length>0){for(const o of s)qr(o,a);if(Ae.length>0){I=i,i.apply();for(const o of Ae)i.#n(o,n);Ae=[],i.deactivate()}}}I=null,le=t}this.committed=!0,Et.delete(this),this.#l?.resolve()}increment(t){this.#s+=1,t&&(this.#r+=1)}decrement(t){this.#s-=1,t&&(this.#r-=1);for(const r of this.#o)H(r,Z),Ve(r);for(const r of this.#a)H(r,Oe),Ve(r);this.#o=[],this.#a=[],this.flush()}add_callback(t){this.#t.add(t)}settled(){return(this.#l??=Or()).promise}static ensure(){if(I===null){const t=I=new ye;Et.add(I),ye.enqueue(()=>{I===t&&t.flush()})}return I}static enqueue(t){qe(t)}apply(){}}function _i(){var e=Ye;Ut=!0;try{var t=0;for(gr(!0);Ae.length>0;){var r=ye.ensure();if(t++>1e3){var n,i;pi()}r.process(Ae),Pe.clear()}}finally{Ut=!1,gr(e),tr=null}}function pi(){try{Un()}catch(e){Xe(e,tr)}}let we=null;function _r(e){var t=e.length;if(t!==0){for(var r=0;r0)){Pe.clear();for(const i of we){if((i.f&(be|Q))!==0)continue;const s=[i];let a=i.parent;for(;a!==null;)we.has(a)&&(we.delete(a),s.push(a)),a=a.parent;for(let o=s.length-1;o>=0;o--){const l=s[o];(l.f&(be|Q))===0&&At(l)}}we.clear()}}we=null}}function qr(e,t){if(e.reactions!==null)for(const r of e.reactions){const n=r.f;(n&U)!==0?qr(r,t):(n&(er|ze))!==0&&Fr(r,t)&&(H(r,Z),Ve(r))}}function Fr(e,t){if(e.deps!==null){for(const r of e.deps)if(t.includes(r)||(r.f&U)!==0&&Fr(r,t))return!0}return!1}function Ve(e){for(var t=tr=e;t.parent!==null;){t=t.parent;var r=t.f;if(Ut&&t===S&&(r&ze)!==0)return;if((r&(je|Se))!==0){if((r&F)===0)return;t.f^=F}}Ae.push(t)}function mi(e){let t=0,r=Te(0),n;return()=>{Pi()&&(g(r),en(()=>(t===0&&(n=Ne(()=>e(()=>ct(r)))),t+=1,()=>{qe(()=>{t-=1,t===0&&(n?.(),n=void 0,ct(r))})})))}}var gi=pt|tt|Pt;function wi(e,t,r){new bi(e,t,r)}class bi{parent;#e=!1;#t;#s=null;#r;#l;#o;#a=null;#n=null;#i=null;#u=null;#f=0;#c=0;#v=!1;#d=null;#m=()=>{this.#d&&Qe(this.#d,this.#f)};#g=mi(()=>(this.#d=Te(this.#f),()=>{this.#d=null}));constructor(t,r,n){this.#t=t,this.#r=r,this.#l=n,this.parent=S.b,this.#e=!!this.#r.pending,this.#o=nt(()=>{S.b=this;{try{this.#a=J(()=>n(this.#t))}catch(i){this.error(i)}this.#c>0?this.#_():this.#e=!1}},gi)}#w(){try{this.#a=J(()=>this.#l(this.#t))}catch(t){this.error(t)}this.#e=!1}#b(){const t=this.#r.pending;t&&(this.#n=J(()=>t(this.#t)),ye.enqueue(()=>{this.#a=this.#h(()=>(ye.ensure(),J(()=>this.#l(this.#t)))),this.#c>0?this.#_():(Ge(this.#n,()=>{this.#n=null}),this.#e=!1)}))}is_pending(){return this.#e||!!this.parent&&this.parent.is_pending()}has_pending_snippet(){return!!this.#r.pending}#h(t){var r=S,n=M,i=R;ae(this.#o),W(this.#o),Je(this.#o.ctx);try{return t()}catch(s){return zr(s),null}finally{ae(r),W(n),Je(i)}}#_(){const t=this.#r.pending;this.#a!==null&&(this.#u=document.createDocumentFragment(),on(this.#a,this.#u)),this.#n===null&&(this.#n=J(()=>t(this.#t)))}#p(t){if(!this.has_pending_snippet()){this.parent&&this.parent.#p(t);return}this.#c+=t,this.#c===0&&(this.#e=!1,this.#n&&Ge(this.#n,()=>{this.#n=null}),this.#u&&(this.#t.before(this.#u),this.#u=null))}update_pending_count(t){this.#p(t),this.#f+=t,Ht.add(this.#m)}get_effect_pending(){return this.#g(),g(this.#d)}error(t){var r=this.#r.onerror;let n=this.#r.failed;if(this.#v||!r&&!n)throw t;this.#a&&(z(this.#a),this.#a=null),this.#n&&(z(this.#n),this.#n=null),this.#i&&(z(this.#i),this.#i=null);var i=!1,s=!1;const a=()=>{if(i){li();return}i=!0,s&&Yn(),ye.ensure(),this.#f=0,this.#i!==null&&Ge(this.#i,()=>{this.#i=null}),this.#e=this.has_pending_snippet(),this.#a=this.#h(()=>(this.#v=!1,J(()=>this.#l(this.#t)))),this.#c>0?this.#_():this.#e=!1};var o=M;try{W(null),s=!0,r?.(t,a),s=!1}catch(l){Xe(l,this.#o&&this.#o.parent)}finally{W(o)}n&&qe(()=>{this.#i=this.#h(()=>{this.#v=!0;try{return J(()=>{n(this.#t,()=>t,()=>a)})}catch(l){return Xe(l,this.#o.parent),null}finally{this.#v=!1}})})}}function Hr(e,t,r){const n=mt()?gt:rr;if(t.length===0){r(e.map(n));return}var i=I,s=S,a=yi();Promise.all(t.map(o=>Ei(o))).then(o=>{a();try{r([...e.map(n),...o])}catch(l){(s.f&be)===0&&Xe(l,s)}i?.deactivate(),Kt()}).catch(o=>{Xe(o,s)})}function yi(){var e=S,t=M,r=R,n=I;return function(){ae(e),W(t),Je(r),n?.activate()}}function Kt(){ae(null),W(null),Je(null)}function gt(e){var t=U|Z,r=M!==null&&(M.f&U)!==0?M:null;return S===null||r!==null&&(r.f&se)!==0?t|=se:S.f|=tt,{ctx:R,deps:null,effects:null,equals:Dr,f:t,fn:e,reactions:null,rv:0,v:L,wv:0,parent:r??S,ac:null}}function Ei(e,t){let r=S;r===null&&jn();var n=r.b,i=void 0,s=Te(L),a=!M,o=new Map;return Ci(()=>{var l=Or();i=l.promise;try{Promise.resolve(e()).then(l.resolve,l.reject).then(()=>{f===I&&f.committed&&f.deactivate(),Kt()})}catch(u){l.reject(u),Kt()}var f=I;if(a){var v=!n.is_pending();n.update_pending_count(1),f.increment(v),o.get(f)?.reject(Be),o.delete(f),o.set(f,l)}const d=(u,h=void 0)=>{if(f.activate(),h)h!==Be&&(s.f|=Re,Qe(s,h));else{(s.f&Re)!==0&&(s.f^=Re),Qe(s,u);for(const[c,p]of o){if(o.delete(c),c===f)break;p.reject(Be)}}a&&(n.update_pending_count(-1),f.decrement(v))};l.promise.then(d,u=>d(null,u||"unknown"))}),sr(()=>{for(const l of o.values())l.reject(Be)}),new Promise(l=>{function f(v){function d(){v===i?l(s):f(i)}v.then(d,d)}f(i)})}function Ie(e){const t=gt(e);return ln(t),t}function rr(e){const t=gt(e);return t.equals=Lr,t}function Ur(e){var t=e.effects;if(t!==null){e.effects=null;for(var r=0;r{if($e===s)return o();var l=M,f=$e;W(null),br(s);var v=o();return W(l),br(f),v};return n&&r.set("length",V(e.length)),new Proxy(e,{defineProperty(o,l,f){(!("value"in f)||f.configurable===!1||f.enumerable===!1||f.writable===!1)&&Bn();var v=r.get(l);return v===void 0?v=a(()=>{var d=V(f.value);return r.set(l,d),d}):O(v,f.value,!0),!0},deleteProperty(o,l){var f=r.get(l);if(f===void 0){if(l in o){const v=a(()=>V(L));r.set(l,v),ct(i)}}else O(f,L),ct(i);return!0},get(o,l,f){if(l===de)return e;var v=r.get(l),d=l in o;if(v===void 0&&(!d||xe(o,l)?.writable)&&(v=a(()=>{var h=ie(d?o[l]:L),c=V(h);return c}),r.set(l,v)),v!==void 0){var u=g(v);return u===L?void 0:u}return Reflect.get(o,l,f)},getOwnPropertyDescriptor(o,l){var f=Reflect.getOwnPropertyDescriptor(o,l);if(f&&"value"in f){var v=r.get(l);v&&(f.value=g(v))}else if(f===void 0){var d=r.get(l),u=d?.v;if(d!==void 0&&u!==L)return{enumerable:!0,configurable:!0,value:u,writable:!0}}return f},has(o,l){if(l===de)return!0;var f=r.get(l),v=f!==void 0&&f.v!==L||Reflect.has(o,l);if(f!==void 0||S!==null&&(!v||xe(o,l)?.writable)){f===void 0&&(f=a(()=>{var u=v?ie(o[l]):L,h=V(u);return h}),r.set(l,f));var d=g(f);if(d===L)return!1}return v},set(o,l,f,v){var d=r.get(l),u=l in o;if(n&&l==="length")for(var h=f;hV(L)),r.set(h+"",c))}if(d===void 0)(!u||xe(o,l)?.writable)&&(d=a(()=>V(void 0)),O(d,ie(f)),r.set(l,d));else{u=d.v!==L;var p=a(()=>ie(f));O(d,p)}var m=Reflect.getOwnPropertyDescriptor(o,l);if(m?.set&&m.set.call(v,f),!u){if(n&&typeof l=="string"){var w=r.get("length"),k=Number(l);Number.isInteger(k)&&k>=w.v&&O(w,k+1)}ct(i)}return!0},ownKeys(o){g(i);var l=Reflect.ownKeys(o).filter(d=>{var u=r.get(d);return u===void 0||u.v!==L});for(var[f,v]of r)v.v!==L&&!(f in o)&&l.push(f);return l},setPrototypeOf(){Wn()}})}function pr(e){try{if(e!==null&&typeof e=="object"&&de in e)return e[de]}catch{}return e}function Ti(e,t){return Object.is(pr(e),pr(t))}var mr,Gr,Yr,Jr;function Si(){if(mr===void 0){mr=window,Gr=/Firefox/.test(navigator.userAgent);var e=Element.prototype,t=Node.prototype,r=Text.prototype;Yr=xe(t,"firstChild").get,Jr=xe(t,"nextSibling").get,vr(e)&&(e.__click=void 0,e.__className=void 0,e.__attributes=null,e.__style=void 0,e.__e=void 0),vr(r)&&(r.__t=void 0)}}function De(e=""){return document.createTextNode(e)}function Ze(e){return Yr.call(e)}function wt(e){return Jr.call(e)}function A(e,t){return Ze(e)}function G(e,t=!1){{var r=Ze(e);return r instanceof Comment&&r.data===""?wt(r):r}}function x(e,t=1,r=!1){let n=e;for(;t--;)n=wt(n);return n}function Ai(e){e.textContent=""}function Xr(){return!1}function Mi(e,t){if(t){const r=document.body;e.autofocus=!0,qe(()=>{document.activeElement===r&&e.focus()})}}function ir(e){var t=M,r=S;W(null),ae(null);try{return e()}finally{W(t),ae(r)}}function Qr(e){S===null&&M===null&&Hn(),M!==null&&(M.f&se)!==0&&S===null&&Fn(),Fe&&qn()}function xi(e,t){var r=t.last;r===null?t.last=t.first=e:(r.next=e,e.prev=r,t.last=e)}function pe(e,t,r,n=!0){var i=S;i!==null&&(i.f&Q)!==0&&(e|=Q);var s={ctx:R,deps:null,nodes_start:null,nodes_end:null,f:e|Z,first:null,fn:t,last:null,next:null,parent:i,b:i&&i.b,prev:null,teardown:null,transitions:null,wv:0,ac:null};if(r)try{At(s),s.f|=Zt}catch(l){throw z(s),l}else t!==null&&Ve(s);if(n){var a=s;if(r&&a.deps===null&&a.teardown===null&&a.nodes_start===null&&a.first===a.last&&(a.f&tt)===0&&(a=a.first),a!==null&&(a.parent=i,i!==null&&xi(a,i),M!==null&&(M.f&U)!==0&&(e&je)===0)){var o=M;(o.effects??=[]).push(a)}}return s}function Pi(){return M!==null&&!ue}function sr(e){const t=pe(xt,null,!1);return H(t,F),t.teardown=e,t}function ve(e){Qr();var t=S.f,r=!M&&(t&Se)!==0&&(t&Zt)===0;if(r){var n=R;(n.e??=[]).push(e)}else return Zr(e)}function Zr(e){return pe(Qt|Cr,e,!1)}function Ni(e){return Qr(),pe(xt|Cr,e,!0)}function Oi(e){ye.ensure();const t=pe(je|tt,e,!0);return(r={})=>new Promise(n=>{r.outro?Ge(t,()=>{z(t),n(void 0)}):(z(t),n(void 0))})}function ar(e){return pe(Qt,e,!1)}function Ci(e){return pe(er|tt,e,!0)}function en(e,t=0){return pe(xt|t,e,!0)}function Ee(e,t=[],r=[]){Hr(t,r,n=>{pe(xt,()=>e(...n.map(g)),!0)})}function nt(e,t=0){var r=pe(ze|t,e,!0);return r}function J(e,t=!0){return pe(Se|tt,e,!0,t)}function tn(e){var t=e.teardown;if(t!==null){const r=Fe,n=M;wr(!0),W(null);try{t.call(null)}finally{wr(r),W(n)}}}function rn(e,t=!1){var r=e.first;for(e.first=e.last=null;r!==null;){const i=r.ac;i!==null&&ir(()=>{i.abort(Be)});var n=r.next;(r.f&je)!==0?r.parent=null:z(r,t),r=n}}function Ri(e){for(var t=e.first;t!==null;){var r=t.next;(t.f&Se)===0&&z(t),t=r}}function z(e,t=!0){var r=!1;(t||(e.f&Ln)!==0)&&e.nodes_start!==null&&e.nodes_end!==null&&(Ii(e.nodes_start,e.nodes_end),r=!0),rn(e,t&&!r),St(e,0),H(e,be);var n=e.transitions;if(n!==null)for(const s of n)s.stop();tn(e);var i=e.parent;i!==null&&i.first!==null&&nn(e),e.next=e.prev=e.teardown=e.ctx=e.deps=e.fn=e.nodes_start=e.nodes_end=e.ac=null}function Ii(e,t){for(;e!==null;){var r=e===t?null:wt(e);e.remove(),e=r}}function nn(e){var t=e.parent,r=e.prev,n=e.next;r!==null&&(r.next=n),n!==null&&(n.prev=r),t!==null&&(t.first===e&&(t.first=n),t.last===e&&(t.last=r))}function Ge(e,t,r=!0){var n=[];or(e,n,!0),sn(n,()=>{r&&z(e),t&&t()})}function sn(e,t){var r=e.length;if(r>0){var n=()=>--r||t();for(var i of e)i.out(n)}else t()}function or(e,t,r){if((e.f&Q)===0){if(e.f^=Q,e.transitions!==null)for(const a of e.transitions)(a.is_global||r)&&t.push(a);for(var n=e.first;n!==null;){var i=n.next,s=(n.f&pt)!==0||(n.f&Se)!==0;or(n,t,s?r:!1),n=i}}}function lr(e){an(e,!0)}function an(e,t){if((e.f&Q)!==0){e.f^=Q,(e.f&F)===0&&(H(e,Z),Ve(e));for(var r=e.first;r!==null;){var n=r.next,i=(r.f&pt)!==0||(r.f&Se)!==0;an(r,i?t:!1),r=n}if(e.transitions!==null)for(const s of e.transitions)(s.is_global||t)&&s.in()}}function on(e,t){for(var r=e.nodes_start,n=e.nodes_end;r!==null;){var i=r===n?null:wt(r);t.append(r),r=i}}let Ye=!1;function gr(e){Ye=e}let Fe=!1;function wr(e){Fe=e}let M=null,ue=!1;function W(e){M=e}let S=null;function ae(e){S=e}let ke=null;function ln(e){M!==null&&(ke===null?ke=[e]:ke.push(e))}let q=null,Y=0,ne=null;function $i(e){ne=e}let un=1,vt=0,$e=vt;function br(e){$e=e}let Me=!1;function fn(){return++un}function Ct(e){var t=e.f;if((t&Z)!==0)return!0;if((t&Oe)!==0){var r=e.deps,n=(t&se)!==0;if(r!==null){var i,s,a=(t&Tt)!==0,o=n&&S!==null&&!Me,l=r.length;if((a||o)&&(S===null||(S.f&be)===0)){var f=e,v=f.parent;for(i=0;ie.wv)return!0}(!n||S!==null&&!Me)&&H(e,F)}return!1}function cn(e,t,r=!0){var n=e.reactions;if(n!==null&&!ke?.includes(e))for(var i=0;i{e.ac.abort(Be)}),e.ac=null);try{e.f|=Ft;var d=e.fn,u=d(),h=e.deps;if(q!==null){var c;if(St(e,Y),h!==null&&Y>0)for(h.length=Y+q.length,c=0;cr?.call(this,s))}return e.startsWith("pointer")||e.startsWith("touch")||e==="wheel"?qe(()=>{t.addEventListener(e,i,n)}):t.addEventListener(e,i,n),i}function _n(e){for(var t=0;t{throw k});throw u}}finally{e.__root=t,delete e.currentTarget,W(v),ae(d)}}}function pn(e){var t=document.createElement("template");return t.innerHTML=e.replaceAll("",""),t.content}function ht(e,t){var r=S;r.nodes_start===null&&(r.nodes_start=e,r.nodes_end=t)}function j(e,t){var r=(t&ri)!==0,n=(t&ni)!==0,i,s=!e.startsWith("");return()=>{i===void 0&&(i=pn(s?e:""+e),r||(i=Ze(i)));var a=n||Gr?document.importNode(i,!0):i.cloneNode(!0);if(r){var o=Ze(a),l=a.lastChild;ht(o,l)}else ht(a,a);return a}}function Bi(e,t,r="svg"){var n=!e.startsWith(""),i=`<${r}>${n?e:""+e}`,s;return()=>{if(!s){var a=pn(i),o=Ze(a);s=Ze(o)}var l=s.cloneNode(!0);return ht(l,l),l}}function Wi(e,t){return Bi(e,t,"svg")}function ee(){var e=document.createDocumentFragment(),t=document.createComment(""),r=De();return e.append(t,r),ht(t,r),e}function P(e,t){e!==null&&e.before(t)}function fe(e,t){var r=t==null?"":typeof t=="object"?t+"":t;r!==(e.__t??=e.nodeValue)&&(e.__t=r,e.nodeValue=r+"")}function Gi(e,t){return Yi(e,t)}const Ue=new Map;function Yi(e,{target:t,anchor:r,props:n={},events:i,context:s,intro:a=!0}){Si();var o=new Set,l=d=>{for(var u=0;u{var d=r??t.appendChild(De());return wi(d,{pending:()=>{}},u=>{if(s){he({});var h=R;h.c=s}i&&(n.$$events=i),f=e(u,n)||{},s&&_e()}),()=>{for(var u of o){t.removeEventListener(u,ft);var h=Ue.get(u);--h===0?(document.removeEventListener(u,ft),Ue.delete(u)):Ue.set(u,h)}Gt.delete(l),d!==r&&d.parentNode?.removeChild(d)}});return Ji.set(f,v),f}let Ji=new WeakMap;class mn{anchor;#e=new Map;#t=new Map;#s=new Map;#r=!0;constructor(t,r=!0){this.anchor=t,this.#r=r}#l=()=>{var t=I;if(this.#e.has(t)){var r=this.#e.get(t),n=this.#t.get(r);if(n)lr(n);else{var i=this.#s.get(r);i&&(this.#t.set(r,i.effect),this.#s.delete(r),i.fragment.lastChild.remove(),this.anchor.before(i.fragment),n=i.effect)}for(const[s,a]of this.#e){if(this.#e.delete(s),s===t)break;const o=this.#s.get(a);o&&(z(o.effect),this.#s.delete(a))}for(const[s,a]of this.#t){if(s===r)continue;const o=()=>{if(Array.from(this.#e.values()).includes(s)){var f=document.createDocumentFragment();on(a,f),f.append(De()),this.#s.set(s,{effect:a,fragment:f})}else z(a);this.#t.delete(s)};this.#r||!n?Ge(a,o,!1):o()}}};ensure(t,r){var n=I,i=Xr();if(r&&!this.#t.has(t)&&!this.#s.has(t))if(i){var s=document.createDocumentFragment(),a=De();s.append(a),this.#s.set(t,{effect:J(()=>r(a)),fragment:s})}else this.#t.set(t,J(()=>r(this.anchor)));if(this.#e.set(n,t),i){for(const[o,l]of this.#t)o===t?n.skipped_effects.delete(l):n.skipped_effects.add(l);for(const[o,l]of this.#s)o===t?n.skipped_effects.delete(l.effect):n.skipped_effects.add(l.effect);n.add_callback(this.#l)}else this.#l()}}function X(e,t,r=!1){var n=new mn(e),i=r?pt:0;function s(a,o){n.ensure(a,o)}nt(()=>{var a=!1;t((o,l=!0)=>{a=!0,s(l,o)}),a||s(!1,null)},i)}function Xi(e,t){return t}function Qi(e,t,r){for(var n=e.items,i=[],s=t.length,a=0;a0&&i.length===0&&r!==null;if(o){var l=r.parentNode;Ai(l),l.append(r),n.clear(),ce(e,t[0].prev,t[s-1].next)}sn(i,()=>{for(var f=0;f{var w=r();return Mt(w)?w:w==null?[]:Jt(w)}),c,p;function m(){Zi(p,c,o,u,a,i,t,n,r),s!==null&&(c.length===0?v?lr(v):v=J(()=>s(a)):v!==null&&Ge(v,()=>{v=null}))}nt(()=>{p??=S,c=g(h);var w=c.length;if(!(d&&w===0)){d=w===0;var k,_,b,T;if(Xr()){var y=new Set,E=I;for(_=0;_0){var D=(a&$r)!==0&&d===0?i:null;if(f){for(E=0;E{if(w!==void 0)for(y of w)y.a?.apply()}),e.first=r.first&&r.first.e,e.last=m&&m.e;for(var me of n.values())z(me.e);n.clear()}function gn(e,t,r,n){(n&Nt)!==0&&Qe(e.v,t),(n&Ot)!==0?Qe(e.i,r):e.i=r}function wn(e,t,r,n,i,s,a,o,l,f,v){var d=(l&Nt)!==0,u=(l&Xn)===0,h=d?u?Br(i,!1,!1):Te(i):i,c=(l&Ot)===0?a:Te(a),p={i:c,v:h,k:s,a:null,e:null,prev:r,next:n};try{if(e===null){var m=document.createDocumentFragment();m.append(e=De())}return p.e=J(()=>o(e,h,c,f),ui),p.e.prev=r&&r.e,p.e.next=n&&n.e,r===null?v||(t.first=p):(r.next=p,r.e.next=p.e),n!==null&&(n.prev=p,n.e.prev=p.e),p}finally{}}function Vt(e,t,r){for(var n=e.next?e.next.e.nodes_start:r,i=t?t.e.nodes_start:r,s=e.e.nodes_start;s!==null&&s!==n;){var a=wt(s);i.before(s),s=a}}function ce(e,t,r){t===null?e.first=r:(t.next=r,t.e.next=r&&r.e),r!==null&&(r.prev=t,r.e.prev=t&&t.e)}function Rt(e,t,r,n,i){var s=t.$$slots?.[r],a=!1;s===!0&&(s=t.children,a=!0),s===void 0||s(e,a?()=>n:n)}function es(e,t,r,n,i,s){var a=null,o=e,l=new mn(o,!1);nt(()=>{const f=t()||null;var v=si;if(f===null){l.ensure(null,null);return}return l.ensure(f,d=>{if(f){if(a=document.createElementNS(v,f),ht(a,a),n){var u=a.appendChild(De());n(a,u)}S.nodes_end=a,d.before(a)}}),()=>{}},pt),sr(()=>{})}function ts(e,t){var r=void 0,n;nt(()=>{r!==(r=t())&&(n&&(z(n),n=null),r&&(n=J(()=>{ar(()=>r(e))})))})}function bn(e){var t,r,n="";if(typeof e=="string"||typeof e=="number")n+=e;else if(typeof e=="object")if(Array.isArray(e)){var i=e.length;for(t=0;t=0;){var o=a+s;(a===0||Er.includes(n[a-1]))&&(o===n.length||Er.includes(n[o]))?n=(a===0?"":n.substring(0,a))+n.substring(o+1):a=o}}return n===""?null:n}function kr(e,t=!1){var r=t?" !important;":";",n="";for(var i in e){var s=e[i];s!=null&&s!==""&&(n+=" "+i+": "+s+r)}return n}function Dt(e){return e[0]!=="-"||e[1]!=="-"?e.toLowerCase():e}function ss(e,t){if(t){var r="",n,i;if(Array.isArray(t)?(n=t[0],i=t[1]):n=t,e){e=String(e).replaceAll(/\s*\/\*.*?\*\/\s*/g,"").trim();var s=!1,a=0,o=!1,l=[];n&&l.push(...Object.keys(n).map(Dt)),i&&l.push(...Object.keys(i).map(Dt));var f=0,v=-1;const p=e.length;for(var d=0;d{Yt(e,e.__value)});t.observe(e,{childList:!0,subtree:!0,attributes:!0,attributeFilter:["value"]}),sr(()=>{t.disconnect()})}function Tr(e){return"__value"in e?e.__value:e.value}const lt=Symbol("class"),ut=Symbol("style"),yn=Symbol("is custom element"),En=Symbol("is html");function os(e,t){t?e.hasAttribute("selected")||e.setAttribute("selected",""):e.removeAttribute("selected")}function et(e,t,r,n){var i=kn(e);i[t]!==(i[t]=r)&&(t==="loading"&&(e[zn]=r),r==null?e.removeAttribute(t):typeof r!="string"&&Tn(e).includes(t)?e[t]=r:e.setAttribute(t,r))}function ls(e,t,r,n,i=!1,s=!1){var a=kn(e),o=a[yn],l=!a[En],f=t||{},v=e.tagName==="OPTION";for(var d in t)d in r||(r[d]=null);r.class?r.class=ns(r.class):r[lt]&&(r.class=null),r[ut]&&(r.style??=null);var u=Tn(e);for(const _ in r){let b=r[_];if(v&&_==="value"&&b==null){e.value=e.__value="",f[_]=b;continue}if(_==="class"){var h=e.namespaceURI==="http://www.w3.org/1999/xhtml";Le(e,h,b,n,t?.[lt],r[lt]),f[_]=b,f[lt]=r[lt];continue}if(_==="style"){It(e,b,t?.[ut],r[ut]),f[_]=b,f[ut]=r[ut];continue}var c=f[_];if(!(b===c&&!(b===void 0&&e.hasAttribute(_)))){f[_]=b;var p=_[0]+_[1];if(p!=="$$")if(p==="on"){const T={},y="$$"+_;let E=_.slice(2);var m=ji(E);if(Li(E)&&(E=E.slice(0,-7),T.capture=!0),!m&&c){if(b!=null)continue;e.removeEventListener(E,f[y],T),f[y]=null}if(b!=null)if(m)e[`__${E}`]=b,_n([E]);else{let $=function(K){f[_].call(this,K)};f[y]=Ki(E,e,$,T)}else m&&(e[`__${E}`]=void 0)}else if(_==="style")et(e,_,b);else if(_==="autofocus")Mi(e,!!b);else if(!o&&(_==="__value"||_==="value"&&b!=null))e.value=e.__value=b;else if(_==="selected"&&v)os(e,b);else{var w=_;l||(w=Fi(w));var k=w==="defaultValue"||w==="defaultChecked";if(b==null&&!o&&!k)if(a[_]=null,w==="value"||w==="checked"){let T=e;const y=t===void 0;if(w==="value"){let E=T.defaultValue;T.removeAttribute(w),T.defaultValue=E,T.value=T.__value=y?E:null}else{let E=T.defaultChecked;T.removeAttribute(w),T.defaultChecked=E,T.checked=y?E:!1}}else e.removeAttribute(_);else k||u.includes(w)&&(o||typeof b!="string")?(e[w]=b,w in a&&(a[w]=L)):typeof b!="function"&&et(e,w,b)}}}return f}function Sr(e,t,r=[],n=[],i,s=!1,a=!1){Hr(r,n,o=>{var l=void 0,f={},v=e.nodeName==="SELECT",d=!1;if(nt(()=>{var h=t(...o.map(g)),c=ls(e,l,h,i,s,a);d&&v&&"value"in h&&Yt(e,h.value);for(let m of Object.getOwnPropertySymbols(f))h[m]||z(f[m]);for(let m of Object.getOwnPropertySymbols(h)){var p=h[m];m.description===ai&&(!l||p!==l[m])&&(f[m]&&z(f[m]),f[m]=J(()=>ts(e,()=>p))),c[m]=p}l=c}),v){var u=e;ar(()=>{Yt(u,l.value,!0),as(u)})}d=!0})}function kn(e){return e.__attributes??={[yn]:e.nodeName.includes("-"),[En]:e.namespaceURI===ii}}var Ar=new Map;function Tn(e){var t=e.getAttribute("is")||e.nodeName,r=Ar.get(t);if(r)return r;Ar.set(t,r=[]);for(var n,i=e,s=Element.prototype;s!==i;){n=Nr(i);for(var a in n)n[a].set&&r.push(a);i=Xt(i)}return r}function Mr(e,t){return e===t||e?.[de]===t}function us(e={},t,r,n){return ar(()=>{var i,s;return en(()=>{i=s,s=[],Ne(()=>{e!==r(...s)&&(t(e,...s),i&&Mr(r(...i),e)&&t(null,...i))})}),()=>{qe(()=>{s&&Mr(r(...s),e)&&t(null,...s)})}}),e}function Sn(e=!1){const t=R,r=t.l.u;if(!r)return;let n=()=>Ke(t.s);if(e){let i=0,s={};const a=gt(()=>{let o=!1;const l=t.s;for(const f in l)l[f]!==s[f]&&(s[f]=l[f],o=!0);return o&&i++,i});n=()=>g(a)}r.b.length&&Ni(()=>{xr(t,n),qt(r.b)}),ve(()=>{const i=Ne(()=>r.m.map(Vn));return()=>{for(const s of i)typeof s=="function"&&s()}}),r.a.length&&ve(()=>{xr(t,n),qt(r.a)})}function xr(e,t){if(e.l.s)for(const r of e.l.s)g(r);t()}function An(e){var t=Te(0);return function(){return arguments.length===1?(O(t,g(t)+1),arguments[0]):(g(t),e())}}let kt=!1;function fs(e){var t=kt;try{return kt=!1,[e(),kt]}finally{kt=t}}const cs={get(e,t){if(!e.exclude.includes(t))return g(e.version),t in e.special?e.special[t]():e.props[t]},set(e,t,r){if(!(t in e.special)){var n=S;try{ae(e.parent_effect),e.special[t]=C({get[t](){return e.props[t]}},t,Vr)}finally{ae(n)}}return e.special[t](r),Bt(e.version),!0},getOwnPropertyDescriptor(e,t){if(!e.exclude.includes(t)&&t in e.props)return{enumerable:!0,configurable:!0,value:e.props[t]}},deleteProperty(e,t){return e.exclude.includes(t)||(e.exclude.push(t),Bt(e.version)),!0},has(e,t){return e.exclude.includes(t)?!1:t in e.props},ownKeys(e){return Reflect.ownKeys(e.props).filter(t=>!e.exclude.includes(t))}};function _t(e,t){return new Proxy({props:e,exclude:t,special:{},version:Te(0),parent_effect:S},cs)}const ds={get(e,t){let r=e.props.length;for(;r--;){let n=e.props[r];if(ot(n)&&(n=n()),typeof n=="object"&&n!==null&&t in n)return n[t]}},set(e,t,r){let n=e.props.length;for(;n--;){let i=e.props[n];ot(i)&&(i=i());const s=xe(i,t);if(s&&s.set)return s.set(r),!0}return!1},getOwnPropertyDescriptor(e,t){let r=e.props.length;for(;r--;){let n=e.props[r];if(ot(n)&&(n=n()),typeof n=="object"&&n!==null&&t in n){const i=xe(n,t);return i&&!i.configurable&&(i.configurable=!0),i}}},has(e,t){if(t===de||t===Rr)return!1;for(let r of e.props)if(ot(r)&&(r=r()),r!=null&&t in r)return!0;return!1},ownKeys(e){const t=[];for(let r of e.props)if(ot(r)&&(r=r()),!!r){for(const n in r)t.includes(n)||t.push(n);for(const n of Object.getOwnPropertySymbols(r))t.includes(n)||t.push(n)}return t}};function fr(...e){return new Proxy({props:e},ds)}function C(e,t,r,n){var i=!rt||(r&Zn)!==0,s=(r&ei)!==0,a=(r&ti)!==0,o=n,l=!0,f=()=>(l&&(l=!1,o=a?Ne(n):n),o),v;if(s){var d=de in e||Rr in e;v=xe(e,t)?.set??(d&&t in e?_=>e[t]=_:void 0)}var u,h=!1;s?[u,h]=fs(()=>e[t]):u=e[t],u===void 0&&n!==void 0&&(u=f(),v&&(i&&Kn(),v(u)));var c;if(i?c=()=>{var _=e[t];return _===void 0?f():(l=!0,_)}:c=()=>{var _=e[t];return _!==void 0&&(o=void 0),_===void 0?o:_},i&&(r&Vr)===0)return c;if(v){var p=e.$$legacy;return(function(_,b){return arguments.length>0?((!i||!b||p||h)&&v(b?c():_),_):c()})}var m=!1,w=((r&Qn)!==0?gt:rr)(()=>(m=!1,c()));s&&g(w);var k=S;return(function(_,b){if(arguments.length>0){const T=b?g(w):i&&s?ie(_):_;return O(w,T),m=!0,o!==void 0&&(o=T),_}return Fe&&m||(k.f&be)!==0?w.v:g(w)})}function cr(e){R===null&&Ir(),rt&&R.l!==null?hs(R).m.push(e):ve(()=>{const t=Ne(e);if(typeof t=="function")return t})}function vs(e){R===null&&Ir(),cr(()=>()=>Ne(e))}function hs(e){var t=e.l;return t.u??={a:[],b:[],m:[]}}const _s="5";typeof window<"u"&&((window.__svelte??={}).v??=new Set).add(_s);di();function ge(e,t){const r=n=>{const{type:i,...s}=n.data;i===e&&t(s)};cr(()=>{window.addEventListener("message",r)}),vs(()=>{window.removeEventListener("message",r)})}const Mn=()=>!window.invokeNative,ps=()=>Mn()?"nui-frame-app":window.GetParentResourceName?window.GetParentResourceName():"unknown";async function xn(e,t={}){const r=ps();if(Mn())return console.warn(`[fetchNui] Browser environment detected. Event: ${e}`,t),{};try{const n=await fetch(`https://${r}/${e}`,{method:"POST",headers:{"Content-Type":"application/json; charset=UTF-8"},body:JSON.stringify(t)});if(!n.ok)throw new Error(`HTTP error! status: ${n.status}`);const i=await n.json();if(!i.ok&&i.error)throw new Error(i.error);return i.data}catch(n){throw console.error(`[fetchNui] Error calling ${e}:`,n),n}}class ms{#e=V(ie([]));get queue(){return g(this.#e)}set queue(t){O(this.#e,t,!0)}#t=V(null);get current(){return g(this.#t)}set current(t){O(this.#t,t,!0)}dismissTimeout=null;idCounter=0;get currentNotification(){return this.current}get queueLength(){return this.queue.length}show(t){const r={...t,id:`notification-${++this.idCounter}`,duration:t.duration??5e3};this.queue.push(r),this.current||this.processQueue()}processQueue(){if(this.queue.length===0){this.current=null;return}this.current=this.queue.shift(),this.dismissTimeout&&clearTimeout(this.dismissTimeout),this.dismissTimeout=window.setTimeout(()=>{this.dismiss()},this.current.duration)}dismiss(){this.dismissTimeout&&(clearTimeout(this.dismissTimeout),this.dismissTimeout=null),this.current=null,setTimeout(()=>{this.processQueue()},300)}clearAll(){this.queue=[],this.dismissTimeout&&(clearTimeout(this.dismissTimeout),this.dismissTimeout=null),this.current=null}}const Pn=new ms;class gs{#e=V(ie({choiceVisible:!1,hudVisible:!1,jumpscareVisible:!1,maxDuration:6e5,exitKey:"X",scareKey:"E",forced:!1,soundVolume:.8}));get state(){return g(this.#e)}set state(t){O(this.#e,t,!0)}showChoice(t=!1){this.state.choiceVisible=!0,this.state.forced=t}hideChoice(){this.state.choiceVisible=!1}showHUD(t,r="X",n="E"){this.state.hudVisible=!0,this.state.maxDuration=t,this.state.exitKey=r,this.state.scareKey=n}hideHUD(){this.state.hudVisible=!1}triggerJumpscare(t=.8){this.state.jumpscareVisible=!0,this.state.soundVolume=t}hideJumpscare(){this.state.jumpscareVisible=!1}}const ws=new gs;class bs{#e=V(ie({hudVisible:!1,currentHouses:0,totalHouses:0,timeRemaining:0,rewardVisible:!1,rewardType:"treat",rewardItem:"",rewardAmount:0}));get state(){return g(this.#e)}set state(t){O(this.#e,t,!0)}startRound(t,r){this.state.hudVisible=!0,this.state.currentHouses=0,this.state.totalHouses=t,this.state.timeRemaining=r}updateProgress(t,r){this.state.currentHouses=t,this.state.timeRemaining=r}endRound(){this.state.hudVisible=!1,this.state.currentHouses=0,this.state.totalHouses=0,this.state.timeRemaining=0}showReward(t,r,n){this.state.rewardType=t,this.state.rewardItem=r,this.state.rewardAmount=n,this.state.rewardVisible=!0}hideReward(){this.state.rewardVisible=!1}hideHUD(){this.state.hudVisible=!1}}const ys=new bs;var Es=j('

');function ks(e,t){let r=C(t,"visible",3,!0);var n=Es();let i;var s=x(A(n),2),a=x(s,2),o=A(a),l=A(o),f=x(o,2),v=A(f);Ee(d=>{i=Le(n,1,`notification notification--${t.size??""} notification--${t.position??""}`,"svelte-1wg8nyl",i,d),Le(s,1,`notification__pumpkin notification__pumpkin--${t.size??""}`,"svelte-1wg8nyl"),fe(l,t.header),fe(v,t.description)},[()=>({"notification--visible":r()})]),P(e,n)}var Ts=j("
");function Ss(e,t){he(t,!0);const r=Ie(()=>Pn.currentNotification);let n=V(!1);ve(()=>{g(r)?(O(n,!1),requestAnimationFrame(()=>{requestAnimationFrame(()=>{O(n,!0)})})):O(n,!1)});function i(l){return l||""}var s=ee(),a=G(s);{var o=l=>{var f=Ts(),v=A(f);ks(v,{get size(){return g(r).size},get position(){return g(r).position},get header(){return g(r).header},get description(){return g(r).description},get visible(){return g(n)}}),Ee(d=>Le(f,1,`notification-container notification-container--${d??""}`,"svelte-v226t6"),[()=>i(g(r).position)]),P(l,f)};X(a,l=>{g(r)&&l(o)})}P(e,s),_e()}var As=(e,t)=>t("ghost"),Ms=(e,t)=>t("normal"),xs=j('
'),Ps=j('
pumpkin

Spooky Opportunity!

You have a chance to respawn as a ghost and haunt the living...

');function Ns(e,t){he(t,!0);let r=C(t,"visible",15,!1),n=C(t,"forced",11,!1),i=V(15),s=null;ve(()=>(s&&(clearInterval(s),s=null),r()&&!n()&&(O(i,15),s=window.setInterval(()=>{Bt(i,-1),g(i)<=0&&(s&&(clearInterval(s),s=null),a("normal"))},1e3)),()=>{s&&(clearInterval(s),s=null)}));function a(v){s&&(clearInterval(s),s=null),xn("ghostChoice",{choice:v}),r(!1)}var o=ee(),l=G(o);{var f=v=>{var d=Ps(),u=A(d),h=x(A(u),6),c=A(h);c.__click=[As,a];var p=x(c,2);p.__click=[Ms,a];var m=x(h,2);{var w=k=>{var _=xs(),b=A(_);Ee(()=>fe(b,`Auto-decline in ${g(i)??""}s`)),P(k,_)};X(m,k=>{n()||k(w)})}P(v,d)};X(l,v=>{r()&&v(f)})}P(e,o),_e()}_n(["click"]);var Os=j('
👻
Ghost Mode
Scare
Press to exit
');function Cs(e,t){he(t,!0);let r=C(t,"visible",15,!1),n=C(t,"maxDuration",11,6e5),i=C(t,"exitKey",11,"X"),s=C(t,"scareKey",11,"E"),a=V(ie(n())),o=null;ve(()=>(r()&&(O(a,n()),o=window.setInterval(()=>{O(a,g(a)-1e3),g(a)<=0&&(O(a,0),r(!1))},1e3)),()=>{o&&(clearInterval(o),o=null)}));function l(u){const h=Math.floor(u/6e4),c=Math.floor(u%6e4/1e3);return`${h}:${c.toString().padStart(2,"0")}`}var f=ee(),v=G(f);{var d=u=>{var h=Os(),c=A(h),p=x(A(c),2),m=x(A(p),2),w=A(m),k=x(c,2),_=A(k),b=A(_),T=A(b),y=x(k,2),E=x(A(y)),$=A(E);Ee(K=>{fe(w,K),fe(T,s()),fe($,i())},[()=>l(g(a))]),P(u,h)};X(v,u=>{r()&&u(d)})}P(e,f),_e()}var Rs=j('
scary
BOO!
');function Is(e,t){he(t,!0);let r=C(t,"visible",15,!1),n=C(t,"soundVolume",11,.8),i=null,s=null;ve(()=>(r()&&(s=new Audio("./assets/scream.wav"),s.volume=n(),s.play().catch(f=>console.error("Jumpscare sound error:",f)),i=window.setTimeout(()=>{r(!1)},2e3)),()=>{i&&(clearTimeout(i),i=null),s&&(s.pause(),s=null)}));var a=ee(),o=G(a);{var l=f=>{var v=Rs();P(f,v)};X(o,f=>{r()&&f(l)})}P(e,a),_e()}/** + * @license lucide-svelte v0.548.0 - ISC + * + * ISC License + * + * Copyright (c) for portions of Lucide are held by Cole Bemis 2013-2023 as part of Feather (MIT). All other copyright (c) for Lucide are held by Lucide Contributors 2025. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * --- + * + * The MIT License (MIT) (for portions derived from Feather) + * + * Copyright (c) 2013-2023 Cole Bemis + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */const $s={xmlns:"http://www.w3.org/2000/svg",width:24,height:24,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor","stroke-width":2,"stroke-linecap":"round","stroke-linejoin":"round"};var Vs=Wi("");function dr(e,t){const r=_t(t,["children","$$slots","$$events","$$legacy"]),n=_t(r,["name","color","size","strokeWidth","absoluteStrokeWidth","iconNode"]);he(t,!1);let i=C(t,"name",8,void 0),s=C(t,"color",8,"currentColor"),a=C(t,"size",8,24),o=C(t,"strokeWidth",8,2),l=C(t,"absoluteStrokeWidth",8,!1),f=C(t,"iconNode",24,()=>[]);const v=(...c)=>c.filter((p,m,w)=>!!p&&w.indexOf(p)===m).join(" ");Sn();var d=Vs();Sr(d,(c,p)=>({...$s,...n,width:a(),height:a(),stroke:s(),"stroke-width":c,class:p}),[()=>(Ke(l()),Ke(o()),Ke(a()),Ne(()=>l()?Number(o())*24/Number(a()):o())),()=>(Ke(i()),Ke(r),Ne(()=>v("lucide-icon","lucide",i()?`lucide-${i()}`:"",r.class)))]);var u=A(d);ur(u,1,f,Xi,(c,p)=>{var m=Ie(()=>Dn(g(p),2));let w=()=>g(m)[0],k=()=>g(m)[1];var _=ee(),b=G(_);es(b,w,!0,(T,y)=>{Sr(T,()=>({...k()}))}),P(c,_)});var h=x(u);Rt(h,t,"default",{}),P(e,d),_e()}function zt(e,t){const r=_t(t,["children","$$slots","$$events","$$legacy"]);/** + * @license lucide-svelte v0.548.0 - ISC + * + * ISC License + * + * Copyright (c) for portions of Lucide are held by Cole Bemis 2013-2023 as part of Feather (MIT). All other copyright (c) for Lucide are held by Lucide Contributors 2025. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * --- + * + * The MIT License (MIT) (for portions derived from Feather) + * + * Copyright (c) 2013-2023 Cole Bemis + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */const n=[["path",{d:"M10 7v10.9"}],["path",{d:"M14 6.1V17"}],["path",{d:"M16 7V3a1 1 0 0 1 1.707-.707 2.5 2.5 0 0 0 2.152.717 1 1 0 0 1 1.131 1.131 2.5 2.5 0 0 0 .717 2.152A1 1 0 0 1 21 8h-4"}],["path",{d:"M16.536 7.465a5 5 0 0 0-7.072 0l-2 2a5 5 0 0 0 0 7.07 5 5 0 0 0 7.072 0l2-2a5 5 0 0 0 0-7.07"}],["path",{d:"M8 17v4a1 1 0 0 1-1.707.707 2.5 2.5 0 0 0-2.152-.717 1 1 0 0 1-1.131-1.131 2.5 2.5 0 0 0-.717-2.152A1 1 0 0 1 3 16h4"}]];dr(e,fr({name:"candy"},()=>r,{get iconNode(){return n},children:(i,s)=>{var a=ee(),o=G(a);Rt(o,t,"default",{}),P(i,a)},$$slots:{default:!0}}))}function Ds(e,t){const r=_t(t,["children","$$slots","$$events","$$legacy"]);/** + * @license lucide-svelte v0.548.0 - ISC + * + * ISC License + * + * Copyright (c) for portions of Lucide are held by Cole Bemis 2013-2023 as part of Feather (MIT). All other copyright (c) for Lucide are held by Lucide Contributors 2025. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * --- + * + * The MIT License (MIT) (for portions derived from Feather) + * + * Copyright (c) 2013-2023 Cole Bemis + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */const n=[["path",{d:"M12 6v6l4 2"}],["circle",{cx:"12",cy:"12",r:"10"}]];dr(e,fr({name:"clock"},()=>r,{get iconNode(){return n},children:(i,s)=>{var a=ee(),o=G(a);Rt(o,t,"default",{}),P(i,a)},$$slots:{default:!0}}))}function Ls(e,t){const r=_t(t,["children","$$slots","$$events","$$legacy"]);/** + * @license lucide-svelte v0.548.0 - ISC + * + * ISC License + * + * Copyright (c) for portions of Lucide are held by Cole Bemis 2013-2023 as part of Feather (MIT). All other copyright (c) for Lucide are held by Lucide Contributors 2025. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * --- + * + * The MIT License (MIT) (for portions derived from Feather) + * + * Copyright (c) 2013-2023 Cole Bemis + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */const n=[["path",{d:"M9 10h.01"}],["path",{d:"M15 10h.01"}],["path",{d:"M12 2a8 8 0 0 0-8 8v12l3-3 2.5 2.5L12 19l2.5 2.5L17 19l3 3V10a8 8 0 0 0-8-8z"}]];dr(e,fr({name:"ghost"},()=>r,{get iconNode(){return n},children:(i,s)=>{var a=ee(),o=G(a);Rt(o,t,"default",{}),P(i,a)},$$slots:{default:!0}}))}const Nn=""+new URL("background.BL28lNcM.webp",import.meta.url).href,On=""+new URL("pumpkin.CPHPpIKO.webp",import.meta.url).href;var zs=j('

Trick or Treat!

');function js(e,t){he(t,!0);let r=C(t,"visible",11,!1),n=C(t,"current",11,0),i=C(t,"total",11,0),s=C(t,"timeRemaining",11,0);const a=Ie(()=>i()>0?n()/i()*100:0),o=Ie(()=>()=>{const h=Math.floor(s()/1e3),c=Math.floor(h/60),p=h%60;return`${c}:${p.toString().padStart(2,"0")}`});let l=V(0),f=V(0);ve(()=>{const c=Date.now(),p=g(l),m=g(f),w=()=>{const k=Date.now()-c,_=Math.min(k/300,1);O(l,p+(g(a)-p)*_),O(f,Math.round(m+(n()-m)*_),!0),_<1&&requestAnimationFrame(w)};w()});var v=ee(),d=G(v);{var u=h=>{var c=zs(),p=A(c),m=A(p),w=x(m,2),k=x(w,2),_=x(A(k),2),b=A(_),T=A(b),y=A(T),E=x(b,2),$=A(E),K=x(_,2),te=A(K);Ds(te,{class:"timer-icon",size:18,strokeWidth:2});var oe=x(te,2),re=A(oe);Ee(He=>{et(m,"src",Nn),et(w,"src",On),It(y,`width: ${g(l)??""}%`),fe($,`${g(f)??""} / ${i()??""} houses`),fe(re,`Time Left: ${He??""}`)},[()=>g(o)()]),P(h,c)};X(d,h=>{r()&&h(u)})}P(e,v),_e()}var qs=j('
'),Fs=j('
'),Hs=j('
');function Us(e,t){he(t,!0);let r=C(t,"visible",15,!1),n=C(t,"rewardType",11,"treat"),i=C(t,"item",11,""),s=C(t,"amount",11,0);ve(()=>{if(r()){const h=setTimeout(()=>{r(!1)},2500);return()=>clearTimeout(h)}});const a=Ie(()=>n()==="treat"),o=Ie(()=>g(a)?"TREAT!":"TRICK!"),l=Ie(()=>g(a)?`You received ${s()}x ${i()}`:"You've been spooked!");let f=V(ie([]));ve(()=>{r()&&g(a)&&O(f,Array.from({length:12},(h,c)=>({id:c,left:Math.random()*100,delay:Math.random()*.3})),!0)});var v=ee(),d=G(v);{var u=h=>{var c=Hs();let p;var m=x(A(c),2);let w;var k=A(m),_=x(k,2),b=x(_,2),T=A(b),y=A(T);{var E=D=>{zt(D,{class:"reward-title-icon",size:40,strokeWidth:2})},$=D=>{Ls(D,{class:"reward-title-icon",size:40,strokeWidth:2})};X(y,D=>{g(a)?D(E):D($,!1)})}var K=x(y,2),te=A(K),oe=x(T,2),re=A(oe),He=x(oe,2);{var it=D=>{var me=qs(),bt=A(me);zt(bt,{class:"item-icon",size:64,strokeWidth:1.5});var at=x(bt,2),yt=A(at);Ee(()=>fe(yt,`+${s()??""}`)),P(D,me)};X(He,D=>{g(a)&&D(it)})}var Ce=x(m,2);{var st=D=>{var me=ee(),bt=G(me);ur(bt,17,()=>g(f),at=>at.id,(at,yt)=>{var $t=Fs(),Cn=A($t);zt(Cn,{size:24,strokeWidth:2}),Ee(()=>It($t,`left: ${g(yt).left??""}%; animation-delay: ${g(yt).delay??""}s;`)),P(at,$t)}),P(D,me)};X(Ce,D=>{g(a)&&D(st)})}Ee((D,me)=>{p=Le(c,1,"reward-popup svelte-1vu0z5b",null,p,D),w=Le(m,1,"reward-card svelte-1vu0z5b",null,w,me),et(k,"src",Nn),et(_,"src",On),fe(te,g(o)),fe(re,g(l))},[()=>({trick:!g(a)}),()=>({trick:!g(a)})]),P(h,c)};X(d,h=>{r()&&h(u)})}P(e,v),_e()}var Ks=j('🍬'),Bs=j(''),Ws=j(''),Gs=j("
"),Ys=j('
');function Js(e,t){he(t,!0);let r=V(ie([])),n=0;const i={candy:{count:12,speed:2,scale:.8,lifetime:2e3,color:"#ff9500",emoji:"🍬"},blood:{count:8,speed:3,scale:.6,lifetime:1500,color:"#8b0000",emoji:"●"},spark:{count:6,speed:4,scale:.4,lifetime:1e3,color:"#ffff00",emoji:"✨"}};function s(d="candy",u=50,h=50){const c=i[d];for(let p=0;p(d.lifetime+=16,d.x+=d.vx,d.y+=d.vy,d.vy+=.1,d.opacity=Math.max(0,1-d.lifetime/d.maxLifetime),d.rotation+=5,d.lifetime0&&requestAnimationFrame(a)}function o(d="candy",u,h){s(d,u,h),a()}function l(){O(r,[],!0)}var f={spawn:s,triggerSpawn:o,clear:l},v=Ys();return ur(v,21,()=>g(r),d=>d.id,(d,u)=>{var h=Gs(),c=A(h);{var p=w=>{var k=Ks();P(w,k)},m=w=>{var k=ee(),_=G(k);{var b=y=>{var E=Bs();P(y,E)},T=y=>{var E=ee(),$=G(E);{var K=te=>{var oe=Ws();P(te,oe)};X($,te=>{g(u).type==="spark"&&te(K)},!0)}P(y,E)};X(_,y=>{g(u).type==="blood"?y(b):y(T,!1)},!0)}P(w,k)};X(c,w=>{g(u).type==="candy"?w(p):w(m,!1)})}Ee(()=>{Le(h,1,`particle particle-${g(u).type??""}`,"svelte-1qiz201"),It(h,` + left: ${g(u).x??""}%; + top: ${g(u).y??""}%; + opacity: ${g(u).opacity??""}; + transform: scale(${g(u).scale??""}) rotateZ(${g(u).rotation??""}deg); + `)}),P(d,h)}),P(e,v),_e(f)}class Xs{sounds=new Map;masterVolume=1;load(t,r,n=1){try{if(this.sounds.has(t)){console.warn(`[SoundManager] Sound "${t}" already loaded, skipping`);return}const i=new Audio;i.src=r,i.preload="auto",i.volume=Math.min(1,Math.max(0,n*this.masterVolume)),i.addEventListener("loadedmetadata",()=>{const s=this.sounds.get(t);s&&(s.duration=i.duration*1e3)}),i.addEventListener("error",()=>{console.error(`[SoundManager] Failed to load sound: ${t} from ${r}`)}),this.sounds.set(t,{element:i,volume:n,duration:0})}catch(i){console.error(`[SoundManager] Error loading sound "${t}":`,i)}}play(t){const r=this.sounds.get(t);if(!r){console.warn(`[SoundManager] Sound not loaded: ${t}`);return}try{r.element.currentTime=0,r.element.play().catch(n=>{console.warn(`[SoundManager] Failed to play "${t}":`,n)})}catch(n){console.error(`[SoundManager] Error playing sound "${t}":`,n)}}stop(t){const r=this.sounds.get(t);if(!r){console.warn(`[SoundManager] Sound not loaded: ${t}`);return}try{r.element.pause(),r.element.currentTime=0}catch(n){console.error(`[SoundManager] Error stopping sound "${t}":`,n)}}stopAll(){this.sounds.forEach(t=>{try{t.element.pause(),t.element.currentTime=0}catch(r){console.error("[SoundManager] Error stopping all sounds:",r)}})}setVolume(t,r){const n=this.sounds.get(t);if(!n){console.warn(`[SoundManager] Sound not loaded: ${t}`);return}const i=Math.min(1,Math.max(0,r));n.volume=i,n.element.volume=i*this.masterVolume}setMasterVolume(t){this.masterVolume=Math.min(1,Math.max(0,t)),this.sounds.forEach(r=>{r.element.volume=r.volume*this.masterVolume})}getMasterVolume(){return this.masterVolume}isPlaying(t){const r=this.sounds.get(t);return r?!r.element.paused&&!r.element.ended:!1}getDuration(t){return this.sounds.get(t)?.duration||0}getCurrentTime(t){const r=this.sounds.get(t);return r?r.element.currentTime*1e3:0}setCurrentTime(t,r){const n=this.sounds.get(t);if(!n){console.warn(`[SoundManager] Sound not loaded: ${t}`);return}n.element.currentTime=r/1e3}unload(t){const r=this.sounds.get(t);if(r){try{r.element.pause(),r.element.src=""}catch(n){console.error(`[SoundManager] Error unloading sound "${t}":`,n)}this.sounds.delete(t)}}unloadAll(){this.sounds.forEach(t=>{try{t.element.pause(),t.element.src=""}catch(r){console.error("[SoundManager] Error unloading sounds:",r)}}),this.sounds.clear()}getLoadedSounds(){return Array.from(this.sounds.keys())}}const dt=new Xs;function Qs(){try{dt.load("trick-success","./assets/trick-success.mp3",.8),dt.load("trick-trick","./assets/trick-trick.mp3",.7)}catch(e){console.error("[SoundManager] Error during initialization:",e)}}var N=An(()=>ws),B=An(()=>ys),Zs=j(" ",1);function ea(e,t){he(t,!1);let r=Br();function n(u){const h=document.documentElement;u.primary&&h.style.setProperty("--primary-color",u.primary),u.secondary&&h.style.setProperty("--secondary-color",u.secondary),u.background&&h.style.setProperty("--background-color",u.background),u.accent&&h.style.setProperty("--accent-color",u.accent),u.logoUrl&&h.style.setProperty("--logo-url",u.logoUrl)}cr(async()=>{try{const u=await xn("ready");n(u),Qs()}catch(u){console.error("[ESX Halloween] Failed to fetch theme colors:",u)}}),ge("showNotification",u=>{Pn.show(u)}),ge("showGhostChoice",u=>{N().showChoice(u?.forced||!1)}),ge("showGhostHUD",u=>{N().showHUD(u.maxDuration,u.exitKey,u.scareKey||"E")}),ge("hideGhostHUD",()=>{N().hideHUD()}),ge("triggerJumpscare",u=>{N().triggerJumpscare(u?.soundVolume||.8)}),ge("trickOrTreatRoundStart",u=>{B().startRound(u.totalHouses,u.timeRemaining),dt.play("trick-success")}),ge("trickOrTreatRoundEnd",u=>{B().endRound()}),ge("trickOrTreatProgress",u=>{B().updateProgress(u.currentHouses,u.timeRemaining)}),ge("trickOrTreatCollect",u=>{B().showReward(u.rewardType,u.rewardItem,u.rewardAmount),u.rewardType==="trick"?dt.play("trick-trick"):dt.play("trick-success")}),Sn();var i=Zs(),s=G(i);Ss(s,{});var a=x(s,2);Ns(a,{get visible(){return N().state.choiceVisible},set visible(u){N(N().state.choiceVisible=u)},get forced(){return N().state.forced},set forced(u){N(N().state.forced=u)},$$legacy:!0});var o=x(a,2);Cs(o,{get visible(){return N().state.hudVisible},set visible(u){N(N().state.hudVisible=u)},get maxDuration(){return N().state.maxDuration},set maxDuration(u){N(N().state.maxDuration=u)},get exitKey(){return N().state.exitKey},set exitKey(u){N(N().state.exitKey=u)},get scareKey(){return N().state.scareKey},set scareKey(u){N(N().state.scareKey=u)},$$legacy:!0});var l=x(o,2);Is(l,{get visible(){return N().state.jumpscareVisible},set visible(u){N(N().state.jumpscareVisible=u)},get soundVolume(){return N().state.soundVolume},set soundVolume(u){N(N().state.soundVolume=u)},$$legacy:!0});var f=x(l,2);js(f,{get visible(){return B().state.hudVisible},get current(){return B().state.currentHouses},get total(){return B().state.totalHouses},get timeRemaining(){return B().state.timeRemaining}});var v=x(f,2);Us(v,{get rewardType(){return B().state.rewardType},get item(){return B().state.rewardItem},get amount(){return B().state.rewardAmount},get visible(){return B().state.rewardVisible},set visible(u){B(B().state.rewardVisible=u)},$$legacy:!0});var d=x(v,2);us(Js(d,{$$legacy:!0}),u=>O(r,u),()=>g(r)),P(e,i),_e()}Gi(ea,{target:document.getElementById("app")}); diff --git a/[esx_addons]/esx_halloween/web/build/assets/index.yHa2iqmf.js b/[esx_addons]/esx_halloween/web/build/assets/index.yHa2iqmf.js new file mode 100644 index 00000000..eb575c12 --- /dev/null +++ b/[esx_addons]/esx_halloween/web/build/assets/index.yHa2iqmf.js @@ -0,0 +1,2 @@ +(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const i of document.querySelectorAll('link[rel="modulepreload"]'))r(i);new MutationObserver(i=>{for(const s of i)if(s.type==="childList")for(const a of s.addedNodes)a.tagName==="LINK"&&a.rel==="modulepreload"&&r(a)}).observe(document,{childList:!0,subtree:!0});function n(i){const s={};return i.integrity&&(s.integrity=i.integrity),i.referrerPolicy&&(s.referrerPolicy=i.referrerPolicy),i.crossOrigin==="use-credentials"?s.credentials="include":i.crossOrigin==="anonymous"?s.credentials="omit":s.credentials="same-origin",s}function r(i){if(i.ep)return;i.ep=!0;const s=n(i);fetch(i.href,s)}})();const St=!1;var Nt=Array.isArray,pn=Array.prototype.indexOf,mn=Array.from,et=Object.defineProperty,me=Object.getOwnPropertyDescriptor,gn=Object.getOwnPropertyDescriptors,wn=Object.prototype,yn=Array.prototype,Pt=Object.getPrototypeOf,pt=Object.isExtensible;function bn(e){return e()}function tt(e){for(var t=0;t{e=r,t=i});return{promise:n,resolve:e,reject:t}}const O=2,Ot=4,Ye=8,fe=16,Z=32,ce=64,Je=128,L=256,Ke=512,P=1024,F=2048,se=4096,B=8192,W=16384,ft=32768,$e=65536,mt=1<<17,En=1<<18,xe=1<<19,At=1<<20,nt=1<<21,ct=1<<22,le=1<<23,ge=Symbol("$state"),xn=Symbol("legacy props"),_e=new class extends Error{name="StaleReactionError";message="The reaction that called `getAbortSignal()` was re-run or destroyed"};function Ct(e){throw new Error("https://svelte.dev/e/lifecycle_outside_component")}function Tn(){throw new Error("https://svelte.dev/e/async_derived_orphan")}function kn(e){throw new Error("https://svelte.dev/e/effect_in_teardown")}function Sn(){throw new Error("https://svelte.dev/e/effect_in_unowned_derived")}function Nn(e){throw new Error("https://svelte.dev/e/effect_orphan")}function Pn(){throw new Error("https://svelte.dev/e/effect_update_depth_exceeded")}function Dn(e){throw new Error("https://svelte.dev/e/props_invalid_value")}function On(){throw new Error("https://svelte.dev/e/state_descriptors_fixed")}function An(){throw new Error("https://svelte.dev/e/state_prototype_fixed")}function Cn(){throw new Error("https://svelte.dev/e/state_unsafe_mutation")}function Rn(){throw new Error("https://svelte.dev/e/svelte_boundary_reset_onerror")}const In=1,jn=2,Fn=4,Mn=8,Vn=16,Ln=1,qn=2,S=Symbol();function Kn(){console.warn("https://svelte.dev/e/svelte_boundary_reset_noop")}function Rt(e){return e===this.v}function Un(e,t){return e!=e?t==t:e!==t||e!==null&&typeof e=="object"||typeof e=="function"}function zn(e){return!Un(e,this.v)}let Ae=!1,Bn=!1;function Gn(){Ae=!0}let x=null;function be(e){x=e}function Te(e,t=!1,n){x={p:x,c:null,e:null,s:e,x:null,l:Ae&&!t?{s:null,u:null,$:[]}:null}}function ke(e){var t=x,n=t.e;if(n!==null){t.e=null;for(var r of n)Yt(r)}return x=t.p,{}}function Ce(){return!Ae||x!==null&&x.l===null}let pe=[];function Hn(){var e=pe;pe=[],tt(e)}function vt(e){if(pe.length===0){var t=pe;queueMicrotask(()=>{t===pe&&Hn()})}pe.push(e)}const Yn=new WeakMap;function It(e){var t=p;if(t===null)return _.f|=le,e;if((t.f&ft)===0){if((t.f&Je)===0)throw!t.parent&&e instanceof Error&&jt(e),e;t.b.error(e)}else Ee(e,t)}function Ee(e,t){for(;t!==null;){if((t.f&Je)!==0)try{t.b.error(e);return}catch(n){e=n}t=t.parent}throw e instanceof Error&&jt(e),e}function jt(e){const t=Yn.get(e);t&&(et(e,"message",{value:t.message}),et(e,"stack",{value:t.stack}))}const Ve=new Set;let E=null,K=null,rt=new Set,ne=[],dt=null,it=!1;class X{committed=!1;current=new Map;#e=new Map;#t=new Set;#s=0;#n=0;#o=null;#a=[];#l=[];skipped_effects=new Set;process(t){ne=[],this.apply();var n={parent:null,effect:null,effects:[],render_effects:[],block_effects:[]};for(const r of t)this.#r(r,n);this.#u(),this.#n>0?(this.#i(n.effects),this.#i(n.render_effects),this.#i(n.block_effects)):(E=null,gt(n.render_effects),gt(n.effects)),K=null}#r(t,n){t.f^=P;for(var r=t.first;r!==null;){var i=r.f,s=(i&(Z|ce))!==0,a=s&&(i&P)!==0,o=a||(i&B)!==0||this.skipped_effects.has(r);if((r.f&Je)!==0&&r.b?.is_pending()&&(n={parent:n,effect:r,effects:[],render_effects:[],block_effects:[]}),!o&&r.fn!==null){s?r.f^=P:(i&Ot)!==0?n.effects.push(r):Qe(r)&&((r.f&fe)!==0&&n.block_effects.push(r),He(r));var l=r.first;if(l!==null){r=l;continue}}var u=r.parent;for(r=r.next;r===null&&u!==null;)u===n.effect&&(this.#i(n.effects),this.#i(n.render_effects),this.#i(n.block_effects),n=n.parent),r=u.next,u=u.parent}}#i(t){for(const n of t)((n.f&F)!==0?this.#a:this.#l).push(n),D(n,P)}capture(t,n){this.#e.has(t)||this.#e.set(t,n),this.current.set(t,t.v),K?.set(t,t.v)}activate(){E=this}deactivate(){E=null,K=null}flush(){if(ne.length>0){if(this.activate(),Jn(),E!==null&&E!==this)return}else this.#u();this.deactivate();for(const t of rt)if(rt.delete(t),t(),E!==null)break}#u(){if(this.#n===0){for(const t of this.#t)t();this.#t.clear()}this.#s===0&&this.#f()}#f(){if(Ve.size>1){this.#e.clear();var t=K,n=!0,r={parent:null,effect:null,effects:[],render_effects:[],block_effects:[]};for(const i of Ve){if(i===this){n=!1;continue}const s=[];for(const[o,l]of this.current){if(i.current.has(o))if(n&&l!==i.current.get(o))i.current.set(o,l);else continue;s.push(o)}if(s.length===0)continue;const a=[...i.current.keys()].filter(o=>!this.current.has(o));if(a.length>0){for(const o of s)Ft(o,a);if(ne.length>0){E=i,i.apply();for(const o of ne)i.#r(o,r);ne=[],i.deactivate()}}}E=null,K=t}this.committed=!0,Ve.delete(this),this.#o?.resolve()}increment(t){this.#s+=1,t&&(this.#n+=1)}decrement(t){this.#s-=1,t&&(this.#n-=1);for(const n of this.#a)D(n,F),oe(n);for(const n of this.#l)D(n,se),oe(n);this.#a=[],this.#l=[],this.flush()}add_callback(t){this.#t.add(t)}settled(){return(this.#o??=Dt()).promise}static ensure(){if(E===null){const t=E=new X;Ve.add(E),X.enqueue(()=>{E===t&&t.flush()})}return E}static enqueue(t){vt(t)}apply(){}}function Jn(){var e=we;it=!0;try{var t=0;for(yt(!0);ne.length>0;){var n=X.ensure();if(t++>1e3){var r,i;$n()}n.process(ne),ie.clear()}}finally{it=!1,yt(e),dt=null}}function $n(){try{Pn()}catch(e){Ee(e,dt)}}let H=null;function gt(e){var t=e.length;if(t!==0){for(var n=0;n0)){ie.clear();for(const i of H){if((i.f&(W|B))!==0)continue;const s=[i];let a=i.parent;for(;a!==null;)H.has(a)&&(H.delete(a),s.push(a)),a=a.parent;for(let o=s.length-1;o>=0;o--){const l=s[o];(l.f&(W|B))===0&&He(l)}}H.clear()}}H=null}}function Ft(e,t){if(e.reactions!==null)for(const n of e.reactions){const r=n.f;(r&O)!==0?Ft(n,t):(r&(ct|fe))!==0&&Mt(n,t)&&(D(n,F),oe(n))}}function Mt(e,t){if(e.deps!==null){for(const n of e.deps)if(t.includes(n)||(n.f&O)!==0&&Mt(n,t))return!0}return!1}function oe(e){for(var t=dt=e;t.parent!==null;){t=t.parent;var n=t.f;if(it&&t===p&&(n&fe)!==0)return;if((n&(ce|Z))!==0){if((n&P)===0)return;t.f^=P}}ne.push(t)}function Wn(e){let t=0,n=Ie(0),r;return()=>{ur()&&(m(n),hr(()=>(t===0&&(r=Fe(()=>e(()=>Ne(n)))),t+=1,()=>{vt(()=>{t-=1,t===0&&(r?.(),r=void 0,Ne(n))})})))}}var Xn=$e|xe|Je;function Qn(e,t,n){new Zn(e,t,n)}class Zn{parent;#e=!1;#t;#s=null;#n;#o;#a;#l=null;#r=null;#i=null;#u=null;#f=0;#c=0;#d=!1;#v=null;#m=()=>{this.#v&&Ue(this.#v,this.#f)};#g=Wn(()=>(this.#v=Ie(this.#f),()=>{this.#v=null}));constructor(t,n,r){this.#t=t,this.#n=n,this.#o=r,this.parent=p.b,this.#e=!!this.#n.pending,this.#a=Jt(()=>{p.b=this;{try{this.#l=Y(()=>r(this.#t))}catch(i){this.error(i)}this.#c>0?this.#_():this.#e=!1}},Xn)}#w(){try{this.#l=Y(()=>this.#o(this.#t))}catch(t){this.error(t)}this.#e=!1}#y(){const t=this.#n.pending;t&&(this.#r=Y(()=>t(this.#t)),X.enqueue(()=>{this.#l=this.#h(()=>(X.ensure(),Y(()=>this.#o(this.#t)))),this.#c>0?this.#_():(Pe(this.#r,()=>{this.#r=null}),this.#e=!1)}))}is_pending(){return this.#e||!!this.parent&&this.parent.is_pending()}has_pending_snippet(){return!!this.#n.pending}#h(t){var n=p,r=_,i=x;G(this.#a),I(this.#a),be(this.#a.ctx);try{return t()}catch(s){return It(s),null}finally{G(n),I(r),be(i)}}#_(){const t=this.#n.pending;this.#l!==null&&(this.#u=document.createDocumentFragment(),en(this.#l,this.#u)),this.#r===null&&(this.#r=Y(()=>t(this.#t)))}#p(t){if(!this.has_pending_snippet()){this.parent&&this.parent.#p(t);return}this.#c+=t,this.#c===0&&(this.#e=!1,this.#r&&Pe(this.#r,()=>{this.#r=null}),this.#u&&(this.#t.before(this.#u),this.#u=null))}update_pending_count(t){this.#p(t),this.#f+=t,rt.add(this.#m)}get_effect_pending(){return this.#g(),m(this.#v)}error(t){var n=this.#n.onerror;let r=this.#n.failed;if(this.#d||!n&&!r)throw t;this.#l&&(q(this.#l),this.#l=null),this.#r&&(q(this.#r),this.#r=null),this.#i&&(q(this.#i),this.#i=null);var i=!1,s=!1;const a=()=>{if(i){Kn();return}i=!0,s&&Rn(),X.ensure(),this.#f=0,this.#i!==null&&Pe(this.#i,()=>{this.#i=null}),this.#e=this.has_pending_snippet(),this.#l=this.#h(()=>(this.#d=!1,Y(()=>this.#o(this.#t)))),this.#c>0?this.#_():this.#e=!1};var o=_;try{I(null),s=!0,n?.(t,a),s=!1}catch(l){Ee(l,this.#a&&this.#a.parent)}finally{I(o)}r&&vt(()=>{this.#i=this.#h(()=>{this.#d=!0;try{return Y(()=>{r(this.#t,()=>t,()=>a)})}catch(l){return Ee(l,this.#a.parent),null}finally{this.#d=!1}})})}}function er(e,t,n){const r=Ce()?Re:Vt;if(t.length===0){n(e.map(r));return}var i=E,s=p,a=tr();Promise.all(t.map(o=>nr(o))).then(o=>{a();try{n([...e.map(r),...o])}catch(l){(s.f&W)===0&&Ee(l,s)}i?.deactivate(),st()}).catch(o=>{Ee(o,s)})}function tr(){var e=p,t=_,n=x,r=E;return function(){G(e),I(t),be(n),r?.activate()}}function st(){G(null),I(null),be(null)}function Re(e){var t=O|F,n=_!==null&&(_.f&O)!==0?_:null;return p===null||n!==null&&(n.f&L)!==0?t|=L:p.f|=xe,{ctx:x,deps:null,effects:null,equals:Rt,f:t,fn:e,reactions:null,rv:0,v:S,wv:0,parent:n??p,ac:null}}function nr(e,t){let n=p;n===null&&Tn();var r=n.b,i=void 0,s=Ie(S),a=!_,o=new Map;return dr(()=>{var l=Dt();i=l.promise;try{Promise.resolve(e()).then(l.resolve,l.reject).then(()=>{u===E&&u.committed&&u.deactivate(),st()})}catch(c){l.reject(c),st()}var u=E;if(a){var f=!r.is_pending();r.update_pending_count(1),u.increment(f),o.get(u)?.reject(_e),o.delete(u),o.set(u,l)}const d=(c,v=void 0)=>{if(u.activate(),v)v!==_e&&(s.f|=le,Ue(s,v));else{(s.f&le)!==0&&(s.f^=le),Ue(s,c);for(const[h,b]of o){if(o.delete(h),h===u)break;b.reject(_e)}}a&&(r.update_pending_count(-1),u.decrement(f))};l.promise.then(d,c=>d(null,c||"unknown"))}),fr(()=>{for(const l of o.values())l.reject(_e)}),new Promise(l=>{function u(f){function d(){f===i?l(s):u(i)}f.then(d,d)}u(i)})}function rr(e){const t=Re(e);return tn(t),t}function Vt(e){const t=Re(e);return t.equals=zn,t}function Lt(e){var t=e.effects;if(t!==null){e.effects=null;for(var n=0;n{if(ae===s)return o();var l=_,u=ae;I(null),Et(s);var f=o();return I(l),Et(u),f};return r&&n.set("length",C(e.length)),new Proxy(e,{defineProperty(o,l,u){(!("value"in u)||u.configurable===!1||u.enumerable===!1||u.writable===!1)&&On();var f=n.get(l);return f===void 0?f=a(()=>{var d=C(u.value);return n.set(l,d),d}):y(f,u.value,!0),!0},deleteProperty(o,l){var u=n.get(l);if(u===void 0){if(l in o){const f=a(()=>C(S));n.set(l,f),Ne(i)}}else y(u,S),Ne(i);return!0},get(o,l,u){if(l===ge)return e;var f=n.get(l),d=l in o;if(f===void 0&&(!d||me(o,l)?.writable)&&(f=a(()=>{var v=J(d?o[l]:S),h=C(v);return h}),n.set(l,f)),f!==void 0){var c=m(f);return c===S?void 0:c}return Reflect.get(o,l,u)},getOwnPropertyDescriptor(o,l){var u=Reflect.getOwnPropertyDescriptor(o,l);if(u&&"value"in u){var f=n.get(l);f&&(u.value=m(f))}else if(u===void 0){var d=n.get(l),c=d?.v;if(d!==void 0&&c!==S)return{enumerable:!0,configurable:!0,value:c,writable:!0}}return u},has(o,l){if(l===ge)return!0;var u=n.get(l),f=u!==void 0&&u.v!==S||Reflect.has(o,l);if(u!==void 0||p!==null&&(!f||me(o,l)?.writable)){u===void 0&&(u=a(()=>{var c=f?J(o[l]):S,v=C(c);return v}),n.set(l,u));var d=m(u);if(d===S)return!1}return f},set(o,l,u,f){var d=n.get(l),c=l in o;if(r&&l==="length")for(var v=u;vC(S)),n.set(v+"",h))}if(d===void 0)(!c||me(o,l)?.writable)&&(d=a(()=>C(void 0)),y(d,J(u)),n.set(l,d));else{c=d.v!==S;var b=a(()=>J(u));y(d,b)}var M=Reflect.getOwnPropertyDescriptor(o,l);if(M?.set&&M.set.call(f,u),!c){if(r&&typeof l=="string"){var A=n.get("length"),k=Number(l);Number.isInteger(k)&&k>=A.v&&y(A,k+1)}Ne(i)}return!0},ownKeys(o){m(i);var l=Reflect.ownKeys(o).filter(d=>{var c=n.get(d);return c===void 0||c.v!==S});for(var[u,f]of n)f.v!==S&&!(u in o)&&l.push(u);return l},setPrototypeOf(){An()}})}var wt,Ut,zt,Bt;function lr(){if(wt===void 0){wt=window,Ut=/Firefox/.test(navigator.userAgent);var e=Element.prototype,t=Node.prototype,n=Text.prototype;zt=me(t,"firstChild").get,Bt=me(t,"nextSibling").get,pt(e)&&(e.__click=void 0,e.__className=void 0,e.__attributes=null,e.__style=void 0,e.__e=void 0),pt(n)&&(n.__t=void 0)}}function ze(e=""){return document.createTextNode(e)}function Be(e){return zt.call(e)}function We(e){return Bt.call(e)}function T(e,t){return Be(e)}function je(e,t=!1){{var n=Be(e);return n instanceof Comment&&n.data===""?We(n):n}}function R(e,t=1,n=!1){let r=e;for(;t--;)r=We(r);return r}function ar(){return!1}function Gt(e){var t=_,n=p;I(null),G(null);try{return e()}finally{I(t),G(n)}}function Ht(e){p===null&&_===null&&Nn(),_!==null&&(_.f&L)!==0&&p===null&&Sn(),ve&&kn()}function or(e,t){var n=t.last;n===null?t.last=t.first=e:(n.next=e,e.prev=n,t.last=e)}function ee(e,t,n,r=!0){var i=p;i!==null&&(i.f&B)!==0&&(e|=B);var s={ctx:x,deps:null,nodes_start:null,nodes_end:null,f:e|F,first:null,fn:t,last:null,next:null,parent:i,b:i&&i.b,prev:null,teardown:null,transitions:null,wv:0,ac:null};if(n)try{He(s),s.f|=ft}catch(l){throw q(s),l}else t!==null&&oe(s);if(r){var a=s;if(n&&a.deps===null&&a.teardown===null&&a.nodes_start===null&&a.first===a.last&&(a.f&xe)===0&&(a=a.first),a!==null&&(a.parent=i,i!==null&&or(a,i),_!==null&&(_.f&O)!==0&&(e&ce)===0)){var o=_;(o.effects??=[]).push(a)}}return s}function ur(){return _!==null&&!U}function fr(e){const t=ee(Ye,null,!1);return D(t,P),t.teardown=e,t}function ue(e){Ht();var t=p.f,n=!_&&(t&Z)!==0&&(t&ft)===0;if(n){var r=x;(r.e??=[]).push(e)}else return Yt(e)}function Yt(e){return ee(Ot|At,e,!1)}function cr(e){return Ht(),ee(Ye|At,e,!0)}function vr(e){X.ensure();const t=ee(ce|xe,e,!0);return(n={})=>new Promise(r=>{n.outro?Pe(t,()=>{q(t),r(void 0)}):(q(t),r(void 0))})}function dr(e){return ee(ct|xe,e,!0)}function hr(e,t=0){return ee(Ye|t,e,!0)}function Xe(e,t=[],n=[]){er(t,n,r=>{ee(Ye,()=>e(...r.map(m)),!0)})}function Jt(e,t=0){var n=ee(fe|t,e,!0);return n}function Y(e,t=!0){return ee(Z|xe,e,!0,t)}function $t(e){var t=e.teardown;if(t!==null){const n=ve,r=_;bt(!0),I(null);try{t.call(null)}finally{bt(n),I(r)}}}function Wt(e,t=!1){var n=e.first;for(e.first=e.last=null;n!==null;){const i=n.ac;i!==null&&Gt(()=>{i.abort(_e)});var r=n.next;(n.f&ce)!==0?n.parent=null:q(n,t),n=r}}function _r(e){for(var t=e.first;t!==null;){var n=t.next;(t.f&Z)===0&&q(t),t=n}}function q(e,t=!0){var n=!1;(t||(e.f&En)!==0)&&e.nodes_start!==null&&e.nodes_end!==null&&(pr(e.nodes_start,e.nodes_end),n=!0),Wt(e,t&&!n),Ge(e,0),D(e,W);var r=e.transitions;if(r!==null)for(const s of r)s.stop();$t(e);var i=e.parent;i!==null&&i.first!==null&&Xt(e),e.next=e.prev=e.teardown=e.ctx=e.deps=e.fn=e.nodes_start=e.nodes_end=e.ac=null}function pr(e,t){for(;e!==null;){var n=e===t?null:We(e);e.remove(),e=n}}function Xt(e){var t=e.parent,n=e.prev,r=e.next;n!==null&&(n.next=r),r!==null&&(r.prev=n),t!==null&&(t.first===e&&(t.first=r),t.last===e&&(t.last=n))}function Pe(e,t,n=!0){var r=[];Qt(e,r,!0),mr(r,()=>{n&&q(e),t&&t()})}function mr(e,t){var n=e.length;if(n>0){var r=()=>--n||t();for(var i of e)i.out(r)}else t()}function Qt(e,t,n){if((e.f&B)===0){if(e.f^=B,e.transitions!==null)for(const a of e.transitions)(a.is_global||n)&&t.push(a);for(var r=e.first;r!==null;){var i=r.next,s=(r.f&$e)!==0||(r.f&Z)!==0;Qt(r,t,s?n:!1),r=i}}}function gr(e){Zt(e,!0)}function Zt(e,t){if((e.f&B)!==0){e.f^=B,(e.f&P)===0&&(D(e,F),oe(e));for(var n=e.first;n!==null;){var r=n.next,i=(n.f&$e)!==0||(n.f&Z)!==0;Zt(n,i?t:!1),n=r}if(e.transitions!==null)for(const s of e.transitions)(s.is_global||t)&&s.in()}}function en(e,t){for(var n=e.nodes_start,r=e.nodes_end;n!==null;){var i=n===r?null:We(n);t.append(n),n=i}}let we=!1;function yt(e){we=e}let ve=!1;function bt(e){ve=e}let _=null,U=!1;function I(e){_=e}let p=null;function G(e){p=e}let Q=null;function tn(e){_!==null&&(Q===null?Q=[e]:Q.push(e))}let N=null,j=0,V=null;function wr(e){V=e}let nn=1,De=0,ae=De;function Et(e){ae=e}let re=!1;function rn(){return++nn}function Qe(e){var t=e.f;if((t&F)!==0)return!0;if((t&se)!==0){var n=e.deps,r=(t&L)!==0;if(n!==null){var i,s,a=(t&Ke)!==0,o=r&&p!==null&&!re,l=n.length;if((a||o)&&(p===null||(p.f&W)===0)){var u=e,f=u.parent;for(i=0;ie.wv)return!0}(!r||p!==null&&!re)&&D(e,P)}return!1}function sn(e,t,n=!0){var r=e.reactions;if(r!==null&&!Q?.includes(e))for(var i=0;i{e.ac.abort(_e)}),e.ac=null);try{e.f|=nt;var d=e.fn,c=d(),v=e.deps;if(N!==null){var h;if(Ge(e,j),v!==null&&j>0)for(v.length=j+N.length,h=0;h{throw k});throw c}}finally{e.__root=t,delete e.currentTarget,I(f),G(d)}}}function Sr(e){var t=document.createElement("template");return t.innerHTML=e.replaceAll("",""),t.content}function ot(e,t){var n=p;n.nodes_start===null&&(n.nodes_start=e,n.nodes_end=t)}function de(e,t){var n=(t&Ln)!==0,r=(t&qn)!==0,i,s=!e.startsWith("");return()=>{i===void 0&&(i=Sr(s?e:""+e),n||(i=Be(i)));var a=r||Ut?document.importNode(i,!0):i.cloneNode(!0);if(n){var o=Be(a),l=a.lastChild;ot(o,l)}else ot(a,a);return a}}function Ze(){var e=document.createDocumentFragment(),t=document.createComment(""),n=ze();return e.append(t,n),ot(t,n),e}function z(e,t){e!==null&&e.before(t)}function ye(e,t){var n=t==null?"":typeof t=="object"?t+"":t;n!==(e.__t??=e.nodeValue)&&(e.__t=n,e.nodeValue=n+"")}function Nr(e,t){return Pr(e,t)}const he=new Map;function Pr(e,{target:t,anchor:n,props:r={},events:i,context:s,intro:a=!0}){lr();var o=new Set,l=d=>{for(var c=0;c{var d=n??t.appendChild(ze());return Qn(d,{pending:()=>{}},c=>{if(s){Te({});var v=x;v.c=s}i&&(r.$$events=i),u=e(c,r)||{},s&&ke()}),()=>{for(var c of o){t.removeEventListener(c,Le);var v=he.get(c);--v===0?(document.removeEventListener(c,Le),he.delete(c)):he.set(c,v)}at.delete(l),d!==n&&d.parentNode?.removeChild(d)}});return Dr.set(u,f),u}let Dr=new WeakMap;class Or{anchor;#e=new Map;#t=new Map;#s=new Map;#n=!0;constructor(t,n=!0){this.anchor=t,this.#n=n}#o=()=>{var t=E;if(this.#e.has(t)){var n=this.#e.get(t),r=this.#t.get(n);if(r)gr(r);else{var i=this.#s.get(n);i&&(this.#t.set(n,i.effect),this.#s.delete(n),i.fragment.lastChild.remove(),this.anchor.before(i.fragment),r=i.effect)}for(const[s,a]of this.#e){if(this.#e.delete(s),s===t)break;const o=this.#s.get(a);o&&(q(o.effect),this.#s.delete(a))}for(const[s,a]of this.#t){if(s===n)continue;const o=()=>{if(Array.from(this.#e.values()).includes(s)){var u=document.createDocumentFragment();en(a,u),u.append(ze()),this.#s.set(s,{effect:a,fragment:u})}else q(a);this.#t.delete(s)};this.#n||!r?Pe(a,o,!1):o()}}};ensure(t,n){var r=E,i=ar();if(n&&!this.#t.has(t)&&!this.#s.has(t))if(i){var s=document.createDocumentFragment(),a=ze();s.append(a),this.#s.set(t,{effect:Y(()=>n(a)),fragment:s})}else this.#t.set(t,Y(()=>n(this.anchor)));if(this.#e.set(r,t),i){for(const[o,l]of this.#t)o===t?r.skipped_effects.delete(l):r.skipped_effects.add(l);for(const[o,l]of this.#s)o===t?r.skipped_effects.delete(l.effect):r.skipped_effects.add(l.effect);r.add_callback(this.#o)}else this.#o()}}function Oe(e,t,n=!1){var r=new Or(e),i=n?$e:0;function s(a,o){r.ensure(a,o)}Jt(()=>{var a=!1;t((o,l=!0)=>{a=!0,s(l,o)}),a||s(!1,null)},i)}const Tt=[...` +\r\f \v\uFEFF`];function Ar(e,t,n){var r=e==null?"":""+e;if(t&&(r=r?r+" "+t:t),n){for(var i in n)if(n[i])r=r?r+" "+i:i;else if(r.length)for(var s=i.length,a=0;(a=r.indexOf(i,a))>=0;){var o=a+s;(a===0||Tt.includes(r[a-1]))&&(o===r.length||Tt.includes(r[o]))?r=(a===0?"":r.substring(0,a))+r.substring(o+1):a=o}}return r===""?null:r}function ut(e,t,n,r,i,s){var a=e.__className;if(a!==n||a===void 0){var o=Ar(n,r,s);o==null?e.removeAttribute("class"):e.className=o,e.__className=n}else if(s&&i!==s)for(var l in s){var u=!!s[l];(i==null||u!==!!i[l])&&e.classList.toggle(l,u)}return s}function Cr(e=!1){const t=x,n=t.l.u;if(!n)return;let r=()=>Er(t.s);if(e){let i=0,s={};const a=Re(()=>{let o=!1;const l=t.s;for(const u in l)l[u]!==s[u]&&(s[u]=l[u],o=!0);return o&&i++,i});r=()=>m(a)}n.b.length&&cr(()=>{kt(t,r),tt(n.b)}),ue(()=>{const i=Fe(()=>n.m.map(bn));return()=>{for(const s of i)typeof s=="function"&&s()}}),n.a.length&&ue(()=>{kt(t,r),tt(n.a)})}function kt(e,t){if(e.l.s)for(const n of e.l.s)m(n);t()}function Rr(e){var t=Ie(0);return function(){return arguments.length===1?(y(t,m(t)+1),arguments[0]):(m(t),e())}}let qe=!1;function Ir(e){var t=qe;try{return qe=!1,[e(),qe]}finally{qe=t}}function $(e,t,n,r){var i=!Ae||(n&jn)!==0,s=(n&Mn)!==0,a=(n&Vn)!==0,o=r,l=!0,u=()=>(l&&(l=!1,o=a?Fe(r):r),o),f;if(s){var d=ge in e||xn in e;f=me(e,t)?.set??(d&&t in e?w=>e[t]=w:void 0)}var c,v=!1;s?[c,v]=Ir(()=>e[t]):c=e[t],c===void 0&&r!==void 0&&(c=u(),f&&(i&&Dn(),f(c)));var h;if(i?h=()=>{var w=e[t];return w===void 0?u():(l=!0,w)}:h=()=>{var w=e[t];return w!==void 0&&(o=void 0),w===void 0?o:w},i&&(n&Fn)===0)return h;if(f){var b=e.$$legacy;return(function(w,te){return arguments.length>0?((!i||!te||b||v)&&f(te?h():w),w):h()})}var M=!1,A=((n&In)!==0?Re:Vt)(()=>(M=!1,h()));s&&m(A);var k=p;return(function(w,te){if(arguments.length>0){const Me=te?m(A):i&&s?J(w):w;return y(A,Me),M=!0,o!==void 0&&(o=Me),w}return ve&&M||(k.f&W)!==0?A.v:m(A)})}function _t(e){x===null&&Ct(),Ae&&x.l!==null?Fr(x).m.push(e):ue(()=>{const t=Fe(e);if(typeof t=="function")return t})}function jr(e){x===null&&Ct(),_t(()=>()=>Fe(e))}function Fr(e){var t=e.l;return t.u??={a:[],b:[],m:[]}}const Mr="5";typeof window<"u"&&((window.__svelte??={}).v??=new Set).add(Mr);Gn();function Se(e,t){const n=r=>{const{type:i,...s}=r.data;i===e&&t(s)};_t(()=>{window.addEventListener("message",n)}),jr(()=>{window.removeEventListener("message",n)})}const un=()=>!window.invokeNative,Vr=()=>un()?"nui-frame-app":window.GetParentResourceName?window.GetParentResourceName():"unknown";async function fn(e,t={}){const n=Vr();if(un())return console.warn(`[fetchNui] Browser environment detected. Event: ${e}`,t),{};try{const r=await fetch(`https://${n}/${e}`,{method:"POST",headers:{"Content-Type":"application/json; charset=UTF-8"},body:JSON.stringify(t)});if(!r.ok)throw new Error(`HTTP error! status: ${r.status}`);const i=await r.json();if(!i.ok&&i.error)throw new Error(i.error);return i.data}catch(r){throw console.error(`[fetchNui] Error calling ${e}:`,r),r}}class Lr{#e=C(J([]));get queue(){return m(this.#e)}set queue(t){y(this.#e,t,!0)}#t=C(null);get current(){return m(this.#t)}set current(t){y(this.#t,t,!0)}dismissTimeout=null;idCounter=0;get currentNotification(){return this.current}get queueLength(){return this.queue.length}show(t){const n={...t,id:`notification-${++this.idCounter}`,duration:t.duration??5e3};this.queue.push(n),this.current||this.processQueue()}processQueue(){if(this.queue.length===0){this.current=null;return}this.current=this.queue.shift(),this.dismissTimeout&&clearTimeout(this.dismissTimeout),this.dismissTimeout=window.setTimeout(()=>{this.dismiss()},this.current.duration)}dismiss(){this.dismissTimeout&&(clearTimeout(this.dismissTimeout),this.dismissTimeout=null),this.current=null,setTimeout(()=>{this.processQueue()},300)}clearAll(){this.queue=[],this.dismissTimeout&&(clearTimeout(this.dismissTimeout),this.dismissTimeout=null),this.current=null}}const cn=new Lr;class qr{#e=C(J({choiceVisible:!1,hudVisible:!1,jumpscareVisible:!1,maxDuration:6e5,exitKey:"X",scareKey:"E",forced:!1,soundVolume:.8}));get state(){return m(this.#e)}set state(t){y(this.#e,t,!0)}showChoice(t=!1){this.state.choiceVisible=!0,this.state.forced=t}hideChoice(){this.state.choiceVisible=!1}showHUD(t,n="X",r="E"){this.state.hudVisible=!0,this.state.maxDuration=t,this.state.exitKey=n,this.state.scareKey=r}hideHUD(){this.state.hudVisible=!1}triggerJumpscare(t=.8){this.state.jumpscareVisible=!0,this.state.soundVolume=t}hideJumpscare(){this.state.jumpscareVisible=!1}}const Kr=new qr;var Ur=de('

');function zr(e,t){let n=$(t,"visible",3,!0);var r=Ur();let i;var s=R(T(r),2),a=R(s,2),o=T(a),l=T(o),u=R(o,2),f=T(u);Xe(d=>{i=ut(r,1,`notification notification--${t.size??""} notification--${t.position??""}`,"svelte-1wg8nyl",i,d),ut(s,1,`notification__pumpkin notification__pumpkin--${t.size??""}`,"svelte-1wg8nyl"),ye(l,t.header),ye(f,t.description)},[()=>({"notification--visible":n()})]),z(e,r)}var Br=de("
");function Gr(e,t){Te(t,!0);const n=rr(()=>cn.currentNotification);let r=C(!1);ue(()=>{m(n)?(y(r,!1),requestAnimationFrame(()=>{requestAnimationFrame(()=>{y(r,!0)})})):y(r,!1)});function i(l){return l||""}var s=Ze(),a=je(s);{var o=l=>{var u=Br(),f=T(u);zr(f,{get size(){return m(n).size},get position(){return m(n).position},get header(){return m(n).header},get description(){return m(n).description},get visible(){return m(r)}}),Xe(d=>ut(u,1,`notification-container notification-container--${d??""}`,"svelte-v226t6"),[()=>i(m(n).position)]),z(l,u)};Oe(a,l=>{m(n)&&l(o)})}z(e,s),ke()}var Hr=(e,t)=>t("ghost"),Yr=(e,t)=>t("normal"),Jr=de('
'),$r=de('
pumpkin

Spooky Opportunity!

You have a chance to respawn as a ghost and haunt the living...

');function Wr(e,t){Te(t,!0);let n=$(t,"visible",15,!1),r=$(t,"forced",11,!1),i=C(15),s=null;ue(()=>(s&&(clearInterval(s),s=null),n()&&!r()&&(y(i,15),s=window.setInterval(()=>{sr(i,-1),m(i)<=0&&(s&&(clearInterval(s),s=null),a("normal"))},1e3)),()=>{s&&(clearInterval(s),s=null)}));function a(f){s&&(clearInterval(s),s=null),fn("ghostChoice",{choice:f}),n(!1)}var o=Ze(),l=je(o);{var u=f=>{var d=$r(),c=T(d),v=R(T(c),6),h=T(v);h.__click=[Hr,a];var b=R(h,2);b.__click=[Yr,a];var M=R(v,2);{var A=k=>{var w=Jr(),te=T(w);Xe(()=>ye(te,`Auto-decline in ${m(i)??""}s`)),z(k,w)};Oe(M,k=>{r()||k(A)})}z(f,d)};Oe(l,f=>{n()&&f(u)})}z(e,o),ke()}kr(["click"]);var Xr=de('
👻
Ghost Mode
Scare
Press to exit
');function Qr(e,t){Te(t,!0);let n=$(t,"visible",15,!1),r=$(t,"maxDuration",11,6e5),i=$(t,"exitKey",11,"X"),s=$(t,"scareKey",11,"E"),a=C(J(r())),o=null;ue(()=>(n()&&(y(a,r()),o=window.setInterval(()=>{y(a,m(a)-1e3),m(a)<=0&&(y(a,0),n(!1))},1e3)),()=>{o&&(clearInterval(o),o=null)}));function l(c){const v=Math.floor(c/6e4),h=Math.floor(c%6e4/1e3);return`${v}:${h.toString().padStart(2,"0")}`}var u=Ze(),f=je(u);{var d=c=>{var v=Xr(),h=T(v),b=R(T(h),2),M=R(T(b),2),A=T(M),k=R(h,2),w=T(k),te=T(w),Me=T(te),vn=R(k,2),dn=R(T(vn)),hn=T(dn);Xe(_n=>{ye(A,_n),ye(Me,s()),ye(hn,i())},[()=>l(m(a))]),z(c,v)};Oe(f,c=>{n()&&c(d)})}z(e,u),ke()}var Zr=de('
scary
BOO!
');function ei(e,t){Te(t,!0);let n=$(t,"visible",15,!1),r=$(t,"soundVolume",11,.8),i=null,s=null;ue(()=>(n()&&(s=new Audio("./assets/scream.wav"),s.volume=r(),s.play().catch(u=>console.error("Jumpscare sound error:",u)),i=window.setTimeout(()=>{n(!1)},2e3)),()=>{i&&(clearTimeout(i),i=null),s&&(s.pause(),s=null)}));var a=Ze(),o=je(a);{var l=u=>{var f=Zr();z(u,f)};Oe(o,u=>{n()&&u(l)})}z(e,a),ke()}var g=Rr(()=>Kr),ti=de(" ",1);function ni(e,t){Te(t,!1);function n(l){const u=document.documentElement;l.primary&&u.style.setProperty("--primary-color",l.primary),l.secondary&&u.style.setProperty("--secondary-color",l.secondary),l.background&&u.style.setProperty("--background-color",l.background),l.accent&&u.style.setProperty("--accent-color",l.accent),l.logoUrl&&u.style.setProperty("--logo-url",l.logoUrl)}_t(async()=>{try{const l=await fn("ready");n(l)}catch(l){console.error("[ESX Halloween] Failed to fetch theme colors:",l)}}),Se("showNotification",l=>{cn.show(l)}),Se("showGhostChoice",l=>{g().showChoice(l?.forced||!1)}),Se("showGhostHUD",l=>{g().showHUD(l.maxDuration,l.exitKey,l.scareKey||"E")}),Se("hideGhostHUD",()=>{g().hideHUD()}),Se("triggerJumpscare",l=>{g().triggerJumpscare(l?.soundVolume||.8)}),Cr();var r=ti(),i=je(r);Gr(i,{});var s=R(i,2);Wr(s,{get visible(){return g().state.choiceVisible},set visible(l){g(g().state.choiceVisible=l)},get forced(){return g().state.forced},set forced(l){g(g().state.forced=l)},$$legacy:!0});var a=R(s,2);Qr(a,{get visible(){return g().state.hudVisible},set visible(l){g(g().state.hudVisible=l)},get maxDuration(){return g().state.maxDuration},set maxDuration(l){g(g().state.maxDuration=l)},get exitKey(){return g().state.exitKey},set exitKey(l){g(g().state.exitKey=l)},get scareKey(){return g().state.scareKey},set scareKey(l){g(g().state.scareKey=l)},$$legacy:!0});var o=R(a,2);ei(o,{get visible(){return g().state.jumpscareVisible},set visible(l){g(g().state.jumpscareVisible=l)},get soundVolume(){return g().state.soundVolume},set soundVolume(l){g(g().state.soundVolume=l)},$$legacy:!0}),z(e,r),ke()}Nr(ni,{target:document.getElementById("app")}); diff --git a/[esx_addons]/esx_halloween/web/build/assets/pumpkin.CPHPpIKO.webp b/[esx_addons]/esx_halloween/web/build/assets/pumpkin.CPHPpIKO.webp new file mode 100644 index 00000000..701fa954 Binary files /dev/null and b/[esx_addons]/esx_halloween/web/build/assets/pumpkin.CPHPpIKO.webp differ diff --git a/[esx_addons]/esx_halloween/web/build/assets/pumpkin.webp b/[esx_addons]/esx_halloween/web/build/assets/pumpkin.webp new file mode 100644 index 00000000..701fa954 Binary files /dev/null and b/[esx_addons]/esx_halloween/web/build/assets/pumpkin.webp differ diff --git a/[esx_addons]/esx_halloween/web/build/assets/scream.wav b/[esx_addons]/esx_halloween/web/build/assets/scream.wav new file mode 100644 index 00000000..2008b0db Binary files /dev/null and b/[esx_addons]/esx_halloween/web/build/assets/scream.wav differ diff --git a/[esx_addons]/esx_halloween/web/build/assets/trick-success.mp3 b/[esx_addons]/esx_halloween/web/build/assets/trick-success.mp3 new file mode 100644 index 00000000..b934f400 Binary files /dev/null and b/[esx_addons]/esx_halloween/web/build/assets/trick-success.mp3 differ diff --git a/[esx_addons]/esx_halloween/web/build/assets/trick-trick.mp3 b/[esx_addons]/esx_halloween/web/build/assets/trick-trick.mp3 new file mode 100644 index 00000000..3157e58c Binary files /dev/null and b/[esx_addons]/esx_halloween/web/build/assets/trick-trick.mp3 differ diff --git a/[esx_addons]/esx_halloween/web/build/index.html b/[esx_addons]/esx_halloween/web/build/index.html new file mode 100644 index 00000000..df3b5900 --- /dev/null +++ b/[esx_addons]/esx_halloween/web/build/index.html @@ -0,0 +1,19 @@ + + + + + + + Halloween Event + + + + + + + + + +
+ + diff --git a/[esx_addons]/esx_halloween/web/build/vite.svg b/[esx_addons]/esx_halloween/web/build/vite.svg new file mode 100644 index 00000000..e7b8dfb1 --- /dev/null +++ b/[esx_addons]/esx_halloween/web/build/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/[esx_addons]/esx_halloween/web/index.html b/[esx_addons]/esx_halloween/web/index.html new file mode 100644 index 00000000..c098a90a --- /dev/null +++ b/[esx_addons]/esx_halloween/web/index.html @@ -0,0 +1,18 @@ + + + + + + + Halloween Event + + + + + + + +
+ + + diff --git a/[esx_addons]/esx_halloween/web/package-lock.json b/[esx_addons]/esx_halloween/web/package-lock.json new file mode 100644 index 00000000..e216747b --- /dev/null +++ b/[esx_addons]/esx_halloween/web/package-lock.json @@ -0,0 +1,1448 @@ +{ + "name": "web", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "web", + "version": "0.0.0", + "dependencies": { + "lucide-svelte": "^0.548.0" + }, + "devDependencies": { + "@sveltejs/vite-plugin-svelte": "^6.2.1", + "@tsconfig/svelte": "^5.0.5", + "@types/node": "^24.6.0", + "svelte": "^5.39.6", + "svelte-check": "^4.3.2", + "typescript": "~5.9.3", + "vite": "^7.1.7" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.11.tgz", + "integrity": "sha512-Xt1dOL13m8u0WE8iplx9Ibbm+hFAO0GsU2P34UNoDGvZYkY8ifSiy6Zuc1lYxfG7svWE2fzqCUmFp5HCn51gJg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.11.tgz", + "integrity": "sha512-uoa7dU+Dt3HYsethkJ1k6Z9YdcHjTrSb5NUy66ZfZaSV8hEYGD5ZHbEMXnqLFlbBflLsl89Zke7CAdDJ4JI+Gg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.11.tgz", + "integrity": "sha512-9slpyFBc4FPPz48+f6jyiXOx/Y4v34TUeDDXJpZqAWQn/08lKGeD8aDp9TMn9jDz2CiEuHwfhRmGBvpnd/PWIQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.11.tgz", + "integrity": "sha512-Sgiab4xBjPU1QoPEIqS3Xx+R2lezu0LKIEcYe6pftr56PqPygbB7+szVnzoShbx64MUupqoE0KyRlN7gezbl8g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.11.tgz", + "integrity": "sha512-VekY0PBCukppoQrycFxUqkCojnTQhdec0vevUL/EDOCnXd9LKWqD/bHwMPzigIJXPhC59Vd1WFIL57SKs2mg4w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.11.tgz", + "integrity": "sha512-+hfp3yfBalNEpTGp9loYgbknjR695HkqtY3d3/JjSRUyPg/xd6q+mQqIb5qdywnDxRZykIHs3axEqU6l1+oWEQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.11.tgz", + "integrity": "sha512-CmKjrnayyTJF2eVuO//uSjl/K3KsMIeYeyN7FyDBjsR3lnSJHaXlVoAK8DZa7lXWChbuOk7NjAc7ygAwrnPBhA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.11.tgz", + "integrity": "sha512-Dyq+5oscTJvMaYPvW3x3FLpi2+gSZTCE/1ffdwuM6G1ARang/mb3jvjxs0mw6n3Lsw84ocfo9CrNMqc5lTfGOw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.11.tgz", + "integrity": "sha512-TBMv6B4kCfrGJ8cUPo7vd6NECZH/8hPpBHHlYI3qzoYFvWu2AdTvZNuU/7hsbKWqu/COU7NIK12dHAAqBLLXgw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.11.tgz", + "integrity": "sha512-Qr8AzcplUhGvdyUF08A1kHU3Vr2O88xxP0Tm8GcdVOUm25XYcMPp2YqSVHbLuXzYQMf9Bh/iKx7YPqECs6ffLA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.11.tgz", + "integrity": "sha512-TmnJg8BMGPehs5JKrCLqyWTVAvielc615jbkOirATQvWWB1NMXY77oLMzsUjRLa0+ngecEmDGqt5jiDC6bfvOw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.11.tgz", + "integrity": "sha512-DIGXL2+gvDaXlaq8xruNXUJdT5tF+SBbJQKbWy/0J7OhU8gOHOzKmGIlfTTl6nHaCOoipxQbuJi7O++ldrxgMw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.11.tgz", + "integrity": "sha512-Osx1nALUJu4pU43o9OyjSCXokFkFbyzjXb6VhGIJZQ5JZi8ylCQ9/LFagolPsHtgw6himDSyb5ETSfmp4rpiKQ==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.11.tgz", + "integrity": "sha512-nbLFgsQQEsBa8XSgSTSlrnBSrpoWh7ioFDUmwo158gIm5NNP+17IYmNWzaIzWmgCxq56vfr34xGkOcZ7jX6CPw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.11.tgz", + "integrity": "sha512-HfyAmqZi9uBAbgKYP1yGuI7tSREXwIb438q0nqvlpxAOs3XnZ8RsisRfmVsgV486NdjD7Mw2UrFSw51lzUk1ww==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.11.tgz", + "integrity": "sha512-HjLqVgSSYnVXRisyfmzsH6mXqyvj0SA7pG5g+9W7ESgwA70AXYNpfKBqh1KbTxmQVaYxpzA/SvlB9oclGPbApw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.11.tgz", + "integrity": "sha512-HSFAT4+WYjIhrHxKBwGmOOSpphjYkcswF449j6EjsjbinTZbp8PJtjsVK1XFJStdzXdy/jaddAep2FGY+wyFAQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.11.tgz", + "integrity": "sha512-hr9Oxj1Fa4r04dNpWr3P8QKVVsjQhqrMSUzZzf+LZcYjZNqhA3IAfPQdEh1FLVUJSiu6sgAwp3OmwBfbFgG2Xg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.11.tgz", + "integrity": "sha512-u7tKA+qbzBydyj0vgpu+5h5AeudxOAGncb8N6C9Kh1N4n7wU1Xw1JDApsRjpShRpXRQlJLb9wY28ELpwdPcZ7A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.11.tgz", + "integrity": "sha512-Qq6YHhayieor3DxFOoYM1q0q1uMFYb7cSpLD2qzDSvK1NAvqFi8Xgivv0cFC6J+hWVw2teCYltyy9/m/14ryHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.11.tgz", + "integrity": "sha512-CN+7c++kkbrckTOz5hrehxWN7uIhFFlmS/hqziSFVWpAzpWrQoAG4chH+nN3Be+Kzv/uuo7zhX716x3Sn2Jduw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.11.tgz", + "integrity": "sha512-rOREuNIQgaiR+9QuNkbkxubbp8MSO9rONmwP5nKncnWJ9v5jQ4JxFnLu4zDSRPf3x4u+2VN4pM4RdyIzDty/wQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.11.tgz", + "integrity": "sha512-nq2xdYaWxyg9DcIyXkZhcYulC6pQ2FuCgem3LI92IwMgIZ69KHeY8T4Y88pcwoLIjbed8n36CyKoYRDygNSGhA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.11.tgz", + "integrity": "sha512-3XxECOWJq1qMZ3MN8srCJ/QfoLpL+VaxD/WfNRm1O3B4+AZ/BnLVgFbUV3eiRYDMXetciH16dwPbbHqwe1uU0Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.11.tgz", + "integrity": "sha512-3ukss6gb9XZ8TlRyJlgLn17ecsK4NSQTmdIXRASVsiS2sQ6zPPZklNJT5GR5tE/MUarymmy8kCEf5xPCNCqVOA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.11.tgz", + "integrity": "sha512-D7Hpz6A2L4hzsRpPaCYkQnGOotdUpDzSGRIv9I+1ITdHROSFUWW95ZPZWQmGka1Fg7W3zFJowyn9WGwMJ0+KPA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "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==", + "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==", + "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==", + "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==", + "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==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.5.tgz", + "integrity": "sha512-8c1vW4ocv3UOMp9K+gToY5zL2XiiVw3k7f1ksf4yO1FlDFQ1C2u72iACFnSOceJFsWskc2WZNqeRhFRPzv+wtQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.5.tgz", + "integrity": "sha512-mQGfsIEFcu21mvqkEKKu2dYmtuSZOBMmAl5CFlPGLY94Vlcm+zWApK7F/eocsNzp8tKmbeBP8yXyAbx0XHsFNA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.5.tgz", + "integrity": "sha512-takF3CR71mCAGA+v794QUZ0b6ZSrgJkArC+gUiG6LB6TQty9T0Mqh3m2ImRBOxS2IeYBo4lKWIieSvnEk2OQWA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.5.tgz", + "integrity": "sha512-W901Pla8Ya95WpxDn//VF9K9u2JbocwV/v75TE0YIHNTbhqUTv9w4VuQ9MaWlNOkkEfFwkdNhXgcLqPSmHy0fA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.5.tgz", + "integrity": "sha512-QofO7i7JycsYOWxe0GFqhLmF6l1TqBswJMvICnRUjqCx8b47MTo46W8AoeQwiokAx3zVryVnxtBMcGcnX12LvA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.5.tgz", + "integrity": "sha512-jr21b/99ew8ujZubPo9skbrItHEIE50WdV86cdSoRkKtmWa+DDr6fu2c/xyRT0F/WazZpam6kk7IHBerSL7LDQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.5.tgz", + "integrity": "sha512-PsNAbcyv9CcecAUagQefwX8fQn9LQ4nZkpDboBOttmyffnInRy8R8dSg6hxxl2Re5QhHBf6FYIDhIj5v982ATQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.5.tgz", + "integrity": "sha512-Fw4tysRutyQc/wwkmcyoqFtJhh0u31K+Q6jYjeicsGJJ7bbEq8LwPWV/w0cnzOqR2m694/Af6hpFayLJZkG2VQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.52.5.tgz", + "integrity": "sha512-a+3wVnAYdQClOTlyapKmyI6BLPAFYs0JM8HRpgYZQO02rMR09ZcV9LbQB+NL6sljzG38869YqThrRnfPMCDtZg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.5.tgz", + "integrity": "sha512-AvttBOMwO9Pcuuf7m9PkC1PUIKsfaAJ4AYhy944qeTJgQOqJYJ9oVl2nYgY7Rk0mkbsuOpCAYSs6wLYB2Xiw0Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.5.tgz", + "integrity": "sha512-DkDk8pmXQV2wVrF6oq5tONK6UHLz/XcEVow4JTTerdeV1uqPeHxwcg7aFsfnSm9L+OO8WJsWotKM2JJPMWrQtA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.5.tgz", + "integrity": "sha512-W/b9ZN/U9+hPQVvlGwjzi+Wy4xdoH2I8EjaCkMvzpI7wJUs8sWJ03Rq96jRnHkSrcHTpQe8h5Tg3ZzUPGauvAw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.5.tgz", + "integrity": "sha512-sjQLr9BW7R/ZiXnQiWPkErNfLMkkWIoCz7YMn27HldKsADEKa5WYdobaa1hmN6slu9oWQbB6/jFpJ+P2IkVrmw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.5.tgz", + "integrity": "sha512-hq3jU/kGyjXWTvAh2awn8oHroCbrPm8JqM7RUpKjalIRWWXE01CQOf/tUNWNHjmbMHg/hmNCwc/Pz3k1T/j/Lg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.5.tgz", + "integrity": "sha512-gn8kHOrku8D4NGHMK1Y7NA7INQTRdVOntt1OCYypZPRt6skGbddska44K8iocdpxHTMMNui5oH4elPH4QOLrFQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.5.tgz", + "integrity": "sha512-hXGLYpdhiNElzN770+H2nlx+jRog8TyynpTVzdlc6bndktjKWyZyiCsuDAlpd+j+W+WNqfcyAWz9HxxIGfZm1Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.5.tgz", + "integrity": "sha512-arCGIcuNKjBoKAXD+y7XomR9gY6Mw7HnFBv5Rw7wQRvwYLR7gBAgV7Mb2QTyjXfTveBNFAtPt46/36vV9STLNg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.5.tgz", + "integrity": "sha512-QoFqB6+/9Rly/RiPjaomPLmR/13cgkIGfA40LHly9zcH1S0bN2HVFYk3a1eAyHQyjs3ZJYlXvIGtcCs5tko9Cw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.5.tgz", + "integrity": "sha512-w0cDWVR6MlTstla1cIfOGyl8+qb93FlAVutcor14Gf5Md5ap5ySfQ7R9S/NjNaMLSFdUnKGEasmVnu3lCMqB7w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.5.tgz", + "integrity": "sha512-Aufdpzp7DpOTULJCuvzqcItSGDH73pF3ko/f+ckJhxQyHtp67rHw3HMNxoIdDMUITJESNE6a8uh4Lo4SLouOUg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.5.tgz", + "integrity": "sha512-UGBUGPFp1vkj6p8wCRraqNhqwX/4kNQPS57BCFc8wYh0g94iVIW33wJtQAx3G7vrjjNtRaxiMUylM0ktp/TRSQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.5.tgz", + "integrity": "sha512-TAcgQh2sSkykPRWLrdyy2AiceMckNf5loITqXxFI5VuQjS5tSuw3WlwdN8qv8vzjLAUTvYaH/mVjSFpbkFbpTg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@sveltejs/acorn-typescript": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.6.tgz", + "integrity": "sha512-4awhxtMh4cx9blePWl10HRHj8Iivtqj+2QdDCSMDzxG+XKa9+VCNupQuCuvzEhYPzZSrX+0gC+0lHA/0fFKKQQ==", + "license": "MIT", + "peerDependencies": { + "acorn": "^8.9.0" + } + }, + "node_modules/@sveltejs/vite-plugin-svelte": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-6.2.1.tgz", + "integrity": "sha512-YZs/OSKOQAQCnJvM/P+F1URotNnYNeU3P2s4oIpzm1uFaqUEqRxUB0g5ejMjEb5Gjb9/PiBI5Ktrq4rUUF8UVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sveltejs/vite-plugin-svelte-inspector": "^5.0.0", + "debug": "^4.4.1", + "deepmerge": "^4.3.1", + "magic-string": "^0.30.17", + "vitefu": "^1.1.1" + }, + "engines": { + "node": "^20.19 || ^22.12 || >=24" + }, + "peerDependencies": { + "svelte": "^5.0.0", + "vite": "^6.3.0 || ^7.0.0" + } + }, + "node_modules/@sveltejs/vite-plugin-svelte-inspector": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-5.0.1.tgz", + "integrity": "sha512-ubWshlMk4bc8mkwWbg6vNvCeT7lGQojE3ijDh3QTR6Zr/R+GXxsGbyH4PExEPpiFmqPhYiVSVmHBjUcVc1JIrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.4.1" + }, + "engines": { + "node": "^20.19 || ^22.12 || >=24" + }, + "peerDependencies": { + "@sveltejs/vite-plugin-svelte": "^6.0.0-next.0", + "svelte": "^5.0.0", + "vite": "^6.3.0 || ^7.0.0" + } + }, + "node_modules/@tsconfig/svelte": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/@tsconfig/svelte/-/svelte-5.0.5.tgz", + "integrity": "sha512-48fAnUjKye38FvMiNOj0J9I/4XlQQiZlpe9xaNPfe8vy2Y1hFBt8g1yqf2EGjVvHavo4jf2lC+TQyENCr4BJBQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "24.9.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.9.1.tgz", + "integrity": "sha512-QoiaXANRkSXK6p0Duvt56W208du4P9Uye9hWLWgGMDTEoKPhuenzNcC4vGUmrNkiOKTlIrBoyNQYNpSwfEZXSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/aria-query": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", + "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/axobject-query": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", + "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "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/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/esbuild": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.11.tgz", + "integrity": "sha512-KohQwyzrKTQmhXDW1PjCv3Tyspn9n5GcY2RTDqeORIdIJY8yKIF7sTSopFmn/wpMPW4rdPXI0UE5LJLuq3bx0Q==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.11", + "@esbuild/android-arm": "0.25.11", + "@esbuild/android-arm64": "0.25.11", + "@esbuild/android-x64": "0.25.11", + "@esbuild/darwin-arm64": "0.25.11", + "@esbuild/darwin-x64": "0.25.11", + "@esbuild/freebsd-arm64": "0.25.11", + "@esbuild/freebsd-x64": "0.25.11", + "@esbuild/linux-arm": "0.25.11", + "@esbuild/linux-arm64": "0.25.11", + "@esbuild/linux-ia32": "0.25.11", + "@esbuild/linux-loong64": "0.25.11", + "@esbuild/linux-mips64el": "0.25.11", + "@esbuild/linux-ppc64": "0.25.11", + "@esbuild/linux-riscv64": "0.25.11", + "@esbuild/linux-s390x": "0.25.11", + "@esbuild/linux-x64": "0.25.11", + "@esbuild/netbsd-arm64": "0.25.11", + "@esbuild/netbsd-x64": "0.25.11", + "@esbuild/openbsd-arm64": "0.25.11", + "@esbuild/openbsd-x64": "0.25.11", + "@esbuild/openharmony-arm64": "0.25.11", + "@esbuild/sunos-x64": "0.25.11", + "@esbuild/win32-arm64": "0.25.11", + "@esbuild/win32-ia32": "0.25.11", + "@esbuild/win32-x64": "0.25.11" + } + }, + "node_modules/esm-env": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz", + "integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==", + "license": "MIT" + }, + "node_modules/esrap": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/esrap/-/esrap-2.1.0.tgz", + "integrity": "sha512-yzmPNpl7TBbMRC5Lj2JlJZNPml0tzqoqP5B1JXycNUwtqma9AKCO0M2wHrdgsHcy1WRW7S9rJknAMtByg3usgA==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/is-reference": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz", + "integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.6" + } + }, + "node_modules/locate-character": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz", + "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==", + "license": "MIT" + }, + "node_modules/lucide-svelte": { + "version": "0.548.0", + "resolved": "https://registry.npmjs.org/lucide-svelte/-/lucide-svelte-0.548.0.tgz", + "integrity": "sha512-aW2BfHWBLWf/XPSKytTPV16AWfFeFIJeUyOg7eHY2rhzVQ0u0LIvoS4pm2oskr+OJVw+NsS8fPvlBVqPfUO1XQ==", + "license": "ISC", + "peerDependencies": { + "svelte": "^3 || ^4 || ^5.0.0-next.42" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/mri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "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/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/rollup": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.5.tgz", + "integrity": "sha512-3GuObel8h7Kqdjt0gxkEzaifHTqLVW56Y/bjN7PSQtkKr0w3V/QYSdt6QWYtd7A1xUtYQigtdUfgj1RvWVtorw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.52.5", + "@rollup/rollup-android-arm64": "4.52.5", + "@rollup/rollup-darwin-arm64": "4.52.5", + "@rollup/rollup-darwin-x64": "4.52.5", + "@rollup/rollup-freebsd-arm64": "4.52.5", + "@rollup/rollup-freebsd-x64": "4.52.5", + "@rollup/rollup-linux-arm-gnueabihf": "4.52.5", + "@rollup/rollup-linux-arm-musleabihf": "4.52.5", + "@rollup/rollup-linux-arm64-gnu": "4.52.5", + "@rollup/rollup-linux-arm64-musl": "4.52.5", + "@rollup/rollup-linux-loong64-gnu": "4.52.5", + "@rollup/rollup-linux-ppc64-gnu": "4.52.5", + "@rollup/rollup-linux-riscv64-gnu": "4.52.5", + "@rollup/rollup-linux-riscv64-musl": "4.52.5", + "@rollup/rollup-linux-s390x-gnu": "4.52.5", + "@rollup/rollup-linux-x64-gnu": "4.52.5", + "@rollup/rollup-linux-x64-musl": "4.52.5", + "@rollup/rollup-openharmony-arm64": "4.52.5", + "@rollup/rollup-win32-arm64-msvc": "4.52.5", + "@rollup/rollup-win32-ia32-msvc": "4.52.5", + "@rollup/rollup-win32-x64-gnu": "4.52.5", + "@rollup/rollup-win32-x64-msvc": "4.52.5", + "fsevents": "~2.3.2" + } + }, + "node_modules/sade": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", + "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "mri": "^1.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "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==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/svelte": { + "version": "5.41.2", + "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.41.2.tgz", + "integrity": "sha512-JetVllvRzfAqg/32ST3SWkb0+OiCpJ/sSMTe206QgiTAN6XaCR3s7nx2L+hQHoUd/1YoK+UsEblGWyKcqY7Efg==", + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.4", + "@jridgewell/sourcemap-codec": "^1.5.0", + "@sveltejs/acorn-typescript": "^1.0.5", + "@types/estree": "^1.0.5", + "acorn": "^8.12.1", + "aria-query": "^5.3.1", + "axobject-query": "^4.1.0", + "clsx": "^2.1.1", + "esm-env": "^1.2.1", + "esrap": "^2.1.0", + "is-reference": "^3.0.3", + "locate-character": "^3.0.0", + "magic-string": "^0.30.11", + "zimmerframe": "^1.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/svelte-check": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-4.3.3.tgz", + "integrity": "sha512-RYP0bEwenDXzfv0P1sKAwjZSlaRyqBn0Fz1TVni58lqyEiqgwztTpmodJrGzP6ZT2aHl4MbTvWP6gbmQ3FOnBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.25", + "chokidar": "^4.0.1", + "fdir": "^6.2.0", + "picocolors": "^1.0.0", + "sade": "^1.7.4" + }, + "bin": { + "svelte-check": "bin/svelte-check" + }, + "engines": { + "node": ">= 18.0.0" + }, + "peerDependencies": { + "svelte": "^4.0.0 || ^5.0.0-next.0", + "typescript": ">=5.0.0" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "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": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + }, + "node_modules/vite": { + "version": "7.1.11", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.11.tgz", + "integrity": "sha512-uzcxnSDVjAopEUjljkWh8EIrg6tlzrjFUfMcR1EVsRDGwf/ccef0qQPRyOrROwhrTDaApueq+ja+KLPlzR/zdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vitefu": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.1.1.tgz", + "integrity": "sha512-B/Fegf3i8zh0yFbpzZ21amWzHmuNlLlmJT6n7bu5e+pCHUKQIfXSYokrqOBGEMMe9UG2sostKQF9mml/vYaWJQ==", + "dev": true, + "license": "MIT", + "workspaces": [ + "tests/deps/*", + "tests/projects/*", + "tests/projects/workspace/packages/*" + ], + "peerDependencies": { + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0" + }, + "peerDependenciesMeta": { + "vite": { + "optional": true + } + } + }, + "node_modules/zimmerframe": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.4.tgz", + "integrity": "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==", + "license": "MIT" + } + } +} diff --git a/[esx_addons]/esx_halloween/web/package.json b/[esx_addons]/esx_halloween/web/package.json new file mode 100644 index 00000000..59cc2dac --- /dev/null +++ b/[esx_addons]/esx_halloween/web/package.json @@ -0,0 +1,25 @@ +{ + "name": "web", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "dev:game": "vite build --watch", + "build": "vite build", + "preview": "vite preview", + "check": "svelte-check --tsconfig ./tsconfig.app.json && tsc -p tsconfig.node.json" + }, + "devDependencies": { + "@sveltejs/vite-plugin-svelte": "^6.2.1", + "@tsconfig/svelte": "^5.0.5", + "@types/node": "^24.6.0", + "svelte": "^5.39.6", + "svelte-check": "^4.3.2", + "typescript": "~5.9.3", + "vite": "^7.1.7" + }, + "dependencies": { + "lucide-svelte": "^0.548.0" + } +} diff --git a/[esx_addons]/esx_halloween/web/public/assets/background.webp b/[esx_addons]/esx_halloween/web/public/assets/background.webp new file mode 100644 index 00000000..e17b5cfe Binary files /dev/null and b/[esx_addons]/esx_halloween/web/public/assets/background.webp differ diff --git a/[esx_addons]/esx_halloween/web/public/assets/pumpkin.webp b/[esx_addons]/esx_halloween/web/public/assets/pumpkin.webp new file mode 100644 index 00000000..701fa954 Binary files /dev/null and b/[esx_addons]/esx_halloween/web/public/assets/pumpkin.webp differ diff --git a/[esx_addons]/esx_halloween/web/public/assets/scream.wav b/[esx_addons]/esx_halloween/web/public/assets/scream.wav new file mode 100644 index 00000000..2008b0db Binary files /dev/null and b/[esx_addons]/esx_halloween/web/public/assets/scream.wav differ diff --git a/[esx_addons]/esx_halloween/web/public/assets/trick-success.mp3 b/[esx_addons]/esx_halloween/web/public/assets/trick-success.mp3 new file mode 100644 index 00000000..b934f400 Binary files /dev/null and b/[esx_addons]/esx_halloween/web/public/assets/trick-success.mp3 differ diff --git a/[esx_addons]/esx_halloween/web/public/assets/trick-trick.mp3 b/[esx_addons]/esx_halloween/web/public/assets/trick-trick.mp3 new file mode 100644 index 00000000..3157e58c Binary files /dev/null and b/[esx_addons]/esx_halloween/web/public/assets/trick-trick.mp3 differ diff --git a/[esx_addons]/esx_halloween/web/public/vite.svg b/[esx_addons]/esx_halloween/web/public/vite.svg new file mode 100644 index 00000000..e7b8dfb1 --- /dev/null +++ b/[esx_addons]/esx_halloween/web/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/[esx_addons]/esx_halloween/web/src/App.svelte b/[esx_addons]/esx_halloween/web/src/App.svelte new file mode 100644 index 00000000..58fa3d0a --- /dev/null +++ b/[esx_addons]/esx_halloween/web/src/App.svelte @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + diff --git a/[esx_addons]/esx_halloween/web/src/app.css b/[esx_addons]/esx_halloween/web/src/app.css new file mode 100644 index 00000000..61ba3678 --- /dev/null +++ b/[esx_addons]/esx_halloween/web/src/app.css @@ -0,0 +1,79 @@ +:root { + font-family: system-ui, Avenir, Helvetica, Arial, sans-serif; + line-height: 1.5; + font-weight: 400; + + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: #242424; + + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} +a:hover { + color: #535bf2; +} + +body { + margin: 0; + display: flex; + place-items: center; + min-width: 320px; + min-height: 100vh; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +.card { + padding: 2em; +} + +#app { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +button { + border-radius: 8px; + border: 1px solid transparent; + padding: 0.6em 1.2em; + font-size: 1em; + font-weight: 500; + font-family: inherit; + background-color: #1a1a1a; + cursor: pointer; + transition: border-color 0.25s; +} +button:hover { + border-color: #646cff; +} +button:focus, +button:focus-visible { + outline: 4px auto -webkit-focus-ring-color; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + a:hover { + color: #747bff; + } + button { + background-color: #f9f9f9; + } +} diff --git a/[esx_addons]/esx_halloween/web/src/assets/background.webp b/[esx_addons]/esx_halloween/web/src/assets/background.webp new file mode 100644 index 00000000..e17b5cfe Binary files /dev/null and b/[esx_addons]/esx_halloween/web/src/assets/background.webp differ diff --git a/[esx_addons]/esx_halloween/web/src/assets/pumpkin.webp b/[esx_addons]/esx_halloween/web/src/assets/pumpkin.webp new file mode 100644 index 00000000..701fa954 Binary files /dev/null and b/[esx_addons]/esx_halloween/web/src/assets/pumpkin.webp differ diff --git a/[esx_addons]/esx_halloween/web/src/assets/svelte.svg b/[esx_addons]/esx_halloween/web/src/assets/svelte.svg new file mode 100644 index 00000000..c5e08481 --- /dev/null +++ b/[esx_addons]/esx_halloween/web/src/assets/svelte.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/[esx_addons]/esx_halloween/web/src/components/GhostChoice.svelte b/[esx_addons]/esx_halloween/web/src/components/GhostChoice.svelte new file mode 100644 index 00000000..ba498451 --- /dev/null +++ b/[esx_addons]/esx_halloween/web/src/components/GhostChoice.svelte @@ -0,0 +1,197 @@ + + +{#if visible} +
+
+
+ pumpkin +
+ +

Spooky Opportunity!

+

You have a chance to respawn as a ghost and haunt the living...

+ +
+ + +
+ + {#if !forced} +
Auto-decline in {countdown}s
+ {/if} +
+
+{/if} + + diff --git a/[esx_addons]/esx_halloween/web/src/components/GhostHUD.svelte b/[esx_addons]/esx_halloween/web/src/components/GhostHUD.svelte new file mode 100644 index 00000000..43ec0ce6 --- /dev/null +++ b/[esx_addons]/esx_halloween/web/src/components/GhostHUD.svelte @@ -0,0 +1,163 @@ + + +{#if visible} +
+
+
👻
+
+
Ghost Mode
+
{formatTime(timeRemaining)}
+
+
+ +
+
+
{scareKey}
+
Scare
+
+
+ +
+ Press {exitKey} to exit +
+
+{/if} + + diff --git a/[esx_addons]/esx_halloween/web/src/components/JumpscareEffect.svelte b/[esx_addons]/esx_halloween/web/src/components/JumpscareEffect.svelte new file mode 100644 index 00000000..4e30996f --- /dev/null +++ b/[esx_addons]/esx_halloween/web/src/components/JumpscareEffect.svelte @@ -0,0 +1,117 @@ + + +{#if visible} +
+
+ scary +
BOO!
+
+
+{/if} + + diff --git a/[esx_addons]/esx_halloween/web/src/components/Notification.svelte b/[esx_addons]/esx_halloween/web/src/components/Notification.svelte new file mode 100644 index 00000000..15f3c8ab --- /dev/null +++ b/[esx_addons]/esx_halloween/web/src/components/Notification.svelte @@ -0,0 +1,203 @@ + + +
+ + +
+

{header}

+

{description}

+
+
+ + diff --git a/[esx_addons]/esx_halloween/web/src/components/NotificationContainer.svelte b/[esx_addons]/esx_halloween/web/src/components/NotificationContainer.svelte new file mode 100644 index 00000000..b9984f60 --- /dev/null +++ b/[esx_addons]/esx_halloween/web/src/components/NotificationContainer.svelte @@ -0,0 +1,111 @@ + + +{#if notification} +
+ +
+{/if} + + diff --git a/[esx_addons]/esx_halloween/web/src/components/ParticleEffect.svelte b/[esx_addons]/esx_halloween/web/src/components/ParticleEffect.svelte new file mode 100644 index 00000000..c00efae0 --- /dev/null +++ b/[esx_addons]/esx_halloween/web/src/components/ParticleEffect.svelte @@ -0,0 +1,196 @@ + + + +
+ {#each particles as particle (particle.id)} +
+ {#if particle.type === 'candy'} + 🍬 + {:else if particle.type === 'blood'} + + {:else if particle.type === 'spark'} + + {/if} +
+ {/each} +
+ + diff --git a/[esx_addons]/esx_halloween/web/src/components/TrickOrTreatHUD.svelte b/[esx_addons]/esx_halloween/web/src/components/TrickOrTreatHUD.svelte new file mode 100644 index 00000000..1e121a4d --- /dev/null +++ b/[esx_addons]/esx_halloween/web/src/components/TrickOrTreatHUD.svelte @@ -0,0 +1,250 @@ + + +{#if visible} +
+
+ + + + + + + +
+ +
+

Trick or Treat!

+
+ + +
+
+
+
+
+
+ + +
{displayedCurrent} / {total} houses
+
+ + +
+ + Time Left: {displayTime()} +
+
+
+
+{/if} + + diff --git a/[esx_addons]/esx_halloween/web/src/components/TrickOrTreatReward.svelte b/[esx_addons]/esx_halloween/web/src/components/TrickOrTreatReward.svelte new file mode 100644 index 00000000..5740566a --- /dev/null +++ b/[esx_addons]/esx_halloween/web/src/components/TrickOrTreatReward.svelte @@ -0,0 +1,346 @@ + + +{#if visible} +
+ +
+ + +
+ + + + + + + +
+ +
+ {#if isPositive} + + {:else} + + {/if} +
{rewardTitle}
+
+ + +
{rewardMessage}
+ + + {#if isPositive} +
+ +
+{amount}
+
+ {/if} +
+
+ + + {#if isPositive} + {#each particles as particle (particle.id)} +
+ +
+ {/each} + {/if} +
+{/if} + + diff --git a/[esx_addons]/esx_halloween/web/src/main.ts b/[esx_addons]/esx_halloween/web/src/main.ts new file mode 100644 index 00000000..928b6c52 --- /dev/null +++ b/[esx_addons]/esx_halloween/web/src/main.ts @@ -0,0 +1,8 @@ +import { mount } from 'svelte' +import App from './App.svelte' + +const app = mount(App, { + target: document.getElementById('app')!, +}) + +export default app diff --git a/[esx_addons]/esx_halloween/web/src/stores/GhostManager.svelte.ts b/[esx_addons]/esx_halloween/web/src/stores/GhostManager.svelte.ts new file mode 100644 index 00000000..dbaf91a6 --- /dev/null +++ b/[esx_addons]/esx_halloween/web/src/stores/GhostManager.svelte.ts @@ -0,0 +1,54 @@ +interface GhostState { + choiceVisible: boolean; + hudVisible: boolean; + jumpscareVisible: boolean; + maxDuration: number; + exitKey: string; + scareKey: string; + forced: boolean; + soundVolume: number; +} + +class GhostManager { + state = $state({ + choiceVisible: false, + hudVisible: false, + jumpscareVisible: false, + maxDuration: 600000, + exitKey: 'X', + scareKey: 'E', + forced: false, + soundVolume: 0.8 + }); + + showChoice(forced: boolean = false) { + this.state.choiceVisible = true; + this.state.forced = forced; + } + + hideChoice() { + this.state.choiceVisible = false; + } + + showHUD(maxDuration: number, exitKey: string = 'X', scareKey: string = 'E') { + this.state.hudVisible = true; + this.state.maxDuration = maxDuration; + this.state.exitKey = exitKey; + this.state.scareKey = scareKey; + } + + hideHUD() { + this.state.hudVisible = false; + } + + triggerJumpscare(soundVolume: number = 0.8) { + this.state.jumpscareVisible = true; + this.state.soundVolume = soundVolume; + } + + hideJumpscare() { + this.state.jumpscareVisible = false; + } +} + +export const ghostManager = new GhostManager(); diff --git a/[esx_addons]/esx_halloween/web/src/stores/NotificationManager.svelte.ts b/[esx_addons]/esx_halloween/web/src/stores/NotificationManager.svelte.ts new file mode 100644 index 00000000..e9fb75e2 --- /dev/null +++ b/[esx_addons]/esx_halloween/web/src/stores/NotificationManager.svelte.ts @@ -0,0 +1,143 @@ +import type { NotificationData, QueuedNotification } from '../types/nui'; + +/** + * Notification queue manager using Svelte 5 runes + * Handles displaying notifications one at a time with auto-dismiss + */ +class NotificationManager { + /** + * Queue of pending notifications + * @private + */ + private queue = $state([]); + + /** + * Currently displayed notification (null if none) + * @private + */ + private current = $state(null); + + /** + * Timeout ID for current notification auto-dismiss + * @private + */ + private dismissTimeout: number | null = null; + + /** + * Counter for generating unique notification IDs + * @private + */ + private idCounter = 0; + + /** + * Get the currently displayed notification + * @returns {QueuedNotification | null} Current notification or null + */ + get currentNotification(): QueuedNotification | null { + return this.current; + } + + /** + * Get the number of pending notifications in queue + * @returns {number} Queue length + */ + get queueLength(): number { + return this.queue.length; + } + + /** + * Add a new notification to the queue + * If no notification is currently showing, display it immediately + * @param {NotificationData} data - Notification configuration + * @returns {void} + * @example + * notificationManager.show({ + * size: 'small', + * position: 'top-right', + * header: 'Success', + * description: 'Item purchased successfully', + * duration: 3000 + * }); + */ + show(data: NotificationData): void { + const notification: QueuedNotification = { + ...data, + id: `notification-${++this.idCounter}`, + duration: data.duration ?? 5000, + }; + + this.queue.push(notification); + + if (!this.current) { + this.processQueue(); + } + } + + /** + * Process the next notification in queue + * Sets up auto-dismiss timer based on notification duration + * @private + */ + private processQueue(): void { + if (this.queue.length === 0) { + this.current = null; + return; + } + + this.current = this.queue.shift()!; + + if (this.dismissTimeout) { + clearTimeout(this.dismissTimeout); + } + + this.dismissTimeout = window.setTimeout(() => { + this.dismiss(); + }, this.current.duration); + } + + /** + * Dismiss the current notification and show next in queue + * Clears any pending auto-dismiss timeout + * @returns {void} + */ + dismiss(): void { + if (this.dismissTimeout) { + clearTimeout(this.dismissTimeout); + this.dismissTimeout = null; + } + + this.current = null; + + setTimeout(() => { + this.processQueue(); + }, 300); + } + + /** + * Clear all pending notifications and dismiss current + * @returns {void} + */ + clearAll(): void { + this.queue = []; + if (this.dismissTimeout) { + clearTimeout(this.dismissTimeout); + this.dismissTimeout = null; + } + this.current = null; + } +} + +/** + * Global notification manager instance + * @example + * import { notificationManager } from './stores/NotificationManager.svelte'; + * + * notificationManager.show({ + * size: 'large', + * position: 'bottom-center', + * header: 'Achievement Unlocked', + * description: 'You found a rare Halloween item!', + * duration: 4000 + * }); + */ +export const notificationManager = new NotificationManager(); diff --git a/[esx_addons]/esx_halloween/web/src/stores/TrickOrTreatManager.svelte.ts b/[esx_addons]/esx_halloween/web/src/stores/TrickOrTreatManager.svelte.ts new file mode 100644 index 00000000..b23f340e --- /dev/null +++ b/[esx_addons]/esx_halloween/web/src/stores/TrickOrTreatManager.svelte.ts @@ -0,0 +1,116 @@ +/** + * Trick-or-Treat State Manager + * Manages all UI state for the trick-or-treating feature + * Provides reactive state and methods for component communication + */ + +/** + * Trick-or-Treat UI state + */ +interface TrickOrTreatUIState { + // HUD State + hudVisible: boolean; + currentHouses: number; + totalHouses: number; + timeRemaining: number; + + // Reward Popup State + rewardVisible: boolean; + rewardType: 'treat' | 'trick'; + rewardItem: string; + rewardAmount: number; +} + +/** + * Trick-or-Treat State Manager Class + * Reactive state container using Svelte 5 runes ($state, $effect) + */ +class TrickOrTreatManager { + /** + * Reactive state object + * Contains all UI-related state for trick-or-treating + */ + state = $state({ + hudVisible: false, + currentHouses: 0, + totalHouses: 0, + timeRemaining: 0, + rewardVisible: false, + rewardType: 'treat', + rewardItem: '', + rewardAmount: 0, + }); + + /** + * Start a new trick-or-treat round + * Initializes HUD with round data + * + * @param total - Total houses available in this round + * @param duration - Round duration in milliseconds + */ + startRound(total: number, duration: number): void { + this.state.hudVisible = true; + this.state.currentHouses = 0; + this.state.totalHouses = total; + this.state.timeRemaining = duration; + } + + /** + * Update round progress + * Called when player collects from a house or when timer ticks + * + * @param current - Current houses collected + * @param remaining - Time remaining in milliseconds + */ + updateProgress(current: number, remaining: number): void { + this.state.currentHouses = current; + this.state.timeRemaining = remaining; + } + + /** + * End the current trick-or-treat round + * Hides HUD and resets state + */ + endRound(): void { + this.state.hudVisible = false; + this.state.currentHouses = 0; + this.state.totalHouses = 0; + this.state.timeRemaining = 0; + } + + /** + * Show reward popup for collected candy + * Triggers animation and auto-dismiss + * + * @param type - Type of reward ('treat' or 'trick') + * @param item - Item name collected + * @param amount - Amount of item collected + */ + showReward(type: 'treat' | 'trick', item: string, amount: number): void { + this.state.rewardType = type; + this.state.rewardItem = item; + this.state.rewardAmount = amount; + this.state.rewardVisible = true; + } + + /** + * Hide reward popup + * Called after reward animation completes + */ + hideReward(): void { + this.state.rewardVisible = false; + } + + /** + * Hide HUD (used if round ends unexpectedly) + */ + hideHUD(): void { + this.state.hudVisible = false; + } +} + +/** + * Singleton instance of Trick-or-Treat Manager + * Exported for use throughout the application + */ +export const trickOrTreatManager = new TrickOrTreatManager(); diff --git a/[esx_addons]/esx_halloween/web/src/styles/animations.css b/[esx_addons]/esx_halloween/web/src/styles/animations.css new file mode 100644 index 00000000..85e09cfa --- /dev/null +++ b/[esx_addons]/esx_halloween/web/src/styles/animations.css @@ -0,0 +1,180 @@ +/** + * Halloween Event Custom Animations + * Includes animations for Trick-or-Treat feature + */ + +/** + * TRICK-OR-TREAT ANIMATIONS + */ + +/* Candy falling particle effect (used in TrickOrTreatReward.svelte) */ +@keyframes candyFall { + 0% { + opacity: 1; + transform: translateY(calc(-100px * var(--hud-scale))) rotateZ(0deg); + } + + 50% { + opacity: 0.8; + } + + 100% { + opacity: 0; + transform: translateY(calc(400px * var(--hud-scale))) rotateZ(360deg); + } +} + +/* Pumpkin floating animation (used in TrickOrTreatHUD.svelte) */ +@keyframes pumpkinFloat { + 0%, 100% { + transform: translateY(0px); + } + + 50% { + transform: translateY(calc(-8px * var(--hud-scale))); + } +} + +/* HUD slide-in from left (used in TrickOrTreatHUD.svelte) */ +@keyframes slideInFromLeft { + from { + opacity: 0; + transform: translateX(calc(-50px * var(--hud-scale))); + } + + to { + opacity: 1; + transform: translateX(0); + } +} + +/* Reward popup scale-in (used in TrickOrTreatReward.svelte) */ +@keyframes rewardScaleIn { + from { + opacity: 0; + transform: scale(0.8); + } + + to { + opacity: 1; + transform: scale(1); + } +} + +/* Shake animation for trick popup (used in TrickOrTreatReward.svelte) */ +@keyframes trickShake { + 0%, 100% { + transform: translateX(0); + } + + 25% { + transform: translateX(calc(-5px * var(--hud-scale))); + } + + 50% { + transform: translateX(calc(5px * var(--hud-scale))); + } + + 75% { + transform: translateX(calc(-5px * var(--hud-scale))); + } +} + +/** + * UNIVERSAL ANIMATIONS + */ + +/* Smooth fade-in (used throughout) */ +@keyframes fadeIn { + from { + opacity: 0; + } + + to { + opacity: 1; + } +} + +/* Smooth fade-out (used throughout) */ +@keyframes fadeOut { + from { + opacity: 1; + } + + to { + opacity: 0; + } +} + +/* Gentle spin (used for loading states) */ +@keyframes spin { + from { + transform: rotateZ(0deg); + } + + to { + transform: rotateZ(360deg); + } +} + +/* Border glow pulse (used in card headers) */ +@keyframes borderGlowPulse { + 0%, 100% { + border-color: rgba(173, 6, 67, 0.3); + } + + 50% { + border-color: rgba(173, 6, 67, 0.6); + } +} + +/* Text glow pulse (used in important text) */ +@keyframes textGlowPulse { + 0%, 100% { + text-shadow: 0 0 10px rgba(173, 6, 67, 0.3); + } + + 50% { + text-shadow: 0 0 20px rgba(173, 6, 67, 0.6); + } +} + +/** + * TIMING PRESETS + * Use with: animation: ; + */ + +/* Fast animations: for immediate feedback */ +.animation-fast { + animation-duration: 300ms; + animation-timing-function: cubic-bezier(0.34, 1.56, 0.64, 1); +} + +/* Medium animations: for standard UI transitions */ +.animation-medium { + animation-duration: 600ms; + animation-timing-function: cubic-bezier(0.25, 0.46, 0.45, 0.94); +} + +/* Slow animations: for background effects */ +.animation-slow { + animation-duration: 1s; + animation-timing-function: ease-in-out; +} + +/* Continuous animations: for looping effects */ +.animation-continuous { + animation-iteration-count: infinite; +} + +/** + * PREFERS-REDUCED-MOTION ACCESSIBILITY + * Respect user's motion preferences + */ +@media (prefers-reduced-motion: reduce) { + * { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + } +} diff --git a/[esx_addons]/esx_halloween/web/src/styles/design-tokens.css b/[esx_addons]/esx_halloween/web/src/styles/design-tokens.css new file mode 100644 index 00000000..e4d06595 --- /dev/null +++ b/[esx_addons]/esx_halloween/web/src/styles/design-tokens.css @@ -0,0 +1,103 @@ +:root { + /* ESX UI Convars - Set dynamically from game */ + --primary-color: #fb9b04; + --secondary-color: #1a1a1a; + --background-color: #000000; + --accent-color: #fb9b04; + --logo-url: ''; + + /* Colors */ + --color-brand: var(--primary-color); + --color-brand-rgb: 251, 155, 4; + + /* Danger/Zone Colors (for Domain Zones) */ + --color-danger: #ff3b3b; + --color-danger-rgb: 255, 59, 59; + + /* Dark Shades */ + --color-darkest: #161616; + --color-darkest-rgb: 22, 22, 22; + + --color-dark: #252525; + --color-dark-rgb: 37, 37, 37; + + --color-mid: #383838; + --color-mid-rgb: 56, 56, 56; + + /* Light Shades */ + --color-light: #969696; + --color-light-rgb: 150, 150, 150; + + --color-lightest: #f2f2f2; + --color-lightest-rgb: 242, 242, 242; + --color-bg-primary: var(--color-darkest); + --color-bg-secondary: var(--color-dark); + --color-bg-tertiary: var(--color-mid); + + --color-text-primary: var(--color-lightest); + --color-text-secondary: var(--color-light); + --color-text-inverse: var(--color-darkest); + + /* Typography */ + --font-family: 'Poppins', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; + --font-family-halloween: 'Creepster', cursive; + --font-size-base: 1rem; + --font-size-h1: 2rem; + --font-size-h2: 1.5rem; + --font-size-h3: 1.25rem; + --font-size-h4: 1.125rem; + --font-size-h5: 1rem; + --font-size-h6: 0.875rem; + --font-size-body: 1rem; + --font-size-small: 0.875rem; + --font-size-tiny: 0.75rem; + --font-weight-normal: 400; + --font-weight-medium: 500; + --font-weight-semibold: 600; + --font-weight-bold: 700; + --line-height-tight: 1.2; + --line-height-normal: 1.5; + --line-height-relaxed: 1.75; + + /* Spacing */ + --space-xs: 0.25rem; + --space-sm: 0.5rem; + --space-md: 1rem; + --space-lg: 1.5rem; + --space-xl: 2rem; + --space-2xl: 3rem; + --space-3xl: 4rem; + + /* Border Radius */ + --radius-sm: 0.25rem; + --radius-md: 0.5rem; + --radius-lg: 0.75rem; + --radius-xl: 1rem; + --radius-full: 9999px; + + /* Shadows */ + --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05); + --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); + --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); + --shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04); + --shadow-brand: 0 0 20px rgba(var(--color-brand-rgb), 0.3); + --shadow-brand-strong: 0 0 30px rgba(var(--color-brand-rgb), 0.5); + --shadow-danger: 0 0 30px rgba(var(--color-danger-rgb), 0.2); + --shadow-danger-strong: 0 0 50px rgba(var(--color-danger-rgb), 0.6); + + /* Transitions */ + --transition-fast: 150ms ease-in-out; + --transition-base: 250ms ease-in-out; + --transition-slow: 350ms ease-in-out; + + /* Z-Index */ + --z-base: 1; + --z-dropdown: 100; + --z-sticky: 200; + --z-fixed: 300; + --z-modal-backdrop: 400; + --z-modal: 500; + --z-popover: 600; + --z-tooltip: 700; + --z-notification: 800; +} diff --git a/[esx_addons]/esx_halloween/web/src/styles/global.css b/[esx_addons]/esx_halloween/web/src/styles/global.css new file mode 100644 index 00000000..865aa824 --- /dev/null +++ b/[esx_addons]/esx_halloween/web/src/styles/global.css @@ -0,0 +1,211 @@ +@import './design-tokens.css'; +@import './reset.css'; + +html { + font-size: clamp(12px, 1vw, 20px); +} + +body { + font-family: var(--font-family); + font-size: var(--font-size-base); + font-weight: var(--font-weight-normal); + line-height: var(--line-height-normal); + color: var(--color-text-primary); + background-color: transparent; +} + +h1 { + font-size: var(--font-size-h1); + font-weight: var(--font-weight-bold); + line-height: var(--line-height-tight); +} + +h2 { + font-size: var(--font-size-h2); + font-weight: var(--font-weight-bold); + line-height: var(--line-height-tight); +} + +h3 { + font-size: var(--font-size-h3); + font-weight: var(--font-weight-semibold); + line-height: var(--line-height-tight); +} + +h4 { + font-size: var(--font-size-h4); + font-weight: var(--font-weight-semibold); + line-height: var(--line-height-tight); +} + +h5 { + font-size: var(--font-size-h5); + font-weight: var(--font-weight-medium); + line-height: var(--line-height-normal); +} + +h6 { + font-size: var(--font-size-h6); + font-weight: var(--font-weight-medium); + line-height: var(--line-height-normal); +} + +p { + font-size: var(--font-size-body); + line-height: var(--line-height-normal); +} + +.flex { + display: flex; +} + +.flex-col { + flex-direction: column; +} + +.flex-row { + flex-direction: row; +} + +.items-center { + align-items: center; +} + +.items-start { + align-items: flex-start; +} + +.items-end { + align-items: flex-end; +} + +.justify-center { + justify-content: center; +} + +.justify-between { + justify-content: space-between; +} + +.justify-start { + justify-content: flex-start; +} + +.justify-end { + justify-content: flex-end; +} + +.gap-xs { + gap: var(--space-xs); +} + +.gap-sm { + gap: var(--space-sm); +} + +.gap-md { + gap: var(--space-md); +} + +.gap-lg { + gap: var(--space-lg); +} + +.gap-xl { + gap: var(--space-xl); +} + +.p-xs { + padding: var(--space-xs); +} + +.p-sm { + padding: var(--space-sm); +} + +.p-md { + padding: var(--space-md); +} + +.p-lg { + padding: var(--space-lg); +} + +.p-xl { + padding: var(--space-xl); +} + +.m-xs { + margin: var(--space-xs); +} + +.m-sm { + margin: var(--space-sm); +} + +.m-md { + margin: var(--space-md); +} + +.m-lg { + margin: var(--space-lg); +} + +.m-xl { + margin: var(--space-xl); +} + +.text-left { + text-align: left; +} + +.text-center { + text-align: center; +} + +.text-right { + text-align: right; +} + +.hidden { + display: none; +} + +.block { + display: block; +} + +.inline-block { + display: inline-block; +} + +.w-full { + width: 100%; +} + +.h-full { + height: 100%; +} + +.container { + width: 100%; + max-width: 1280px; + margin-left: auto; + margin-right: auto; + padding-left: var(--space-md); + padding-right: var(--space-md); +} + +@media (min-width: 640px) { + .container { + padding-left: var(--space-lg); + padding-right: var(--space-lg); + } +} + +@media (min-width: 1024px) { + .container { + padding-left: var(--space-xl); + padding-right: var(--space-xl); + } +} diff --git a/[esx_addons]/esx_halloween/web/src/styles/reset.css b/[esx_addons]/esx_halloween/web/src/styles/reset.css new file mode 100644 index 00000000..da9f5c47 --- /dev/null +++ b/[esx_addons]/esx_halloween/web/src/styles/reset.css @@ -0,0 +1,83 @@ +*, +*::before, +*::after { + box-sizing: border-box; +} + +* { + margin: 0; + padding: 0; +} + +html { + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +html, +body { + height: 100%; +} + +body { + line-height: 1.5; + text-rendering: optimizeSpeed; +} + +input, +button, +textarea, +select { + font: inherit; +} + +button { + background: none; + border: none; + cursor: pointer; + color: inherit; +} + +ul, +ol { + list-style: none; +} + +a { + text-decoration: none; + color: inherit; +} + +img, +picture, +video, +canvas, +svg { + display: block; + max-width: 100%; +} + +@media (prefers-reduced-motion: reduce) { + *, + *::before, + *::after { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + scroll-behavior: auto !important; + } +} + +:focus { + outline: none; +} + +:focus-visible { + outline: 2px solid var(--color-brand); + outline-offset: 2px; +} + +:disabled { + cursor: not-allowed; + opacity: 0.6; +} diff --git a/[esx_addons]/esx_halloween/web/src/types/nui.ts b/[esx_addons]/esx_halloween/web/src/types/nui.ts new file mode 100644 index 00000000..f4d3e277 --- /dev/null +++ b/[esx_addons]/esx_halloween/web/src/types/nui.ts @@ -0,0 +1,163 @@ +/** + * NUI event message structure sent from game client + * @template T - Type of data payload + */ +export interface NuiMessage { + /** Event type identifier */ + type: string; + /** Event data payload (merged with root for some events) */ + [key: string]: any; +} + +/** + * Response structure for NUI callbacks + * @template T - Type of response data + */ +export interface NuiCallbackResponse { + /** Indicates if the request was successful */ + ok: boolean; + /** Response data if successful */ + data?: T; + /** Error message if unsuccessful */ + error?: string; +} + +/** + * UI visibility state + */ +export interface VisibilityState { + /** Current visibility state */ + visible: boolean; +} + +/** + * Configuration options for debug mode + */ +export interface DebugOptions { + /** Whether debug mode is enabled */ + enabled: boolean; + /** Delay in milliseconds before triggering debug events */ + delay?: number; +} + +/** + * Event handler function for NUI events + * @template T - Type of event data + */ +export type NuiEventHandler = (data: T) => void; + +/** + * Checks if the code is running in a browser environment (not FiveM) + * @returns {boolean} True if running in browser, false if in FiveM + * @example + * if (isEnvBrowser()) { + * console.log('Running in browser for development'); + * } + */ +export const isEnvBrowser = (): boolean => !(window as any).invokeNative; + +/** + * Gets the name of the parent FiveM resource + * Falls back to 'nui-frame-app' in browser environment + * @returns {string} Resource name + * @example + * const resource = getResourceName(); // Returns 'halloween' in FiveM + */ +export const getResourceName = (): string => { + if (isEnvBrowser()) { + return 'nui-frame-app'; + } + return (window as any).GetParentResourceName + ? (window as any).GetParentResourceName() + : 'unknown'; +}; + +/** + * Position where notification should appear + */ +export type NotificationPosition = 'top-left' | 'top-right' | 'top-center' | 'bottom-center'; + +/** + * Size variant for notification cards + */ +export type NotificationSize = 'small' | 'large'; + +/** + * Notification data structure sent from FiveM client + */ +export interface NotificationData { + /** Size of the notification card */ + size: NotificationSize; + /** Screen position where notification appears */ + position: NotificationPosition; + /** Header/title text */ + header: string; + /** Description/body text */ + description: string; + /** Duration in milliseconds before auto-dismiss (default: 5000) */ + duration?: number; +} + +/** + * Internal notification with unique ID for queue management + */ +export interface QueuedNotification extends NotificationData { + /** Unique identifier for this notification instance */ + id: string; +} + +// ============================================================================ +// TRICK-OR-TREAT NUI EVENTS & TYPES +// ============================================================================ + +/** + * Trick-or-Treat round start data sent from server + */ +export interface TrickOrTreatRoundStartData { + /** Total number of active houses in this round */ + totalHouses: number; + /** Duration of the round in milliseconds */ + duration: number; + /** Array of house IDs that are active this round */ + activeHouseIds: string[]; +} + +/** + * Trick-or-Treat candy collection response from server + */ +export interface TrickOrTreatCollectData { + /** Whether collection was successful */ + success: boolean; + /** Type of reward: 'treat' or 'trick' */ + rewardType: 'treat' | 'trick'; + /** Item name collected (e.g., 'halloween_candy') */ + rewardItem: string; + /** Amount of item collected */ + rewardAmount: number; + /** Number of remaining active houses in round */ + remainingHouses: number; + /** Error message if unsuccessful */ + error?: string; +} + +/** + * Trick-or-Treat round progress update from server + */ +export interface TrickOrTreatProgressData { + /** Houses collected so far in this round */ + currentHouses: number; + /** Total houses in this round */ + totalHouses: number; + /** Time remaining in round (milliseconds) */ + timeRemaining: number; +} + +/** + * Trick-or-Treat round end data from server + */ +export interface TrickOrTreatRoundEndData { + /** Total houses that were collected */ + totalCollected: number; + /** Total houses available */ + totalHouses: number; +} diff --git a/[esx_addons]/esx_halloween/web/src/utils/debugData.ts b/[esx_addons]/esx_halloween/web/src/utils/debugData.ts new file mode 100644 index 00000000..ac5520f5 --- /dev/null +++ b/[esx_addons]/esx_halloween/web/src/utils/debugData.ts @@ -0,0 +1,69 @@ +import type { NuiMessage, DebugOptions } from '../types/nui'; +import { isEnvBrowser } from '../types/nui'; + +/** + * Dispatches mock NUI events for testing in browser environment + * Simulates SendNUIMessage calls from FiveM for development purposes + * @template T - Type of data to send + * @param {string} action - The action/event name to trigger + * @param {T} data - Mock data payload to send + * @param {number} delay - Delay in milliseconds before dispatching (default: 100) + * @returns {void} + * @example + * // Simulate showing UI after 1 second + * debugData('showUI', { visible: true }, 1000); + * + * // Simulate player data update + * debugData('setPlayerData', { name: 'TestPlayer', health: 100 }); + */ +export function debugData( + action: string, + data: T, + delay = 100 +): void { + if (!isEnvBrowser()) { + console.warn('[debugData] Only works in browser environment'); + return; + } + + setTimeout(() => { + const message: NuiMessage = { + action, + data, + }; + + window.dispatchEvent( + new MessageEvent('message', { + data: message, + }) + ); + + console.log(`[debugData] Dispatched event: ${action}`, data); + }, delay); +} + +/** + * Sets up debug events on component mount for browser development + * Allows testing NUI functionality without running in FiveM + * @param {DebugOptions} options - Debug configuration + * @param {() => void} callback - Function to execute with debug setup + * @returns {void} + * @example + * setupDebugData({ enabled: true, delay: 1000 }, () => { + * debugData('showUI', {}); + * debugData('setPlayerData', { name: 'Dev', id: 1 }, 2000); + * }); + */ +export function setupDebugData( + options: DebugOptions, + callback: () => void +): void { + if (!options.enabled || !isEnvBrowser()) { + return; + } + + setTimeout(() => { + console.log('[debugData] Setting up debug data...'); + callback(); + }, options.delay || 100); +} diff --git a/[esx_addons]/esx_halloween/web/src/utils/fetchNui.ts b/[esx_addons]/esx_halloween/web/src/utils/fetchNui.ts new file mode 100644 index 00000000..01ff9fa3 --- /dev/null +++ b/[esx_addons]/esx_halloween/web/src/utils/fetchNui.ts @@ -0,0 +1,57 @@ +import type { NuiCallbackResponse } from '../types/nui'; +import { getResourceName, isEnvBrowser } from '../types/nui'; + +/** + * Sends a message to the FiveM game client via NUI callback + * Handles communication between the UI and Lua scripts + * @template T - Expected response data type + * @param {string} eventName - The NUI callback event name registered in Lua + * @param {unknown} data - Data to send with the request + * @returns {Promise} Response from the game client + * @throws {Error} If the request fails or server returns an error + * @example + * // Call a Lua RegisterNUICallback + * const result = await fetchNui<{ success: boolean }>('getPlayerData', { id: 1 }); + * if (result.success) { + * console.log('Player data retrieved'); + * } + */ +export async function fetchNui( + eventName: string, + data: unknown = {} +): Promise { + const resourceName = getResourceName(); + + if (isEnvBrowser()) { + console.warn( + `[fetchNui] Browser environment detected. Event: ${eventName}`, + data + ); + return {} as T; + } + + try { + const response = await fetch(`https://${resourceName}/${eventName}`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json; charset=UTF-8', + }, + body: JSON.stringify(data), + }); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const responseData: NuiCallbackResponse = await response.json(); + + if (!responseData.ok && responseData.error) { + throw new Error(responseData.error); + } + + return responseData.data as T; + } catch (error) { + console.error(`[fetchNui] Error calling ${eventName}:`, error); + throw error; + } +} diff --git a/[esx_addons]/esx_halloween/web/src/utils/soundManager.ts b/[esx_addons]/esx_halloween/web/src/utils/soundManager.ts new file mode 100644 index 00000000..9a9f988a --- /dev/null +++ b/[esx_addons]/esx_halloween/web/src/utils/soundManager.ts @@ -0,0 +1,294 @@ +/** + * Sound Manager + * Centralized audio management for Halloween event sounds + * Handles loading, playing, and stopping audio files with volume control + */ + +/** + * Sound configuration interface + */ +interface SoundConfig { + element: HTMLAudioElement; + volume: number; + duration: number; +} + +/** + * Sound Manager Class + * Manages audio playback for UI events + */ +class SoundManager { + private sounds: Map = new Map(); + private masterVolume: number = 1.0; + + /** + * Load an audio file and cache it + * Preloading ensures sounds play immediately when requested + * + * @param name - Unique identifier for the sound + * @param path - Path to audio file relative to public folder + * @param volume - Volume level (0.0 to 1.0), default 1.0 + */ + load(name: string, path: string, volume: number = 1.0): void { + try { + if (this.sounds.has(name)) { + console.warn(`[SoundManager] Sound "${name}" already loaded, skipping`); + return; + } + + const audio = new Audio(); + audio.src = path; + audio.preload = 'auto'; + audio.volume = Math.min(1.0, Math.max(0.0, volume * this.masterVolume)); + + // Store duration once loaded + audio.addEventListener('loadedmetadata', () => { + const config = this.sounds.get(name); + if (config) { + config.duration = audio.duration * 1000; // Convert to ms + } + }); + + audio.addEventListener('error', () => { + console.error(`[SoundManager] Failed to load sound: ${name} from ${path}`); + }); + + this.sounds.set(name, { + element: audio, + volume: volume, + duration: 0, + }); + } catch (error) { + console.error(`[SoundManager] Error loading sound "${name}":`, error); + } + } + + /** + * Play a loaded sound + * Restarts playback if already playing + * + * @param name - Sound identifier to play + */ + play(name: string): void { + const config = this.sounds.get(name); + + if (!config) { + console.warn(`[SoundManager] Sound not loaded: ${name}`); + return; + } + + try { + // Reset to beginning if already playing + config.element.currentTime = 0; + config.element.play().catch((error) => { + console.warn(`[SoundManager] Failed to play "${name}":`, error); + }); + } catch (error) { + console.error(`[SoundManager] Error playing sound "${name}":`, error); + } + } + + /** + * Stop playback and reset position + * + * @param name - Sound identifier to stop + */ + stop(name: string): void { + const config = this.sounds.get(name); + + if (!config) { + console.warn(`[SoundManager] Sound not loaded: ${name}`); + return; + } + + try { + config.element.pause(); + config.element.currentTime = 0; + } catch (error) { + console.error(`[SoundManager] Error stopping sound "${name}":`, error); + } + } + + /** + * Stop all currently playing sounds + */ + stopAll(): void { + this.sounds.forEach((config) => { + try { + config.element.pause(); + config.element.currentTime = 0; + } catch (error) { + console.error('[SoundManager] Error stopping all sounds:', error); + } + }); + } + + /** + * Set volume for a specific sound + * Volume is multiplied by master volume + * + * @param name - Sound identifier + * @param volume - Volume level (0.0 to 1.0) + */ + setVolume(name: string, volume: number): void { + const config = this.sounds.get(name); + + if (!config) { + console.warn(`[SoundManager] Sound not loaded: ${name}`); + return; + } + + const clampedVolume = Math.min(1.0, Math.max(0.0, volume)); + config.volume = clampedVolume; + config.element.volume = clampedVolume * this.masterVolume; + } + + /** + * Set master volume (affects all sounds) + * Individual sound volumes are multiplied by this value + * + * @param volume - Master volume level (0.0 to 1.0) + */ + setMasterVolume(volume: number): void { + this.masterVolume = Math.min(1.0, Math.max(0.0, volume)); + + // Apply to all loaded sounds + this.sounds.forEach((config) => { + config.element.volume = config.volume * this.masterVolume; + }); + } + + /** + * Get master volume level + * + * @returns Current master volume (0.0 to 1.0) + */ + getMasterVolume(): number { + return this.masterVolume; + } + + /** + * Check if a sound is currently playing + * + * @param name - Sound identifier + * @returns True if sound is playing, false otherwise + */ + isPlaying(name: string): boolean { + const config = this.sounds.get(name); + + if (!config) { + return false; + } + + return !config.element.paused && !config.element.ended; + } + + /** + * Get duration of a loaded sound + * + * @param name - Sound identifier + * @returns Duration in milliseconds, or 0 if not loaded + */ + getDuration(name: string): number { + const config = this.sounds.get(name); + return config?.duration || 0; + } + + /** + * Get current playback position + * + * @param name - Sound identifier + * @returns Current time in milliseconds, or 0 if not loaded + */ + getCurrentTime(name: string): number { + const config = this.sounds.get(name); + return config ? config.element.currentTime * 1000 : 0; + } + + /** + * Set playback position + * + * @param name - Sound identifier + * @param time - Time in milliseconds + */ + setCurrentTime(name: string, time: number): void { + const config = this.sounds.get(name); + + if (!config) { + console.warn(`[SoundManager] Sound not loaded: ${name}`); + return; + } + + config.element.currentTime = time / 1000; + } + + /** + * Remove a sound from cache (cleanup) + * + * @param name - Sound identifier + */ + unload(name: string): void { + const config = this.sounds.get(name); + + if (!config) { + return; + } + + try { + config.element.pause(); + config.element.src = ''; + } catch (error) { + console.error(`[SoundManager] Error unloading sound "${name}":`, error); + } + + this.sounds.delete(name); + } + + /** + * Unload all sounds (cleanup on resource stop) + */ + unloadAll(): void { + this.sounds.forEach((config) => { + try { + config.element.pause(); + config.element.src = ''; + } catch (error) { + console.error('[SoundManager] Error unloading sounds:', error); + } + }); + + this.sounds.clear(); + } + + /** + * Get list of all loaded sound names + * + * @returns Array of sound identifiers + */ + getLoadedSounds(): string[] { + return Array.from(this.sounds.keys()); + } +} + +/** + * Singleton instance + * Use throughout the application via: + * import { soundManager } from './utils/soundManager' + */ +export const soundManager = new SoundManager(); + +/** + * Sound Library Initialization + * Call this once on app startup to preload all sounds + */ +export function initializeSounds(): void { + try { + // Trick-or-Treat sounds + soundManager.load('trick-success', './assets/trick-success.mp3', 0.8); + soundManager.load('trick-trick', './assets/trick-trick.mp3', 0.7); + } catch (error) { + console.error('[SoundManager] Error during initialization:', error); + } +} + +export default soundManager; diff --git a/[esx_addons]/esx_halloween/web/src/utils/useNuiEvent.ts b/[esx_addons]/esx_halloween/web/src/utils/useNuiEvent.ts new file mode 100644 index 00000000..ebea8ca3 --- /dev/null +++ b/[esx_addons]/esx_halloween/web/src/utils/useNuiEvent.ts @@ -0,0 +1,40 @@ +import { onMount, onDestroy } from 'svelte'; +import type { NuiMessage, NuiEventHandler } from '../types/nui'; + +/** + * Svelte hook for listening to NUI events from the FiveM game client + * Automatically registers and cleans up event listeners on component mount/destroy + * @template T - Expected data type for the event + * @param {string} eventType - The event type to listen for (must match SendNUIMessage type) + * @param {NuiEventHandler} handler - Callback function to handle the event data + * @returns {void} + * @example + * // Listen for player data updates from Lua + * useNuiEvent<{ name: string; id: number }>('setPlayerData', (data) => { + * playerName = data.name; + * playerId = data.id; + * }); + * + * // Lua side: + * // SendNUIMessage({ type = 'setPlayerData', name = 'John', id = 1 }) + */ +export function useNuiEvent( + eventType: string, + handler: NuiEventHandler +): void { + const eventListener = (event: MessageEvent>) => { + const { type, ...data } = event.data; + + if (type === eventType) { + handler(data as T); + } + }; + + onMount(() => { + window.addEventListener('message', eventListener as EventListener); + }); + + onDestroy(() => { + window.removeEventListener('message', eventListener as EventListener); + }); +} diff --git a/[esx_addons]/esx_halloween/web/src/utils/visibility.svelte.ts b/[esx_addons]/esx_halloween/web/src/utils/visibility.svelte.ts new file mode 100644 index 00000000..91047aa2 --- /dev/null +++ b/[esx_addons]/esx_halloween/web/src/utils/visibility.svelte.ts @@ -0,0 +1,79 @@ +import { fetchNui } from './fetchNui'; + +/** + * Visibility store using Svelte 5 runes + * Manages UI visibility state and communicates with game client + */ +class VisibilityStore { + private _visible = $state(false); + + /** + * Get current visibility state + * @returns {boolean} + */ + get visible(): boolean { + return this._visible; + } + + /** + * Show the UI + * @returns {void} + */ + show(): void { + this._visible = true; + } + + /** + * Hide the UI and notify game client + * @returns {Promise} + */ + async hide(): Promise { + this._visible = false; + + try { + await fetchNui('hideUI'); + } catch (error) { + console.error('[visibility] Error hiding UI:', error); + } + } + + /** + * Toggle visibility + * @returns {void} + */ + toggle(): void { + if (this._visible) { + this.hide(); + } else { + this.show(); + } + } + + /** + * Set visibility state directly + * @param {boolean} value - Visibility state + * @returns {void} + */ + set(value: boolean): void { + if (value) { + this.show(); + } else { + this.hide(); + } + } +} + +/** + * Global visibility store instance + * @example + * import { visibility } from './utils/visibility'; + * + * // In component + * {#if visibility.visible} + *
UI Content
+ * {/if} + * + * // To hide + * visibility.hide(); + */ +export const visibility = new VisibilityStore(); diff --git a/[esx_addons]/esx_halloween/web/svelte.config.js b/[esx_addons]/esx_halloween/web/svelte.config.js new file mode 100644 index 00000000..96b34554 --- /dev/null +++ b/[esx_addons]/esx_halloween/web/svelte.config.js @@ -0,0 +1,8 @@ +import { vitePreprocess } from '@sveltejs/vite-plugin-svelte' + +/** @type {import("@sveltejs/vite-plugin-svelte").SvelteConfig} */ +export default { + // Consult https://svelte.dev/docs#compile-time-svelte-preprocess + // for more information about preprocessors + preprocess: vitePreprocess(), +} diff --git a/[esx_addons]/esx_halloween/web/tsconfig.app.json b/[esx_addons]/esx_halloween/web/tsconfig.app.json new file mode 100644 index 00000000..31c18cfd --- /dev/null +++ b/[esx_addons]/esx_halloween/web/tsconfig.app.json @@ -0,0 +1,21 @@ +{ + "extends": "@tsconfig/svelte/tsconfig.json", + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + "target": "ES2022", + "useDefineForClassFields": true, + "module": "ESNext", + "types": ["svelte", "vite/client"], + "noEmit": true, + /** + * Typecheck JS in `.svelte` and `.js` files by default. + * Disable checkJs if you'd like to use dynamic types in JS. + * Note that setting allowJs false does not prevent the use + * of JS in `.svelte` files. + */ + "allowJs": true, + "checkJs": true, + "moduleDetection": "force" + }, + "include": ["src/**/*.ts", "src/**/*.js", "src/**/*.svelte"] +} diff --git a/[esx_addons]/esx_halloween/web/tsconfig.json b/[esx_addons]/esx_halloween/web/tsconfig.json new file mode 100644 index 00000000..1ffef600 --- /dev/null +++ b/[esx_addons]/esx_halloween/web/tsconfig.json @@ -0,0 +1,7 @@ +{ + "files": [], + "references": [ + { "path": "./tsconfig.app.json" }, + { "path": "./tsconfig.node.json" } + ] +} diff --git a/[esx_addons]/esx_halloween/web/tsconfig.node.json b/[esx_addons]/esx_halloween/web/tsconfig.node.json new file mode 100644 index 00000000..8a67f62f --- /dev/null +++ b/[esx_addons]/esx_halloween/web/tsconfig.node.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "target": "ES2023", + "lib": ["ES2023"], + "module": "ESNext", + "types": ["node"], + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/[esx_addons]/esx_halloween/web/vite.config.ts b/[esx_addons]/esx_halloween/web/vite.config.ts new file mode 100644 index 00000000..f4bddbbe --- /dev/null +++ b/[esx_addons]/esx_halloween/web/vite.config.ts @@ -0,0 +1,21 @@ +import { defineConfig } from 'vite' +import { svelte } from '@sveltejs/vite-plugin-svelte' + +// https://vite.dev/config/ +export default defineConfig({ + plugins: [svelte()], + base: './', + build: { + outDir: 'build', + emptyOutDir: true, + minify: 'esbuild', + target: 'esnext', + rollupOptions: { + output: { + entryFileNames: 'assets/[name].[hash].js', + chunkFileNames: 'assets/[name].[hash].js', + assetFileNames: 'assets/[name].[hash].[ext]' + } + } + } +})