-- Ghost Menu Ultimate v5.3
-- Universal rig resolver, bone skeleton, weapon silhouette, nameplate scaling,
-- LoS map + prediction cone, minimap grid, ESP Advanced card,
-- Combat Advanced card: ping norm, tick rate comp, flick assist,
-- multi-target suppressor, aim shake dampener, target weight system,
-- anti-aim detector + indicator, anti-lag compensation.

--// Services
local Players      = game:GetService("Players")
local RunService   = game:GetService("RunService")
local UIS          = game:GetService("UserInputService")
local TweenService = game:GetService("TweenService")
local CoreGui      = game:GetService("CoreGui")
local Lighting     = game:GetService("Lighting")
local HttpService  = game:GetService("HttpService")
local Stats        = game:GetService("Stats")
local Workspace    = game:GetService("Workspace")

local lp     = Players.LocalPlayer
local camera = Workspace.CurrentCamera

local GhostTextMain   = Color3.fromRGB(245,245,250)
local GhostTextSub    = Color3.fromRGB(165,165,178)
local GhostControlOn  = Color3.fromRGB(225,225,235)
local GhostControlFill= Color3.fromRGB(210,210,225)
local GhostIcon       = Color3.fromRGB(220,220,232)

if not Drawing then
    warn("Your executor does not support Drawing API.")
end

pcall(function()
    if CoreGui:FindFirstChild("GhostMenu_v5") then CoreGui.GhostMenu_v5:Destroy() end
end)

-- =====================
-- HELPERS
-- =====================
function safeCall(fn,...) local ok,r=pcall(fn,...); if ok then return r end end
function tween(obj,t,props,style,dir) TweenService:Create(obj,TweenInfo.new(t or 0.2,style or Enum.EasingStyle.Quad,dir or Enum.EasingDirection.Out),props):Play() end
function clamp(n,a,b) return math.max(a,math.min(b,n)) end
function lerp(a,b,t) return a+(b-a)*t end
function now() return tick() end
function safePairs(t) if type(t)=="table" then return pairs(t) end; return function() return nil end,nil,nil end
function safeIpairs(t) if type(t)=="table" then return ipairs(t) end; return function() return nil end,nil,nil end
function safeRemove(o) if o then pcall(function() o:Remove() end) end end
local menuBlocksInput

function getChar(p) return p and p.Character end
function getHum(p) local c=getChar(p); return c and c:FindFirstChildOfClass("Humanoid") end
function isAlive(p) local h=getHum(p); local r=getRoot(p); return h and r and h.Health>0 end

-- =====================
-- UNIVERSAL RIG RESOLVER
-- Works on any game: R6, R15, custom blocky, Frontlines, Deadline, etc.
-- No hardcoded part names — resolves dynamically from character geometry.
-- =====================
local rigCache = {} -- [character] = {root,head,parts,bones,timestamp}
local RIG_CACHE_TTL = 2.0

function getAllParts(character)
    local parts = {}
    for _,v in pairs(character:GetDescendants()) do
        if v:IsA("BasePart") and v.Size.Magnitude > 0.1 then
            table.insert(parts, v)
        end
    end
    return parts
end

function getCenterOfMass(parts)
    local sum = Vector3.zero
    local total = 0
    for _,p in ipairs(parts) do
        local m = p:GetMass()
        sum = sum + p.Position * m
        total = total + m
    end
    if total == 0 then return parts[1] and parts[1].Position or Vector3.zero end
    return sum / total
end

function resolveRig(character)
    if not character then return nil end
    local cached = rigCache[character]
    if cached and (now() - cached.timestamp) < RIG_CACHE_TTL then
        return cached
    end

    local parts = getAllParts(character)
    if #parts == 0 then return nil end

    local com = getCenterOfMass(parts)
    local hum = character:FindFirstChildOfClass("Humanoid")

    -- Root: humanoid RootPart first, else part closest to CoM at bottom half
    local root = hum and hum.RootPart
    if not root then
        local bestDist = math.huge
        for _,p in ipairs(parts) do
            local d = (Vector3.new(p.Position.X, com.Y, p.Position.Z) - Vector3.new(com.X, com.Y, com.Z)).Magnitude
            if d < bestDist then bestDist = d; root = p end
        end
    end

    -- Head: part furthest up from CoM (highest Y)
    local head = nil
    local highestY = -math.huge
    for _,p in ipairs(parts) do
        if p.Position.Y > highestY then
            highestY = p.Position.Y
            head = p
        end
    end

    -- Aim part: for aim purposes, prefer head, else torso region
    -- Torso = part closest to CoM
    local torso = root
    local bestTorsoDist = math.huge
    for _,p in ipairs(parts) do
        local d = (p.Position - com).Magnitude
        if d < bestTorsoDist then bestTorsoDist = d; torso = p end
    end

    -- Build bone connections: proximity graph
    -- Connect each part to its nearest neighbor (MST-like, practical for drawing)
    local bones = {}
    local used = {}
    -- Sort parts top to bottom for cleaner skeleton
    table.sort(parts, function(a,b) return a.Position.Y > b.Position.Y end)
    for i,p in ipairs(parts) do
        local bestDist = math.huge
        local bestPart = nil
        for j,q in ipairs(parts) do
            if i ~= j then
                local d = (p.Position - q.Position).Magnitude
                if d < bestDist and d < 8.0 then -- max bone length 8 studs
                    bestDist = d
                    bestPart = q
                end
            end
        end
        if bestPart then
            -- avoid duplicate pairs
            local key = tostring(math.min(i,#parts)) .. "_" .. tostring(math.max(i,#parts))
            if not used[key] then
                used[key] = true
                table.insert(bones, {p, bestPart})
            end
        end
    end

    local rig = {
        root     = root,
        head     = head,
        torso    = torso,
        parts    = parts,
        bones    = bones,
        com      = com,
        hum      = hum,
        timestamp= now(),
    }
    rigCache[character] = rig
    return rig
end

function getRoot(p)
    local c = getChar(p); if not c then return nil end
    local rig = resolveRig(c)
    return rig and rig.root
end

function getTeamValue(p)
    if not p then return nil end
    if p.Team then return "Team:"..tostring(p.Team) end
    if p.TeamColor then return "TeamColor:"..tostring(p.TeamColor) end
    local c = p.Character
    if c then
        local a = c:GetAttribute("Team") or c:GetAttribute("TeamName") or c:GetAttribute("Faction") or c:GetAttribute("Side")
        if a then return "Attr:"..tostring(a) end
    end
    return nil
end

function isTeammate(p)
    if not p or p == lp then return true end
    if not Config or not Config.TeamCheck then return false end
    local mine  = getTeamValue(lp)
    local theirs= getTeamValue(p)
    return mine ~= nil and theirs ~= nil and mine == theirs
end

function teamDebugSummary()
    local mine = getTeamValue(lp) or "none"
    local friendly,other,unknown = 0,0,0
    for _,p in safeIpairs(Players:GetPlayers()) do
        if p ~= lp then
            local tv = getTeamValue(p)
            if not tv then unknown=unknown+1
            elseif isTeammate(p) then friendly=friendly+1
            else other=other+1 end
        end
    end
    return "Local: "..mine.." | Friendly: "..friendly.." | Other: "..other.." | Unknown: "..unknown
end

function getPing()
    local ok,p = pcall(function() return math.floor(Stats.Network.ServerStatsItem["Data Ping"]:GetValue()) end)
    return ok and p or 0
end

-- =====================
-- CONFIG
-- =====================
local Config = {
    MenuKey="F1", PanicKey="End", Hidden=false,
    TeamCheck=true, WallCheck=true, DistanceLimit=5000,

    ESP=true, BoxESP=false, BoxStyle="Full",
    NameESP=true, HealthESP=true, DistanceESP=true,
    ReadableDetails=true, DetailMaxDistance=1800,
    ESPStackMode="Clean", ESPNameOptional=true,
    SkeletonMode="Bone", -- "Bone" | "Dots" | "Both"
    SkeletonDistanceFade=true,
    ArrowSmartStack=true, ArrowLabelMode="Distance",
    ArrowVerticalHints=true, ArrowVerticalThreshold=18,
    TracerESP=false, SkeletonESP=true, Chams=false,
    OffscreenArrows=true, ArrowSize=24, ArrowPadding=90,
    ArrowThreatPulse=true, TopIndicators=false, TargetInfo=true,

    -- ESP Advanced
    WeaponSilhouette=false,
    NameplateDistanceScale=true,
    NameplateMaxSize=16,
    NameplateMinSize=9,
    NameplateScaleRange=800,
    LOSMapLines=true,
    LOSPredictionCone=true,
    LOSConeLength=28,
    LOSConeAngle=22,
    LOSLineLength=20,

    -- Combat
    Aimbot=false, AimKey="MouseButton2",
    AimPart="Auto", -- Auto uses universal resolver
    AimFOV=30, AimSmooth=1,
    AimPrediction=false, AimPredictionScale=0.5,
    AimHumanize=true, AimDeadzone=0,
    AimAssist=true, AimAssistStrength=0.25,
    SmartAimPart=true,

    SilentAim=false, SilentFOV=100, SilentHitChance=100,
    TriggerBot=true, TriggerFOV=20, TriggerFOVVisible=true,
    TriggerDelay=0.05, TriggerHold=true,
    AutoPistol=false, NoRecoil=false,

    -- Combat Advanced
    PingNormalize=false,
    TickRateCompensate=false,
    FlickAssist=false,
    FlickAssistStrength=0.7,
    MultiTargetSuppressor=false,
    MultiTargetRadius=3,
    AimShakeDampener=false,
    AimShakeDampStrength=0.6,
    TargetWeightSystem=true,
    TargetWeightShootable=true,
    TargetWeightDistance=true,
    TargetWeightHealth=true,
    AntiAimDetect=true,
    AntiAimIndicator=true,
    AntiLag=false,
    AntiLagMode="Compensate", -- "Compensate" | "Match"
    AntiLagOffset=0,

    Crosshair=true, CrosshairGap=6, CrosshairLength=10,
    CrosshairThickness=2, CrosshairDot=true, CrosshairSpin=false,
    CrosshairProfile="Classic", CrosshairShootPulse=true,
    CrosshairDynamicGap=true, CrosshairAutoSize=true,
    ScreenAutoScale=true, ScreenScaleMin=0.72, ScreenScaleMax=1.08,
    CrosshairColor=Color3.fromRGB(210,120,255),

    FOVVisible=true, FOVRainbow=false, FOVThickness=1, FOVTransparency=1,
    TargetLockCircle=true,
    UIAccent=Color3.fromRGB(170,80,255),
    Glass=true, SoftStrokes=true, ShowPreviews=true, ApplyPopups=true,
    ThemePreset="Bright Phantom",
    GhostBackground=true, GhostBackgroundOpacity=0.68,
    GhostBackgroundImage="rbxassetid://136190147927115",
    BackgroundMode="Ghost Drift", IdleGlow=true, TabTransition="Fade",
    ProfileCard=false, DockMode=true, DockCompact=true,
    DockAutoHide=true, DockAutoHideDelay=3, DockWakeDistance=90,
    SearchSpotlight=true, HoverHelp=true, HoverHelpDelay=0.35,
    ScannerMaxResults=80, ScannerShowPaths=true,
    ScannerPassiveOnly=true, AggressiveScanDepth=true,
    AggressiveRiskScoring=true, ScannerWatch=false, ScannerWatchInterval=2,
    ScannerGuideMode=true, GhostParallax=true, GhostParallaxStrength=0.025,
    ThemeAutoImage=false, AutoSaveSettings=true, AutoLoadSettings=true,
    SettingsFile="GhostMenu/settings.json",
    ReloadPredictor=true, ReloadStateHUD=true,
    OpponentStateDebug=true, OpponentStateDetails=true,
    OpponentStateSource=true, OpponentPushDebug=true,
    SurroundMapHUD=true, BlindSpotMonitor=true,
    SurroundRadius=120, BlindSpotRadius=80, BlindSpotAngle=120,
    OpponentPushDetails=true, PushSpeedThreshold=8,
    ReloadStateDetails=true, ReloadWarnLowAmmo=true,
    Resolver=true,
    MiniMap=false, MiniMapSize=160, MiniMapOpacity=0.55,
    MiniMapZoom=2, MiniMapTeam=true, MiniMapLabels=false,
    MiniMapTrail=true, MiniMapWaypoints=false,
    MiniMapGrid=true, MiniMapGridSpacing=20,
}

-- =====================
-- SETTINGS AUTOSAVE
-- =====================
local ghostRuntimeOnlyKeys={Hidden=true,ESP=true}

function ghostCanUseFiles() return type(writefile)=="function" and type(readfile)=="function" end
local ghostLastSettingsPath=""; local ghostLastSaveStatus="not saved yet"

function ghostSettingsPaths()
    local p=Config.SettingsFile or "GhostMenu/settings.json"
    local t={p}
    if p~="GhostMenu/settings.json" then table.insert(t,"GhostMenu/settings.json") end
    if p~="GhostMenu_settings.json" then table.insert(t,"GhostMenu_settings.json") end
    if p~="settings.json" then table.insert(t,"settings.json") end
    return t
end

function ghostConfigToSaveTable()
    local copy={}
    for k,v in safePairs(Config) do
        if not ghostRuntimeOnlyKeys[k] and type(v)~="function" then
            if typeof(v)=="Color3" then copy[k]={__color=true,r=v.R,g=v.G,b=v.B}
            else copy[k]=v end
        end
    end
    copy.__FavoriteKeys={}
    if FavoriteKeys then for k,v in safePairs(FavoriteKeys) do copy.__FavoriteKeys[k]=v end end
    copy.__SavedLayout=SavedLayout
    copy.__ghost_version="v5.3"
    copy.__settings_locked=false
    return copy
end

function ghostApplySaveTable(data)
    if type(data)~="table" then return false end
    for k,v in safePairs(data) do
        if type(k)=="string" and string.sub(k,1,2)~="__" and Config[k]~=nil and not ghostRuntimeOnlyKeys[k] then
            if type(v)=="table" and v.__color then
                Config[k]=Color3.new(tonumber(v.r) or 1,tonumber(v.g) or 1,tonumber(v.b) or 1)
            else Config[k]=v end
        end
    end
    if type(data.__FavoriteKeys)=="table" then
        FavoriteKeys=FavoriteKeys or {}
        for k,v in safePairs(data.__FavoriteKeys) do FavoriteKeys[k]=v end
    end
    if type(data.__SavedLayout)=="table" then
        SavedLayout=SavedLayout or {}
        for k,v in safePairs(data.__SavedLayout) do SavedLayout[k]=v end
    end
    if favoriteStarButtons then for k,_ in safePairs(FavoriteKeys or {}) do pcall(function() favoriteRefreshKey(k) end) end end
    if rebuildFavorites then pcall(rebuildFavorites) end
    return true
end

function ghostEnsureSettingsFolder()
    if type(makefolder)=="function" then
        pcall(function()
            if type(isfolder)=="function" then
                if not isfolder("GhostMenu") then makefolder("GhostMenu") end
            else makefolder("GhostMenu") end
        end)
    end
end

function ghostSaveConfigFile(silent)
    if not ghostCanUseFiles() then ghostLastSaveStatus="failed: no writefile"; if not silent then print("Ghost: no writefile") end; return false end
    ghostEnsureSettingsFolder()
    local ok,json=pcall(function() return HttpService:JSONEncode(ghostConfigToSaveTable()) end)
    if not ok then ghostLastSaveStatus="failed: JSON encode"; return false end
    for _,path in safeIpairs(ghostSettingsPaths()) do
        local wrote=pcall(function() writefile(path,json) end)
        if wrote then ghostLastSettingsPath=path; ghostLastSaveStatus="saved: "..path; Config.SettingsFile=path; if not silent then print("Ghost saved: "..path) end; return true end
    end
    ghostLastSaveStatus="failed: all paths rejected"; return false
end

function ghostLoadConfigFile(silent)
    if not ghostCanUseFiles() then return false end
    for _,path in safeIpairs(ghostSettingsPaths()) do
        local exists=true
        if type(isfile)=="function" then exists=isfile(path) end
        if exists then
            local ok,txt=pcall(function() return readfile(path) end)
            if ok and type(txt)=="string" and txt~="" then
                local dok,data=pcall(function() return HttpService:JSONDecode(txt) end)
                if dok and type(data)=="table" and data.__settings_locked==false and ghostApplySaveTable(data) then
                    ghostLastSettingsPath=path; ghostLastSaveStatus="loaded: "..path; Config.SettingsFile=path
                    if not silent then print("Ghost loaded: "..path) end; return true
                end
            end
        end
    end
    return false
end

local ghostAutoSaveQueued=false
function ghostScheduleAutoSave()
    if not Config.AutoSaveSettings or ghostAutoSaveQueued then return end
    ghostAutoSaveQueued=true
    task.delay(0.75,function() ghostAutoSaveQueued=false; if Config.AutoSaveSettings and not ghostUnloaded then ghostSaveConfigFile(true) end end)
end

if Config.AutoLoadSettings then pcall(function() ghostLoadConfigFile(true) end) end
task.spawn(function() while task.wait(5) do if Config and Config.AutoSaveSettings and not ghostUnloaded then pcall(function() ghostSaveConfigFile(true) end) end end end)

-- =====================
-- STATE
-- =====================
local ESPObjects={}
local CHLines={}
local CHDot=nil
local FOVCircle=nil; local SACircle=nil; local TriggerCircle=nil
local AimCircle=nil; local AimLine=nil; local ReloadLbl=nil
local OffArrows={}
local resolverData={}
local trackedHumanoids={}
local lastShot=0; local triggerHolding=false
local menuVisible=true; local dragging=false; local currentTab="Combat"
local aimAssistConn=nil; local autoPistolConn=nil; local recoilConn=nil
local reloadConnections={}
local lastTarget=nil; local arrowBuckets={}
local topInfoText=nil; local topInfoShadow=nil
local targetInfoText=nil; local targetInfoShadow=nil
local surroundText=nil; local surroundShadow=nil
local blindText=nil; local blindShadow=nil
local TargetLock=nil
local MiniMapFrame=nil; local MiniMapDots={}; local MiniMapWaypoints={}
local MiniMapTrail={}; local lastMiniMapTrail=0; local MiniMapDragging=false
-- LoS map drawings pool
local LOSLines={}; local LOSCones={}
local ghostUnloaded=false; local mouseDown=false
-- Anti-aim detection state
local antiAimData={} -- [player]={lastYaws={},spinDetected=false,flipDetected=false}
local antiAimIndicator=nil -- Drawing.Text
-- Aim shake dampener
local camHistory={}; local CAM_HISTORY=8
-- Tick rate detection
local tickRateSmooth=60; local tickRateSamples={}
-- Flick state
local flickActive=false; local flickTarget=nil; local flickDone=false

menuBlocksInput=function() return ghostUnloaded or menuVisible or Config.Hidden end

-- =====================
-- DRAWING FACTORY
-- =====================
function dnew(class,props)
    if not Drawing then return nil end
    local o=Drawing.new(class)
    for k,v in safePairs(props or {}) do o[k]=v end
    return o
end

function worldToScreen(pos)
    local v,on=camera:WorldToViewportPoint(pos)
    return Vector2.new(v.X,v.Y),on,v.Z
end

function visibleFromCamera(part,ignore)
    if not Config.WallCheck then return true end
    if not part then return false end
    local origin=camera.CFrame.Position
    local dir=part.Position-origin
    local params=RaycastParams.new()
    params.FilterType=Enum.RaycastFilterType.Blacklist
    params.FilterDescendantsInstances={lp.Character,ignore}
    params.IgnoreWater=true
    local hit=Workspace:Raycast(origin,dir,params)
    return (not hit) or hit.Instance:IsDescendantOf(ignore)
end

function validEnemy(p)
    if not p or p==lp then return false end
    if Config.TeamCheck and isTeammate(p) then return false end
    if not isAlive(p) then return false end
    local r=getRoot(p)
    if not r then return false end
    if (r.Position-camera.CFrame.Position).Magnitude>Config.DistanceLimit then return false end
    return true
end

-- =====================
-- TARGET WEIGHT SYSTEM
-- Priority: shootable + distance, then health
-- =====================
function scoreTarget(p, part, screenDist)
    if not Config.TargetWeightSystem then return -screenDist end
    local score = 0
    local ch = getChar(p)
    -- 1. Shootable (wall check) - highest weight
    if Config.TargetWeightShootable then
        if visibleFromCamera(part, ch) then score = score + 10000 end
    end
    -- 2. Distance - second weight (closer = higher score)
    if Config.TargetWeightDistance then
        local r = getRoot(p)
        local worldDist = r and (r.Position - camera.CFrame.Position).Magnitude or 9999
        score = score + (Config.DistanceLimit - worldDist) * 5
    end
    -- 3. Health - third weight (lower health = higher score, easier kill)
    if Config.TargetWeightHealth then
        local hum = getHum(p)
        if hum then
            local hpPct = hum.Health / math.max(hum.MaxHealth, 1)
            score = score + (1 - hpPct) * 1000
        end
    end
    return score
end

-- =====================
-- UNIVERSAL AIM PART
-- Finds best aim target on any rig
-- =====================
function getAimPart(p)
    local ch = getChar(p); if not ch then return nil end
    local rig = resolveRig(ch); if not rig then return nil end
    if Config.AimPart == "Head" then return rig.head or rig.torso end
    if Config.AimPart == "Torso" then return rig.torso end
    if Config.AimPart == "Root" then return rig.root end
    -- Auto: prefer head if visible, else torso, else any visible part
    local mouse = UIS:GetMouseLocation()
    local candidates = {
        {part=rig.head,   priority=3},
        {part=rig.torso,  priority=2},
        {part=rig.root,   priority=1},
    }
    local best, bestScore = nil, -math.huge
    for _,c in ipairs(candidates) do
        if c.part then
            local visible = visibleFromCamera(c.part, ch)
            local scr,on,z = worldToScreen(c.part.Position)
            if on and z > 0 then
                local screenDist = (scr - mouse).Magnitude
                local s = c.priority * 100 + (visible and 500 or 0) - screenDist
                if s > bestScore then bestScore=s; best=c.part end
            end
        end
    end
    if best then return best end
    -- Fallback: scan all parts
    local bestPart, bestDist = nil, math.huge
    for _,part in ipairs(rig.parts) do
        if visibleFromCamera(part, ch) then
            local scr,on,z = worldToScreen(part.Position)
            if on and z > 0 then
                local d = (scr - mouse).Magnitude
                if d < bestDist then bestDist=d; bestPart=part end
            end
        end
    end
    return bestPart or rig.root
end

function predictedPos(p, part, scale)
    if not part then return nil end
    local basePos = part.Position
    -- Anti-lag: offset position to compensate for latency
    if Config.AntiLag and part:IsA("BasePart") then
        local vel = part.AssemblyLinearVelocity or Vector3.zero
        local ping = getPing() / 1000
        if Config.AntiLagMode == "Compensate" then
            -- Lead target by ping time to compensate for your shot delay
            basePos = basePos + vel * ping
        elseif Config.AntiLagMode == "Match" then
            -- Match lag: aim where they WERE ping ms ago (for server-side hit reg)
            basePos = basePos - vel * ping
        end
        -- Additional manual offset
        if Config.AntiLagOffset ~= 0 then
            basePos = basePos + vel.Unit * Config.AntiLagOffset * 0.01
        end
    end
    if not Config.AimPrediction then return basePos end
    local vel = part.AssemblyLinearVelocity or Vector3.zero
    local ping = getPing() / 1000
    -- Tick rate compensation: adjust prediction step to match server tick
    local tickAdj = 1.0
    if Config.TickRateCompensate and tickRateSmooth > 0 then
        tickAdj = 60 / tickRateSmooth
    end
    return basePos + vel * ((scale or Config.AimPredictionScale) + ping * 0.5) * tickAdj
end

-- =====================
-- CLOSEST TARGET (weighted)
-- =====================
function closestTarget(fov, needVisible)
    local best, bestScore, bestPart = nil, -math.huge, nil
    local mouse = UIS:GetMouseLocation()
    for _,p in safeIpairs(Players:GetPlayers()) do
        if validEnemy(p) then
            local part = getAimPart(p)
            if part and (not needVisible or visibleFromCamera(part, getChar(p))) then
                local pos = predictedPos(p, part)
                if pos then
                    local scr,on,z = worldToScreen(pos)
                    if on and z > 0 then
                        local screenDist = (scr - mouse).Magnitude
                        if screenDist < (fov or Config.AimFOV) then
                            local s = scoreTarget(p, part, screenDist)
                            if s > bestScore then
                                bestScore=s; best=p; bestPart=part
                            end
                        end
                    end
                end
            end
        end
    end
    return best, bestPart, bestScore
end

-- =====================
-- ANTI-AIM DETECTION
-- =====================
local ANTIAIM_SPIN_THRESHOLD = math.rad(90)  -- >90 deg/frame = spinbot
local ANTIAIM_FLIP_THRESHOLD = math.rad(150) -- >150 deg flip = flip bot
local ANTIAIM_HISTORY = 6

function updateAntiAimDetection()
    if not Config.AntiAimDetect then
        for _,p in pairs(Players:GetPlayers()) do
            if antiAimData[p] then antiAimData[p] = nil end
        end
        return
    end
    for _,p in safeIpairs(Players:GetPlayers()) do
        if p ~= lp and validEnemy(p) then
            local ch = getChar(p)
            local rig = ch and resolveRig(ch)
            if rig and rig.root then
                local _,yaw,_ = rig.root.CFrame:ToEulerAnglesYXZ()
                local data = antiAimData[p] or {lastYaws={}, spinDetected=false, flipDetected=false, timer=0}
                table.insert(data.lastYaws, yaw)
                if #data.lastYaws > ANTIAIM_HISTORY then table.remove(data.lastYaws, 1) end
                -- Detect spin: large yaw delta each frame
                if #data.lastYaws >= 2 then
                    local delta = math.abs(data.lastYaws[#data.lastYaws] - data.lastYaws[#data.lastYaws-1])
                    -- Normalize to 0-pi
                    while delta > math.pi do delta = delta - math.pi*2 end
                    delta = math.abs(delta)
                    data.spinDetected = delta > ANTIAIM_SPIN_THRESHOLD
                    data.flipDetected = delta > ANTIAIM_FLIP_THRESHOLD
                end
                data.timer = now()
                antiAimData[p] = data
            end
        end
    end
    -- Cleanup stale
    for p,_ in safePairs(antiAimData) do
        if not Players:FindFirstChild(p.Name) then antiAimData[p] = nil end
    end
end

function isAntiAiming(p)
    local d = antiAimData[p]
    return d and (d.spinDetected or d.flipDetected)
end

function getAntiAimType(p)
    local d = antiAimData[p]
    if not d then return nil end
    if d.flipDetected then return "FLIP" end
    if d.spinDetected then return "SPIN" end
    return nil
end

-- =====================
-- AIM SHAKE DAMPENER
-- Smooths out game-induced camera shake before aim runs
-- =====================
function getDampenedCameraLook()
    local cf = camera.CFrame
    table.insert(camHistory, cf)
    if #camHistory > CAM_HISTORY then table.remove(camHistory, 1) end
    if not Config.AimShakeDampener or #camHistory < 3 then return cf end
    -- Average look vectors to remove high-frequency shake
    local avgLook = Vector3.zero
    local avgPos  = Vector3.zero
    local w = 0
    for i,c in ipairs(camHistory) do
        local weight = i -- more recent = higher weight
        avgLook = avgLook + c.LookVector * weight
        avgPos  = avgPos  + c.Position  * weight
        w = w + weight
    end
    avgLook = (avgLook / w)
    avgPos  = avgPos  / w
    local strength = Config.AimShakeDampStrength or 0.6
    local smoothedLook = cf.LookVector:Lerp(avgLook, strength)
    if smoothedLook.Magnitude < 0.001 then return cf end
    return CFrame.new(cf.Position, cf.Position + smoothedLook.Unit)
end

-- =====================
-- TICK RATE DETECTOR
-- =====================
local lastTickSample = now()
RunService.Heartbeat:Connect(function()
    local t = now()
    local dt = t - lastTickSample
    lastTickSample = t
    table.insert(tickRateSamples, dt)
    if #tickRateSamples > 60 then table.remove(tickRateSamples, 1) end
    if #tickRateSamples > 10 then
        local avg = 0
        for _,v in ipairs(tickRateSamples) do avg = avg + v end
        avg = avg / #tickRateSamples
        tickRateSmooth = tickRateSmooth * 0.9 + (1/math.max(avg,0.001)) * 0.1
    end
end)

-- =====================
-- DRAWING SETUP
-- =====================
if Drawing then
    FOVCircle    = dnew("Circle",{Radius=Config.SilentFOV,Thickness=1,Color=GhostControlFill,Transparency=0.55,Filled=false,Visible=true})
    SACircle     = dnew("Circle",{Radius=Config.AimFOV,Thickness=1,Color=Color3.fromRGB(90,220,255),Transparency=0.35,Filled=false,Visible=true})
    TriggerCircle= dnew("Circle",{Radius=Config.TriggerFOV,Thickness=1,Color=Color3.fromRGB(255,190,70),Transparency=0.45,Filled=false,Visible=false})
    AimCircle    = dnew("Circle",{Radius=3,Thickness=1,Color=Color3.fromRGB(255,255,255),Transparency=1,Filled=true,Visible=false})
    AimLine      = dnew("Line",  {Thickness=1,Color=Color3.fromRGB(255,255,255),Transparency=0.7,Visible=false})
    TargetLock   = dnew("Circle",{Radius=8,Thickness=2,Color=Color3.fromRGB(90,255,150),Transparency=0.85,Filled=false,Visible=false})
    topInfoShadow= dnew("Text",  {Text="",Size=13,Center=false,Outline=false,Color=Color3.fromRGB(0,0,0),Transparency=0.75,Visible=false})
    topInfoText  = dnew("Text",  {Text="",Size=13,Center=false,Outline=true, Color=Color3.fromRGB(235,235,255),Transparency=1,Visible=false})
    targetInfoShadow=dnew("Text",{Text="",Size=12,Center=true,Outline=false,Color=Color3.fromRGB(0,0,0),Transparency=0.75,Visible=false})
    targetInfoText  =dnew("Text",{Text="",Size=12,Center=true,Outline=true, Color=Color3.fromRGB(235,255,235),Transparency=1,Visible=false})
    ReloadLbl    = dnew("Text",  {Text="",Size=16,Center=true,Outline=true, Color=Color3.fromRGB(255,210,90),Visible=false})
    CHDot        = dnew("Circle",{Radius=2,Filled=true,Color=Config.CrosshairColor,Transparency=1,Visible=true})
    for i=1,4 do CHLines[i]=dnew("Line",{Thickness=Config.CrosshairThickness,Color=Config.CrosshairColor,Transparency=1,Visible=true}) end
    antiAimIndicator = dnew("Text",{Text="",Size=13,Center=true,Outline=true,Color=Color3.fromRGB(255,80,80),Visible=false})
    -- Pre-allocate LoS line pool (one per player)
    for i=1,20 do
        LOSLines[i]  = dnew("Line",    {Visible=false,Thickness=1,Color=Color3.fromRGB(255,255,80),Transparency=0.55})
        LOSCones[i]  = dnew("Triangle",{Visible=false,Filled=true,Color=Color3.fromRGB(80,255,120),Transparency=0.82})
    end
end

-- =====================
-- ESP OBJECT FUNCTIONS
-- =====================
function newESP(p)
    if ESPObjects[p] then return ESPObjects[p] end
    local o = {BoneLines={}, DotCircles={}, Highlights={}}
    if Drawing then
        o.Box     = dnew("Square",{Thickness=1,Color=Color3.fromRGB(255,255,255),Transparency=1,Filled=false,Visible=false})
        o.BoxFill = dnew("Square",{Thickness=1,Color=Color3.fromRGB(0,0,0),Transparency=0.15,Filled=true,Visible=false})
        o.Name    = dnew("Text",  {Size=14,Center=true,Outline=true,Color=Color3.new(1,1,1),Transparency=0.92,Visible=false})
        o.Health  = dnew("Line",  {Thickness=3,Color=Color3.fromRGB(70,255,120),Visible=false})
        o.Tracer  = dnew("Line",  {Thickness=1,Color=GhostControlFill,Transparency=0.75,Visible=false})
        o.Distance= dnew("Text",  {Size=13,Center=true,Outline=true,Color=Color3.fromRGB(210,210,210),Transparency=0.92,Visible=false})
        o.AntiAimLbl = dnew("Text",{Size=12,Center=true,Outline=true,Color=Color3.fromRGB(255,80,80),Visible=false})
        -- Bone lines (max 30 per player for complex rigs)
        for i=1,30 do
            o.BoneLines[i] = dnew("Line",{Visible=false,Thickness=1,Color=Color3.fromRGB(0,255,80),Transparency=0.55})
        end
        -- Dot circles (max 20 per player)
        for i=1,20 do
            o.DotCircles[i] = dnew("Circle",{Radius=4,Filled=true,NumSides=18,Color=Color3.fromRGB(0,255,80),Transparency=1,Visible=false})
        end
        -- Weapon silhouette (2 lines = simple L shape representing tool)
        o.WepSil = {}
        for i=1,4 do
            o.WepSil[i] = dnew("Line",{Visible=false,Thickness=2,Color=Color3.fromRGB(255,200,80),Transparency=0.65})
        end
        -- Arrow drawings
        o.ArrowShadow = dnew("Triangle",{Color=Color3.fromRGB(0,0,0),Filled=true,Transparency=0.35,Visible=false})
        o.ArrowGlow   = dnew("Triangle",{Color=Color3.fromRGB(70,255,120),Filled=true,Transparency=0.18,Visible=false})
        o.Arrow       = dnew("Triangle",{Color=Color3.fromRGB(70,255,120),Filled=true,Transparency=0.92,Visible=false})
        o.ArrowRidge  = dnew("Line",    {Thickness=1,Color=Color3.fromRGB(255,255,255),Transparency=0.7,Visible=false})
        o.ArrowLabel  = dnew("Text",    {Text="",Size=13,Center=true,Outline=true,Color=Color3.fromRGB(230,255,235),Transparency=0.88,Visible=false})
        o.ArrowSmooth = nil
    end
    ESPObjects[p] = o
    return o
end

function hideAll(o)
    if not o then return end
    for _,v in safePairs(o) do
        if type(v)=="table" then
            for _,x in safePairs(v) do pcall(function() if x and x.Visible~=nil then x.Visible=false end end) end
        else pcall(function() if v and v.Visible~=nil then v.Visible=false end end) end
    end
end

function removeESP(p)
    local o=ESPObjects[p]; if not o then return end
    hideAll(o)
    for _,v in safePairs(o) do
        if type(v)=="table" then for _,x in safePairs(v) do safeRemove(x) end
        else safeRemove(v) end
    end
    ESPObjects[p]=nil
end

function hideArrow(o)
    if not o then return end
    for _,k in ipairs({"Arrow","ArrowShadow","ArrowGlow","ArrowRidge","ArrowLabel"}) do
        if o[k] then o[k].Visible=false end
    end
end

function visualFadeByDistance(dist)
    if not dist or dist<250 then return 1 end
    return clamp(1-((dist-250)/1600),0.28,1)
end

function ghostDistanceColor(dist)
    dist=tonumber(dist) or 9999
    if dist<=100 then return Color3.fromRGB(255,60,70) end
    if dist<=250 then return Color3.fromRGB(255,170,55) end
    if dist<=600 then return Color3.fromRGB(210,255,80) end
    return Color3.fromRGB(70,255,120)
end

function boxFromCharacter(ch)
    local cf,size = ch:GetBoundingBox()
    local corners = {}
    for x=-1,1,2 do for y=-1,1,2 do for z=-1,1,2 do
        table.insert(corners,(cf*CFrame.new(size.X/2*x,size.Y/2*y,size.Z/2*z)).Position)
    end end end
    local minX,minY,maxX,maxY = math.huge,math.huge,-math.huge,-math.huge
    local any=false
    for _,p in safeIpairs(corners) do
        local s,on=worldToScreen(p)
        if on then any=true end
        minX=math.min(minX,s.X);minY=math.min(minY,s.Y);maxX=math.max(maxX,s.X);maxY=math.max(maxY,s.Y)
    end
    return Vector2.new(minX,minY),Vector2.new(maxX-minX,maxY-minY),any
end

-- =====================
-- BONE SKELETON DRAW
-- Universal — uses rig.bones from resolver
-- =====================
function drawBoneSkeleton(p, o, dist)
    local ch = getChar(p)
    if not ch or not o or not Config.SkeletonESP or menuBlocksInput() then
        for _,l in ipairs(o.BoneLines) do l.Visible=false end
        return
    end
    local rig = resolveRig(ch)
    if not rig then
        for _,l in ipairs(o.BoneLines) do l.Visible=false end
        return
    end
    local fade = Config.SkeletonDistanceFade and visualFadeByDistance(dist) or 1
    local bones = rig.bones
    local mode = Config.SkeletonMode or "Bone"

    -- Draw bone lines
    local lineIdx = 0
    if mode == "Bone" or mode == "Both" then
        for _,bone in ipairs(bones) do
            lineIdx = lineIdx + 1
            if lineIdx > #o.BoneLines then break end
            local pA, pB = bone[1], bone[2]
            local sA, onA, zA = worldToScreen(pA.Position)
            local sB, onB, zB = worldToScreen(pB.Position)
            local line = o.BoneLines[lineIdx]
            if onA and onB and zA > 0 and zB > 0 then
                local visA = visibleFromCamera(pA, ch)
                local visB = visibleFromCamera(pB, ch)
                line.From  = sA
                line.To    = sB
                line.Color = (visA and visB) and Color3.fromRGB(0,255,80) or Color3.fromRGB(255,60,70)
                line.Transparency = clamp(0.55 + (1-fade)*0.3, 0.3, 0.85)
                line.Thickness = 1
                line.Visible = true
            else
                line.Visible = false
            end
        end
    end
    for i=lineIdx+1,#o.BoneLines do o.BoneLines[i].Visible=false end

    -- Draw dot circles
    local dotIdx = 0
    if mode == "Dots" or mode == "Both" then
        for _,part in ipairs(rig.parts) do
            dotIdx = dotIdx + 1
            if dotIdx > #o.DotCircles then break end
            local scr,on,z = worldToScreen(part.Position)
            local dot = o.DotCircles[dotIdx]
            if on and z > 0 then
                local vis = visibleFromCamera(part, ch)
                dot.Position    = scr
                dot.Radius      = clamp(4.3 - z*0.006, 1.1, 4.3)
                dot.Color       = vis and Color3.fromRGB(0,255,80) or Color3.fromRGB(255,60,70)
                dot.Transparency= vis and clamp(0.92+(fade-1)*0.25,0.65,1) or clamp(0.72+(fade-1)*0.25,0.45,0.9)
                dot.Visible     = true
            else dot.Visible = false end
        end
    end
    for i=dotIdx+1,#o.DotCircles do o.DotCircles[i].Visible=false end
end

-- =====================
-- WEAPON SILHOUETTE
-- Draws a small L-shape icon near the player to represent held tool
-- =====================
function drawWeaponSilhouette(p, o, screenPos, screenSize)
    if not Config.WeaponSilhouette then
        if o.WepSil then for _,l in ipairs(o.WepSil) do l.Visible=false end end
        return
    end
    if not o.WepSil then return end
    local ch = getChar(p)
    local tool = nil
    if ch then for _,v in pairs(ch:GetChildren()) do if v:IsA("Tool") then tool=v;break end end end
    if not tool then
        for _,l in ipairs(o.WepSil) do l.Visible=false end
        return
    end
    -- Draw simple silhouette at bottom-right of bounding box
    -- Shape: stock | barrel — two lines making a gun profile
    local ox = screenPos.X + screenSize.X/2 + 8
    local oy = screenPos.Y + screenSize.Y - 4
    local col = Color3.fromRGB(255,200,80)
    -- Barrel (horizontal)
    o.WepSil[1].From=Vector2.new(ox,oy);       o.WepSil[1].To=Vector2.new(ox+14,oy)
    o.WepSil[1].Color=col;                      o.WepSil[1].Visible=true
    -- Stock (angled down-left)
    o.WepSil[2].From=Vector2.new(ox,oy);        o.WepSil[2].To=Vector2.new(ox-4,oy+7)
    o.WepSil[2].Color=col;                      o.WepSil[2].Visible=true
    -- Grip (vertical short)
    o.WepSil[3].From=Vector2.new(ox+4,oy);      o.WepSil[3].To=Vector2.new(ox+4,oy+6)
    o.WepSil[3].Color=col;                      o.WepSil[3].Visible=true
    -- Suppressor tip (tiny)
    o.WepSil[4].From=Vector2.new(ox+14,oy-2);   o.WepSil[4].To=Vector2.new(ox+14,oy+2)
    o.WepSil[4].Color=col;                      o.WepSil[4].Visible=true
end

-- =====================
-- OFFSCREEN ARROWS (unchanged from previous build)
-- =====================
function drawOffscreen(p, o)
    if not o.Arrow then return end
    if not Config.OffscreenArrows or not Config.ESP or menuBlocksInput() then
        hideArrow(o); return
    end
    local r=getRoot(p); local ch=getChar(p); local myRoot=getRoot(lp)
    if not r or not ch or not myRoot then hideArrow(o); return end
    local raw,on,z=worldToScreen(r.Position)
    local vp=camera.ViewportSize
    local center=Vector2.new(vp.X/2,vp.Y/2)
    local margin=Config.ArrowPadding or 42
    local topMargin=math.max(74,margin)
    local projected=Vector2.new(raw.X,raw.Y)
    local dir=projected-center
    if z<0 then dir=-dir end
    if dir.Magnitude<1 then local rel=camera.CFrame:PointToObjectSpace(r.Position); dir=Vector2.new(rel.X,-rel.Y) end
    if dir.Magnitude<1 then dir=Vector2.new(1,0) end
    dir=dir.Unit
    if z>0 and projected.X>margin and projected.X<vp.X-margin and projected.Y>topMargin and projected.Y<vp.Y-margin then
        hideArrow(o); return
    end
    local tx,ty=math.huge,math.huge
    if dir.X>0 then tx=(vp.X-margin-center.X)/dir.X elseif dir.X<0 then tx=(margin-center.X)/dir.X end
    if dir.Y>0 then ty=(vp.Y-margin-center.Y)/dir.Y elseif dir.Y<0 then ty=(topMargin-center.Y)/dir.Y end
    local tEdge=math.min(math.abs(tx),math.abs(ty))
    if tEdge==math.huge or tEdge~=tEdge then tEdge=math.min(vp.X,vp.Y)*0.42 end
    local targetPos=center+dir*tEdge
    targetPos=Vector2.new(clamp(targetPos.X,margin,vp.X-margin),clamp(targetPos.Y,topMargin,vp.Y-margin))
    local angle=math.atan2(dir.Y,dir.X)
    local bucket=math.floor(((angle+math.pi)/(math.pi*2))*48+0.5)
    arrowBuckets[bucket]=(arrowBuckets[bucket] or 0)+1
    local stackIndex=arrowBuckets[bucket]
    if Config.ArrowSmartStack and stackIndex>1 then
        local tangent=Vector2.new(-dir.Y,dir.X)
        local side=((stackIndex%2==0) and 1 or -1)
        local tier=math.ceil((stackIndex-1)/2)
        local offset=clamp(tier*13,-52,52)*side
        local radial=dir*clamp(tier*3,0,14)
        targetPos=targetPos+tangent*offset-radial
        targetPos=Vector2.new(clamp(targetPos.X,margin,vp.X-margin),clamp(targetPos.Y,topMargin,vp.Y-margin))
    end
    o.ArrowSmooth=o.ArrowSmooth and o.ArrowSmooth:Lerp(targetPos,0.22) or targetPos
    local p2=o.ArrowSmooth
    local lookPart=ch:FindFirstChild("Head") or r
    local toMe=myRoot.Position-lookPart.Position
    local lookingAtMe=false; local canSeeMe=false
    if toMe.Magnitude>0 then
        lookingAtMe=lookPart.CFrame.LookVector:Dot(toMe.Unit)>0.72
        local params=RaycastParams.new(); params.FilterType=Enum.RaycastFilterType.Blacklist
        params.FilterDescendantsInstances={ch,lp.Character}; params.IgnoreWater=true
        local hit=Workspace:Raycast(lookPart.Position,toMe,params); canSeeMe=not hit
    end
    local dist=(r.Position-camera.CFrame.Position).Magnitude
    local verticalDelta=r.Position.Y-myRoot.Position.Y
    local verticalTag=""
    if Config.ArrowVerticalHints and math.abs(verticalDelta)>=(Config.ArrowVerticalThreshold or 18) then
        verticalTag=(verticalDelta>0) and "UP" or "DOWN"
    end
    local far=dist>900
    local pulse=(Config.ArrowThreatPulse and lookingAtMe and canSeeMe) and (math.sin(tick()*8)*2.5) or 0
    local size=clamp((Config.ArrowSize or 16)-(dist/300)+pulse,10,22)
    local spread=size*0.58; local perp=Vector2.new(-dir.Y,dir.X)
    local color=ghostDistanceColor(dist)
    if lookingAtMe and canSeeMe then color=Color3.fromRGB(255,60,70) end
    local alpha=far and 0.42 or 0.92
    local tip=p2+dir*size; local base=p2-dir*(size*0.78)
    local left=base+perp*spread; local right=base-perp*spread
    local shadowOffset=Vector2.new(3,3)
    local glowSize=size*1.28; local glowSpread=spread*1.25
    local glowTip=p2+dir*glowSize; local glowBase=p2-dir*(glowSize*0.78)
    if o.ArrowGlow then
        o.ArrowGlow.PointA=glowTip; o.ArrowGlow.PointB=glowBase+perp*glowSpread; o.ArrowGlow.PointC=glowBase-perp*glowSpread
        o.ArrowGlow.Color=color; o.ArrowGlow.Transparency=far and 0.13 or 0.24; o.ArrowGlow.Visible=true
    end
    if o.ArrowShadow then
        o.ArrowShadow.PointA=tip+shadowOffset; o.ArrowShadow.PointB=left+shadowOffset; o.ArrowShadow.PointC=right+shadowOffset
        o.ArrowShadow.Color=Color3.fromRGB(0,0,0); o.ArrowShadow.Transparency=0.36; o.ArrowShadow.Visible=true
    end
    o.Arrow.PointA=tip; o.Arrow.PointB=left; o.Arrow.PointC=right
    o.Arrow.Color=color; o.Arrow.Transparency=alpha; o.Arrow.Visible=true
    if o.ArrowRidge then
        o.ArrowRidge.From=p2-dir*(size*0.25); o.ArrowRidge.To=tip-dir*(size*0.18)
        o.ArrowRidge.Color=Color3.fromRGB(255,255,255); o.ArrowRidge.Transparency=far and 0.25 or 0.55
        o.ArrowRidge.Thickness=1; o.ArrowRidge.Visible=true
    end
    if o.ArrowLabel then
        local labelMode=Config.ArrowLabelMode or "Distance"
        local distText=tostring(math.floor(dist)).."m"
        local vText=(verticalTag~="") and (" "..verticalTag.." "..tostring(math.floor(math.abs(verticalDelta))).."m") or ""
        if labelMode=="Threat" and lookingAtMe and canSeeMe then o.ArrowLabel.Text="THREAT"..vText
        elseif labelMode=="Threat" and verticalTag~="" then o.ArrowLabel.Text=verticalTag.." "..tostring(math.floor(math.abs(verticalDelta))).."m"
        else o.ArrowLabel.Text=distText..vText end
        o.ArrowLabel.Position=p2-dir*(size+22)+Vector2.new(0,2)
        o.ArrowLabel.Color=color; o.ArrowLabel.Transparency=far and 0.55 or 0.88; o.ArrowLabel.Visible=labelMode~="Off"
    end
end

-- =====================
-- MULTI-TARGET SUPPRESSOR
-- When 3+ enemies in screen FOV, widen aim and pick closest shootable
-- =====================
function getMultiTargetCount()
    local count = 0
    local vp = camera.ViewportSize
    local center = Vector2.new(vp.X/2, vp.Y/2)
    for _,p in safeIpairs(Players:GetPlayers()) do
        if validEnemy(p) then
            local r = getRoot(p)
            if r then
                local scr,on,z = worldToScreen(r.Position)
                if on and z > 0 and (scr-center).Magnitude < Config.AimFOV * (Config.MultiTargetRadius or 3) then
                    count = count + 1
                end
            end
        end
    end
    return count
end

-- =====================
-- FLICK ASSIST
-- Single fast flick to bring target into FOV, then hands back to smooth assist
-- =====================
function tryFlickAssist()
    if not Config.FlickAssist or menuBlocksInput() then flickActive=false; return end
    if flickActive and flickDone then flickActive=false; flickDone=false; return end
    -- Find nearest target outside current FOV but within 3x FOV
    local mouse = UIS:GetMouseLocation()
    local best, bestDist, bestPart = nil, math.huge, nil
    for _,p in safeIpairs(Players:GetPlayers()) do
        if validEnemy(p) then
            local part = getAimPart(p)
            if part then
                local scr,on,z = worldToScreen(part.Position)
                if on and z > 0 then
                    local d = (scr - mouse).Magnitude
                    if d > Config.AimFOV and d < Config.AimFOV * 3 and d < bestDist then
                        bestDist=d; best=p; bestPart=part
                    end
                end
            end
        end
    end
    if best and bestPart and not flickActive then
        flickActive = true; flickDone = false; flickTarget = best
        local scr,on,z = worldToScreen(bestPart.Position)
        if on and z > 0 then
            local delta = scr - mouse
            local strength = Config.FlickAssistStrength or 0.7
            pcall(function() mousemoverel(delta.X * strength, delta.Y * strength) end)
            task.delay(0.12, function() flickDone = true end)
        end
    end
end

-- =====================
-- MAIN ESP UPDATE
-- =====================
local losLineIdx = 0; local losConeIdx = 0

function updateESP()
    arrowBuckets = {}
    losLineIdx   = 0
    losConeIdx   = 0

    local antiAimLabelTargets = {}

    for _,p in safeIpairs(Players:GetPlayers()) do
        if p ~= lp then
            local o = newESP(p)
            if Config.ESP and validEnemy(p) and not menuBlocksInput() then
                local ch=getChar(p); local hum=getHum(p); local root=getRoot(p)
                local rig = ch and resolveRig(ch)
                local topLeft,size,on=boxFromCharacter(ch)
                if on and root then
                    local dist=(root.Position-camera.CFrame.Position).Magnitude
                    local col=visibleFromCamera(rig and rig.head or root,ch) and Color3.fromRGB(120,255,170) or Color3.fromRGB(255,90,90)
                    o.Box.Position=topLeft;o.Box.Size=size;o.Box.Color=col;o.Box.Visible=Config.BoxESP
                    o.BoxFill.Position=topLeft;o.BoxFill.Size=size;o.BoxFill.Visible=Config.BoxESP and Config.Glass
                    local stackMode=Config.ESPStackMode or "Clean"
                    local hp=hum.Health/math.max(hum.MaxHealth,1)
                    local fade=visualFadeByDistance(dist)
                    local centerX=topLeft.X+size.X/2
                    local topY=topLeft.Y; local bottomY=topLeft.Y+size.Y
                    local showName=Config.NameESP and Config.ESPNameOptional and dist<650

                    -- Nameplate distance scaling
                    local nameSize = Config.NameplateMaxSize or 14
                    if Config.NameplateDistanceScale then
                        local minS = Config.NameplateMinSize  or 9
                        local maxS = Config.NameplateMaxSize  or 14
                        local range= Config.NameplateScaleRange or 800
                        nameSize = clamp(lerp(maxS, minS, dist/range), minS, maxS)
                    end

                    o.Name.Text=p.Name; o.Name.Size=nameSize
                    o.Name.Position=Vector2.new(centerX,topY-20)
                    o.Name.Color=Color3.fromRGB(245,245,250)
                    o.Name.Transparency=clamp(0.68+fade*0.28,0.62,0.95)
                    o.Name.Visible=showName and stackMode~="Minimal"

                    o.Health.From=Vector2.new(topLeft.X-6,bottomY)
                    o.Health.To=Vector2.new(topLeft.X-6,topY+size.Y*(1-hp))
                    o.Health.Color=Color3.fromRGB(255*(1-hp),255*hp,95)
                    o.Health.Transparency=clamp(0.52+fade*0.42,0.4,0.95)
                    o.Health.Thickness=(stackMode=="Compact") and 2 or 3
                    o.Health.Visible=Config.HealthESP

                    o.Distance.Text=(Config.HealthESP and stackMode=="Clean") and (math.floor(hum.Health).." HP | "..math.floor(dist).."m") or (math.floor(dist).."m")
                    o.Distance.Position=Vector2.new(centerX,bottomY+((stackMode=="Compact") and 4 or 7))
                    o.Distance.Color=ghostDistanceColor(dist)
                    o.Distance.Transparency=clamp(0.72+fade*0.22,0.65,0.94)
                    o.Distance.Visible=(Config.DistanceESP or (Config.HealthESP and stackMode=="Clean")) and dist<(Config.DetailMaxDistance or 1800)

                    o.Tracer.From=Vector2.new(camera.ViewportSize.X/2,camera.ViewportSize.Y)
                    o.Tracer.To=Vector2.new(topLeft.X+size.X/2,topLeft.Y+size.Y)
                    o.Tracer.Color=col; o.Tracer.Visible=Config.TracerESP

                    -- Bone skeleton
                    drawBoneSkeleton(p, o, dist)

                    -- Weapon silhouette
                    drawWeaponSilhouette(p, o, topLeft, size)

                    -- Anti-aim indicator above nameplate
                    local aaType = isAntiAiming(p) and getAntiAimType(p)
                    if aaType and Config.AntiAimIndicator then
                        o.AntiAimLbl.Text = "AA:"..aaType
                        o.AntiAimLbl.Position = Vector2.new(centerX, topY - 34)
                        o.AntiAimLbl.Visible = true
                    else o.AntiAimLbl.Visible = false end

                    hideArrow(o)
                else
                    hideAll(o)
                    -- Clear bone/dot lines explicitly
                    for _,l in ipairs(o.BoneLines) do l.Visible=false end
                    for _,d in ipairs(o.DotCircles) do d.Visible=false end
                    drawOffscreen(p, o)
                end
            else
                hideAll(o)
            end
        end
    end

    -- Global anti-aim indicator (center of screen warning)
    if antiAimIndicator then
        local spinning = {}
        for _,p in safeIpairs(Players:GetPlayers()) do
            if p ~= lp and isAntiAiming(p) then
                table.insert(spinning, p.Name.."("..( getAntiAimType(p) or "?")..")")
            end
        end
        if #spinning > 0 and Config.AntiAimDetect and Config.AntiAimIndicator and not menuBlocksInput() then
            local vp = camera.ViewportSize
            antiAimIndicator.Text = "AA DETECTED: "..table.concat(spinning,", ")
            antiAimIndicator.Position = Vector2.new(vp.X/2, 22)
            antiAimIndicator.Visible = true
        else antiAimIndicator.Visible = false end
    end
end

-- =====================
-- CHAMS
-- =====================
function applyChams(p,ch)
    if not ch or ch:FindFirstChild("GhostCham") then return end
    local h=Instance.new("Highlight")
    h.Name="GhostCham";h.FillTransparency=0.65;h.OutlineTransparency=0
    h.FillColor=Color3.fromRGB(170,80,255);h.OutlineColor=Color3.fromRGB(255,255,255)
    h.Parent=ch
end

function updateChams()
    for _,p in safeIpairs(Players:GetPlayers()) do
        local ch=getChar(p)
        if p~=lp and ch then
            local h=ch:FindFirstChild("GhostCham")
            if Config.Chams and Config.ESP and validEnemy(p) and not Config.Hidden then
                if not h then applyChams(p,ch) end
            elseif h then h:Destroy() end
        end
    end
end

-- =====================
-- RESOLVER TRACKING
-- =====================
function trackCharacter(p)
    local r=getRoot(p); if not r then return end
    resolverData[p]=resolverData[p] or {last=r.Position,vel=Vector3.zero,t=now()}
end

function updateResolver()
    for _,p in safeIpairs(Players:GetPlayers()) do
        local r=getRoot(p)
        if p~=lp and r then
            local d=resolverData[p] or {last=r.Position,t=now(),vel=Vector3.zero}
            local dt=math.max(now()-d.t,1/60)
            d.vel=(r.Position-d.last)/dt; d.last=r.Position; d.t=now()
            resolverData[p]=d
        end
    end
    updateAntiAimDetection()
end

function setupHitDetection(p)
    local h=getHum(p); if not h or trackedHumanoids[h] then return end
    trackedHumanoids[h]=h.Health
    h.HealthChanged:Connect(function(new)
        trackedHumanoids[h]=new
    end)
end

function onPlayerAdded(p)
    p.CharacterAdded:Connect(function(char)
        task.wait(0.5)
        rigCache[char] = nil -- clear rig cache on respawn
        if Config.Chams then applyChams(p,char) end
        trackCharacter(p); setupHitDetection(p)
    end)
end

for _,p in safeIpairs(Players:GetPlayers()) do onPlayerAdded(p) end
Players.PlayerAdded:Connect(onPlayerAdded)
Players.PlayerRemoving:Connect(function(p)
    removeESP(p); resolverData[p]=nil; antiAimData[p]=nil
    local ch=getChar(p); if ch then rigCache[ch]=nil end
end)

-- =====================
-- BLUR + GUI
-- =====================
local menuBlur=Instance.new("BlurEffect")
menuBlur.Size=0; menuBlur.Parent=Lighting; menuBlur.Enabled=false

local ScreenGui=Instance.new("ScreenGui")
ScreenGui.Name="GhostMenu_v5"; ScreenGui.Parent=CoreGui
ScreenGui.ResetOnSpawn=false; ScreenGui.ZIndexBehavior=Enum.ZIndexBehavior.Sibling

-- =====================
-- DOCK
-- =====================
local Dock=nil; local lastDockHover=0; local dockFadeHidden=false

function updateDockAutoHide()
    if not Dock then return end
    if not Config.DockMode or Config.Hidden or ghostUnloaded then Dock.Visible=false; return end
    if menuVisible then dockFadeHidden=false; return end
    Dock.Visible=true
    if Config.DockAutoHide then
        local mouse=UIS:GetMouseLocation()
        local center=Vector2.new(Dock.AbsolutePosition.X+Dock.AbsoluteSize.X/2,Dock.AbsolutePosition.Y+Dock.AbsoluteSize.Y/2)
        local near=(mouse-center).Magnitude<=(Config.DockWakeDistance or 90)
        if near then lastDockHover=now(); dockFadeHidden=false
        elseif now()-lastDockHover>=(Config.DockAutoHideDelay or 3) then dockFadeHidden=true end
    else dockFadeHidden=false end
end

function updateDockStyle()
    if not Dock then return end
    Dock.BackgroundColor3=Color3.fromRGB(9,6,20)
    local st=Dock:FindFirstChild("Stroke"); if st then st.Color=Config.UIAccent end
    local targetHidden=dockFadeHidden and Config.DockAutoHide and not menuVisible
    Dock.BackgroundTransparency=targetHidden and 0.86 or 0.06
    for _,child in safeIpairs(Dock:GetChildren()) do
        if child:IsA("TextButton") then
            child.BackgroundTransparency=targetHidden and 0.72 or 0.08
            child.TextTransparency=targetHidden and 0.55 or 0
        end
    end
end

function setDockVisible(state)
    if Dock then
        Dock.Visible=state and Config.DockMode and not ghostUnloaded and not Config.Hidden
        if Dock.Visible then lastDockHover=now(); dockFadeHidden=false end
    end
end

function makeDockButton(parent,text,w,callback)
    local b=Instance.new("TextButton",parent)
    b.Size=UDim2.new(0,w or 58,1,-12); b.Position=UDim2.new(0,0,0,6)
    b.BackgroundColor3=Color3.fromRGB(22,10,38); b.BackgroundTransparency=0.08
    b.Text=text; b.Font=Enum.Font.GothamBlack; b.TextSize=11
    b.TextColor3=Color3.fromRGB(245,245,250); b.AutoButtonColor=false; b.ZIndex=141
    Instance.new("UICorner",b).CornerRadius=UDim.new(0,10)
    local s=Instance.new("UIStroke",b); s.Color=Config.UIAccent; s.Transparency=0.58
    b.MouseButton1Click:Connect(function() callback(); ghostScheduleAutoSave() end)
    return b
end

function createDock()
    if Dock then return end
    Dock=Instance.new("Frame",ScreenGui)
    Dock.Name="GhostDock"; Dock.Size=UDim2.new(0,430,0,48)
    Dock.Position=UDim2.new(0.5,-215,1,-72)
    Dock.BackgroundColor3=Color3.fromRGB(9,6,20); Dock.BackgroundTransparency=0.06
    Dock.BorderSizePixel=0; Dock.Visible=false; Dock.ZIndex=140
    Instance.new("UICorner",Dock).CornerRadius=UDim.new(0,18)
    local st=Instance.new("UIStroke",Dock); st.Name="Stroke"; st.Color=Config.UIAccent; st.Transparency=0.25; st.Thickness=1.2
    local layout=Instance.new("UIListLayout",Dock)
    layout.FillDirection=Enum.FillDirection.Horizontal
    layout.HorizontalAlignment=Enum.HorizontalAlignment.Center
    layout.VerticalAlignment=Enum.VerticalAlignment.Center
    layout.Padding=UDim.new(0,8)
    local pad=Instance.new("UIPadding",Dock); pad.PaddingLeft=UDim.new(0,8); pad.PaddingRight=UDim.new(0,8)
    makeDockButton(Dock,"GHOST",70,function()
        menuVisible=true; Config.Hidden=false; Root.Visible=true; setDockVisible(false)
        if _G.GhostShowTab then _G.GhostShowTab(currentTab or "Favorited") end
    end)
    makeDockButton(Dock,"FAV",52,function()
        menuVisible=true; Config.Hidden=false; Root.Visible=true; setDockVisible(false)
        if _G.GhostShowTab then _G.GhostShowTab("Favorited") end
    end)
    makeDockButton(Dock,"THEME",66,function()
        menuVisible=true; Config.Hidden=false; Root.Visible=true; setDockVisible(false)
        if _G.GhostShowTab then _G.GhostShowTab("Theme Studio") end
    end)
    makeDockButton(Dock,"NOTES",62,function()
        menuVisible=true; Config.Hidden=false; Root.Visible=true; setDockVisible(false)
        if _G.GhostShowTab then _G.GhostShowTab("Notes") end
    end)
    makeDockButton(Dock,"HIDE",58,function()
        menuVisible=false; Root.Visible=false; setDockVisible(true)
    end)
    makeDockButton(Dock,"X",42,function()
        if _G.GhostUnload then _G.GhostUnload() end
    end)
    Dock.MouseEnter:Connect(function() lastDockHover=now(); dockFadeHidden=false end)
    Dock.MouseLeave:Connect(function() lastDockHover=now() end)
    Dock.InputBegan:Connect(function(i)
        if i.UserInputType==Enum.UserInputType.MouseButton1 then
            local draggingDock=true; local start=i.Position; local startPos=Dock.Position
            local conn; conn=UIS.InputChanged:Connect(function(ch)
                if draggingDock and ch.UserInputType==Enum.UserInputType.MouseMovement then
                    local d=ch.Position-start
                    Dock.Position=UDim2.new(startPos.X.Scale,startPos.X.Offset+d.X,startPos.Y.Scale,startPos.Y.Offset+d.Y)
                end
            end)
            i.Changed:Connect(function() if i.UserInputState==Enum.UserInputState.End then draggingDock=false; if conn then conn:Disconnect() end end end)
        end
    end)
end

-- =====================
-- ROOT FRAME
-- =====================
local Root=Instance.new("Frame")
Root.Name="Root"; Root.Parent=ScreenGui; Root.AnchorPoint=Vector2.new(0.5,0.5)
Root.Size=UDim2.new(0,880,0,520); Root.Position=UDim2.new(0.5,0,0.5,0)
Root.BackgroundColor3=Color3.fromRGB(7,5,15); Root.BackgroundTransparency=0
Root.BorderSizePixel=0; Root.ClipsDescendants=true
local RootScale=Instance.new("UIScale",Root); RootScale.Scale=1
Instance.new("UICorner",Root).CornerRadius=UDim.new(0,28)
local rootStroke=Instance.new("UIStroke",Root); rootStroke.Color=Config.UIAccent; rootStroke.Thickness=1.2; rootStroke.Transparency=0.22
local rootGradient=Instance.new("UIGradient",Root); rootGradient.Rotation=90
rootGradient.Color=ColorSequence.new{ColorSequenceKeypoint.new(0,Color3.fromRGB(16,16,18)),ColorSequenceKeypoint.new(0.35,Color3.fromRGB(3,3,5)),ColorSequenceKeypoint.new(1,Color3.fromRGB(0,0,0))}

-- =====================
-- THEMES
-- =====================
local GhostThemeColors={
    ["Bright Phantom"]={accent=Color3.fromRGB(170,80,255),accent2=Color3.fromRGB(190,70,255),cross=Color3.fromRGB(210,120,255),main=Color3.fromRGB(6,4,10),side=Color3.fromRGB(4,3,8),top=Color3.fromRGB(8,5,13),card=Color3.fromRGB(14,8,24),row=Color3.fromRGB(11,7,20),stroke=Color3.fromRGB(120,55,190),text=Color3.fromRGB(245,245,250),sub=Color3.fromRGB(165,165,178)},
    ["Neon Wraith"]={accent=Color3.fromRGB(190,70,255),accent2=Color3.fromRGB(150,60,255),cross=Color3.fromRGB(220,135,255),main=Color3.fromRGB(4,2,8),side=Color3.fromRGB(2,2,5),top=Color3.fromRGB(6,3,11),card=Color3.fromRGB(10,5,18),row=Color3.fromRGB(8,4,15),stroke=Color3.fromRGB(130,55,210),text=Color3.fromRGB(245,245,250),sub=Color3.fromRGB(165,165,178)},
    ["Deep Violet"]={accent=Color3.fromRGB(150,75,255),accent2=Color3.fromRGB(175,85,255),cross=Color3.fromRGB(205,125,255),main=Color3.fromRGB(5,3,10),side=Color3.fromRGB(3,2,7),top=Color3.fromRGB(7,4,14),card=Color3.fromRGB(12,7,24),row=Color3.fromRGB(10,6,20),stroke=Color3.fromRGB(105,52,175),text=Color3.fromRGB(245,245,250),sub=Color3.fromRGB(165,165,178)},
    ["Void Glow"]={accent=Color3.fromRGB(130,60,255),accent2=Color3.fromRGB(200,80,255),cross=Color3.fromRGB(190,115,255),main=Color3.fromRGB(2,2,5),side=Color3.fromRGB(0,0,3),top=Color3.fromRGB(4,3,8),card=Color3.fromRGB(8,5,14),row=Color3.fromRGB(6,4,12),stroke=Color3.fromRGB(90,45,165),text=Color3.fromRGB(245,245,250),sub=Color3.fromRGB(165,165,178)},
    ["Glass Noir"]={accent=Color3.fromRGB(170,80,255),accent2=Color3.fromRGB(190,70,255),cross=Color3.fromRGB(210,120,255),main=Color3.fromRGB(6,4,10),side=Color3.fromRGB(4,3,8),top=Color3.fromRGB(8,5,13),card=Color3.fromRGB(14,8,24),row=Color3.fromRGB(11,7,20),stroke=Color3.fromRGB(120,55,190),text=Color3.fromRGB(245,245,250),sub=Color3.fromRGB(165,165,178)},
    ["High Contrast"]={accent=Color3.fromRGB(210,165,255),accent2=Color3.fromRGB(175,85,255),cross=Color3.fromRGB(235,210,255),main=Color3.fromRGB(0,0,0),side=Color3.fromRGB(0,0,0),top=Color3.fromRGB(0,0,0),card=Color3.fromRGB(8,6,10),row=Color3.fromRGB(5,4,7),stroke=Color3.fromRGB(150,90,210),text=Color3.fromRGB(245,245,250),sub=Color3.fromRGB(165,165,178)},
}

function ghostResolveBackgroundImage()
    local img=Config.GhostBackgroundImage or ""
    pcall(function()
        if isfile and getcustomasset and isfile("GhostMenu/ghost.png") then img=getcustomasset("GhostMenu/ghost.png")
        elseif isfile and getcustomasset and isfile("ghost.png") then img=getcustomasset("ghost.png") end
    end)
    return img
end

local GhostBg=Instance.new("Frame",Root)
GhostBg.Name="GhostBackground"; GhostBg.BackgroundTransparency=1
GhostBg.Size=UDim2.new(1,0,1,0); GhostBg.ZIndex=2
local GhostAura=Instance.new("TextLabel",GhostBg)
GhostAura.Name="GhostAura"; GhostAura.BackgroundTransparency=1
GhostAura.AnchorPoint=Vector2.new(0.5,0.5); GhostAura.Position=UDim2.new(0.70,0,0.60,0)
GhostAura.Size=UDim2.new(0,420,0,460); GhostAura.Text=""
GhostAura.Font=Enum.Font.GothamBlack; GhostAura.TextSize=64
GhostAura.TextColor3=Color3.fromRGB(18,14,24); GhostAura.TextTransparency=1
GhostAura.Rotation=-8; GhostAura.ZIndex=2; GhostAura.Visible=false
local GhostImg=Instance.new("ImageLabel",GhostBg)
GhostImg.Name="GhostImage"; GhostImg.BackgroundTransparency=1
GhostImg.AnchorPoint=Vector2.new(0.5,0.5); GhostImg.Position=UDim2.new(0.12,0,0.58,0)
GhostImg.Size=UDim2.new(0,330,0,520); GhostImg.ImageTransparency=0.36
GhostImg.ScaleType=Enum.ScaleType.Fit; GhostImg.ZIndex=2

function applyGhostTheme(name)
    local th=GhostThemeColors[name or Config.ThemePreset] or GhostThemeColors["Bright Phantom"]
    Config.ThemePreset=name or Config.ThemePreset
    Config.UIAccent=th.accent; Config.CrosshairColor=th.cross
    if Root then Root.BackgroundColor3=Color3.fromRGB(7,5,15) end
    if Main then Main.BackgroundColor3=th.main end
    if Sidebar then Sidebar.BackgroundColor3=th.side end
    if Topbar then Topbar.BackgroundColor3=th.top end
    if rootStroke then rootStroke.Color=th.accent; rootStroke.Transparency=0.18 end
    pcall(function() if Content then Content.ScrollBarImageColor3=GhostControlFill end end)
    if SearchBox then SearchBox.BackgroundColor3=Color3.fromRGB(13,7,20); SearchBox.TextColor3=Color3.fromRGB(245,245,250); SearchBox.PlaceholderColor3=Color3.fromRGB(165,125,205) end
    if Title then Title.TextColor3=th.text end
    if GhostImg then GhostImg.ImageColor3=Color3.fromRGB(255,255,255) end
    for _,fn in safePairs(controls) do pcall(fn) end
end

function previewsEnabled() return Config.ShowPreviews~=false end

local bgTick=0
function updateAnimatedBackground(dt)
    if not GhostBg or not Config.GhostBackground then return end
    bgTick=bgTick+(dt or 0.016)
    local mode=Config.BackgroundMode or "Ghost Drift"
    if GhostImg then
        local px,py=0,0
        if Config.GhostParallax then
            local m=UIS:GetMouseLocation()
            local vp=camera and camera.ViewportSize or Vector2.new(1920,1080)
            local strength=Config.GhostParallaxStrength or 0.025
            px=((m.X/vp.X)-0.5)*strength; py=((m.Y/vp.Y)-0.5)*strength
        end
        if mode=="Ghost Drift" then
            GhostImg.Position=UDim2.new(0.12+math.sin(bgTick*0.35)*0.010-px,0,0.58+math.cos(bgTick*0.28)*0.010-py,0)
            GhostImg.Rotation=math.sin(bgTick*0.22)*3
        elseif mode=="Spirit Pulse" then
            GhostImg.ImageTransparency=1-clamp(0.30+(math.sin(bgTick*2)*0.08),0.1,0.75)
        elseif mode=="Ghost Fog" then
            GhostImg.Position=UDim2.new(0.12+math.sin(bgTick*0.18)*0.018-px,0,0.56-py,0)
            GhostImg.Rotation=math.sin(bgTick*0.12)*7
        end
    end
end

local idlePulse=0
function updateIdleGlow(dt)
    if not Config.IdleGlow then return end
    idlePulse=idlePulse+(dt or 0.016)
    if rootStroke then
        rootStroke.Transparency=0.18+(math.sin(idlePulse*1.4)*0.05)
        rootStroke.Thickness=1.2+(math.sin(idlePulse*1.4)*0.18)
    end
end

local glassTargets={}
function registerGlass(obj,glassAlpha,solidAlpha)
    if obj then table.insert(glassTargets,{obj=obj,glass=glassAlpha,solid=solidAlpha}) end
end
function applyGlassLook()
    local useGlass=Config.Glass~=false
    for _,item in safeIpairs(glassTargets) do
        local obj=item.obj
        if obj and obj.Parent then obj.BackgroundTransparency=useGlass and item.glass or item.solid end
    end
end

function updateGhostBackground()
    local show=Config.GhostBackground and not Config.Hidden
    local img=ghostResolveBackgroundImage()
    if GhostBg then GhostBg.Visible=show end
    if GhostAura then GhostAura.Visible=false; GhostAura.TextTransparency=1 end
    if GhostImg then
        GhostImg.Image=img
        GhostImg.ImageColor3=Color3.fromRGB(255,255,255)
        GhostImg.ImageTransparency=(img~="" and show) and clamp(1-(Config.GhostBackgroundOpacity or 0.68),0.28,0.82) or 1
    end
    if SidebarGhostImg then
        SidebarGhostImg.Image=img
        SidebarGhostImg.ImageTransparency=(img~="" and show) and 0.48 or 1
    end
end

local Shadow=Instance.new("ImageLabel",Root)
Shadow.Name="Shadow"; Shadow.BackgroundTransparency=1
Shadow.Image="rbxassetid://1316045217"; Shadow.ImageTransparency=1
Shadow.Size=UDim2.new(1,60,1,60); Shadow.Position=UDim2.new(0,-30,0,-30); Shadow.ZIndex=0; Shadow.Visible=false

-- =====================
-- SIDEBAR
-- =====================
local Sidebar=Instance.new("Frame",Root)
Sidebar.Size=UDim2.new(0,190,1,0); Sidebar.BackgroundColor3=Color3.fromRGB(4,3,8)
Sidebar.BackgroundTransparency=0.04; Sidebar.BorderSizePixel=0; Sidebar.ZIndex=2
Instance.new("UICorner",Sidebar).CornerRadius=UDim.new(0,24)
local sbStroke=Instance.new("UIStroke",Sidebar); sbStroke.Color=Color3.fromRGB(88,58,110); sbStroke.Transparency=0.45
local sbGrad=Instance.new("UIGradient",Sidebar); sbGrad.Rotation=90
sbGrad.Color=ColorSequence.new{ColorSequenceKeypoint.new(0,Color3.fromRGB(28,28,34)),ColorSequenceKeypoint.new(0.4,Color3.fromRGB(5,5,8)),ColorSequenceKeypoint.new(1,Color3.fromRGB(0,0,0))}

local Logo=Instance.new("TextLabel",Sidebar)
Logo.Size=UDim2.new(1,0,0,72); Logo.BackgroundTransparency=1; Logo.Text="GHOST"
Logo.Font=Enum.Font.GothamBlack; Logo.TextSize=24; Logo.TextColor3=Color3.fromRGB(255,255,255); Logo.ZIndex=3

local SubLogo=Instance.new("TextLabel",Sidebar)
SubLogo.Size=UDim2.new(1,-22,0,20); SubLogo.Position=UDim2.new(0,11,0,48)
SubLogo.BackgroundTransparency=1; SubLogo.Text="Ultimate v5.3"
SubLogo.Font=Enum.Font.Gotham; SubLogo.TextSize=12; SubLogo.TextColor3=Color3.fromRGB(150,150,165)
SubLogo.TextXAlignment=Enum.TextXAlignment.Center; SubLogo.ZIndex=3

local TabHolder=Instance.new("Frame",Sidebar)
TabHolder.BackgroundTransparency=1; TabHolder.Position=UDim2.new(0,14,0,92)
TabHolder.Size=UDim2.new(1,-28,1,-106); TabHolder.ZIndex=3
local tabLayout=Instance.new("UIListLayout",TabHolder); tabLayout.Padding=UDim.new(0,6); tabLayout.SortOrder=Enum.SortOrder.LayoutOrder

local SidebarGhostImg=Instance.new("ImageLabel",Sidebar)
SidebarGhostImg.Name="SidebarGhostImage"; SidebarGhostImg.BackgroundTransparency=1
SidebarGhostImg.AnchorPoint=Vector2.new(0.5,0.5); SidebarGhostImg.Position=UDim2.new(0.52,0,0.55,0)
SidebarGhostImg.Size=UDim2.new(0,250,0,420); SidebarGhostImg.Image=ghostResolveBackgroundImage()
SidebarGhostImg.ImageTransparency=0.42; SidebarGhostImg.ImageColor3=Color3.fromRGB(255,255,255)
SidebarGhostImg.ScaleType=Enum.ScaleType.Fit; SidebarGhostImg.ZIndex=3

local Main=Instance.new("Frame",Root)
Main.Position=UDim2.new(0,190,0,0); Main.Size=UDim2.new(1,-190,1,0)
Main.BackgroundColor3=Color3.fromRGB(6,4,10); Main.BackgroundTransparency=0.12; Main.ZIndex=2
Instance.new("UICorner",Main).CornerRadius=UDim.new(0,24)

pcall(function()
    GhostBg.Parent=Main; GhostBg.Size=UDim2.new(1,0,1,0); GhostBg.Position=UDim2.new(0,0,0,0)
    GhostBg.ZIndex=2; GhostAura.ZIndex=2; GhostImg.ZIndex=2
end)

local Topbar=Instance.new("Frame",Main)
Topbar.Size=UDim2.new(1,0,0,68); Topbar.BackgroundColor3=Color3.fromRGB(8,5,13)
Topbar.BackgroundTransparency=0.06; Topbar.BorderSizePixel=0; Topbar.ZIndex=60
Instance.new("UICorner",Topbar).CornerRadius=UDim.new(0,20)
local topStroke=Instance.new("UIStroke",Topbar); topStroke.Color=Color3.fromRGB(92,64,112); topStroke.Transparency=0.62

local Title=Instance.new("TextLabel",Topbar)
Title.Size=UDim2.new(0.45,0,1,0); Title.Position=UDim2.new(0,18,0,0); Title.BackgroundTransparency=1
Title.Font=Enum.Font.GothamBlack; Title.TextSize=21; Title.TextColor3=Color3.fromRGB(255,255,255)
Title.TextXAlignment=Enum.TextXAlignment.Left; Title.Text="Combat"; Title.ZIndex=61

local SearchBox=Instance.new("TextBox",Topbar)
SearchBox.Size=UDim2.new(0,220,0,34); SearchBox.Position=UDim2.new(1,-372,0.5,-17)
SearchBox.BackgroundColor3=Color3.fromRGB(0,0,0)
SearchBox.PlaceholderText="Search settings..."; SearchBox.Text=""
SearchBox.TextColor3=Color3.fromRGB(245,245,250); SearchBox.PlaceholderColor3=Color3.fromRGB(165,125,205)
SearchBox.Font=Enum.Font.Gotham; SearchBox.TextSize=13; SearchBox.ZIndex=61
Instance.new("UICorner",SearchBox).CornerRadius=UDim.new(0,17)
local ss=Instance.new("UIStroke",SearchBox); ss.Color=Color3.fromRGB(45,50,70); ss.Transparency=0.5

local Close=Instance.new("TextButton",Topbar)
Close.Size=UDim2.new(0,34,0,34); Close.Position=UDim2.new(1,-44,0.5,-17); Close.Text="H"
Close.Font=Enum.Font.GothamBold; Close.TextSize=15; Close.TextColor3=Color3.fromRGB(255,120,140)
Close.BackgroundColor3=Color3.fromRGB(8,0,4); Close.ZIndex=62
Instance.new("UICorner",Close).CornerRadius=UDim.new(0,13)

local FullClose=Instance.new("TextButton",Topbar)
FullClose.Size=UDim2.new(0,34,0,34); FullClose.Position=UDim2.new(1,-84,0.5,-17); FullClose.Text="X"
FullClose.Font=Enum.Font.GothamBold; FullClose.TextSize=16; FullClose.TextColor3=Color3.fromRGB(255,80,120)
FullClose.BackgroundColor3=Color3.fromRGB(22,4,12); FullClose.ZIndex=62
Instance.new("UICorner",FullClose).CornerRadius=UDim.new(0,13)

local Min=Instance.new("TextButton",Topbar)
Min.Size=UDim2.new(0,34,0,34); Min.Position=UDim2.new(1,-124,0.5,-17); Min.Text="-"
Min.Font=Enum.Font.GothamBold; Min.TextSize=24; Min.TextColor3=Color3.fromRGB(210,240,255)
Min.BackgroundColor3=Color3.fromRGB(5,3,10); Min.ZIndex=62
Instance.new("UICorner",Min).CornerRadius=UDim.new(0,13)

local Content=Instance.new("ScrollingFrame",Main)
Content.Position=UDim2.new(0,28,0,84); Content.Size=UDim2.new(1,-56,1,-104)
Content.BackgroundTransparency=1; Content.BorderSizePixel=0
Content.ScrollBarThickness=4; Content.ScrollBarImageColor3=GhostControlFill
Content.CanvasSize=UDim2.new(0,0,0,0); Content.ZIndex=3

local contentLayout=Instance.new("UIListLayout",Content)
contentLayout.Padding=UDim.new(0,12); contentLayout.SortOrder=Enum.SortOrder.LayoutOrder
contentLayout:GetPropertyChangedSignal("AbsoluteContentSize"):Connect(function()
    Content.CanvasSize=UDim2.new(0,0,0,contentLayout.AbsoluteContentSize.Y+12)
end)

local SearchDim=Instance.new("Frame",Main)
SearchDim.Name="SearchSpotlightDim"; SearchDim.Position=UDim2.new(0,0,0,68)
SearchDim.Size=UDim2.new(1,0,1,-68); SearchDim.BackgroundColor3=Color3.fromRGB(0,0,0)
SearchDim.BackgroundTransparency=1; SearchDim.BorderSizePixel=0; SearchDim.Visible=false; SearchDim.ZIndex=50

local HoverTip=Instance.new("Frame",ScreenGui)
HoverTip.Name="HoverHelpTip"; HoverTip.Size=UDim2.new(0,285,0,82)
HoverTip.BackgroundColor3=Color3.fromRGB(8,5,13); HoverTip.BackgroundTransparency=0.06
HoverTip.BorderSizePixel=0; HoverTip.Visible=false; HoverTip.ZIndex=150
Instance.new("UICorner",HoverTip).CornerRadius=UDim.new(0,14)
local HoverTipStroke=Instance.new("UIStroke",HoverTip); HoverTipStroke.Color=Config.UIAccent; HoverTipStroke.Transparency=0.25; HoverTipStroke.Thickness=1
local HoverTipTitle=Instance.new("TextLabel",HoverTip)
HoverTipTitle.BackgroundTransparency=1; HoverTipTitle.Position=UDim2.new(0,12,0,8)
HoverTipTitle.Size=UDim2.new(1,-24,0,18); HoverTipTitle.Font=Enum.Font.GothamBlack; HoverTipTitle.TextSize=12
HoverTipTitle.TextColor3=Color3.fromRGB(245,245,250); HoverTipTitle.TextXAlignment=Enum.TextXAlignment.Left; HoverTipTitle.ZIndex=151
local HoverTipBody=Instance.new("TextLabel",HoverTip)
HoverTipBody.BackgroundTransparency=1; HoverTipBody.Position=UDim2.new(0,12,0,30)
HoverTipBody.Size=UDim2.new(1,-24,1,-38); HoverTipBody.Font=Enum.Font.Gotham; HoverTipBody.TextSize=11
HoverTipBody.TextColor3=Color3.fromRGB(165,165,178); HoverTipBody.TextXAlignment=Enum.TextXAlignment.Left
HoverTipBody.TextYAlignment=Enum.TextYAlignment.Top; HoverTipBody.TextWrapped=true; HoverTipBody.ZIndex=151
local hoverToken=0

-- Startup overlay
local StartupOverlay=Instance.new("Frame",Root)
StartupOverlay.Name="StartupOverlay"; StartupOverlay.Size=UDim2.new(1,0,1,0)
StartupOverlay.BackgroundColor3=Color3.fromRGB(0,0,0); StartupOverlay.BackgroundTransparency=0.05; StartupOverlay.ZIndex=100
Instance.new("UICorner",StartupOverlay).CornerRadius=UDim.new(0,22)
local StartupTitle=Instance.new("TextLabel",StartupOverlay)
StartupTitle.BackgroundTransparency=1; StartupTitle.Position=UDim2.new(0,40,0.5,-55); StartupTitle.Size=UDim2.new(1,-80,0,36)
StartupTitle.Font=Enum.Font.GothamBlack; StartupTitle.TextSize=28; StartupTitle.TextColor3=GhostTextMain
StartupTitle.TextXAlignment=Enum.TextXAlignment.Left; StartupTitle.Text="GHOST"; StartupTitle.ZIndex=101
local StartupStatus=Instance.new("TextLabel",StartupOverlay)
StartupStatus.BackgroundTransparency=1; StartupStatus.Position=UDim2.new(0,42,0.5,-15); StartupStatus.Size=UDim2.new(1,-84,0,22)
StartupStatus.Font=Enum.Font.Gotham; StartupStatus.TextSize=13; StartupStatus.TextColor3=Color3.fromRGB(190,190,210)
StartupStatus.TextXAlignment=Enum.TextXAlignment.Left; StartupStatus.Text="Loading UI..."; StartupStatus.ZIndex=101
local StartupBar=Instance.new("Frame",StartupOverlay)
StartupBar.Position=UDim2.new(0,42,0.5,18); StartupBar.Size=UDim2.new(1,-84,0,6)
StartupBar.BackgroundColor3=Color3.fromRGB(30,30,42); StartupBar.BorderSizePixel=0; StartupBar.ZIndex=101
Instance.new("UICorner",StartupBar).CornerRadius=UDim.new(1,0)
local StartupFill=Instance.new("Frame",StartupBar)
StartupFill.Size=UDim2.new(0,0,1,0); StartupFill.BackgroundColor3=GhostControlFill
StartupFill.BorderSizePixel=0; StartupFill.ZIndex=102
Instance.new("UICorner",StartupFill).CornerRadius=UDim.new(1,0)
function startupStep(text,pct)
    if StartupStatus then StartupStatus.Text=text end
    if StartupFill then tween(StartupFill,0.18,{Size=UDim2.new(pct,0,1,0)}) end
end
task.spawn(function()
    startupStep("Loading UI...",0.25); task.wait(0.15)
    startupStep("Applying ghost theme...",0.5); task.wait(0.15)
    startupStep("Restoring layout...",0.75); task.wait(0.15)
    startupStep("Ready",1); task.wait(0.35)
    if StartupOverlay then tween(StartupOverlay,0.3,{BackgroundTransparency=1}); task.wait(0.32); pcall(function() StartupOverlay:Destroy() end) end
end)

-- =====================
-- NOTIFICATION SYSTEM
-- =====================
local activePopups={}; local notificationHistory={}
local NotificationDrawer=nil; local NotificationList=nil

function playUISound(kind) return end
_G.GhostDockSound=function() return end

function showApplyPopup(title,body,kind)
    table.insert(notificationHistory,{title=title,body=body,kind=kind,time=os.time()})
    if #notificationHistory>40 then table.remove(notificationHistory,1) end
    if NotificationDrawer and NotificationDrawer.Visible then rebuildNotificationDrawer() end
    if not Config.ApplyPopups then return end
    local pop=Instance.new("Frame",ScreenGui)
    pop.BackgroundColor3=Color3.fromRGB(8,5,13); pop.BackgroundTransparency=0.08
    pop.BorderSizePixel=0; pop.Size=UDim2.new(0,310,0,82)
    pop.Position=UDim2.new(1,-330,0,92+(#activePopups*90)); pop.ZIndex=120
    Instance.new("UICorner",pop).CornerRadius=UDim.new(0,16)
    local st=Instance.new("UIStroke",pop); st.Color=(kind=="error") and Color3.fromRGB(255,80,120) or Config.UIAccent; st.Transparency=0.28; st.Thickness=1
    local ttl=Instance.new("TextLabel",pop)
    ttl.BackgroundTransparency=1; ttl.Position=UDim2.new(0,14,0,9); ttl.Size=UDim2.new(1,-28,0,20)
    ttl.Font=Enum.Font.GothamBlack; ttl.TextSize=13; ttl.TextColor3=Color3.fromRGB(245,245,250)
    ttl.TextXAlignment=Enum.TextXAlignment.Left; ttl.Text=title or "Applied"; ttl.ZIndex=121
    local msg=Instance.new("TextLabel",pop)
    msg.BackgroundTransparency=1; msg.Position=UDim2.new(0,14,0,33); msg.Size=UDim2.new(1,-28,0,38)
    msg.Font=Enum.Font.Gotham; msg.TextSize=11; msg.TextColor3=Color3.fromRGB(165,165,178)
    msg.TextXAlignment=Enum.TextXAlignment.Left; msg.TextYAlignment=Enum.TextYAlignment.Top
    msg.TextWrapped=true; msg.Text=body or "Done"; msg.ZIndex=121
    table.insert(activePopups,pop)
    task.delay(3,function()
        pcall(function() tween(pop,0.25,{BackgroundTransparency=1,Position=pop.Position+UDim2.new(0,30,0,0)}) end)
        task.wait(0.28)
        for i,v in safeIpairs(activePopups) do if v==pop then table.remove(activePopups,i); break end end
        pcall(function() pop:Destroy() end)
    end)
end

function rebuildNotificationDrawer()
    if not NotificationList then return end
    for _,c in safeIpairs(NotificationList:GetChildren()) do if not c:IsA("UIListLayout") then c:Destroy() end end
    for i=#notificationHistory,math.max(1,#notificationHistory-8),-1 do
        local item=notificationHistory[i]
        local r=Instance.new("TextLabel",NotificationList)
        r.BackgroundColor3=Color3.fromRGB(11,7,18); r.BackgroundTransparency=0.12
        r.BorderSizePixel=0; r.Size=UDim2.new(1,0,0,46); r.Font=Enum.Font.Gotham; r.TextSize=11
        r.TextWrapped=true; r.TextXAlignment=Enum.TextXAlignment.Left
        r.TextColor3=Color3.fromRGB(228,210,245); r.Text="  "..(item.title or "").." - "..(item.body or ""); r.ZIndex=132
        Instance.new("UICorner",r).CornerRadius=UDim.new(0,10)
    end
end

function toggleNotificationDrawer()
    if NotificationDrawer and NotificationDrawer.Parent then
        NotificationDrawer.Visible=not NotificationDrawer.Visible; rebuildNotificationDrawer(); return
    end
    NotificationDrawer=Instance.new("Frame",ScreenGui)
    NotificationDrawer.BackgroundColor3=Color3.fromRGB(9,6,20); NotificationDrawer.BackgroundTransparency=0.06
    NotificationDrawer.BorderSizePixel=0; NotificationDrawer.Size=UDim2.new(0,360,0,360)
    NotificationDrawer.Position=UDim2.new(1,-380,0,90); NotificationDrawer.ZIndex=130
    Instance.new("UICorner",NotificationDrawer).CornerRadius=UDim.new(0,18)
    local st=Instance.new("UIStroke",NotificationDrawer); st.Color=Config.UIAccent; st.Transparency=0.28
    local title=Instance.new("TextLabel",NotificationDrawer)
    title.BackgroundTransparency=1; title.Position=UDim2.new(0,16,0,12); title.Size=UDim2.new(1,-70,0,24)
    title.Text="Notifications"; title.Font=Enum.Font.GothamBlack; title.TextSize=15
    title.TextColor3=Color3.fromRGB(245,245,250); title.TextXAlignment=Enum.TextXAlignment.Left; title.ZIndex=131
    local clear=Instance.new("TextButton",NotificationDrawer)
    clear.Size=UDim2.new(0,58,0,26); clear.Position=UDim2.new(1,-72,0,12); clear.Text="Clear"
    clear.Font=Enum.Font.GothamBold; clear.TextSize=11; clear.TextColor3=Color3.fromRGB(245,245,250)
    clear.BackgroundColor3=Color3.fromRGB(22,10,38); clear.ZIndex=131
    Instance.new("UICorner",clear).CornerRadius=UDim.new(0,9)
    NotificationList=Instance.new("Frame",NotificationDrawer)
    NotificationList.BackgroundTransparency=1; NotificationList.Position=UDim2.new(0,14,0,50)
    NotificationList.Size=UDim2.new(1,-28,1,-64); NotificationList.ZIndex=131
    local l=Instance.new("UIListLayout",NotificationList); l.Padding=UDim.new(0,8); l.SortOrder=Enum.SortOrder.LayoutOrder
    clear.MouseButton1Click:Connect(function() notificationHistory={}; rebuildNotificationDrawer() end)
    rebuildNotificationDrawer()
end

-- =====================
-- TAB / PAGE SYSTEM
-- =====================
local pages={}; local tabs={}; local controls={}
local cardRegistry={}; local rowRegistry={}; local collapsedCards={}
local FavoriteKeys={ESP=true,AimAssist=true,TriggerBot=true,TriggerFOVVisible=true,Crosshair=true,OffscreenArrows=true,TargetInfo=true,AimFOV=true,TriggerFOV=true,ArrowSize=true,ArrowPadding=true,FOVTransparency=true}
local favoriteItems={}; local favoriteCategories={}
local favoriteOrder={"Combat","Visuals","Crosshair","HUD","Config","Settings","Other"}
local favoriteStarButtons={}; local favDynamicBody=nil
local rebuildFavorites
local SavedLayout={LastTab="Favorited",MenuPos=nil,MenuSize=nil,Collapsed={}}

local ProfileCard=Instance.new("Frame",Sidebar)
ProfileCard.Name="ProfileCard"; ProfileCard.Position=UDim2.new(0,14,1,-62)
ProfileCard.Size=UDim2.new(1,-28,0,48); ProfileCard.BackgroundColor3=Color3.fromRGB(14,8,28)
ProfileCard.BackgroundTransparency=0.08; ProfileCard.BorderSizePixel=0; ProfileCard.ZIndex=4; ProfileCard.Visible=false
Instance.new("UICorner",ProfileCard).CornerRadius=UDim.new(0,16)
local pcStroke=Instance.new("UIStroke",ProfileCard); pcStroke.Color=Config.UIAccent; pcStroke.Transparency=0.48
local pcTitle=Instance.new("TextLabel",ProfileCard)
pcTitle.BackgroundTransparency=1; pcTitle.Position=UDim2.new(0,10,0,5); pcTitle.Size=UDim2.new(1,-20,0,16)
pcTitle.Font=Enum.Font.GothamBlack; pcTitle.TextSize=11; pcTitle.TextColor3=Color3.fromRGB(245,245,250)
pcTitle.TextXAlignment=Enum.TextXAlignment.Left; pcTitle.Text="GHOST"; pcTitle.ZIndex=62
local pcBody=Instance.new("TextLabel",ProfileCard)
pcBody.BackgroundTransparency=1; pcBody.Position=UDim2.new(0,10,0,21); pcBody.Size=UDim2.new(1,-20,0,24)
pcBody.Font=Enum.Font.Gotham; pcBody.TextSize=9; pcBody.TextColor3=Color3.fromRGB(165,165,178)
pcBody.TextXAlignment=Enum.TextXAlignment.Left; pcBody.TextYAlignment=Enum.TextYAlignment.Top
pcBody.TextWrapped=true; pcBody.Text="Bright Phantom\nRuntime 0m"; pcBody.ZIndex=62

-- Sidebar auto-collapse
local sidebarExpanded=true; local sidebarExpandedWidth=190; local sidebarCollapsedWidth=86
local sidebarLastHover=now(); local sidebarHoverPad=18

function setSidebarExpanded(state,instant)
    sidebarExpanded=state
    local w=state and sidebarExpandedWidth or sidebarCollapsedWidth
    local t=instant and 0 or 0.24
    tween(Sidebar,t,{Size=UDim2.new(0,w,1,0)},Enum.EasingStyle.Quint)
    tween(Main,t,{Position=UDim2.new(0,w,0,0),Size=UDim2.new(1,-w,1,0)},Enum.EasingStyle.Quint)
    tween(TabHolder,t,{Position=UDim2.new(0,state and 14 or 12,0,92),Size=UDim2.new(1,state and -28 or -24,1,-106)},Enum.EasingStyle.Quint)
    tween(Logo,t,{TextTransparency=state and 0 or 1,TextSize=state and 24 or 1},Enum.EasingStyle.Quint)
    tween(SubLogo,t,{TextTransparency=state and 0 or 1},Enum.EasingStyle.Quint)
    ProfileCard.Visible=(state and Config.ProfileCard==true)
    for name,btn in safePairs(tabs or {}) do
        btn.Text=state and (btn:GetAttribute("FullLabel") or btn.Text) or (btn:GetAttribute("IconOnly") or "*")
        btn.TextXAlignment=state and Enum.TextXAlignment.Left or Enum.TextXAlignment.Center
        btn.TextSize=state and 13 or 16
        btn.Size=UDim2.new(1,0,0,state and 36 or 42)
    end
end

function sidebarMouseInside()
    local m=UIS:GetMouseLocation()
    local p=Sidebar.AbsolutePosition; local sz=Sidebar.AbsoluteSize
    return m.X>=p.X-sidebarHoverPad and m.X<=p.X+sz.X+sidebarHoverPad and m.Y>=p.Y-sidebarHoverPad and m.Y<=p.Y+sz.Y+sidebarHoverPad
end

RunService.RenderStepped:Connect(function()
    if ghostUnloaded then return end
    if not Root.Visible or Config.Hidden then return end
    if sidebarMouseInside() then
        sidebarLastHover=now()
        if not sidebarExpanded then setSidebarExpanded(true,false) end
    elseif sidebarExpanded and (now()-sidebarLastHover)>0.55 then
        setSidebarExpanded(false,false)
    end
end)

function clearContent()
    for _,c in safeIpairs(Content:GetChildren()) do if not c:IsA("UIListLayout") then c.Visible=false end end
end

function makeTab(name,icon)
    local b=Instance.new("TextButton",TabHolder)
    b.Size=UDim2.new(1,0,0,36); b.BackgroundColor3=Color3.fromRGB(4,4,8)
    b.Text="  "..icon.."  "..name
    b:SetAttribute("FullLabel","  "..icon.."  "..name)
    b:SetAttribute("IconOnly",icon)
    b.Font=Enum.Font.GothamBold; b.TextSize=13
    b.TextXAlignment=Enum.TextXAlignment.Left
    b.TextColor3=Color3.fromRGB(170,170,185); b.AutoButtonColor=false; b.ZIndex=4
    Instance.new("UICorner",b).CornerRadius=UDim.new(0,15)
    local st=Instance.new("UIStroke",b); st.Color=Config.UIAccent; st.Transparency=0.78; st.Thickness=1
    tabs[name]=b
    b.MouseEnter:Connect(function()
        if currentTab~=name then tween(b,0.16,{BackgroundColor3=Color3.fromRGB(20,12,34),TextColor3=Color3.fromRGB(220,205,245)},Enum.EasingStyle.Quint) end
    end)
    b.MouseLeave:Connect(function()
        if currentTab~=name then tween(b,0.16,{BackgroundColor3=Color3.fromRGB(4,4,8),TextColor3=Color3.fromRGB(170,170,185)},Enum.EasingStyle.Quint) end
    end)
    b.MouseButton1Click:Connect(function() showTab(name) end)
end

function makePage(name)
    local f=Instance.new("Frame",Content)
    f.Name=name; f.BackgroundTransparency=1; f.Size=UDim2.new(1,0,0,10); f.Visible=false; f.ZIndex=3
    local l=Instance.new("UIListLayout",f); l.Padding=UDim.new(0,12); l.SortOrder=Enum.SortOrder.LayoutOrder
    l:GetPropertyChangedSignal("AbsoluteContentSize"):Connect(function() f.Size=UDim2.new(1,0,0,l.AbsoluteContentSize.Y+4) end)
    pages[name]=f
    return f
end

function card(parent,title,icon)
    local c=Instance.new("Frame",parent)
    c.BackgroundColor3=Color3.fromRGB(14,8,22); c.BackgroundTransparency=0
    c.BorderSizePixel=0; c.Size=UDim2.new(1,0,0,70); c.ZIndex=55
    c:SetAttribute("GhostTitle",title)
    registerGlass(c,0.22,0.02)
    Instance.new("UICorner",c).CornerRadius=UDim.new(0,22)
    local st=Instance.new("UIStroke",c); st.Color=Color3.fromRGB(120,55,190); st.Transparency=0.38
    local cg=Instance.new("UIGradient",c); cg.Rotation=90
    cg.Color=ColorSequence.new{ColorSequenceKeypoint.new(0,Color3.fromRGB(36,16,68)),ColorSequenceKeypoint.new(0.45,Color3.fromRGB(18,10,36)),ColorSequenceKeypoint.new(1,Color3.fromRGB(6,4,14))}
    local t=Instance.new("TextLabel",c)
    t.Size=UDim2.new(1,-68,0,28); t.Position=UDim2.new(0,14,0,10); t.BackgroundTransparency=1
    t.Text=icon.."  "..title; t.Font=Enum.Font.GothamBlack; t.TextSize=15
    t.TextColor3=Color3.fromRGB(255,255,255); t.TextXAlignment=Enum.TextXAlignment.Left; t.ZIndex=57
    local collapse=Instance.new("TextButton",c)
    collapse.Size=UDim2.new(0,34,0,28); collapse.Position=UDim2.new(1,-46,0,10)
    collapse.BackgroundColor3=Color3.fromRGB(10,8,18); collapse.BackgroundTransparency=0.1
    collapse.Text="v"; collapse.Font=Enum.Font.GothamBlack; collapse.TextSize=14
    collapse.TextColor3=GhostTextMain; collapse.AutoButtonColor=false; collapse.ZIndex=58
    Instance.new("UICorner",collapse).CornerRadius=UDim.new(0,9)
    local headerHit=Instance.new("TextButton",c)
    headerHit.Size=UDim2.new(1,-60,0,44); headerHit.Position=UDim2.new(0,0,0,0)
    headerHit.BackgroundTransparency=1; headerHit.Text=""; headerHit.AutoButtonColor=false; headerHit.ZIndex=56
    local body=Instance.new("Frame",c)
    body.BackgroundTransparency=1; body.Position=UDim2.new(0,14,0,44)
    body.Size=UDim2.new(1,-28,1,-54); body.ZIndex=56
    body:SetAttribute("FavCategory",parent and parent.Name or "Other")
    local l=Instance.new("UIListLayout",body); l.Padding=UDim.new(0,8); l.SortOrder=Enum.SortOrder.LayoutOrder
    local record={card=c,body=body,title=title,collapsed=false,height=70}
    table.insert(cardRegistry,record)
    local function resizeCard()
        local h=l.AbsoluteContentSize.Y
        record.height=h+60; body.Size=UDim2.new(1,-28,0,h)
        if record.collapsed then body.Visible=false; c.Size=UDim2.new(1,0,0,48); collapse.Text=">"
        else body.Visible=true; c.Size=UDim2.new(1,0,0,h+60); collapse.Text="v" end
    end
    record.resize=resizeCard
    local function toggleCollapse()
        record.collapsed=not record.collapsed; collapsedCards[title]=record.collapsed; resizeCard()
    end
    collapse.MouseButton1Click:Connect(toggleCollapse)
    headerHit.MouseButton1Click:Connect(toggleCollapse)
    l:GetPropertyChangedSignal("AbsoluteContentSize"):Connect(resizeCard)
    resizeCard()
    return body,c
end

function row(parent,label,desc)
    local r=Instance.new("Frame",parent)
    r.BackgroundColor3=Color3.fromRGB(11,7,18); r.BackgroundTransparency=0.16
    r.BorderSizePixel=0; r.Size=UDim2.new(1,0,0,46); r.ZIndex=57
    r:SetAttribute("SearchText",(label.." "..(desc or "")):lower())
    registerGlass(r,0.16,0.02)
    Instance.new("UICorner",r).CornerRadius=UDim.new(0,18)
    local st=Instance.new("UIStroke",r); st.Color=Color3.fromRGB(120,55,190); st.Transparency=0.58
    local l=Instance.new("TextLabel",r)
    l.BackgroundTransparency=1; l.Position=UDim2.new(0,36,0,5); l.Size=UDim2.new(0.54,0,0,18)
    l.Text=label; l.Font=Enum.Font.GothamBold; l.TextSize=14; l.TextColor3=Color3.fromRGB(255,248,255)
    l.TextXAlignment=Enum.TextXAlignment.Left; l.ZIndex=58
    local d=Instance.new("TextLabel",r)
    d.BackgroundTransparency=1; d.Position=UDim2.new(0,36,0,24); d.Size=UDim2.new(0.64,0,0,16)
    d.Text=desc or ""; d.Font=Enum.Font.Gotham; d.TextSize=11; d.TextColor3=Color3.fromRGB(195,180,220)
    d.TextXAlignment=Enum.TextXAlignment.Left; d.ZIndex=58
    table.insert(rowRegistry,{row=r,parent=parent,text=(label.." "..(desc or "")):lower()})
    return r
end

function saveLayoutState()
    SavedLayout.LastTab=currentTab
    SavedLayout.MenuPos={Root.Position.X.Scale,Root.Position.X.Offset,Root.Position.Y.Scale,Root.Position.Y.Offset}
    SavedLayout.Collapsed=collapsedCards
end

function favoriteRefreshKey(key)
    for _,btn in safeIpairs(favoriteStarButtons[key] or {}) do
        btn.Text=FavoriteKeys[key] and "*" or "o"
        btn.TextColor3=FavoriteKeys[key] and GhostTextMain or Color3.fromRGB(105,105,125)
    end
end

function favoriteCategoryFromParent(parent)
    local cur=parent
    while cur do
        if cur.GetAttribute and cur:GetAttribute("FavCategory") then return cur:GetAttribute("FavCategory") end
        cur=cur.Parent
    end
    return "Other"
end

function addFavoriteStar(rowObj,key,label,desc,kind,args,category)
    if not key then return end
    favoriteItems[key]=favoriteItems[key] or {key=key,label=label,desc=desc,kind=kind,args=args,category=category or favoriteCategoryFromParent(rowObj.Parent)}
    favoriteCategories[key]=favoriteItems[key].category or "Other"
    favoriteStarButtons[key]=favoriteStarButtons[key] or {}
    local star=Instance.new("TextButton",rowObj)
    star.Size=UDim2.new(0,22,0,22); star.Position=UDim2.new(0,8,0.5,-11)
    star.BackgroundTransparency=1; star.Text=FavoriteKeys[key] and "*" or "o"
    star.Font=Enum.Font.GothamBlack; star.TextSize=15
    star.TextColor3=FavoriteKeys[key] and GhostTextMain or Color3.fromRGB(105,105,125)
    star.AutoButtonColor=false; star.ZIndex=60
    table.insert(favoriteStarButtons[key],star)
    star.MouseButton1Click:Connect(function()
        FavoriteKeys[key]=not FavoriteKeys[key]; favoriteRefreshKey(key)
        if rebuildFavorites then rebuildFavorites() end; ghostSaveConfigFile(true)
    end)
end

function favRow(parent,label,desc)
    local r=Instance.new("Frame",parent)
    r.BackgroundColor3=Color3.fromRGB(11,7,18); r.BackgroundTransparency=0.16
    r.BorderSizePixel=0; r.Size=UDim2.new(1,0,0,46); r.ZIndex=57
    Instance.new("UICorner",r).CornerRadius=UDim.new(0,18)
    local st=Instance.new("UIStroke",r); st.Color=Color3.fromRGB(120,55,190); st.Transparency=0.58
    local l=Instance.new("TextLabel",r)
    l.BackgroundTransparency=1; l.Position=UDim2.new(0,36,0,5); l.Size=UDim2.new(0.54,0,0,18)
    l.Text=label; l.Font=Enum.Font.GothamBold; l.TextSize=14; l.TextColor3=Color3.fromRGB(255,248,255)
    l.TextXAlignment=Enum.TextXAlignment.Left; l.ZIndex=58
    local d=Instance.new("TextLabel",r)
    d.BackgroundTransparency=1; d.Position=UDim2.new(0,36,0,24); d.Size=UDim2.new(0.64,0,0,16)
    d.Text=desc or ""; d.Font=Enum.Font.Gotham; d.TextSize=11; d.TextColor3=Color3.fromRGB(195,180,220)
    d.TextXAlignment=Enum.TextXAlignment.Left; d.ZIndex=58
    return r
end

function buildFavoriteControl(parent,item)
    if not item or not item.key then return end
    local key=item.key
    local r=favRow(parent,item.label,item.desc)
    local unstar=Instance.new("TextButton",r)
    unstar.Size=UDim2.new(0,22,0,22); unstar.Position=UDim2.new(0,8,0.5,-11)
    unstar.BackgroundTransparency=1; unstar.Text="*"; unstar.Font=Enum.Font.GothamBlack
    unstar.TextSize=15; unstar.TextColor3=GhostTextMain; unstar.AutoButtonColor=false; unstar.ZIndex=60
    unstar.MouseButton1Click:Connect(function()
        FavoriteKeys[key]=false; favoriteRefreshKey(key)
        if rebuildFavorites then rebuildFavorites() end; ghostSaveConfigFile(true)
    end)
    if item.kind=="toggle" then
        local b=Instance.new("TextButton",r)
        b.Size=UDim2.new(0,54,0,26); b.Position=UDim2.new(1,-66,0.5,-13)
        b.BackgroundColor3=Config[key] and GhostControlOn or Color3.fromRGB(45,48,62)
        b.Text=""; b.AutoButtonColor=false; b.ZIndex=59
        Instance.new("UICorner",b).CornerRadius=UDim.new(1,0)
        local knob=Instance.new("Frame",b)
        knob.Size=UDim2.new(0,20,0,20)
        knob.Position=Config[key] and UDim2.new(1,-23,0.5,-10) or UDim2.new(0,3,0.5,-10)
        knob.BackgroundColor3=Color3.fromRGB(255,255,255); knob.ZIndex=60
        Instance.new("UICorner",knob).CornerRadius=UDim.new(1,0)
        local function refresh()
            tween(b,0.16,{BackgroundColor3=Config[key] and GhostControlOn or Color3.fromRGB(45,48,62)})
            tween(knob,0.16,{Position=Config[key] and UDim2.new(1,-23,0.5,-10) or UDim2.new(0,3,0.5,-10)})
        end
        b.MouseButton1Click:Connect(function()
            Config[key]=not Config[key]; refresh()
            if controls[key] then pcall(controls[key]) end; ghostScheduleAutoSave()
        end)
    elseif item.kind=="slider" then
        local args=item.args or {}
        local min,max,step=args.min or 0,args.max or 100,args.step or 1
        local val=Instance.new("TextLabel",r)
        val.BackgroundTransparency=1; val.Size=UDim2.new(0,82,0,20); val.Position=UDim2.new(1,-90,0,4)
        val.Font=Enum.Font.GothamBold; val.TextSize=12; val.TextColor3=GhostTextMain; val.ZIndex=59
        local bar=Instance.new("Frame",r)
        bar.Size=UDim2.new(0,180,0,6); bar.Position=UDim2.new(1,-214,0,31)
        bar.BackgroundColor3=Color3.fromRGB(52,46,72); bar.ZIndex=59
        Instance.new("UICorner",bar).CornerRadius=UDim.new(1,0)
        local fill=Instance.new("Frame",bar)
        fill.BackgroundColor3=GhostControlFill; fill.ZIndex=60
        Instance.new("UICorner",fill).CornerRadius=UDim.new(1,0)
        local draggingS=false
        local function refresh()
            local pct=(Config[key]-min)/(max-min)
            fill.Size=UDim2.new(pct,0,1,0); val.Text=tostring(Config[key])
        end
        local function setFromX(x)
            local pct=clamp((x-bar.AbsolutePosition.X)/bar.AbsoluteSize.X,0,1)
            local v=math.floor((min+(max-min)*pct)/(step or 1)+0.5)*(step or 1)
            Config[key]=clamp(v,min,max); refresh()
            if controls[key] then pcall(controls[key]) end; ghostScheduleAutoSave()
        end
        refresh()
        bar.InputBegan:Connect(function(i) if i.UserInputType==Enum.UserInputType.MouseButton1 then draggingS=true; setFromX(i.Position.X) end end)
        UIS.InputEnded:Connect(function(i) if i.UserInputType==Enum.UserInputType.MouseButton1 then draggingS=false end end)
        UIS.InputChanged:Connect(function(i) if draggingS and i.UserInputType==Enum.UserInputType.MouseMovement then setFromX(i.Position.X) end end)
    elseif item.kind=="dropdown" then
        local options=item.args and item.args.options or {}
        local b=Instance.new("TextButton",r)
        b.Size=UDim2.new(0,160,0,28); b.Position=UDim2.new(1,-172,0.5,-14)
        b.BackgroundColor3=Color3.fromRGB(22,10,38); b.Text=tostring(Config[key]).."  v"
        b.Font=Enum.Font.GothamBold; b.TextSize=12; b.TextColor3=Color3.fromRGB(240,240,255); b.ZIndex=59
        Instance.new("UICorner",b).CornerRadius=UDim.new(0,15)
        local idx=1; for i,v in safeIpairs(options) do if v==Config[key] then idx=i end end
        b.MouseButton1Click:Connect(function()
            if #options<1 then return end; idx=idx%#options+1; Config[key]=options[idx]
            b.Text=tostring(Config[key]).."  v"
            if controls[key] then pcall(controls[key]) end; ghostScheduleAutoSave()
        end)
    elseif item.kind=="keybind" then
        local b=Instance.new("TextButton",r)
        b.Size=UDim2.new(0,130,0,28); b.Position=UDim2.new(1,-142,0.5,-14)
        b.BackgroundColor3=Color3.fromRGB(22,10,38); b.Text=Config[key]
        b.Font=Enum.Font.GothamBold; b.TextSize=12; b.TextColor3=Color3.fromRGB(240,240,255); b.ZIndex=59
        Instance.new("UICorner",b).CornerRadius=UDim.new(0,15)
        local listening=false
        b.MouseButton1Click:Connect(function() listening=true; b.Text="Press key..." end)
        UIS.InputBegan:Connect(function(input,gp)
            if listening then
                listening=false
                if input.UserInputType==Enum.UserInputType.MouseButton1 then Config[key]="MouseButton1"
                elseif input.UserInputType==Enum.UserInputType.MouseButton2 then Config[key]="MouseButton2"
                elseif input.KeyCode~=Enum.KeyCode.Unknown then Config[key]=input.KeyCode.Name end
                b.Text=Config[key]
                if controls[key] then pcall(controls[key]) end; ghostScheduleAutoSave()
            end
        end)
    end
end

function toggle(parent,label,desc,key)
    local r=row(parent,label,desc)
    addFavoriteStar(r,key,label,desc,"toggle")
    local b=Instance.new("TextButton",r)
    b.Size=UDim2.new(0,54,0,26); b.Position=UDim2.new(1,-66,0.5,-13)
    b.BackgroundColor3=Config[key] and GhostControlOn or Color3.fromRGB(45,48,62)
    b.Text=""; b.AutoButtonColor=false; b.ZIndex=59
    Instance.new("UICorner",b).CornerRadius=UDim.new(1,0)
    local knob=Instance.new("Frame",b)
    knob.Size=UDim2.new(0,20,0,20)
    knob.Position=Config[key] and UDim2.new(1,-23,0.5,-10) or UDim2.new(0,3,0.5,-10)
    knob.BackgroundColor3=Color3.fromRGB(255,255,255); knob.ZIndex=60
    Instance.new("UICorner",knob).CornerRadius=UDim.new(1,0)
    local function refresh()
        tween(b,0.16,{BackgroundColor3=Config[key] and GhostControlOn or Color3.fromRGB(45,48,62)})
        tween(knob,0.16,{Position=Config[key] and UDim2.new(1,-23,0.5,-10) or UDim2.new(0,3,0.5,-10)})
    end
    b.MouseButton1Click:Connect(function() Config[key]=not Config[key]; refresh(); ghostScheduleAutoSave() end)
    controls[key]=refresh
end

function slider(parent,label,desc,key,min,max,step)
    local r=row(parent,label,desc)
    addFavoriteStar(r,key,label,desc,"slider",{min=min,max=max,step=step})
    local val=Instance.new("TextLabel",r)
    val.BackgroundTransparency=1; val.Size=UDim2.new(0,82,0,20); val.Position=UDim2.new(1,-90,0,4)
    val.Font=Enum.Font.GothamBold; val.TextSize=12; val.TextColor3=GhostTextMain; val.ZIndex=59
    local bar=Instance.new("Frame",r)
    bar.Size=UDim2.new(0,180,0,6); bar.Position=UDim2.new(1,-214,0,31)
    bar.BackgroundColor3=Color3.fromRGB(52,46,72); bar.ZIndex=59
    Instance.new("UICorner",bar).CornerRadius=UDim.new(1,0)
    local fill=Instance.new("Frame",bar)
    fill.BackgroundColor3=GhostControlFill; fill.Size=UDim2.new(0,0,1,0); fill.ZIndex=60
    Instance.new("UICorner",fill).CornerRadius=UDim.new(1,0)
    local draggingS=false
    local function setFromX(x)
        local pct=clamp((x-bar.AbsolutePosition.X)/bar.AbsoluteSize.X,0,1)
        local v=math.floor((min+(max-min)*pct)/(step or 1)+0.5)*(step or 1)
        Config[key]=clamp(v,min,max); ghostScheduleAutoSave()
        fill.Size=UDim2.new((Config[key]-min)/(max-min),0,1,0); val.Text=tostring(Config[key])
    end
    local function refresh()
        fill.Size=UDim2.new((Config[key]-min)/(max-min),0,1,0); val.Text=tostring(Config[key])
    end
    refresh()
    bar.InputBegan:Connect(function(i) if i.UserInputType==Enum.UserInputType.MouseButton1 then draggingS=true; setFromX(i.Position.X) end end)
    UIS.InputEnded:Connect(function(i) if i.UserInputType==Enum.UserInputType.MouseButton1 then draggingS=false end end)
    UIS.InputChanged:Connect(function(i) if draggingS and i.UserInputType==Enum.UserInputType.MouseMovement then setFromX(i.Position.X) end end)
    controls[key]=refresh
end

function dropdown(parent,label,desc,key,options)
    local r=row(parent,label,desc)
    addFavoriteStar(r,key,label,desc,"dropdown",{options=options})
    local b=Instance.new("TextButton",r)
    b.Size=UDim2.new(0,160,0,28); b.Position=UDim2.new(1,-172,0.5,-14)
    b.BackgroundColor3=Color3.fromRGB(22,10,38); b.Text=tostring(Config[key]).."  v"
    b.Font=Enum.Font.GothamBold; b.TextSize=12; b.TextColor3=Color3.fromRGB(240,240,255); b.ZIndex=59
    Instance.new("UICorner",b).CornerRadius=UDim.new(0,15)
    local idx=1; for i,v in safeIpairs(options) do if v==Config[key] then idx=i end end
    b.MouseButton1Click:Connect(function()
        idx=idx%#options+1; Config[key]=options[idx]; b.Text=tostring(Config[key]).."  v"; ghostScheduleAutoSave()
    end)
    controls[key]=function() b.Text=tostring(Config[key]).."  v" end
end

function keybind(parent,label,desc,key)
    local r=row(parent,label,desc)
    addFavoriteStar(r,key,label,desc,"keybind")
    local b=Instance.new("TextButton",r)
    b.Size=UDim2.new(0,130,0,28); b.Position=UDim2.new(1,-142,0.5,-14)
    b.BackgroundColor3=Color3.fromRGB(22,10,38); b.Text=Config[key]
    b.Font=Enum.Font.GothamBold; b.TextSize=12; b.TextColor3=Color3.fromRGB(240,240,255); b.ZIndex=59
    Instance.new("UICorner",b).CornerRadius=UDim.new(0,15)
    local listening=false
    b.MouseButton1Click:Connect(function() listening=true; b.Text="Press key..." end)
    UIS.InputBegan:Connect(function(input,gp)
        if listening then
            listening=false
            if input.UserInputType==Enum.UserInputType.MouseButton1 then Config[key]="MouseButton1"
            elseif input.UserInputType==Enum.UserInputType.MouseButton2 then Config[key]="MouseButton2"
            elseif input.KeyCode~=Enum.KeyCode.Unknown then Config[key]=input.KeyCode.Name end
            b.Text=Config[key]; ghostScheduleAutoSave()
        end
    end)
end

function button(parent,label,desc,callback)
    local r=row(parent,label,desc)
    local b=Instance.new("TextButton",r)
    b.Size=UDim2.new(0,130,0,28); b.Position=UDim2.new(1,-142,0.5,-14)
    b.BackgroundColor3=Color3.fromRGB(28,28,36); b.Text="Run"
    b.Font=Enum.Font.GothamBlack; b.TextSize=12; b.TextColor3=Color3.fromRGB(255,255,255); b.ZIndex=59
    Instance.new("UICorner",b).CornerRadius=UDim.new(0,15)
    b.MouseButton1Click:Connect(function() callback(); ghostScheduleAutoSave() end)
end

-- =====================
-- PREVIEW HELPERS
-- =====================
function miniLabel(parent,text,pos,size,color)
    local l=Instance.new("TextLabel",parent)
    l.BackgroundTransparency=1; l.Position=pos; l.Size=size; l.Text=text
    l.Font=Enum.Font.GothamBold; l.TextSize=11; l.TextColor3=color or Color3.fromRGB(235,235,245)
    l.TextXAlignment=Enum.TextXAlignment.Left; l.ZIndex=12
    return l
end

function crosshairPreview(parent)
    local r=row(parent,"Live Preview","Shows crosshair while menu is open")
    r.Size=UDim2.new(1,0,0,120)
    local box=Instance.new("Frame",r)
    box.Position=UDim2.new(1,-172,0,8); box.Size=UDim2.new(0,160,0,104)
    box.BackgroundColor3=Color3.fromRGB(5,5,8); box.BorderSizePixel=0; box.ZIndex=8
    Instance.new("UICorner",box).CornerRadius=UDim.new(0,14)
    local st=Instance.new("UIStroke",box); st.Color=Config.UIAccent; st.Transparency=0.55
    local dot=Instance.new("Frame",box)
    dot.Size=UDim2.new(0,4,0,4); dot.AnchorPoint=Vector2.new(0.5,0.5); dot.Position=UDim2.new(0.5,0,0.5,0)
    dot.BorderSizePixel=0; dot.ZIndex=10; Instance.new("UICorner",dot).CornerRadius=UDim.new(1,0)
    local lines={}
    for i=1,4 do local l=Instance.new("Frame",box); l.BorderSizePixel=0; l.ZIndex=10; lines[i]=l end
    RunService.RenderStepped:Connect(function()
        if ghostUnloaded or not box.Parent then return end
        local col=Config.CrosshairColor
        dot.BackgroundColor3=col; dot.Visible=Config.Crosshair and Config.CrosshairDot
        local gap=Config.CrosshairGap; local len=Config.CrosshairLength; local th=Config.CrosshairThickness
        local profile=Config.CrosshairProfile or "Classic"
        local cx,cy=80,52
        local specs={{cx+gap,cy-th/2,len,th},{cx-gap-len,cy-th/2,len,th},{cx-th/2,cy+gap,th,len},{cx-th/2,cy-gap-len,th,len}}
        for i,l in ipairs(lines) do
            local s=specs[i]
            l.Position=UDim2.new(0,s[1],0,s[2]); l.Size=UDim2.new(0,s[3],0,s[4]); l.BackgroundColor3=col
            l.Visible=Config.Crosshair and profile~="Dot" and not (profile=="T-Shape" and i==3)
        end
    end)
end

function visualPreview(parent)
    local r=row(parent,"Live Preview","ESP/arrows/FOV sample while editing")
    r.Size=UDim2.new(1,0,0,150)
    local box=Instance.new("Frame",r)
    box.Position=UDim2.new(1,-242,0,8); box.Size=UDim2.new(0,230,0,132)
    box.BackgroundColor3=Color3.fromRGB(5,5,8); box.BorderSizePixel=0; box.ZIndex=8
    Instance.new("UICorner",box).CornerRadius=UDim.new(0,14)
    local st=Instance.new("UIStroke",box); st.Color=Config.UIAccent; st.Transparency=0.55
    miniLabel(box,"VISUALS",UDim2.new(0,10,0,6),UDim2.new(1,-20,0,16),Color3.fromRGB(180,180,200))
    local dummy=Instance.new("Frame",box); dummy.Position=UDim2.new(0,96,0,38); dummy.Size=UDim2.new(0,36,0,54); dummy.BackgroundColor3=Color3.fromRGB(24,26,34); dummy.BorderSizePixel=0; dummy.ZIndex=10; Instance.new("UICorner",dummy).CornerRadius=UDim.new(0,8)
    local boxLine=Instance.new("Frame",box); boxLine.BackgroundTransparency=1; boxLine.Position=UDim2.new(0,91,0,33); boxLine.Size=UDim2.new(0,46,0,64); boxLine.ZIndex=11
    local boxStroke=Instance.new("UIStroke",boxLine); boxStroke.Color=Color3.fromRGB(120,255,170); boxStroke.Thickness=1; boxStroke.Transparency=0.05
    local name=miniLabel(box,"Target",UDim2.new(0,94,0,18),UDim2.new(0,80,0,14),Color3.fromRGB(255,255,255))
    local dist=miniLabel(box,"42m",UDim2.new(0,103,0,98),UDim2.new(0,60,0,14),Color3.fromRGB(210,210,210))
    local hpBack=Instance.new("Frame",box); hpBack.Position=UDim2.new(0,84,0,34); hpBack.Size=UDim2.new(0,4,0,62); hpBack.BackgroundColor3=Color3.fromRGB(45,48,62); hpBack.BorderSizePixel=0; hpBack.ZIndex=10
    local hp=Instance.new("Frame",hpBack); hp.AnchorPoint=Vector2.new(0,1); hp.Position=UDim2.new(0,0,1,0); hp.Size=UDim2.new(1,0,0.72,0); hp.BackgroundColor3=Color3.fromRGB(90,255,150); hp.BorderSizePixel=0; hp.ZIndex=11
    local tracer=Instance.new("Frame",box); tracer.AnchorPoint=Vector2.new(0.5,0.5); tracer.BackgroundColor3=GhostControlFill; tracer.BorderSizePixel=0; tracer.ZIndex=9
    local arrow=Instance.new("TextLabel",box); arrow.BackgroundTransparency=1; arrow.Position=UDim2.new(1,-38,0,50); arrow.Size=UDim2.new(0,28,0,28); arrow.Text=">"; arrow.Font=Enum.Font.GothamBlack; arrow.TextSize=26; arrow.TextColor3=Color3.fromRGB(255,190,70); arrow.ZIndex=12
    local fov=Instance.new("Frame",box); fov.AnchorPoint=Vector2.new(0.5,0.5); fov.Position=UDim2.new(0,44,0,78); fov.Size=UDim2.new(0,48,0,48); fov.BackgroundTransparency=1; fov.ZIndex=10; Instance.new("UICorner",fov).CornerRadius=UDim.new(1,0)
    local fs=Instance.new("UIStroke",fov); fs.Color=Color3.fromRGB(90,220,255); fs.Thickness=1; fs.Transparency=0.35
    local trig=Instance.new("Frame",box); trig.AnchorPoint=Vector2.new(0.5,0.5); trig.Position=UDim2.new(0,44,0,78); trig.Size=UDim2.new(0,30,0,30); trig.BackgroundTransparency=1; trig.ZIndex=11; Instance.new("UICorner",trig).CornerRadius=UDim.new(1,0)
    local ts=Instance.new("UIStroke",trig); ts.Color=Color3.fromRGB(255,190,70); ts.Thickness=1; ts.Transparency=0.25
    RunService.RenderStepped:Connect(function()
        if ghostUnloaded or not box.Parent then return end
        local active=(currentTab=="Visuals" or currentTab=="Favorited")
        box.Visible=active and previewsEnabled()
        boxStroke.Enabled=Config.BoxESP; name.Visible=Config.NameESP; dist.Visible=Config.DistanceESP
        hpBack.Visible=Config.HealthESP; hp.Visible=Config.HealthESP; tracer.Visible=Config.TracerESP
        arrow.Visible=Config.OffscreenArrows; fs.Thickness=Config.FOVThickness; fs.Transparency=Config.FOVTransparency
        fov.Visible=Config.FOVVisible; ts.Thickness=Config.FOVThickness; ts.Transparency=Config.FOVTransparency
        trig.Visible=Config.TriggerFOVVisible; arrow.TextSize=Config.ArrowSize+10
        st.Color=Config.UIAccent; tracer.BackgroundColor3=GhostControlFill
        tracer.Position=UDim2.new(0,70,0,104); tracer.Size=UDim2.new(0,70,0,2); tracer.Rotation=-28
    end)
end

function combatPreview(parent)
    local r=row(parent,"Live Preview","Aim/trigger target HUD sample")
    r.Size=UDim2.new(1,0,0,150)
    local box=Instance.new("Frame",r)
    box.Position=UDim2.new(1,-242,0,8); box.Size=UDim2.new(0,230,0,132)
    box.BackgroundColor3=Color3.fromRGB(5,5,8); box.BorderSizePixel=0; box.ZIndex=8
    Instance.new("UICorner",box).CornerRadius=UDim.new(0,14)
    local st=Instance.new("UIStroke",box); st.Color=Config.UIAccent; st.Transparency=0.55
    miniLabel(box,"COMBAT",UDim2.new(0,10,0,6),UDim2.new(1,-20,0,16),Color3.fromRGB(180,180,200))
    local aim=Instance.new("Frame",box); aim.AnchorPoint=Vector2.new(0.5,0.5); aim.Position=UDim2.new(0,58,0,76); aim.Size=UDim2.new(0,64,0,64); aim.BackgroundTransparency=1; aim.ZIndex=9; Instance.new("UICorner",aim).CornerRadius=UDim.new(1,0)
    local as=Instance.new("UIStroke",aim); as.Color=Color3.fromRGB(90,220,255); as.Thickness=1; as.Transparency=0.35
    local trig=Instance.new("Frame",box); trig.AnchorPoint=Vector2.new(0.5,0.5); trig.Position=UDim2.new(0,58,0,76); trig.Size=UDim2.new(0,34,0,34); trig.BackgroundTransparency=1; trig.ZIndex=10; Instance.new("UICorner",trig).CornerRadius=UDim.new(1,0)
    local ts=Instance.new("UIStroke",trig); ts.Color=Color3.fromRGB(255,190,70); ts.Thickness=1; ts.Transparency=0.25
    local target=Instance.new("Frame",box); target.Position=UDim2.new(0,134,0,48); target.Size=UDim2.new(0,34,0,48); target.BackgroundColor3=Color3.fromRGB(24,26,34); target.BorderSizePixel=0; target.ZIndex=10; Instance.new("UICorner",target).CornerRadius=UDim.new(0,8)
    local lock=Instance.new("Frame",box); lock.AnchorPoint=Vector2.new(0.5,0.5); lock.Position=UDim2.new(0,151,0,60); lock.Size=UDim2.new(0,24,0,24); lock.BackgroundTransparency=1; lock.ZIndex=12; Instance.new("UICorner",lock).CornerRadius=UDim.new(1,0)
    local ls=Instance.new("UIStroke",lock); ls.Color=Color3.fromRGB(90,255,150); ls.Thickness=2; ls.Transparency=0.1
    local info=miniLabel(box,"100 HP | 42m",UDim2.new(0,118,0,98),UDim2.new(0,100,0,16),Color3.fromRGB(235,255,235))
    local status=miniLabel(box,"TRG HOLD",UDim2.new(0,10,0,112),UDim2.new(1,-20,0,14),Color3.fromRGB(255,190,70))
    RunService.RenderStepped:Connect(function()
        if ghostUnloaded or not box.Parent then return end
        local active=(currentTab=="Combat" or currentTab=="Favorited")
        box.Visible=active and previewsEnabled(); st.Color=Config.UIAccent
        aim.Visible=Config.FOVVisible and (Config.Aimbot or Config.AimAssist); as.Thickness=Config.FOVThickness; as.Transparency=Config.FOVTransparency
        trig.Visible=Config.TriggerBot and Config.TriggerFOVVisible; ts.Thickness=Config.FOVThickness; ts.Transparency=Config.FOVTransparency
        lock.Visible=Config.TargetLockCircle; info.Visible=Config.TargetInfo
        status.Text=Config.TriggerBot and (Config.TriggerHold and "TRG HOLD" or "TRG TAP") or "TRG OFF"
        status.TextColor3=Config.TriggerBot and Color3.fromRGB(255,190,70) or Color3.fromRGB(120,120,135)
    end)
end

function hudPreview(parent)
    local r=row(parent,"HUD Preview","Top indicators and target info sample")
    r.Size=UDim2.new(1,0,0,112)
    local box=Instance.new("Frame",r)
    box.Position=UDim2.new(1,-242,0,8); box.Size=UDim2.new(0,230,0,94)
    box.BackgroundColor3=Color3.fromRGB(5,5,8); box.BorderSizePixel=0; box.ZIndex=8
    Instance.new("UICorner",box).CornerRadius=UDim.new(0,14)
    local st=Instance.new("UIStroke",box); st.Color=Config.UIAccent; st.Transparency=0.55
    local topShadow=miniLabel(box,"FPS 144 | Ping 57 | Target none",UDim2.new(0,12,0,16),UDim2.new(1,-20,0,16),Color3.fromRGB(0,0,0))
    local top=miniLabel(box,"FPS 144 | Ping 57 | Target none",UDim2.new(0,10,0,14),UDim2.new(1,-20,0,16),Color3.fromRGB(235,235,255))
    local target=miniLabel(box,"100 HP | 42m",UDim2.new(0,70,0,56),UDim2.new(1,-20,0,16),Color3.fromRGB(235,255,235))
    RunService.RenderStepped:Connect(function()
        if ghostUnloaded or not box.Parent then return end
        st.Color=Config.UIAccent
        top.Visible=Config.TopIndicators; topShadow.Visible=Config.TopIndicators; target.Visible=Config.TargetInfo
    end)
end

-- =====================
-- MINIMAP (enhanced: grid + LoS)
-- =====================
function makeMiniMap()
    if MiniMapFrame then return end
    MiniMapFrame=Instance.new("Frame",ScreenGui)
    MiniMapFrame.Name="GhostMiniMap"
    MiniMapFrame.Size=UDim2.new(0,Config.MiniMapSize,0,Config.MiniMapSize)
    MiniMapFrame.Position=UDim2.new(1,-190,1,-210)
    MiniMapFrame.BackgroundColor3=Color3.fromRGB(0,0,0)
    MiniMapFrame.BackgroundTransparency=Config.MiniMapOpacity
    MiniMapFrame.BorderSizePixel=0; MiniMapFrame.ZIndex=20
    Instance.new("UICorner",MiniMapFrame).CornerRadius=UDim.new(1,0)
    local stroke=Instance.new("UIStroke",MiniMapFrame); stroke.Name="Stroke"; stroke.Color=Config.UIAccent; stroke.Transparency=0.25; stroke.Thickness=1.2
    -- Grid overlay (distance rings every N studs)
    -- Drawn as concentric circle strokes inside the map frame
    local gridHolder=Instance.new("Frame",MiniMapFrame)
    gridHolder.Name="GridHolder"; gridHolder.BackgroundTransparency=1
    gridHolder.Size=UDim2.new(1,0,1,0); gridHolder.ZIndex=21
    local function rebuildGrid()
        for _,c in pairs(gridHolder:GetChildren()) do c:Destroy() end
        if not Config.MiniMapGrid then return end
        local mapSize=Config.MiniMapSize or 160
        local zoom=Config.MiniMapZoom or 2
        local spacing=Config.MiniMapGridSpacing or 20 -- studs
        local pixelsPerStud=mapSize/2 / (mapSize/2 * zoom / mapSize * mapSize)
        -- How many pixels per stud: mapSize/2 pixels = zoom*? studs from center
        -- At zoom=2, center to edge = mapSize/2 px = mapSize*zoom/2 studs... let's compute:
        -- pixelsPerStud = (mapSize/2) / (mapSize*zoom/2) = 1/zoom
        -- So in pixels: ringRadius = spacing * (mapSize/2) / (mapSize*zoom/2) * i
        --             = spacing * i / zoom  (in fractional map units * mapSize/2)
        local halfPx = mapSize/2
        local studsPerHalf = halfPx * zoom -- studs from center to edge
        local ringsMax = math.floor(studsPerHalf / spacing)
        for i=1,ringsMax do
            local ringPx = (spacing * i / studsPerHalf) * halfPx * 2 -- diameter in px
            if ringPx > 0 and ringPx <= mapSize+4 then
                local ring=Instance.new("Frame",gridHolder)
                ring.AnchorPoint=Vector2.new(0.5,0.5); ring.Position=UDim2.new(0.5,0,0.5,0)
                ring.Size=UDim2.new(0,ringPx,0,ringPx)
                ring.BackgroundTransparency=1; ring.BorderSizePixel=0; ring.ZIndex=21
                Instance.new("UICorner",ring).CornerRadius=UDim.new(1,0)
                local rs=Instance.new("UIStroke",ring); rs.Color=Color3.fromRGB(60,60,80); rs.Transparency=0.55; rs.Thickness=1
            end
        end
    end
    rebuildGrid()
MiniMapFrame:GetAttributeChangedSignal("MiniMapGrid"):Connect(function() rebuildGrid() end)

    local north=Instance.new("TextLabel",MiniMapFrame)
    north.Name="North"; north.BackgroundTransparency=1; north.Size=UDim2.new(0,26,0,18)
    north.Position=UDim2.new(0.5,-13,0,5); north.Text="N"; north.Font=Enum.Font.GothamBlack
    north.TextSize=13; north.TextColor3=Color3.fromRGB(235,235,255); north.ZIndex=22

    local crossA=Instance.new("Frame",MiniMapFrame)
    crossA.BackgroundColor3=Color3.fromRGB(65,65,80); crossA.BackgroundTransparency=0.45
    crossA.BorderSizePixel=0; crossA.AnchorPoint=Vector2.new(0.5,0.5); crossA.Position=UDim2.new(0.5,0,0.5,0)
    crossA.Size=UDim2.new(1,-22,0,1); crossA.ZIndex=21
    local crossB=Instance.new("Frame",MiniMapFrame)
    crossB.BackgroundColor3=Color3.fromRGB(65,65,80); crossB.BackgroundTransparency=0.45
    crossB.BorderSizePixel=0; crossB.AnchorPoint=Vector2.new(0.5,0.5); crossB.Position=UDim2.new(0.5,0,0.5,0)
    crossB.Size=UDim2.new(0,1,1,-22); crossB.ZIndex=21

    MiniMapFrame.InputBegan:Connect(function(i)
        if i.UserInputType==Enum.UserInputType.MouseButton1 then
            MiniMapDragging=true
            local start=i.Position; local startPos=MiniMapFrame.Position
            local conn; conn=UIS.InputChanged:Connect(function(ch)
                if MiniMapDragging and ch.UserInputType==Enum.UserInputType.MouseMovement then
                    local d=ch.Position-start
                    MiniMapFrame.Position=UDim2.new(startPos.X.Scale,startPos.X.Offset+d.X,startPos.Y.Scale,startPos.Y.Offset+d.Y)
                end
            end)
            i.Changed:Connect(function()
                if i.UserInputState==Enum.UserInputState.End then MiniMapDragging=false; if conn then conn:Disconnect() end end
            end)
        end
    end)
end

function minimapDot(name,color,size)
    makeMiniMap()
    if MiniMapDots[name] then return MiniMapDots[name] end
    local d=Instance.new("Frame",MiniMapFrame)
    d.Name=name; d.Size=UDim2.new(0,size or 7,0,size or 7)
    d.AnchorPoint=Vector2.new(0.5,0.5); d.BackgroundColor3=color
    d.BorderSizePixel=0; d.ZIndex=23
    Instance.new("UICorner",d).CornerRadius=UDim.new(1,0)
    local s=Instance.new("UIStroke",d); s.Color=Color3.fromRGB(0,0,0); s.Transparency=0.25; s.Thickness=1
    local lbl=Instance.new("TextLabel",d)
    lbl.Name="Label"; lbl.BackgroundTransparency=1; lbl.Position=UDim2.new(1,3,0.5,-7)
    lbl.Size=UDim2.new(0,70,0,14); lbl.Font=Enum.Font.GothamBold; lbl.TextSize=10
    lbl.TextColor3=Color3.fromRGB(245,245,250); lbl.TextXAlignment=Enum.TextXAlignment.Left
    lbl.Text=""; lbl.ZIndex=24
    MiniMapDots[name]=d
    return d
end

function minimapWorldToMap(worldPos)
    local root=getRoot(lp); if not root then return nil end
    local mapSize=Config.MiniMapSize or 160
    local zoom=Config.MiniMapZoom or 2
    local center=Vector2.new(mapSize/2,mapSize/2)
    local rel=worldPos-root.Position
    local yaw=math.atan2(camera.CFrame.LookVector.X,camera.CFrame.LookVector.Z)
    local cos=math.cos(-yaw); local sin=math.sin(-yaw)
    local x=(rel.X*cos-rel.Z*sin)/zoom
    local y=(rel.X*sin+rel.Z*cos)/zoom
    local maxR=(mapSize/2)-12
    local v=Vector2.new(x,-y)
    if v.Magnitude>maxR then v=v.Unit*maxR end
    return Vector2.new(center.X+v.X,center.Y+v.Y)
end

function minimapSetDot(name,worldPos,color,size,label)
    makeMiniMap()
    local mapPos=minimapWorldToMap(worldPos)
    if not mapPos then return end
    local mapSize=Config.MiniMapSize or 160
    local d=minimapDot(name,color,size)
    d.Position=UDim2.new(0,mapPos.X,0,mapPos.Y)
    d.BackgroundColor3=color
    local lbl=d:FindFirstChild("Label")
    if lbl then lbl.Text=(Config.MiniMapLabels and label) and tostring(label) or "" end
    d.Visible=true
end

-- LoS lines + prediction cones drawn on minimap using pre-allocated Drawing pool
function updateLOSMap()
    if not Config.MiniMap or not MiniMapFrame or not MiniMapFrame.Visible then
        for _,l in ipairs(LOSLines) do l.Visible=false end
        for _,c in ipairs(LOSCones) do c.Visible=false end
        return
    end
    local root=getRoot(lp); if not root then return end
    local mapSize=Config.MiniMapSize or 160
    local zoom=Config.MiniMapZoom or 2
    local mapPos=MiniMapFrame.AbsolutePosition -- screen position of map frame

    local li=0; local ci=0

    for _,p in safeIpairs(Players:GetPlayers()) do
        if p~=lp and validEnemy(p) then
            local ch=getChar(p); local r=getRoot(p)
            if r and ch then
                local rig=resolveRig(ch)
                -- Convert world pos to map-frame pixel position
                local dot=minimapWorldToMap(r.Position)
                if dot then
                    local screenDot=Vector2.new(mapPos.X+dot.X, mapPos.Y+dot.Y)

                    -- LoS line: short line from dot in the direction they're facing
                    if Config.LOSMapLines then
                        li=li+1; if li<=#LOSLines then
                            local line=LOSLines[li]
                            -- Use root CFrame look vector projected onto XZ plane
                            local look=Vector3.new(r.CFrame.LookVector.X,0,r.CFrame.LookVector.Z)
                            local yaw=math.atan2(camera.CFrame.LookVector.X,camera.CFrame.LookVector.Z)
                            local cos=math.cos(-yaw); local sin=math.sin(-yaw)
                            -- Rotate look into map space
                            local mx=( look.X*cos-look.Z*sin)
                            local my=( look.X*sin+look.Z*cos)
                            local lineLen=(Config.LOSLineLength or 20)/zoom
                            local endPt=screenDot+Vector2.new(mx,-my).Unit*lineLen
                            line.From=screenDot; line.To=endPt
                            line.Color=Color3.fromRGB(255,255,80)
                            line.Transparency=0.45; line.Thickness=1; line.Visible=true
                        end
                    end

                    -- Prediction cone: green filled triangle in velocity direction
                    if Config.LOSPredictionCone then
                        ci=ci+1; if ci<=#LOSCones then
                            local cone=LOSCones[ci]
                            local vel=r.AssemblyLinearVelocity or Vector3.zero
                            local flatVel=Vector3.new(vel.X,0,vel.Z)
                            if flatVel.Magnitude>1 then
                                local yaw=math.atan2(camera.CFrame.LookVector.X,camera.CFrame.LookVector.Z)
                                local cos=math.cos(-yaw); local sin=math.sin(-yaw)
                                local vx=(flatVel.X*cos-flatVel.Z*sin)/zoom
                                local vy=(flatVel.X*sin+flatVel.Z*cos)/zoom
                                local vDir=Vector2.new(vx,-vy).Unit
                                local coneLen=(Config.LOSConeLength or 28)/zoom
                                local halfAngle=math.rad(Config.LOSConeAngle or 22)
                                local perp=Vector2.new(-vDir.Y,vDir.X)
                                local tip=screenDot+vDir*coneLen
                                local base1=screenDot+perp*math.tan(halfAngle)*coneLen*0.5
                                local base2=screenDot-perp*math.tan(halfAngle)*coneLen*0.5
                                cone.PointA=tip; cone.PointB=base1; cone.PointC=base2
                                cone.Color=Color3.fromRGB(80,255,120)
                                cone.Transparency=0.72; cone.Filled=true; cone.Visible=true
                            else cone.Visible=false end
                        end
                    end
                end
            end
        end
    end

    -- Hide unused pool slots
    for i=li+1,#LOSLines do LOSLines[i].Visible=false end
    for i=ci+1,#LOSCones do LOSCones[i].Visible=false end
end

function updateMiniMap()
    if not Config.MiniMap or Config.Hidden or ghostUnloaded then
        if MiniMapFrame then MiniMapFrame.Visible=false end
        for _,l in ipairs(LOSLines) do l.Visible=false end
        for _,c in ipairs(LOSCones) do c.Visible=false end
        return
    end
    makeMiniMap()
    MiniMapFrame.Visible=true
    MiniMapFrame.Size=UDim2.new(0,Config.MiniMapSize,0,Config.MiniMapSize)
    MiniMapFrame.BackgroundTransparency=Config.MiniMapOpacity
    local stroke=MiniMapFrame:FindFirstChild("Stroke"); if stroke then stroke.Color=Config.UIAccent end
    for _,dot in safePairs(MiniMapDots) do dot.Visible=false end

    local root=getRoot(lp)
    if root then
        if Config.MiniMapTrail and now()-lastMiniMapTrail>0.35 then
            lastMiniMapTrail=now()
            table.insert(MiniMapTrail,root.Position)
            if #MiniMapTrail>18 then table.remove(MiniMapTrail,1) end
        end
        local selfDot=minimapDot("self",GhostControlFill,11)
        selfDot.Position=UDim2.new(0.5,0,0.5,0); selfDot.BackgroundColor3=GhostControlFill; selfDot.Visible=true
        selfDot.Rotation=math.deg(math.atan2(camera.CFrame.LookVector.X,camera.CFrame.LookVector.Z))
        local lbl=selfDot:FindFirstChild("Label"); if lbl then lbl.Text=Config.MiniMapLabels and "You" or "" end
        if Config.MiniMapTrail then
            for i,pos in safeIpairs(MiniMapTrail) do
                local alpha=i/#MiniMapTrail
                minimapSetDot("trail_"..i,pos,Color3.fromRGB(120,120,150),4,nil)
                local dot=MiniMapDots["trail_"..i]
                if dot then dot.BackgroundTransparency=0.75-(alpha*0.35) end
            end
        end
        -- Enemy dots
        for _,p in safeIpairs(Players:GetPlayers()) do
            if p~=lp and validEnemy(p) then
                local r=getRoot(p)
                if r then
                    local aaType=getAntiAimType(p)
                    local col=aaType and Color3.fromRGB(255,80,80) or Color3.fromRGB(255,90,90)
                    minimapSetDot("enemy_"..p.UserId,r.Position,col,7,p.Name)
                end
            end
        end
        if Config.MiniMapTeam then
            for _,p in safeIpairs(Players:GetPlayers()) do
                if p~=lp and lp.Team and p.Team and p.Team==lp.Team and isAlive(p) then
                    local r=getRoot(p)
                    if r then minimapSetDot("team_"..p.UserId,r.Position,Color3.fromRGB(90,220,255),7,p.Name) end
                end
            end
        end
        if Config.MiniMapWaypoints then
            for i,pos in safeIpairs(MiniMapWaypoints) do minimapSetDot("wp_"..i,pos,Color3.fromRGB(255,220,90),7,"WP"..i) end
        end
    end
    updateLOSMap()
end

function minimapPreview(parent)
    local r=row(parent,"Mini Map Preview","Self/enemy/team radar sample")
    r.Size=UDim2.new(1,0,0,150)
    local box=Instance.new("Frame",r)
    box.Position=UDim2.new(1,-172,0,8); box.Size=UDim2.new(0,132,0,132)
    box.BackgroundColor3=Color3.fromRGB(5,5,8); box.BackgroundTransparency=Config.MiniMapOpacity
    box.BorderSizePixel=0; box.ZIndex=8
    Instance.new("UICorner",box).CornerRadius=UDim.new(1,0)
    local st=Instance.new("UIStroke",box); st.Color=Config.UIAccent; st.Transparency=0.35; st.Thickness=1
    miniLabel(box,"N",UDim2.new(0.5,-8,0,5),UDim2.new(0,20,0,16),Color3.fromRGB(235,235,255))
    local self=Instance.new("Frame",box); self.AnchorPoint=Vector2.new(0.5,0.5); self.Position=UDim2.new(0.5,0,0.5,0); self.Size=UDim2.new(0,9,0,9); self.BackgroundColor3=GhostControlFill; self.BorderSizePixel=0; self.ZIndex=10; Instance.new("UICorner",self).CornerRadius=UDim.new(1,0)
    local enemy=Instance.new("Frame",box); enemy.AnchorPoint=Vector2.new(0.5,0.5); enemy.Position=UDim2.new(0.65,0,0.38,0); enemy.Size=UDim2.new(0,7,0,7); enemy.BackgroundColor3=Color3.fromRGB(255,90,90); enemy.BorderSizePixel=0; enemy.ZIndex=10; Instance.new("UICorner",enemy).CornerRadius=UDim.new(1,0)
    local wp=Instance.new("Frame",box); wp.AnchorPoint=Vector2.new(0.5,0.5); wp.Position=UDim2.new(0.35,0,0.68,0); wp.Size=UDim2.new(0,7,0,7); wp.BackgroundColor3=Color3.fromRGB(255,220,90); wp.BorderSizePixel=0; wp.ZIndex=10; Instance.new("UICorner",wp).CornerRadius=UDim.new(1,0)
    RunService.RenderStepped:Connect(function()
        if ghostUnloaded or not box.Parent then return end
        box.Visible=(currentTab=="Visuals" or currentTab=="Favorited") and previewsEnabled()
        box.BackgroundTransparency=Config.MiniMapOpacity
        st.Color=Config.UIAccent; self.BackgroundColor3=GhostControlFill
        wp.Visible=Config.MiniMapWaypoints
    end)
end

-- =====================
-- SCANNER (unchanged logic, kept in full)
-- =====================
local scanSnapshotA=nil; local scanSnapshotB=nil
local scannerLastText="Run Scanner Guide first. Flow: Environment Check -> Scan Game -> Aggressive Defense Scan -> Snapshot Diff."
local scannerOutput=nil; local scannerSummary=nil
local interestingWords={"remote","weapon","gun","ammo","reload","damage","hit","shoot","ray","projectile","tool","inventory","round","match","team","anti","kick","ban","flag","objective","loot","crate","key","door","vehicle","admin","moderator","report","detect","security","guard","validate","server","client","fire","invoke","replicate","cooldown","spread","recoil","camera","humanoid","health","stamina","sprint","cash","coin","xp","level","quest","mission","zone","safe","checkpoint"}

function pathOf(inst) local ok,r=pcall(function() return inst:GetFullName() end); return ok and r or tostring(inst) end
function isInterestingName(name) local n=tostring(name or ""):lower(); for _,w in safeIpairs(interestingWords) do if n:find(w,1,true) then return true,w end end; return false,nil end
function scannerSet(text,summary) scannerLastText=text or ""; if scannerOutput then scannerOutput.Text=scannerLastText end; if scannerSummary then scannerSummary.Text=summary or "Ready" end end

function collectGameMap()
    local data={remotes={},remoteFunctions={},modules={},scripts={},tools={},sounds={},interesting={},all={}}
    local limit=Config.ScannerMaxResults or 80
    local function push(list,inst) if #list<limit then table.insert(list,Config.ScannerShowPaths and pathOf(inst) or inst.Name) end end
    for _,inst in safeIpairs(game:GetDescendants()) do
        data.all[pathOf(inst)]=inst.ClassName
        if inst:IsA("RemoteEvent") then push(data.remotes,inst)
        elseif inst:IsA("RemoteFunction") then push(data.remoteFunctions,inst)
        elseif inst:IsA("ModuleScript") then push(data.modules,inst)
        elseif inst:IsA("LocalScript") or inst:IsA("Script") then push(data.scripts,inst)
        elseif inst:IsA("Tool") then push(data.tools,inst)
        elseif inst:IsA("Sound") then push(data.sounds,inst) end
        local hit,word=isInterestingName(inst.Name)
        if hit and #data.interesting<limit then table.insert(data.interesting,"["..inst.ClassName.."] "..pathOf(inst).."  tag="..word) end
    end
    return data
end

function formatList(title,list)
    local out={"-- "..title.." ("..tostring(#list)..")"}
    for _,v in safeIpairs(list) do table.insert(out,"  "..tostring(v)) end
    return table.concat(out,"\n")
end

function runPassiveScan()
    local d=collectGameMap()
    local parts={}
    table.insert(parts,formatList("RemoteEvents",d.remotes))
    table.insert(parts,formatList("RemoteFunctions",d.remoteFunctions))
    table.insert(parts,formatList("Tools",d.tools))
    table.insert(parts,formatList("Modules",d.modules))
    table.insert(parts,formatList("Scripts",d.scripts))
    table.insert(parts,formatList("Sounds",d.sounds))
    table.insert(parts,formatList("Interesting names",d.interesting))
    local summary="Remotes "..#d.remotes.." | RF "..#d.remoteFunctions.." | Tools "..#d.tools.." | Modules "..#d.modules.." | Scripts "..#d.scripts
    scannerSet(table.concat(parts,"\n\n"),summary)
    showApplyPopup("Scanner complete",summary,"success")
end

function runEnvCheck()
    local checks={
        {"Drawing API",Drawing~=nil},{"mouse1press",type(mouse1press)=="function"},{"mouse1release",type(mouse1release)=="function"},
        {"getgc",type(getgc)=="function"},{"getconnections",type(getconnections)=="function"},{"hookfunction",type(hookfunction)=="function"},
        {"getrawmetatable",type(getrawmetatable)=="function"},{"setclipboard",type(setclipboard)=="function"},
        {"request/http",type(request)=="function" or type(http_request)=="function"},{"ScreenGui",ScreenGui~=nil},{"Camera",camera~=nil},
    }
    local out={"-- Environment Capability Check"}; local okCount=0
    for _,c in safeIpairs(checks) do
        if c[2] then okCount=okCount+1 end
        table.insert(out,(c[2] and "OK   " or "MISS ")..c[1])
    end
    scannerSet(table.concat(out,"\n"),"Capabilities "..okCount.."/"..#checks)
end

function takeScannerSnapshot(slot)
    local d=collectGameMap(); local snap={time=now(),all=d.all}
    if slot=="A" then scanSnapshotA=snap else scanSnapshotB=snap end
    local c=0; for _ in safePairs(snap.all) do c=c+1 end
    scannerSet("Snapshot "..slot.." saved with "..tostring(c).." objects.","Snapshot "..slot.." saved")
end

function diffScannerSnapshots()
    if not scanSnapshotA or not scanSnapshotB then scannerSet("Take Snapshot A and B first.","Missing snapshots"); return end
    local added,removed={},{}
    for path,cls in safePairs(scanSnapshotB.all) do if not scanSnapshotA.all[path] then table.insert(added,"+ ["..cls.."] "..path) end end
    for path,cls in safePairs(scanSnapshotA.all) do if not scanSnapshotB.all[path] then table.insert(removed,"- ["..cls.."] "..path) end end
    table.sort(added); table.sort(removed)
    local limit=Config.ScannerMaxResults or 80
    local out={"-- Snapshot Diff","Added: "..#added,"Removed: "..#removed,"","-- Added"}
    for i=1,math.min(#added,limit) do table.insert(out,added[i]) end
    table.insert(out,""); table.insert(out,"-- Removed")
    for i=1,math.min(#removed,limit) do table.insert(out,removed[i]) end
    scannerSet(table.concat(out,"\n"),"Diff +"..#added.." / -"..#removed)
end

function classifyRemoteName(name)
    local n=tostring(name or ""):lower()
    if n:find("admin") or n:find("ban") or n:find("kick") or n:find("anti") or n:find("security") then return "Security/Admin" end
    if n:find("damage") or n:find("hit") or n:find("shoot") or n:find("fire") or n:find("weapon") or n:find("gun") then return "Combat/Weapon" end
    if n:find("reload") or n:find("ammo") or n:find("spread") or n:find("recoil") then return "Weapon State" end
    if n:find("cash") or n:find("coin") or n:find("xp") or n:find("level") or n:find("reward") then return "Economy/Reward" end
    if n:find("team") or n:find("round") or n:find("match") then return "Match/Team" end
    return "General"
end

function scanValueObjects(root,limit)
    local out={}
    for _,inst in safeIpairs(root:GetDescendants()) do
        if #out>=limit then break end
        if inst:IsA("ValueBase") then
            local ok,val=pcall(function() return tostring(inst.Value) end)
            table.insert(out,"["..inst.ClassName.."] "..pathOf(inst).." = "..(ok and val or "?"))
        end
    end
    return out
end

function runAggressiveDefenseScan()
    local limit=Config.ScannerMaxResults or 80
    local d=collectGameMap()
    local out={"-- Aggressive Defensive Scan","Passive deep scan only. No remotes fired.",""}
    local risk=0; local categories={}
    for _,inst in safeIpairs(game:GetDescendants()) do
        if inst:IsA("RemoteEvent") or inst:IsA("RemoteFunction") then
            local cat=classifyRemoteName(inst.Name)
            categories[cat]=categories[cat] or {}
            if #categories[cat]<limit then table.insert(categories[cat],"["..inst.ClassName.."] "..pathOf(inst)) end
            if cat=="Security/Admin" then risk=risk+4 elseif cat=="Combat/Weapon" or cat=="Economy/Reward" then risk=risk+2 else risk=risk+1 end
        end
    end
    table.insert(out,"Risk Score: "..tostring(risk)); table.insert(out,"")
    for cat,list in safePairs(categories) do
        table.insert(out,"-- "..cat.." ("..#list..")")
        for _,v in safeIpairs(list) do table.insert(out,"  "..v) end
        table.insert(out,"")
    end
    local values=scanValueObjects(game,limit)
    table.insert(out,"-- Value Objects ("..#values..")")
    for _,v in safeIpairs(values) do table.insert(out,"  "..v) end
    scannerSet(table.concat(out,"\n"),"Aggressive scan complete | risk "..risk)
    showApplyPopup("Aggressive scan complete","Risk score "..risk..".","success")
end

function runScriptIndexer()
    local out={"-- Script / Module Indexer"}; local count=0
    for _,inst in safeIpairs(game:GetDescendants()) do
        if inst:IsA("ModuleScript") or inst:IsA("LocalScript") or inst:IsA("Script") then
            local hit,word=isInterestingName(inst.Name)
            if hit then count=count+1; table.insert(out,"["..inst.ClassName.."] tag="..word.."  "..pathOf(inst)); if count>=(Config.ScannerMaxResults or 80) then break end end
        end
    end
    scannerSet(table.concat(out,"\n"),"Indexed interesting scripts: "..count)
end

local scannerWatchConn=nil; local scannerWatchLast=nil
function toggleScannerWatch()
    if scannerWatchConn then scannerWatchConn:Disconnect(); scannerWatchConn=nil; Config.ScannerWatch=false; scannerSet("Scanner watch stopped.","Watch stopped"); return end
    Config.ScannerWatch=true; scannerWatchLast=collectGameMap(); local lastTick=0
    scannerWatchConn=RunService.Heartbeat:Connect(function()
        if not Config.ScannerWatch or ghostUnloaded then if scannerWatchConn then scannerWatchConn:Disconnect(); scannerWatchConn=nil end; return end
        if now()-lastTick<(Config.ScannerWatchInterval or 2) then return end; lastTick=now()
        local current=collectGameMap(); local added,removed=0,0
        for path,_ in safePairs(current.all) do if not scannerWatchLast.all[path] then added=added+1 end end
        for path,_ in safePairs(scannerWatchLast.all) do if not current.all[path] then removed=removed+1 end end
        if added>0 or removed>0 then scannerSet("Watch update: +"..added.." / -"..removed.." objects.","Watch +"..added.." / -"..removed); scannerWatchLast=current end
    end)
    scannerSet("Scanner watch started.","Watch running")
end

function showScannerGuide()
    scannerSet(table.concat({"-- Scanner Quick Guide","1) Environment Check","2) Scan Game","3) Aggressive Defense Scan","4) Script Indexer","5) Snapshot A -> action -> Snapshot B -> Compare A/B","6) Object Watch (passive live monitor)"},"\n"),"Scanner guide loaded")
    showApplyPopup("Scanner guide","Guide loaded.","success")
end

function copyScannerOutput()
    if setclipboard then setclipboard(scannerLastText or ""); showApplyPopup("Scanner copied","Output copied to clipboard.","success")
    else showApplyPopup("Clipboard unavailable","setclipboard not supported.","error") end
end

-- =====================
-- PAGES SETUP
-- =====================
makeTab("Favorited","*"); makeTab("Combat","*"); makeTab("Visuals","*")
makeTab("Crosshair","+"); makeTab("Config","#"); makeTab("Settings","*")
makeTab("Theme Studio","*"); makeTab("Scanner","#"); makeTab("Notes","#")

fav=makePage("Favorited"); combat=makePage("Combat"); visuals=makePage("Visuals")
cross=makePage("Crosshair"); configP=makePage("Config"); settings=makePage("Settings")
themeStudio=makePage("Theme Studio"); scanner=makePage("Scanner"); notes=makePage("Notes")

-- =====================
-- FAVORITED PAGE
-- =====================
;(function()
local q,_ = card(fav,"Starred Settings","*")
favDynamicBody=q

rebuildFavorites=function()
    if not favDynamicBody then return end
    for _,child in safeIpairs(favDynamicBody:GetChildren()) do
        if not child:IsA("UIListLayout") then child:Destroy() end
    end
    local grouped={}; local count=0
    for key,item in safePairs(favoriteItems) do
        if FavoriteKeys[key] then
            local cat=item.category or favoriteCategories[key] or "Other"
            grouped[cat]=grouped[cat] or {}; table.insert(grouped[cat],item); count=count+1
        end
    end
    if count==0 then favRow(favDynamicBody,"No favorites yet","Click stars beside settings to add them here"); return end
    for _,cat in safeIpairs(favoriteOrder) do
        local list=grouped[cat]
        if list and #list>0 then
            local header=Instance.new("TextLabel",favDynamicBody)
            header.BackgroundTransparency=1; header.Size=UDim2.new(1,0,0,24)
            header.Text="  "..cat; header.Font=Enum.Font.GothamBlack; header.TextSize=13
            header.TextColor3=GhostTextMain; header.TextXAlignment=Enum.TextXAlignment.Left; header.ZIndex=7
            table.sort(list,function(a,b) return tostring(a.label)<tostring(b.label) end)
            for _,item in safeIpairs(list) do buildFavoriteControl(favDynamicBody,item) end
        end
    end
end

local qp,_ = card(fav,"Quick Presets","#")
button(qp,"Load Default Preset","Restore defaults",function()
    Config.Aimbot=false; Config.AimSmooth=1; Config.AimFOV=30; Config.AimAssist=true; Config.AimAssistStrength=0.25
    Config.TriggerBot=true; Config.TriggerHold=true; Config.TriggerFOV=20; Config.ESP=true; Config.SkeletonESP=true
    Config.SkeletonMode="Bone"; Config.OffscreenArrows=true; Config.Crosshair=true; Config.FOVVisible=true
    Config.WeaponSilhouette=false; Config.NameplateDistanceScale=true; Config.LOSMapLines=true; Config.LOSPredictionCone=true
    Config.PingNormalize=false; Config.TickRateCompensate=false; Config.FlickAssist=false
    Config.MultiTargetSuppressor=false; Config.AimShakeDampener=false; Config.TargetWeightSystem=true
    Config.AntiAimDetect=true; Config.AntiAimIndicator=true; Config.AntiLag=false
    for _,fn in safePairs(controls) do pcall(fn) end; if rebuildFavorites then rebuildFavorites() end
end)
button(qp,"Hide Visuals","Quick panic visual hide",function()
    Config.Hidden=true; Root.Visible=false; mouseDown=false; triggerHolding=false; pcall(mouse1release)
end)
button(qp,"Reset Drawings","Clear stuck drawings",function()
    for _,obj in safePairs(ESPObjects) do hideAll(obj); obj.ArrowSmooth=nil end
    if FOVCircle then FOVCircle.Visible=false end; if SACircle then SACircle.Visible=false end
    if TriggerCircle then TriggerCircle.Visible=false end; if TargetLock then TargetLock.Visible=false end
    if ReloadLbl then ReloadLbl.Visible=false end; if CHDot then CHDot.Visible=false end
    for _,l in safePairs(CHLines) do if l then l.Visible=false end end
    for _,l in ipairs(LOSLines) do l.Visible=false end; for _,c in ipairs(LOSCones) do c.Visible=false end
end)
end)()

local favModes,_ = card(fav,"Favorite Sets","*")
button(favModes,"Save Favorites","Save starred favorites now",function() local ok=ghostSaveConfigFile(false); showApplyPopup(ok and "Saved" or "Failed",ghostLastSaveStatus,ok and "success" or "error") end)
button(favModes,"Combat Favorites","Focus combat controls",function() FavoriteKeys.AimAssist=true; FavoriteKeys.AimFOV=true; FavoriteKeys.TriggerBot=true; FavoriteKeys.TriggerFOV=true; FavoriteKeys.ESP=false; FavoriteKeys.Crosshair=false; if rebuildFavorites then rebuildFavorites() end; ghostSaveConfigFile(true) end)
button(favModes,"Visual Favorites","Focus visual controls",function() FavoriteKeys.ESP=true; FavoriteKeys.OffscreenArrows=true; FavoriteKeys.ArrowSize=true; FavoriteKeys.TargetInfo=true; FavoriteKeys.AimAssist=false; if rebuildFavorites then rebuildFavorites() end; ghostSaveConfigFile(true) end)

-- =====================
-- COMBAT PAGE
-- =====================
;(function()
local a,_ = card(combat,"Aimbot","*")
toggle(a,"Enable Aimbot","Smooth camera aim toward target","Aimbot")
keybind(a,"Aim Key","Hold this key to aim","AimKey")
dropdown(a,"Aim Part","Target body part (Auto uses universal resolver)","AimPart",{"Auto","Head","Torso","Root"})
slider(a,"Aim FOV","Aimbot field of view","AimFOV",0,800,1)
slider(a,"Smoothness","Lower is snappier","AimSmooth",0.01,10,0.01)
toggle(a,"Prediction","Lead moving targets","AimPrediction")
slider(a,"Prediction Scale","Velocity prediction amount","AimPredictionScale",0,0.5,0.01)
toggle(a,"Humanize","Tiny natural randomness","AimHumanize")
slider(a,"Deadzone","Avoid micro jitter","AimDeadzone",0,20,1)
toggle(a,"Aim Assist","Magnetic camera assist","AimAssist")
slider(a,"Assist Strength","Camera assist power","AimAssistStrength",0.01,1.0,0.01)
toggle(a,"Target Lock Circle","Show lock circle on visible target","TargetLockCircle")
combatPreview(a)

local s,_ = card(combat,"Silent / Trigger","*")
toggle(s,"Silent Aim","Redirect supported ray calls","SilentAim")
slider(s,"Silent FOV","Silent aim field of view","SilentFOV",0,800,1)
slider(s,"Hit Chance","Percent chance to redirect","SilentHitChance",1,100,1)
toggle(s,"Triggerbot","Auto fire when on target","TriggerBot")
slider(s,"Trigger Delay","Hold cooldown / safety delay","TriggerDelay",0.05,0.5,0.01)
slider(s,"Trigger FOV","Triggerbot circle size","TriggerFOV",0,500,1)
toggle(s,"Trigger FOV Circle","Show triggerbot FOV circle","TriggerFOVVisible")
toggle(s,"Trigger Hold","Hold fire while target detected","TriggerHold")
toggle(s,"Auto Pistol","Rapid semi-auto clicking","AutoPistol")
toggle(s,"No Recoil","Dampen camera recoil","NoRecoil")

-- COMBAT ADVANCED CARD
local adv,_ = card(combat,"Advanced","*")
-- Aim shake dampener
toggle(adv,"Aim Shake Dampener","Smooth out game-induced camera shake before aim runs","AimShakeDampener")
slider(adv,"Dampener Strength","How aggressively shake is removed","AimShakeDampStrength",0.1,1.0,0.05)
-- Target weight system
toggle(adv,"Target Weight System","Score targets by shootable + distance + health","TargetWeightSystem")
toggle(adv,"Weight: Shootable","Prefer visible unobstructed targets","TargetWeightShootable")
toggle(adv,"Weight: Distance","Prefer closer targets","TargetWeightDistance")
toggle(adv,"Weight: Health","Prefer lower HP targets","TargetWeightHealth")
-- Ping normalizer
toggle(adv,"Ping Normalizer","Offset shot timing by your ping for consistent hit reg","PingNormalize")
-- Tick rate compensator
toggle(adv,"Tick Rate Compensator","Adjust prediction step to match detected server tick rate","TickRateCompensate")
-- Anti-aim detector
toggle(adv,"Anti-Aim Detector","Detect spinbot and flipbot movement patterns","AntiAimDetect")
toggle(adv,"Anti-Aim Indicator","Draw AA warning above player and at screen top","AntiAimIndicator")
-- Anti-lag
toggle(adv,"Anti-Lag","Compensate or match server lag for shot timing","AntiLag")
dropdown(adv,"Anti-Lag Mode","Compensate = lead by ping | Match = aim at past position","AntiLagMode",{"Compensate","Match"})
slider(adv,"Anti-Lag Offset","Manual fine-tune offset","AntiLagOffset",-50,50,1)
-- Flick assist
toggle(adv,"Flick Assist","Single fast flick toward targets outside FOV","FlickAssist")
slider(adv,"Flick Strength","How far the flick snaps","FlickAssistStrength",0.1,1.0,0.05)
-- Multi-target suppressor
toggle(adv,"Multi-Target Suppressor","When 3+ enemies in FOV area weight system prioritizes best","MultiTargetSuppressor")
slider(adv,"Multi-Target Radius","FOV multiplier to count nearby enemies","MultiTargetRadius",1,6,0.5)
end)()

-- =====================
-- VISUALS PAGE
-- =====================
;(function()
local e,_ = card(visuals,"ESP","*")
toggle(e,"Master ESP","Enable all enemy visuals","ESP")
toggle(e,"Box ESP","Draw boxes","BoxESP")
dropdown(e,"Box Style","Visual box type","BoxStyle",{"Full","Corner"})
toggle(e,"Name ESP","Show player names","NameESP")
toggle(e,"Health ESP","Show health bars","HealthESP")
toggle(e,"Distance ESP","Show distance text","DistanceESP")
dropdown(e,"ESP Stack","Name/health/distance layout","ESPStackMode",{"Clean","Compact","Minimal"})
toggle(e,"Optional Names","Allow name text in ESP stack","ESPNameOptional")
toggle(e,"Tracers","Lines to enemies","TracerESP")
toggle(e,"Skeleton ESP","Show bone/dot skeleton","SkeletonESP")
dropdown(e,"Skeleton Mode","Bone = joint lines | Dots = old circles | Both = all","SkeletonMode",{"Bone","Dots","Both"})
toggle(e,"Skeleton Distance Fade","Fade skeleton at long range","SkeletonDistanceFade")
toggle(e,"Chams","Highlight enemy models","Chams")
toggle(e,"Offscreen Arrows","Show arrows for offscreen enemies","OffscreenArrows")
slider(e,"Arrow Size","Threat pointer size","ArrowSize",10,24,1)
slider(e,"Arrow Padding","Distance from screen edge","ArrowPadding",24,90,2)
toggle(e,"Arrow Smart Stack","Separate overlapping arrows","ArrowSmartStack")
dropdown(e,"Arrow Labels","Label style","ArrowLabelMode",{"Distance","Threat","Off"})
toggle(e,"Vertical Hints","Show UP/DOWN on arrows","ArrowVerticalHints")
slider(e,"Vertical Threshold","Height needed for UP/DOWN","ArrowVerticalThreshold",5,80,1)
toggle(e,"Threat Pulse","Pulse arrows for visible threats","ArrowThreatPulse")
visualPreview(e)

-- ESP ADVANCED CARD
local ea,_ = card(visuals,"ESP Advanced","*")
toggle(ea,"Weapon Silhouette","Draw held tool icon on enemy ESP","WeaponSilhouette")
toggle(ea,"Nameplate Distance Scale","Shrink name text smoothly with distance","NameplateDistanceScale")
slider(ea,"Nameplate Max Size","Closest nameplate font size","NameplateMaxSize",10,24,1)
slider(ea,"Nameplate Min Size","Farthest nameplate font size","NameplateMinSize",6,16,1)
slider(ea,"Nameplate Scale Range","Distance at which text reaches minimum size","NameplateScaleRange",100,2000,50)
toggle(ea,"LoS Map Lines","Draw facing lines on minimap per enemy","LOSMapLines")
slider(ea,"LoS Line Length","How far the LoS line extends on map (studs)","LOSLineLength",5,60,1)
toggle(ea,"LoS Prediction Cone","Green velocity cone on minimap per enemy","LOSPredictionCone")
slider(ea,"Cone Length","How far the prediction cone extends (studs)","LOSConeLength",8,80,2)
slider(ea,"Cone Angle","Width of the prediction cone (degrees)","LOSConeAngle",5,60,1)

local w,_ = card(visuals,"World / Info","*")
w:SetAttribute("FavCategory","HUD")
toggle(w,"FOV Circles","Show aim/silent FOV","FOVVisible")
slider(w,"FOV Thickness","Circle line thickness","FOVThickness",1,5,1)
slider(w,"FOV Alpha","Circle transparency","FOVTransparency",0.1,1,0.05)
toggle(w,"Top Indicators","Small HUD in top right","TopIndicators")
toggle(w,"Target Info","Show health/studs on aimed target","TargetInfo")
toggle(w,"Opponent State Debug","Show replicated opponent weapon state","OpponentStateDebug")
toggle(w,"Opponent State Details","Show tool/ammo values","OpponentStateDetails")
toggle(w,"Opponent State Source","Show source path","OpponentStateSource")
toggle(w,"Opponent Movement Debug","Show movement relation state","OpponentPushDebug")
toggle(w,"Surround Map","Compass-style nearby player counts","SurroundMapHUD")
toggle(w,"Blind Spot Monitor","Warn when close players enter rear/sides","BlindSpotMonitor")
slider(w,"Surround Radius","Distance for surround counts","SurroundRadius",40,300,10)
slider(w,"Blind Spot Radius","Distance for blind spot warning","BlindSpotRadius",25,180,5)
toggle(w,"Movement Details","Show speed/closing values","OpponentPushDetails")
slider(w,"Push Speed Threshold","Speed needed for push/backoff labels","PushSpeedThreshold",2,30,1)
toggle(w,"Rainbow FOV","Animated FOV colors","FOVRainbow")
toggle(w,"Reload Predictor","Show reload guess near crosshair","ReloadPredictor")
toggle(w,"Reload State HUD","Show equipped weapon reload/ammo state","ReloadStateHUD")
toggle(w,"Reload Details","Show reserve ammo if available","ReloadStateDetails")
toggle(w,"Low Ammo Warning","Warn when local ammo is low","ReloadWarnLowAmmo")
slider(w,"Distance Limit","Max ESP target distance","DistanceLimit",100,5000,50)
toggle(w,"Team Check","Ignore teammates","TeamCheck")
toggle(w,"Wall Check","Visibility checks","WallCheck")
hudPreview(w)

local mm,_ = card(visuals,"Mini Map","*")
toggle(mm,"Mini Map","Enable minimap overlay","MiniMap")
slider(mm,"Map Size","Minimap pixel size","MiniMapSize",80,300,10)
slider(mm,"Map Opacity","Background transparency","MiniMapOpacity",0.1,0.9,0.05)
slider(mm,"Zoom","Studs per half-map","MiniMapZoom",0.5,8,0.5)
toggle(mm,"Team Dots","Show teammates","MiniMapTeam")
toggle(mm,"Labels","Show names on dots","MiniMapLabels")
toggle(mm,"Movement Trail","Show your recent path","MiniMapTrail")
toggle(mm,"Waypoints","Show placed waypoints","MiniMapWaypoints")
toggle(mm,"Grid Overlay","Distance rings on map background","MiniMapGrid")
slider(mm,"Grid Spacing","Stud interval between rings","MiniMapGridSpacing",5,100,5)
button(mm,"Add Waypoint","Place waypoint at current position",function()
    table.insert(MiniMapWaypoints,getRoot(lp) and getRoot(lp).Position or Vector3.zero)
    if #MiniMapWaypoints>8 then table.remove(MiniMapWaypoints,1) end
    showApplyPopup("Waypoint added","WP"..#MiniMapWaypoints.." placed.","success")
end)
button(mm,"Clear Waypoints","Remove all waypoints",function()
    MiniMapWaypoints={}
    for k,d in safePairs(MiniMapDots) do if tostring(k):find("wp_",1,true) then d:Destroy(); MiniMapDots[k]=nil end end
    showApplyPopup("Waypoints cleared","","success")
end)
minimapPreview(mm)
end)()

-- =====================
-- CROSSHAIR PAGE
-- =====================
;(function()
local c,_ = card(cross,"Crosshair Designer","+")
toggle(c,"Enable Crosshair","Custom drawing crosshair","Crosshair")
slider(c,"Gap","Center gap","CrosshairGap",0,60,1)
slider(c,"Length","Line length","CrosshairLength",2,80,1)
slider(c,"Thickness","Line thickness","CrosshairThickness",1,10,1)
toggle(c,"Dot","Center dot","CrosshairDot")
toggle(c,"Spin","Animated spin","CrosshairSpin")
toggle(c,"Shot Pulse","Visual kick when you click","CrosshairShootPulse")
toggle(c,"Auto Size","Scale with screen size","CrosshairAutoSize")
dropdown(c,"Profile","Crosshair shape preset","CrosshairProfile",{"Classic","Dot","T-Shape","Minimal","Circle","Box","Diamond","X","Sniper","Cyber"})
crosshairPreview(c)
button(c,"Save Crosshair","Save crosshair settings now",function() local ok=ghostSaveConfigFile(false); showApplyPopup(ok and "Saved" or "Failed",ghostLastSaveStatus,ok and "success" or "error") end)
button(c,"Ghost Accent","Set crosshair ghost blue",function() Config.CrosshairColor=Color3.fromRGB(210,240,255); ghostSaveConfigFile(true) end)
button(c,"Green Accent","Set crosshair green",function() Config.CrosshairColor=Color3.fromRGB(90,255,150); ghostSaveConfigFile(true) end)
button(c,"White Accent","Set crosshair white",function() Config.CrosshairColor=Color3.fromRGB(255,255,255); ghostSaveConfigFile(true) end)
end)()

-- =====================
-- CONFIG PAGE
-- =====================
;(function()
local cf,_ = card(configP,"Profiles","#")
button(cf,"Save Settings File","Write current Config to GhostMenu/settings.json",function()
    local ok=ghostSaveConfigFile(false); showApplyPopup(ok and "Settings saved" or "Save failed",ghostLastSaveStatus,ok and "success" or "error")
end)
button(cf,"Load Settings File","Load GhostMenu/settings.json",function()
    local ok=ghostLoadConfigFile(false)
    if ok then for _,fn in safePairs(controls) do pcall(fn) end end
    showApplyPopup(ok and "Settings loaded" or "Load failed",ghostLastSaveStatus,ok and "success" or "error")
end)
toggle(cf,"Auto Save","Auto-save changes","AutoSaveSettings")
toggle(cf,"Auto Load","Load settings on script start","AutoLoadSettings")
button(cf,"Save Status","Print current save status",function() showApplyPopup("Save status",ghostLastSaveStatus,"success") end)
button(cf,"Export Config","Copy JSON to clipboard",function()
    local json=HttpService:JSONEncode(ghostConfigToSaveTable())
    if setclipboard then setclipboard(json) end
    showApplyPopup("Config exported","JSON copied to clipboard.","success")
end)
button(cf,"Import Config","Paste JSON from clipboard",function()
    if not getclipboard then return end
    local txt=getclipboard()
    local ok,data=pcall(function() return HttpService:JSONDecode(txt) end)
    if ok and type(data)=="table" then
        ghostApplySaveTable(data); for _,fn in safePairs(controls) do pcall(fn) end
        ghostScheduleAutoSave(); showApplyPopup("Config imported","Applied successfully.","success")
    end
end)
button(cf,"Legit Preset","Low-key settings",function()
    Config.Aimbot=true; Config.AimSmooth=0.35; Config.AimFOV=30; Config.SilentAim=false; Config.TriggerBot=false; Config.ESP=true; Config.BoxESP=true; Config.TracerESP=false
    Config.SkeletonMode="Bone"; Config.WeaponSilhouette=false; Config.AntiAimDetect=true
    for _,fn in safePairs(controls) do pcall(fn) end; ghostScheduleAutoSave()
end)
button(cf,"Rage Preset","Strong settings",function()
    Config.Aimbot=true; Config.AimSmooth=0.05; Config.AimFOV=260; Config.SilentAim=true; Config.SilentFOV=240
    Config.TriggerBot=true; Config.ESP=true; Config.TracerESP=true; Config.Chams=true
    Config.TargetWeightSystem=true; Config.AntiAimDetect=true; Config.AntiLag=true; Config.AntiLagMode="Compensate"
    for _,fn in safePairs(controls) do pcall(fn) end; ghostScheduleAutoSave()
end)
button(cf,"Panic Hide","Toggle hidden state",function()
    Config.Hidden=not Config.Hidden; mouseDown=false; pcall(mouse1release); Root.Visible=not Config.Hidden
end)
end)()

-- =====================
-- SETTINGS PAGE
-- =====================
;(function()
local st,_ = card(settings,"General","*")
st:SetAttribute("FavCategory","Settings")
keybind(st,"Panic Key","Toggle hidden state","PanicKey")
toggle(st,"Glass Look","Transparent glass panels","Glass")
toggle(st,"Screen Auto Scale","Auto size menu for resolution","ScreenAutoScale")
slider(st,"Scale Min","Smallest auto UI scale","ScreenScaleMin",0.5,1,0.01)
slider(st,"Scale Max","Largest auto UI scale","ScreenScaleMax",0.8,1.5,0.01)
toggle(st,"Show Previews","Live editor preview panels","ShowPreviews")
toggle(st,"Apply Popups","Show preset change summaries","ApplyPopups")
toggle(st,"Dock Mode","Show mini dock when menu is hidden","DockMode")
toggle(st,"Search Spotlight","Dim non-matching settings while searching","SearchSpotlight")
toggle(st,"Hover Help","Explain settings when hovered","HoverHelp")
toggle(st,"Auto-Hide Dock","Fade dock when idle","DockAutoHide")
slider(st,"Dock Hide Delay","Seconds before fade","DockAutoHideDelay",1,10,0.5)
slider(st,"Dock Wake Range","Mouse distance to reveal dock","DockWakeDistance",30,220,10)
button(st,"Notifications","Open notification history",toggleNotificationDrawer)
dropdown(st,"Theme Preset","Ghost color theme","ThemePreset",{"Bright Phantom","Neon Wraith","Deep Violet","Void Glow","Glass Noir","High Contrast"})
toggle(st,"Ghost Background","Ghost watermark/image in menu","GhostBackground")
slider(st,"Ghost BG Alpha","Background ghost opacity","GhostBackgroundOpacity",0,0.75,0.01)
button(st,"Apply Theme","Refresh theme colors",function() applyGhostTheme(Config.ThemePreset); applyGlassLook(); updateGhostBackground(); ghostSaveConfigFile(true) end)
button(st,"Unload","Remove menu and drawings",function() if _G.GhostUnload then _G.GhostUnload() end end)

local clean,_ = card(settings,"Cleanup / Safety","*")
clean:SetAttribute("FavCategory","Settings")
button(clean,"Hide All Visuals","Temporarily hide menu and drawings",function()
    Config.Hidden=true; menuVisible=false; mouseDown=false; pcall(mouse1release); Root.Visible=false
    for _,obj in safePairs(ESPObjects) do hideAll(obj) end
    if FOVCircle then FOVCircle.Visible=false end; if SACircle then SACircle.Visible=false end
    if TriggerCircle then TriggerCircle.Visible=false end; if TargetLock then TargetLock.Visible=false end
    if ReloadLbl then ReloadLbl.Visible=false end; if CHDot then CHDot.Visible=false end
    for _,l in safePairs(CHLines) do l.Visible=false end
    for _,l in ipairs(LOSLines) do l.Visible=false end; for _,c in ipairs(LOSCones) do c.Visible=false end
end)
button(clean,"Reset Drawings","Hide stuck ESP/arrows",function()
    for _,obj in safePairs(ESPObjects) do hideAll(obj); obj.ArrowSmooth=nil end
    if FOVCircle then FOVCircle.Visible=false end; if SACircle then SACircle.Visible=false end
    if TriggerCircle then TriggerCircle.Visible=false end; if TargetLock then TargetLock.Visible=false end
    if ReloadLbl then ReloadLbl.Visible=false end; if CHDot then CHDot.Visible=false end
    for _,l in safePairs(CHLines) do if l then l.Visible=false end end
    if topInfoText then topInfoText.Visible=false end; if targetInfoText then targetInfoText.Visible=false end
    for _,l in ipairs(LOSLines) do l.Visible=false end; for _,c in ipairs(LOSCones) do c.Visible=false end
end)
button(clean,"Clear Chams","Remove GhostCham highlights",function()
    for _,p in safeIpairs(Players:GetPlayers()) do
        local ch=getChar(p); local h=ch and ch:FindFirstChild("GhostCham")
        if h then pcall(function() h:Destroy() end) end
    end
end)
button(clean,"Reset UI Position","Center the menu",function()
    Root.Size=UDim2.new(0,880,0,520); Root.Position=UDim2.new(0.5,0,0.5,0)
    menuVisible=true; Config.Hidden=false; Root.Visible=true
end)
button(clean,"Clear Runtime State","Clear target/resolver cache",function()
    lastTarget=nil; resolverData={}; trackedHumanoids={}; arrowBuckets={}; rigCache={}; antiAimData={}
end)
button(clean,"Clear Rig Cache","Force re-resolve all characters",function()
    rigCache={}; showApplyPopup("Rig cache cleared","Will re-resolve on next frame.","success")
end)

local teamDbg,_ = card(settings,"Team Debug","*")
teamDbg:SetAttribute("FavCategory","Settings")
local teamRow=row(teamDbg,"Team Detection","Friendly filter diagnostics")
local teamText=Instance.new("TextLabel",teamRow)
teamText.BackgroundTransparency=1; teamText.Position=UDim2.new(0,36,0,22); teamText.Size=UDim2.new(1,-48,0,18)
teamText.Font=Enum.Font.Gotham; teamText.TextSize=11; teamText.TextColor3=Color3.fromRGB(150,150,165)
teamText.TextXAlignment=Enum.TextXAlignment.Left; teamText.ZIndex=8
RunService.RenderStepped:Connect(function()
    if ghostUnloaded then return end
    if teamText and teamText.Parent then teamText.Text=teamDebugSummary() end
end)

-- Rig debug card
local rigDbg,_ = card(settings,"Resolver Debug","*")
rigDbg:SetAttribute("FavCategory","Settings")
local rigRow=row(rigDbg,"Rig Info","Universal rig resolver status")
local rigText=Instance.new("TextLabel",rigRow)
rigText.BackgroundTransparency=1; rigText.Position=UDim2.new(0,36,0,22); rigText.Size=UDim2.new(1,-48,0,18)
rigText.Font=Enum.Font.Gotham; rigText.TextSize=11; rigText.TextColor3=Color3.fromRGB(150,150,165)
rigText.TextXAlignment=Enum.TextXAlignment.Left; rigText.ZIndex=8
RunService.RenderStepped:Connect(function()
    if ghostUnloaded then return end
    if rigText and rigText.Parent then
        local count=0; for _ in safePairs(rigCache) do count=count+1 end
        rigText.Text="Cached rigs: "..count.." | Tick: "..math.floor(tickRateSmooth).."hz | AA: "..
            (function() local n=0; for _ in safePairs(antiAimData) do n=n+1 end; return n end)().." tracked"
    end
end)
button(rigDbg,"Print Rig Info","Print resolver data for all enemies",function()
    for _,p in safeIpairs(Players:GetPlayers()) do
        if validEnemy(p) then
            local ch=getChar(p); local rig=ch and resolveRig(ch)
            if rig then
                print("[Ghost Resolver] "..p.Name.." | parts: "..#rig.parts.." | bones: "..#rig.bones.." | root: "..(rig.root and rig.root.Name or "nil").." | head: "..(rig.head and rig.head.Name or "nil").." | torso: "..(rig.torso and rig.torso.Name or "nil"))
            end
        end
    end
end)

local info,_ = card(settings,"Status","*")
local status=row(info,"Runtime","Live stats")
local statText=Instance.new("TextLabel",status)
statText.BackgroundTransparency=1; statText.Position=UDim2.new(0,12,0,22); statText.Size=UDim2.new(1,-24,0,18)
statText.Font=Enum.Font.Gotham; statText.TextSize=11; statText.TextColor3=Color3.fromRGB(150,150,165)
statText.TextXAlignment=Enum.TextXAlignment.Left; statText.ZIndex=8
RunService.RenderStepped:Connect(function()
    if ghostUnloaded then return end
    if statText and statText.Parent then
        statText.Text="Ping: "..getPing().."ms | Players: "..#Players:GetPlayers().." | Target: "..(lastTarget and lastTarget.Name or "none")
    end
end)
end)()

-- =====================
-- THEME STUDIO PAGE
-- =====================
;(function()
local s,_ = card(themeStudio,"Theme Studio","*")
dropdown(s,"Theme Preset","Choose base palette","ThemePreset",{"Bright Phantom","Neon Wraith","Deep Violet","Void Glow","Glass Noir","High Contrast"})
dropdown(s,"Background Mode","Animated menu background","BackgroundMode",{"Ghost Drift","Ghost Fog","Spirit Pulse","Static Glass"})
toggle(s,"Idle Glow","Breathing border glow","IdleGlow")
toggle(s,"Ghost Parallax","Background follows mouse slightly","GhostParallax")
slider(s,"Parallax Strength","Mouse depth amount","GhostParallaxStrength",0,0.08,0.005)
dropdown(s,"Tab Transition","Page switch animation","TabTransition",{"Fade","Slide","Zoom","Instant","Glitch"})
toggle(s,"Profile Card","Show sidebar identity card","ProfileCard")
button(s,"Apply Theme Studio","Apply selected theme/background",function()
    applyGhostTheme(Config.ThemePreset); applyGlassLook(); updateGhostBackground(); ghostSaveConfigFile(true)
    showApplyPopup("Theme updated",Config.ThemePreset.." + "..Config.BackgroundMode,"success")
end)
end)()

-- =====================
-- SCANNER PAGE
-- =====================
;(function()
local guideCard,_ = card(scanner,"How To Use Scanner","i")
button(guideCard,"Scanner Guide","Show scanner workflow",showScannerGuide)
button(guideCard,"Copy Scanner Report","Copy report",function()
    if setclipboard then setclipboard(scannerLastText or ""); showApplyPopup("Copied","Scanner report copied.","success") end
end)

local s,_ = card(scanner,"Passive Game Scanner","i")
toggle(s,"Passive Only","Scanner never fires remotes","ScannerPassiveOnly")
toggle(s,"Show Full Paths","Show object full paths","ScannerShowPaths")
slider(s,"Max Results","Limit rows per section","ScannerMaxResults",20,200,10)
toggle(s,"Aggressive Depth","Enable deeper passive audit","AggressiveScanDepth")
toggle(s,"Risk Scoring","Score exposed surfaces","AggressiveRiskScoring")
slider(s,"Watch Interval","Seconds between watch checks","ScannerWatchInterval",1,10,0.5)
button(s,"Scan Game","Map remotes/tools/modules/scripts",runPassiveScan)
button(s,"Aggressive Defense Scan","Deep passive audit/risk scoring",runAggressiveDefenseScan)
button(s,"Environment Check","Check executor/API capabilities",runEnvCheck)
button(s,"Script Indexer","Find interesting scripts/modules",runScriptIndexer)
button(s,"Copy Scanner Output","Copy current report",copyScannerOutput)

local d,_ = card(scanner,"Snapshot Diff","#")
button(d,"Take Snapshot A","Save current object map",function() takeScannerSnapshot("A") end)
button(d,"Take Snapshot B","Save later object map",function() takeScannerSnapshot("B") end)
button(d,"Compare A/B","Show added and removed objects",diffScannerSnapshots)
button(d,"Toggle Object Watch","Live passive object change monitor",toggleScannerWatch)

local o,_ = card(scanner,"Scanner Output","*")
scannerSummary=Instance.new("TextLabel",o)
scannerSummary.BackgroundTransparency=1; scannerSummary.Size=UDim2.new(1,0,0,22)
scannerSummary.Font=Enum.Font.GothamBold; scannerSummary.TextSize=12; scannerSummary.TextColor3=GhostTextMain
scannerSummary.TextXAlignment=Enum.TextXAlignment.Left; scannerSummary.Text="Ready"; scannerSummary.ZIndex=58
scannerOutput=Instance.new("TextLabel",o)
scannerOutput.BackgroundColor3=Color3.fromRGB(8,5,18); scannerOutput.BackgroundTransparency=0.14
scannerOutput.BorderSizePixel=0; scannerOutput.Position=UDim2.new(0,0,0,30); scannerOutput.Size=UDim2.new(1,0,0,360)
scannerOutput.Font=Enum.Font.Code; scannerOutput.TextSize=11; scannerOutput.TextColor3=Color3.fromRGB(220,215,240)
scannerOutput.TextXAlignment=Enum.TextXAlignment.Left; scannerOutput.TextYAlignment=Enum.TextYAlignment.Top
scannerOutput.TextWrapped=false; scannerOutput.Text=scannerLastText; scannerOutput.ZIndex=58
Instance.new("UICorner",scannerOutput).CornerRadius=UDim.new(0,12)
end)()

-- =====================
-- NOTES PAGE
-- =====================
;(function()
local n,_ = card(notes,"Ghost Notes v5.3","#")
function noteLine(parent,title,body)
    local r=row(parent,title,body); r.Size=UDim2.new(1,0,0,58); return r
end
noteLine(n,"v5.3","Universal rig resolver, bone skeleton, weapon silhouette, LoS minimap, combat advanced card.")
noteLine(n,"Universal Resolver","Works on any game rig — R6, R15, custom blocky, Frontlines, Deadline, etc. No hardcoded part names.")
noteLine(n,"Skeleton Modes","Bone = joint-to-joint lines. Dots = old circle system. Both = render all. Toggle in Visuals > ESP.")
noteLine(n,"Weapon Silhouette","Draws a small gun icon near players holding a Tool. Toggleable in ESP Advanced.")
noteLine(n,"Nameplate Scaling","Name text shrinks with distance. Min/max size and range are sliders in ESP Advanced.")
noteLine(n,"LoS Map","Minimap shows each enemy's facing direction and a green velocity prediction cone.")
noteLine(n,"Minimap Grid","Distance rings every N studs drawn on the map background. Toggle + spacing in Visuals > Mini Map.")
noteLine(n,"Target Weight System","Scores targets: shootable first, then distance, then low health. Toggleable per criterion.")
noteLine(n,"Anti-Aim Detector","Detects spinbot and flipbot. Draws AA: SPIN/FLIP above enemy and global screen warning.")
noteLine(n,"Anti-Lag","Compensate = lead by ping. Match = aim at past position. Fine-tune with offset slider.")
noteLine(n,"Ping Normalizer","Offsets shot timing by your ping for consistent server-side hit registration.")
noteLine(n,"Tick Rate Compensator","Detects server tick rate over 60 frames and scales prediction step to match.")
noteLine(n,"Flick Assist","Off by default. Single fast flick toward targets just outside FOV, hands back to smooth assist.")
noteLine(n,"Aim Shake Dampener","Averages recent camera frames to filter out game-induced shake before aim runs.")
noteLine(n,"Multi-Target Suppressor","When 3+ enemies in FOV zone, weight system handles prioritization automatically.")
noteLine(n,"Resolver Debug","Settings > Resolver Debug shows cached rig count, tick rate, and AA tracking count.")
end)()

-- =====================
-- SHOW TAB
-- =====================
function showTab(name)
    if not pages[name] then return end
    currentTab=name; Title.Text=name; clearContent(); pages[name].Visible=true
    pages[name].Position=UDim2.new(0,14,0,0)
    pcall(function() tween(pages[name],0.18,{Position=UDim2.new(0,0,0,0)},Enum.EasingStyle.Quint) end)
    for n,btn in safePairs(tabs) do
        local active=n==name
        tween(btn,0.18,{BackgroundColor3=active and Color3.fromRGB(42,16,70) or Color3.fromRGB(4,4,8),TextColor3=active and Color3.fromRGB(245,245,250) or Color3.fromRGB(165,165,178)},Enum.EasingStyle.Quint)
    end
    if not sidebarExpanded then setSidebarExpanded(false,true) end
end

if rebuildFavorites then rebuildFavorites() end
_G.GhostShowTab=function(name) showTab(name) end
createDock()
showTab("Favorited")

-- =====================
-- DRAGGING
-- =====================
Topbar.InputBegan:Connect(function(i)
    if i.UserInputType==Enum.UserInputType.MouseButton1 then
        dragging=true; local start=i.Position; local startPos=Root.Position
        local conn; conn=UIS.InputChanged:Connect(function(ch)
            if dragging and ch.UserInputType==Enum.UserInputType.MouseMovement then
                local delta=ch.Position-start
                Root.Position=UDim2.new(startPos.X.Scale,startPos.X.Offset+delta.X,startPos.Y.Scale,startPos.Y.Offset+delta.Y)
                saveLayoutState()
            end
        end)
        i.Changed:Connect(function() if i.UserInputState==Enum.UserInputState.End then dragging=false; if conn then conn:Disconnect() end end end)
    end
end)

-- =====================
-- SEARCH FILTER
-- =====================
function applySearch()
    local q=(SearchBox.Text or ""):lower(); local searching=q~=""
    local cardHits={}
    for _,rec in safeIpairs(rowRegistry) do
        local rowHit=(not searching) or rec.text:find(q,1,true)~=nil
        rec.row.Visible=rowHit
        if rowHit then cardHits[rec.parent]=true end
    end
    if SearchDim then
        SearchDim.Visible=searching and Config.SearchSpotlight
        SearchDim.BackgroundTransparency=(searching and Config.SearchSpotlight) and 0.42 or 1
    end
    for _,rec in safeIpairs(cardRegistry) do
        local titleHit=(rec.title or ""):lower():find(q,1,true)~=nil
        local hit=(not searching) or titleHit or cardHits[rec.body]
        rec.card.Visible=hit
        if searching and hit then rec.body.Visible=true; rec.collapsed=false
        elseif not searching then rec.collapsed=collapsedCards[rec.title] or false end
        if rec.resize then rec.resize() end
    end
end
SearchBox:GetPropertyChangedSignal("Text"):Connect(applySearch)
applySearch()

-- =====================
-- UI BUTTONS
-- =====================
Min.MouseButton1Click:Connect(function()
    local small=Root.Size.Y.Offset>80
    if small then tween(Root,0.25,{Size=UDim2.new(0,880,0,68)}) else tween(Root,0.25,{Size=UDim2.new(0,880,0,520)}) end
    task.delay(0.3,saveLayoutState)
end)
Close.MouseButton1Click:Connect(function()
    menuVisible=false; Root.Visible=false; setDockVisible(true)
end)
FullClose.MouseButton1Click:Connect(function()
    if _G.GhostUnload then _G.GhostUnload() end
end)

-- =====================
-- AIM HELPERS
-- =====================
function aimKeyDown()
    if Config.AimKey=="MouseButton2" then return UIS:IsMouseButtonPressed(Enum.UserInputType.MouseButton2) end
    if Config.AimKey=="MouseButton1" then return UIS:IsMouseButtonPressed(Enum.UserInputType.MouseButton1) end
    local ok,key=pcall(function() return Enum.KeyCode[Config.AimKey] end)
    return ok and UIS:IsKeyDown(key)
end

function moveMouseTo(screenPos,smooth)
    local mouse=UIS:GetMouseLocation()
    local delta=screenPos-mouse
    if delta.Magnitude<Config.AimDeadzone then return end
    if Config.AimHumanize then delta=delta+Vector2.new(math.random(-2,2),math.random(-2,2)) end
    if mousemoverel then mousemoverel(delta.X*(1-smooth),delta.Y*(1-smooth))
    else
        local cf=getDampenedCameraLook()
        local ray=camera:ViewportPointToRay(screenPos.X,screenPos.Y)
        camera.CFrame=cf:Lerp(CFrame.new(cf.Position,cf.Position+ray.Direction),1-smooth)
    end
end

-- =====================
-- TRIGGER BOT
-- =====================
function triggerRayTarget()
    local origin=camera.CFrame.Position
    local dir=camera.CFrame.LookVector*Config.DistanceLimit
    local params=RaycastParams.new()
    params.FilterType=Enum.RaycastFilterType.Blacklist
    params.FilterDescendantsInstances={lp.Character}
    params.IgnoreWater=true
    local hit=Workspace:Raycast(origin,dir,params)
    if not hit then return nil,nil,nil end
    local model=hit.Instance and hit.Instance:FindFirstAncestorOfClass("Model")
    local plr=model and Players:GetPlayerFromCharacter(model)
    if plr and validEnemy(plr) then
        local hum=getHum(plr)
        if hum and hum.Health>0 then return plr,hit.Instance,hit.Position end
    end
    return nil,nil,nil
end

function triggerCheck()
    if not Config.TriggerBot or menuBlocksInput() then
        if triggerHolding then triggerHolding=false; pcall(mouse1release) end; return
    end
    local target,part=triggerRayTarget()
    if target and part then
        lastTarget=target
        if now()-lastShot>=Config.TriggerDelay then
            if Config.TriggerHold then
                if not triggerHolding then triggerHolding=true; pcall(mouse1press) end
            else lastShot=now(); pcall(mouse1click) end
        end
    else
        if triggerHolding then triggerHolding=false; lastShot=now(); pcall(mouse1release) end
    end
end

-- =====================
-- AUTO PISTOL
-- =====================
mouseDown=false
UIS.InputBegan:Connect(function(i,gp) if not ghostUnloaded and not menuBlocksInput() and i.UserInputType==Enum.UserInputType.MouseButton1 then mouseDown=true end end)
UIS.InputEnded:Connect(function(i,gp) if i.UserInputType==Enum.UserInputType.MouseButton1 then mouseDown=false; if not triggerHolding then pcall(mouse1release) end end end)

local lastCamCF=camera.CFrame

-- =====================
-- RELOAD / WEAPON STATE HUD
-- =====================
function getEquippedTool()
    local ch=getChar(lp); if not ch then return nil end
    for _,obj in safeIpairs(ch:GetChildren()) do if obj:IsA("Tool") then return obj end end
    return nil
end

function findToolValue(tool,names)
    if not tool then return nil,nil end
    for _,name in safeIpairs(names) do
        local direct=tool:FindFirstChild(name)
        if direct and direct:IsA("ValueBase") then return direct,direct.Value end
    end
    for _,obj in safeIpairs(tool:GetDescendants()) do
        if obj:IsA("ValueBase") then
            local n=obj.Name:lower()
            for _,name in safeIpairs(names) do
                if n==name:lower() or n:find(name:lower(),1,true) then return obj,obj.Value end
            end
        end
    end
    return nil,nil
end

function readLocalReloadState()
    local tool=getEquippedTool()
    if not tool then return {tool="None",state="NO TOOL",ammo=nil,reserve=nil,reloading=false,low=false} end
    local _,ammo=findToolValue(tool,{"Ammo","Clip","Magazine","Mag","CurrentAmmo","Bullets","Rounds"})
    local _,reserve=findToolValue(tool,{"Reserve","ReserveAmmo","StoredAmmo","TotalAmmo","AmmoReserve","ExtraAmmo"})
    local _,reloading=findToolValue(tool,{"Reloading","IsReloading","Reload","ReloadState"})
    local _,cooldown=findToolValue(tool,{"Cooldown","FireCooldown","Busy","Shooting","CanShoot","CanFire"})
    local reloadBool=false
    if typeof(reloading)=="boolean" then reloadBool=reloading elseif typeof(reloading)=="number" then reloadBool=reloading>0 end
    local ammoNum=tonumber(ammo); local low=ammoNum~=nil and ammoNum<=3
    local state="READY"
    if reloadBool then state="RELOADING" elseif low and Config.ReloadWarnLowAmmo then state="LOW AMMO"
    elseif typeof(cooldown)=="boolean" and cooldown==false then state="COOLDOWN" end
    return {tool=tool.Name,state=state,ammo=ammo,reserve=reserve,reloading=reloadBool,low=low}
end

function getEquippedToolFor(p)
    local ch=getChar(p); if not ch then return nil end
    for _,obj in safeIpairs(ch:GetChildren()) do if obj:IsA("Tool") then return obj end end
    return nil
end

function readPlayerReplicatedWeaponState(p)
    local tool=getEquippedToolFor(p)
    if not tool then return {tool="None",state="UNKNOWN",ammo=nil,reserve=nil,reloading=nil,source="no equipped Tool"} end
    local ammoObj,ammo=findToolValue(tool,{"Ammo","Clip","Magazine","Mag","CurrentAmmo","Bullets","Rounds"})
    local reserveObj,reserve=findToolValue(tool,{"Reserve","ReserveAmmo","StoredAmmo","TotalAmmo","AmmoReserve","ExtraAmmo"})
    local reloadObj,reloading=findToolValue(tool,{"Reloading","IsReloading","Reload","ReloadState"})
    local cooldownObj,cooldown=findToolValue(tool,{"Cooldown","FireCooldown","Busy","Shooting","CanShoot","CanFire"})
    local reloadBool=nil
    if typeof(reloading)=="boolean" then reloadBool=reloading elseif typeof(reloading)=="number" then reloadBool=reloading>0 end
    local ammoNum=tonumber(ammo); local low=ammoNum~=nil and ammoNum<=3
    local state="UNKNOWN"
    if reloadBool==true then state="RELOADING" elseif reloadBool==false or ammo~=nil or cooldown~=nil then state="READY" end
    if state~="RELOADING" and low then state="LOW AMMO" end
    local src="replicated values"
    if reloadObj then src=pathOf(reloadObj) elseif ammoObj then src=pathOf(ammoObj) end
    return {tool=tool.Name,state=state,ammo=ammo,reserve=reserve,reloading=reloadBool,source=src,low=low}
end

function formatOpponentState(p)
    if not Config.OpponentStateDebug or not p then return "" end
    local s=readPlayerReplicatedWeaponState(p)
    local text=" | "..s.state
    if Config.OpponentStateDetails then
        text=text.." | "..s.tool
        if s.ammo~=nil then text=text.." "..tostring(s.ammo) end
        if s.reserve~=nil then text=text.."/"..tostring(s.reserve) end
    end
    if Config.OpponentStateSource then text=text.."\nsrc: "..tostring(s.source) end
    return text
end

function readOpponentMovementState(p)
    local myRoot=getRoot(lp); local root=getRoot(p)
    if not myRoot or not root then return {state="UNKNOWN",speed=0,closing=0} end
    local vel=root.AssemblyLinearVelocity or Vector3.zero
    local flatVel=Vector3.new(vel.X,0,vel.Z); local speed=flatVel.Magnitude
    local toMe=Vector3.new(myRoot.Position.X-root.Position.X,0,myRoot.Position.Z-root.Position.Z)
    local closing=0
    if toMe.Magnitude>1 and speed>0.5 then closing=flatVel:Dot(toMe.Unit) end
    local state="HOLDING"; local threshold=Config.PushSpeedThreshold or 8
    if speed<2 then state="STILL"
    elseif closing>threshold then state="PUSHING"
    elseif closing<-threshold then state="BACKING OFF"
    else
        local right=camera and camera.CFrame.RightVector or Vector3.new(1,0,0)
        local side=flatVel:Dot(Vector3.new(right.X,0,right.Z))
        if math.abs(side)>threshold then state=(side>0) and "CROSSING RIGHT" or "CROSSING LEFT" else state="MOVING" end
    end
    local hum=getHum(p)
    if hum then
        local hs=hum:GetState()
        if hs==Enum.HumanoidStateType.Jumping then state="JUMPING" elseif hs==Enum.HumanoidStateType.Freefall then state="FALLING" end
    end
    return {state=state,speed=speed,closing=closing}
end

function formatOpponentMovement(p)
    if not Config.OpponentPushDebug or not p then return "" end
    local m=readOpponentMovementState(p)
    if Config.OpponentPushDetails then return " | move: "..m.state.." | spd "..tostring(math.floor(m.speed)).." | close "..tostring(math.floor(m.closing)) end
    return " | move: "..m.state
end

function updateReloadStateHUD()
    if not Config.ReloadStateHUD or not Config.ReloadPredictor or menuBlocksInput() or not ReloadLbl then return end
    local s=readLocalReloadState()
    if s.tool=="None" then ReloadLbl.Visible=false; return end
    local ammoText=""
    if s.ammo~=nil then ammoText=" | "..tostring(s.ammo) end
    if Config.ReloadStateDetails and s.reserve~=nil then ammoText=ammoText.."/"..tostring(s.reserve) end
    ReloadLbl.Text=s.state.." - "..s.tool..ammoText
    if s.state=="RELOADING" then ReloadLbl.Color=Color3.fromRGB(255,190,70)
    elseif s.state=="LOW AMMO" then ReloadLbl.Color=Color3.fromRGB(255,85,120)
    elseif s.state=="COOLDOWN" then ReloadLbl.Color=Color3.fromRGB(120,190,255)
    else ReloadLbl.Color=Color3.fromRGB(130,255,170) end
    ReloadLbl.Visible=true
end

function setupTool(tool)
    if not tool:IsA("Tool") then return end
    if reloadConnections[tool] then return end
    local con=tool.Activated:Connect(function()
        if Config.ReloadPredictor and Drawing then
            ReloadLbl.Text="Shot fired"; ReloadLbl.Visible=true
            task.delay(0.45,function() if ReloadLbl then ReloadLbl.Text="Reload window?" end end)
            task.delay(1.2,function() if ReloadLbl then ReloadLbl.Visible=false end end)
        end
    end)
    reloadConnections[tool]=con
end

function watchTools(ch)
    for _,t in safeIpairs(ch:GetChildren()) do setupTool(t) end
    ch.ChildAdded:Connect(setupTool)
end

if lp.Character then watchTools(lp.Character) end
lp.CharacterAdded:Connect(watchTools)

-- =====================
-- SURROUND / BLIND SPOT
-- =====================
if Drawing then
    surroundText  = dnew("Text",{Text="",Size=13,Center=false,Outline=true, Color=Color3.fromRGB(220,210,255),Visible=false})
    surroundShadow= dnew("Text",{Text="",Size=13,Center=false,Outline=false,Color=Color3.fromRGB(0,0,0),Transparency=0.75,Visible=false})
    blindText     = dnew("Text",{Text="",Size=14,Center=true, Outline=true, Color=Color3.fromRGB(255,180,110),Visible=false})
    blindShadow   = dnew("Text",{Text="",Size=14,Center=true, Outline=false,Color=Color3.fromRGB(0,0,0),Transparency=0.75,Visible=false})
end

function getRelativeZone(p)
    local myRoot=getRoot(lp); local r=getRoot(p)
    if not myRoot or not r then return nil,999 end
    local rel=r.Position-myRoot.Position; local dist=rel.Magnitude
    if dist>(Config.SurroundRadius or 120) then return nil,dist end
    local flat=Vector3.new(rel.X,0,rel.Z)
    if flat.Magnitude<1 then return "Front",dist end
    local look=camera.CFrame.LookVector; local right=camera.CFrame.RightVector
    local f=flat.Unit:Dot(Vector3.new(look.X,0,look.Z).Unit)
    local s=flat.Unit:Dot(Vector3.new(right.X,0,right.Z).Unit)
    if f>0.55 then return "Front",dist end
    if f<-0.55 then return "Behind",dist end
    if s>0 then return "Right",dist end
    return "Left",dist
end

function updateSurroundAndBlindSpot()
    if menuBlocksInput() or ghostUnloaded then
        if surroundText then surroundText.Visible=false end; if surroundShadow then surroundShadow.Visible=false end
        if blindText then blindText.Visible=false end; if blindShadow then blindShadow.Visible=false end
        return
    end
    local counts={Front=0,Left=0,Right=0,Behind=0}
    local nearestBlind=nil; local nearestBlindDist=99999; local closeTotal=0
    for _,p in safeIpairs(Players:GetPlayers()) do
        if validEnemy(p) then
            local zone,dist=getRelativeZone(p)
            if zone then
                counts[zone]=counts[zone]+1; closeTotal=closeTotal+1
                if (zone=="Behind" or zone=="Left" or zone=="Right") and dist<(Config.BlindSpotRadius or 80) and dist<nearestBlindDist then
                    nearestBlind=zone; nearestBlindDist=dist
                end
            end
        end
    end
    if Config.SurroundMapHUD and surroundText and surroundShadow then
        local pos=Vector2.new(22,180)
        local txt="SURROUND\nF "..counts.Front.." | L "..counts.Left.." | R "..counts.Right.." | B "..counts.Behind.."\nclose: "..closeTotal
        surroundShadow.Text=txt; surroundShadow.Position=pos+Vector2.new(2,2); surroundShadow.Visible=true
        surroundText.Text=txt; surroundText.Position=pos; surroundText.Visible=true
        surroundText.Color=(counts.Behind>0 or counts.Left+counts.Right>1) and Color3.fromRGB(255,180,110) or Color3.fromRGB(220,210,255)
    elseif surroundText then surroundText.Visible=false; if surroundShadow then surroundShadow.Visible=false end end
    if Config.BlindSpotMonitor and blindText and blindShadow and nearestBlind then
        local vp=camera.ViewportSize
        local label="BLIND SPOT: "..string.upper(nearestBlind).."  "..math.floor(nearestBlindDist).."m"
        local pos=Vector2.new(vp.X/2,vp.Y-135)
        blindShadow.Text=label; blindShadow.Position=pos+Vector2.new(2,2); blindShadow.Visible=true
        blindText.Text=label; blindText.Position=pos; blindText.Visible=true
    elseif blindText then blindText.Visible=false; if blindShadow then blindShadow.Visible=false end end
end

-- =====================
-- CROSSHAIR UPDATE
-- =====================
local spin=0; local shotPulse=0; local fpsSmooth=60

UIS.InputBegan:Connect(function(input,gp)
    if input.UserInputType==Enum.UserInputType.MouseButton1 then shotPulse=1 end
end)

function updateCrosshair(dt)
    if not Drawing then return end
    local allowMenuPreview=(menuVisible and currentTab=="Crosshair" and not Config.Hidden and not ghostUnloaded)
    local m=UIS:GetMouseLocation()
    local center=Vector2.new(m.X,m.Y)
    local vp=camera and camera.ViewportSize or Vector2.new(1280,720)
    local scale=1
    if Config.CrosshairAutoSize then scale=clamp(math.min(vp.X/1280,vp.Y/720),0.75,1.35) end
    spin=spin+dt*3; shotPulse=math.max(0,shotPulse-(dt*5.5))
    local color=Config.CrosshairColor
    if Config.FOVRainbow then color=Color3.fromHSV((tick()%5)/5,0.8,1) end
    if Config.CrosshairShootPulse and shotPulse>0 then color=color:Lerp(Color3.fromRGB(255,255,255),shotPulse*0.75) end
    if FOVCircle then FOVCircle.Position=center; FOVCircle.Radius=Config.SilentFOV; FOVCircle.Thickness=Config.FOVThickness; FOVCircle.Transparency=Config.FOVTransparency; FOVCircle.Visible=Config.FOVVisible and Config.SilentAim and not menuBlocksInput(); FOVCircle.Color=color end
    if SACircle then SACircle.Position=center; SACircle.Radius=Config.AimFOV; SACircle.Thickness=Config.FOVThickness; SACircle.Transparency=Config.FOVTransparency; SACircle.Visible=Config.FOVVisible and (Config.Aimbot or Config.AimAssist) and not menuBlocksInput(); SACircle.Color=Color3.fromRGB(90,220,255) end
    if TriggerCircle then TriggerCircle.Position=center; TriggerCircle.Radius=Config.TriggerFOV; TriggerCircle.Thickness=Config.FOVThickness; TriggerCircle.Transparency=Config.FOVTransparency; TriggerCircle.Visible=Config.TriggerFOVVisible and Config.TriggerBot and not menuBlocksInput(); TriggerCircle.Color=Color3.fromRGB(255,190,70) end
    local visible=Config.Crosshair and (not menuBlocksInput() or allowMenuPreview)
    local profile=Config.CrosshairProfile or "Classic"
    local gap=(Config.CrosshairGap or 6)*scale; local len=(Config.CrosshairLength or 10)*scale; local th=(Config.CrosshairThickness or 2)
    if Config.CrosshairDynamicGap then gap=gap+(shotPulse*7*scale) end
    len=len+(shotPulse*5*scale); th=clamp(th+(shotPulse*1.5),1,10)
    for _,line in safeIpairs(CHLines) do line.Visible=false end
    if CHDot then CHDot.Visible=false end
    local function setLine(i,a,b,t)
        local line=CHLines[i]; if not line then return end
        line.From=a; line.To=b; line.Thickness=t or th; line.Color=color; line.Visible=visible
    end
    local function rot(v,ang) local cs=math.cos(ang); local sn=math.sin(ang); return Vector2.new(v.X*cs-v.Y*sn,v.X*sn+v.Y*cs) end
    local angle=Config.CrosshairSpin and spin or 0
    local dirs={Vector2.new(1,0),Vector2.new(-1,0),Vector2.new(0,1),Vector2.new(0,-1)}
    for i,d in safeIpairs(dirs) do dirs[i]=rot(d,angle) end
    if profile=="Dot" then
        if CHDot then CHDot.Position=center; CHDot.Color=color; CHDot.Radius=(3+shotPulse*2)*scale; CHDot.Filled=true; CHDot.Visible=visible end; return
    elseif profile=="Circle" then
        if CHDot then CHDot.Position=center; CHDot.Color=color; CHDot.Radius=(gap+len*0.65); CHDot.Filled=false; CHDot.Thickness=th; CHDot.Visible=visible end; return
    elseif profile=="Box" then
        local r=gap+len*0.55
        local pts={Vector2.new(-r,-r),Vector2.new(r,-r),Vector2.new(r,r),Vector2.new(-r,r)}
        for i=1,4 do pts[i]=center+rot(pts[i],angle) end
        setLine(1,pts[1],pts[2]); setLine(2,pts[2],pts[3]); setLine(3,pts[3],pts[4]); setLine(4,pts[4],pts[1])
        if CHDot and Config.CrosshairDot then CHDot.Position=center; CHDot.Color=color; CHDot.Radius=2*scale; CHDot.Filled=true; CHDot.Visible=visible end; return
    elseif profile=="Diamond" then
        local r=gap+len*0.75
        local pts={Vector2.new(0,-r),Vector2.new(r,0),Vector2.new(0,r),Vector2.new(-r,0)}
        for i=1,4 do pts[i]=center+rot(pts[i],angle) end
        setLine(1,pts[1],pts[2]); setLine(2,pts[2],pts[3]); setLine(3,pts[3],pts[4]); setLine(4,pts[4],pts[1]); return
    elseif profile=="X" then
        dirs={Vector2.new(1,1).Unit,Vector2.new(-1,-1).Unit,Vector2.new(-1,1).Unit,Vector2.new(1,-1).Unit}
        for i,d in safeIpairs(dirs) do d=rot(d,angle); setLine(i,center+d*gap,center+d*(gap+len),th) end; return
    elseif profile=="Sniper" then
        local long=len*1.8
        setLine(1,center+dirs[1]*(gap+long*0.25),center+dirs[1]*(gap+long),th)
        setLine(2,center+dirs[2]*(gap+long*0.25),center+dirs[2]*(gap+long),th)
        setLine(3,center+dirs[3]*(gap+long*0.45),center+dirs[3]*(gap+long*1.15),math.max(1,th-0.5))
        setLine(4,center+dirs[4]*(gap+long*0.45),center+dirs[4]*(gap+long*1.15),math.max(1,th-0.5))
        if CHDot then CHDot.Position=center; CHDot.Color=color; CHDot.Radius=(gap*0.55); CHDot.Filled=false; CHDot.Thickness=math.max(1,th-0.5); CHDot.Visible=visible end; return
    elseif profile=="Cyber" then
        local a=rot(Vector2.new(1,0),angle); local b=rot(Vector2.new(0,1),angle)
        setLine(1,center+a*gap,center+a*(gap+len*1.2),th+0.5)
        setLine(2,center-a*gap,center-a*(gap+len*0.75),th)
        setLine(3,center+b*gap,center+b*(gap+len*0.75),th)
        setLine(4,center-b*gap,center-b*(gap+len*1.2),th+0.5)
        if CHDot and Config.CrosshairDot then CHDot.Position=center; CHDot.Color=color; CHDot.Radius=(2+shotPulse*2)*scale; CHDot.Filled=true; CHDot.Visible=visible end; return
    end
    if profile=="Minimal" then len=math.max(4,math.floor(len*0.65)) end
    for i,line in safeIpairs(CHLines) do
        local d=dirs[i]; local hideLine=(profile=="T-Shape" and i==3)
        line.From=center+d*gap; line.To=center+d*(gap+len); line.Thickness=th; line.Color=color; line.Visible=visible and not hideLine
    end
    if CHDot then CHDot.Position=center; CHDot.Color=color; CHDot.Radius=(Config.CrosshairDot and 2 or 0)+(shotPulse*1.5); CHDot.Filled=true; CHDot.Visible=visible and Config.CrosshairDot end
end

-- =====================
-- TARGET HUD
-- =====================
function updateTargetHud(target,part,scr,dt)
    if not Drawing then return end
    local vp=camera.ViewportSize
    fpsSmooth=fpsSmooth*0.92+(1/math.max(dt or 1/60,1/240))*0.08
    if topInfoText and topInfoShadow then
        local targetName=target and target.Name or "none"
        local txt="FPS "..math.floor(fpsSmooth).." | Ping "..getPing().." | Players "..#Players:GetPlayers().." | Target "..targetName
        local pos=Vector2.new(vp.X-430,82)
        topInfoShadow.Text=txt; topInfoShadow.Position=pos+Vector2.new(2,2); topInfoShadow.Visible=Config.TopIndicators and not menuBlocksInput()
        topInfoText.Text=txt; topInfoText.Position=pos; topInfoText.Visible=Config.TopIndicators and not menuBlocksInput()
    end
    if target and part and scr and Config.TargetInfo and not menuBlocksInput() then
        local hum=getHum(target); local root=getRoot(target)
        local hp=hum and math.floor(hum.Health) or 0
        local dist=root and math.floor((root.Position-camera.CFrame.Position).Magnitude) or 0
        local txt=hp.." HP | "..dist.."m"..formatOpponentState(target)..formatOpponentMovement(target)
        targetInfoShadow.Text=txt; targetInfoShadow.Position=scr+Vector2.new(2,20); targetInfoShadow.Visible=true
        targetInfoText.Text=txt; targetInfoText.Position=scr+Vector2.new(0,18); targetInfoText.Visible=true
        if TargetLock then TargetLock.Position=scr; TargetLock.Radius=clamp(16-(dist/120),7,16); TargetLock.Thickness=2; TargetLock.Color=Color3.fromRGB(90,255,150); TargetLock.Visible=Config.TargetLockCircle end
    else
        if targetInfoShadow then targetInfoShadow.Visible=false end
        if targetInfoText then targetInfoText.Visible=false end
        if TargetLock then TargetLock.Visible=false end
    end
end

-- =====================
-- MAIN RENDER LOOP
-- =====================
local ghostStart = tick()

RunService.RenderStepped:Connect(function(dt)
    if ghostUnloaded then return end
    camera=Workspace.CurrentCamera

    updateResolver() -- includes anti-aim detection
    updateESP()
    updateChams()
    applyGlassLook()
    updateGhostBackground()
    updateAnimatedBackground(dt)
    updateIdleGlow(dt)
    updateDockAutoHide()
    updateDockStyle()
    updateMiniMap()
    updateSurroundAndBlindSpot()

    if ProfileCard then
        ProfileCard.Visible=(sidebarExpanded and Config.ProfileCard==true)
        pcStroke.Color=Config.UIAccent
        local runtime=math.floor((tick()-ghostStart)/60)
        pcBody.Text=(Config.ThemePreset or "Custom").."  |  "..runtime.."m"
    end

    getDampenedCameraLook() -- tick camera history every frame

    updateCrosshair(dt)
    triggerCheck()
    updateReloadStateHUD()

    local hudTarget,hudPart,hudScreen=nil,nil,nil

    -- Multi-target suppressor: widen FOV if many enemies nearby
    local activeFOV=Config.AimFOV
    if Config.MultiTargetSuppressor and getMultiTargetCount()>=3 then
        activeFOV=activeFOV*(Config.MultiTargetRadius or 3)
    end

    if Config.Aimbot and aimKeyDown() and not menuBlocksInput() then
        local target,part=closestTarget(activeFOV,true)
        lastTarget=target
        if target and part then
            local pos=predictedPos(target,part)
            local scr,on=worldToScreen(pos)
            if on then
                hudTarget,hudPart,hudScreen=target,part,scr
                moveMouseTo(scr,Config.AimSmooth)
                if AimCircle then AimCircle.Position=scr; AimCircle.Visible=true end
                if AimLine then AimLine.From=UIS:GetMouseLocation(); AimLine.To=scr; AimLine.Visible=true end
            end
        else
            if AimCircle then AimCircle.Visible=false end; if AimLine then AimLine.Visible=false end
        end
    else
        if not triggerHolding then lastTarget=nil end
        if AimCircle then AimCircle.Visible=false end; if AimLine then AimLine.Visible=false end
    end

    if Config.AimAssist and not menuBlocksInput() then
        local target,part=closestTarget(activeFOV,true)
        if target and part then
            local scr,on=worldToScreen(predictedPos(target,part))
            if on then hudTarget,hudPart,hudScreen=target,part,scr; moveMouseTo(scr,1-Config.AimAssistStrength) end
        end
    end

    -- Flick assist (runs when neither aimbot nor assist found a target in FOV)
    if Config.FlickAssist and not menuBlocksInput() and not hudTarget then
        tryFlickAssist()
    end

    if not hudTarget then
        local t,p=closestTarget(activeFOV,true)
        if t and p then local s,on=worldToScreen(p.Position); if on then hudTarget,hudPart,hudScreen=t,p,s end end
    end

    updateTargetHud(hudTarget,hudPart,hudScreen,dt)

    if Config.AutoPistol and mouseDown and not menuBlocksInput() and now()-lastShot>0.08 then
        lastShot=now(); pcall(mouse1click)
    end

    if Config.NoRecoil then
        local cf=camera.CFrame
        camera.CFrame=lastCamCF:Lerp(cf,0.35)
    end
    lastCamCF=camera.CFrame

    if ReloadLbl then
        ReloadLbl.Position=UIS:GetMouseLocation()+Vector2.new(0,35)
        ReloadLbl.Visible=ReloadLbl.Visible and Config.ReloadPredictor and not menuBlocksInput()
    end
end)

-- =====================
-- KEYBINDS
-- =====================
UIS.InputBegan:Connect(function(input,gp)
    if ghostUnloaded then return end
    if gp then return end
    if input.KeyCode==Enum.KeyCode.F1 then
        menuVisible=not menuVisible; Root.Visible=menuVisible; setDockVisible(not menuVisible)
        if menuVisible then
            setDockVisible(false); mouseDown=false; triggerHolding=false; pcall(mouse1release)
            for _,obj in safePairs(ESPObjects) do hideAll(obj) end
            if FOVCircle then FOVCircle.Visible=false end; if SACircle then SACircle.Visible=false end
            if TriggerCircle then TriggerCircle.Visible=false end; if TargetLock then TargetLock.Visible=false end
            if ReloadLbl then ReloadLbl.Visible=false end; if CHDot then CHDot.Visible=false end
            for _,l in safePairs(CHLines) do l.Visible=false end
        end
        return
    end
    local ok,key=pcall(function() return Enum.KeyCode[Config.PanicKey] end)
    if ok and input.KeyCode==key then
        Config.Hidden=not Config.Hidden; mouseDown=false; triggerHolding=false; pcall(mouse1release)
        if Config.Hidden then
            for _,obj in safePairs(ESPObjects) do hideAll(obj) end
            if FOVCircle then FOVCircle.Visible=false end; if SACircle then SACircle.Visible=false end
            if TriggerCircle then TriggerCircle.Visible=false end; if TargetLock then TargetLock.Visible=false end
            if ReloadLbl then ReloadLbl.Visible=false end
            for _,l in safePairs(CHLines) do l.Visible=false end; if CHDot then CHDot.Visible=false end
            Root.Visible=false
        else Root.Visible=menuVisible end
    end
end)

-- =====================
-- UNLOAD
-- =====================
_G.GhostUnload=function()
    if ghostUnloaded then return end
    ghostUnloaded=true
    Config.Hidden=true; Config.ESP=false; Config.Aimbot=false; Config.AimAssist=false
    Config.SilentAim=false; Config.TriggerBot=false; Config.AutoPistol=false; Config.NoRecoil=false
    Config.Crosshair=false; Config.FOVVisible=false; Config.ReloadPredictor=false; Config.AntiAimDetect=false
    mouseDown=false; triggerHolding=false; pcall(mouse1release)
    if aimAssistConn then pcall(function() aimAssistConn:Disconnect() end) end
    if autoPistolConn then pcall(function() autoPistolConn:Disconnect() end) end
    if recoilConn then pcall(function() recoilConn:Disconnect() end) end
    for _,c in safePairs(reloadConnections) do pcall(function() c:Disconnect() end) end
    reloadConnections={}
    for _,obj in safePairs(ESPObjects) do hideAll(obj) end
    for player,_ in safePairs(ESPObjects) do removeESP(player) end
    safeRemove(FOVCircle); safeRemove(SACircle); safeRemove(TriggerCircle)
    safeRemove(AimCircle); safeRemove(AimLine); safeRemove(TargetLock)
    safeRemove(topInfoShadow); safeRemove(topInfoText); safeRemove(targetInfoShadow); safeRemove(targetInfoText)
    safeRemove(surroundText); safeRemove(surroundShadow); safeRemove(blindText); safeRemove(blindShadow)
    safeRemove(antiAimIndicator); safeRemove(HoverTip); safeRemove(ReloadLbl)
    for _,l in ipairs(LOSLines) do safeRemove(l) end; LOSLines={}
    for _,c in ipairs(LOSCones) do safeRemove(c) end; LOSCones={}
    if MiniMapFrame then pcall(function() MiniMapFrame:Destroy() end); MiniMapFrame=nil end
    MiniMapDots={}; MiniMapWaypoints={}
    for _,l in safePairs(CHLines) do safeRemove(l) end; CHLines={}
    safeRemove(CHDot); CHDot=nil
    for _,a in safePairs(OffArrows) do safeRemove(a) end; OffArrows={}
    for _,p in safeIpairs(Players:GetPlayers()) do
        local ch=getChar(p); local h=ch and ch:FindFirstChild("GhostCham")
        if h then pcall(function() h:Destroy() end) end
    end
    pcall(function() menuBlur.Enabled=false; menuBlur:Destroy() end)
    if scannerWatchConn then pcall(function() scannerWatchConn:Disconnect() end); scannerWatchConn=nil end
    if Dock then pcall(function() Dock:Destroy() end); Dock=nil end
    pcall(function() ScreenGui:Destroy() end)
    trackedHumanoids={}; resolverData={}; arrowBuckets={}; ESPObjects={}; rigCache={}; antiAimData={}
    print("Ghost Menu v5.3 fully unloaded.")
end

applyGhostTheme(Config.ThemePreset)
applyGlassLook()
updateGhostBackground()
print("Ghost Menu v5.3 loaded. F1 = toggle | "..Config.PanicKey.." = panic | Universal rig resolver active")

Embed on website

To embed this project on your website, copy the following code and paste it into your website's HTML: