FE Guest 1337 animation script
Key RequiredUniversal

FE Guest 1337 animation script

Universal Scriptwasitpiercewasitpierce 7.4K views 11.3K executesUpdated May 14, 2026
Unrated
00
Lua Script•1785 lines (73223 chars)
--[[

███████╗ ██████╗ ██████╗ ███████╗ █████╗ ██╗  ██╗███████╗███╗   ██╗
██╔════╝██╔═══██╗██╔══██╗██╔════╝██╔══██╗██║ ██╔╝██╔════╝████╗  ██║
█████╗  ██║   ██║██████╔╝███████╗███████║█████╔╝ █████╗  ██╔██╗ ██║
██╔══╝  ██║   ██║██╔══██╗╚════██║██╔══██║██╔═██╗ ██╔══╝  ██║╚██╗██║
██║     ╚██████╔╝██║  ██║███████║██║  ██║██║  ██╗███████╗██║ ╚████║
╚═╝      ╚═════╝ ╚═╝  ╚═╝╚══════╝╚═╝  ╚═╝╚═╝  ╚═╝╚══════╝╚═╝  ╚═══╝

          GUEST 1337 FE ANIMATION SYSTEM  —  v5.1 (FIXED)

────────────────────────────────────────────────────────────
Created by  : sonicisnumber12
Uploaded by : wasitpierce
Platform    : Roblox / ScriptBlox
Fixes by    : Claude AI
────────────────────────────────────────────────────────────

FIXES IN THIS VERSION:
  • stale animation track references cleared on respawn
  • double RenderStepped loop on respawn fixed
  • headTrackRef / faceTrackRef reset on respawn
  • ca (current action) reset on respawn so locomotion unblocks
  • pcall guards on track AnimationId reads (prevents errors on destroyed tracks)
  • animateConns grows unbounded — now cleaned per-character
  • mobile UI: larger touch targets, bottom action row, responsive sizing

]]


local Players          = game:GetService("Players")
local RunService       = game:GetService("RunService")
local Debris           = game:GetService("Debris")
local UserInputService = game:GetService("UserInputService")
local ContentProvider  = game:GetService("ContentProvider")
local TweenService     = game:GetService("TweenService")
local Lighting         = game:GetService("Lighting")
local SoundService     = game:GetService("SoundService")

local lp        = Players.LocalPlayer
local playerGui = lp:WaitForChild("PlayerGui")
local camera    = workspace.CurrentCamera

if getgenv().forsakenthingyguys then return end
getgenv().forsakenthingyguys = true

local ins = Instance.new
local c3  = Color3.fromRGB
local ud2 = UDim2.new
local v2  = Vector2.new
local v3  = Vector3.new

-- ─────────────────────────────────────────────────────────
-- MOBILE DETECTION
-- ─────────────────────────────────────────────────────────

local isMobile = UserInputService.TouchEnabled and not UserInputService.KeyboardEnabled

local function mobScale(desktop, mobile)
	return isMobile and mobile or desktop
end

-- ─────────────────────────────────────────────────────────
-- TWEEN HELPER
-- ─────────────────────────────────────────────────────────

local function tw(obj, t, props)
	TweenService:Create(obj,
		TweenInfo.new(t, Enum.EasingStyle.Quint, Enum.EasingDirection.Out),
		props
	):Play()
end

-- ─────────────────────────────────────────────────────────
-- SOUND HELPER
-- ─────────────────────────────────────────────────────────

local function playSound(id, vol, pitch)
	local s = ins("Sound")
	s.SoundId        = "rbxassetid://" .. tostring(id)
	s.Volume         = vol or 0.4
	s.PlaybackSpeed  = pitch or 1
	s.Parent         = SoundService
	s:Play()
	Debris:AddItem(s, 3)
end

local SFX_CLICK = 6042053626
local SFX_OPEN  = 4590662766
local SFX_CLOSE = 4590662766

-- ─────────────────────────────────────────────────────────
-- ANIMATION IDs
-- ─────────────────────────────────────────────────────────

local P_ID         = "rbxassetid://80057445812313"
local B_ID         = "rbxassetid://105310177683245"
local I_ID         = "rbxassetid://98946450554814"
local W_ID         = "rbxassetid://119545916455209"
local R_ID         = "rbxassetid://108891884872744"
local SL_ID        = "rbxassetid://127436401745505"
local E1_ID        = "rbxassetid://76389296606994"
local C_ID         = "rbxassetid://126452924444711"
local H_ID         = "rbxassetid://74100161379681"
local BU_ID        = "rbxassetid://88570432473654"
local MM_ID        = "rbxassetid://79481824762169"
local S3_ID        = "rbxassetid://111993447655346"
local CD_ID        = "rbxassetid://102908521825429"
local UN_ID        = "rbxassetid://87033513223989"
local DP_ID        = "rbxassetid://100370504940450"
local WW_ID        = "rbxassetid://112767927202660"
local X_ID         = "rbxassetid://126801473720398"
local LW_ID        = "rbxassetid://108330858686045"
local S_ID         = "rbxassetid://112109141479594"
local RI_ID        = "rbxassetid://112757618232626"
local FACE_IDLE_ID = "rbxassetid://1"
local FACE_EMOTE_ID= "rbxassetid://1"

-- ─────────────────────────────────────────────────────────
-- ANIMATION LIBRARY
-- ─────────────────────────────────────────────────────────

local ANIM_LIBRARY = {
	idle = {
		{ name = "Default",              id = "rbxassetid://98946450554814"   },
		{ name = "Thinker",              id = "rbxassetid://134947274496123"  },
		{ name = "Salute",               id = "rbxassetid://94066777876508"   },
		{ name = "Tweaker",              id = "rbxassetid://78718423818649"   },
		{ name = "Shy",                  id = "rbxassetid://134305047738024"  },
		{ name = "C00LKIDD",             id = "rbxassetid://92946600983329"   },
		{ name = "Old R15",              id = "rbxassetid://119325894749018"  },
		{ name = "John Doe",             id = "rbxassetid://72526127498800"   },
		{ name = "Noli",                 id = "rbxassetid://113047257611459"  },
		{ name = "Injured",              id = "rbxassetid://90685886162807"   },
		{ name = "Two Time Crouching",   id = "rbxassetid://120943636070660"  },
		{ name = "Nosferatu",            id = "rbxassetid://129243618391641"  },
		{ name = "Mafioso",              id = "rbxassetid://120527173641109"  },
		{ name = "Slasher/Jason",        id = "rbxassetid://95612637518693"   },
		{ name = "1x1x1x1",             id = "rbxassetid://74574367987310"   },
		{ name = "Retro",                id = "rbxassetid://118461919095618"  },
		{ name = "Freddy",               id = "rbxassetid://105174189783870"  },
		{ name = "Rewrite",              id = "rbxassetid://135462734638074"  },
		{ name = "Rewrite 2",            id = "rbxassetid://93819532575638"   },
		{ name = "Float",                id = "rbxassetid://121210361161914"  },
		{ name = "Impatient",            id = "rbxassetid://130912259300899"  },
		{ name = "2011x",                id = "rbxassetid://80041404401815"   },
		{ name = "Tall",                 id = "rbxassetid://91348372558295"   },
	},
	walk = {
		{ name = "Default",    id = "rbxassetid://119545916455209"  },
		{ name = "Gangnam",    id = "rbxassetid://112342491908786"  },
		{ name = "Livesey",    id = "rbxassetid://126769395026240"  },
		{ name = "Zombie",     id = "rbxassetid://140703855480494"  },
		{ name = "C00LKIDD",   id = "rbxassetid://93206157668596"   },
		{ name = "Retro",      id = "rbxassetid://107806791584829"  },
		{ name = "1x1x1x1",   id = "rbxassetid://138379855143069"  },
		{ name = "Old R15",    id = "rbxassetid://94436206974808"   },
		{ name = "Nonchalant", id = "rbxassetid://95970874156673"   },
		{ name = "Chud",       id = "rbxassetid://85887415033585"   },
		{ name = "Float",      id = "rbxassetid://84988218571738"   },
		{ name = "Tall",       id = "rbxassetid://100019669366103"  },
		{ name = "John Doe",   id = "rbxassetid://138795419712161"  },
		{ name = "Injured",    id = "rbxassetid://101924559363550"  },
		{ name = "Noli",       id = "rbxassetid://90175656540190"   },
		{ name = "Styleized",  id = "rbxassetid://117819103741451"  },
	},
	run = {
		{ name = "Default",       id = "rbxassetid://108891884872744"  },
		{ name = "Scared",        id = "rbxassetid://99823446208760"   },
		{ name = "Bird",          id = "rbxassetid://115296225052210"  },
		{ name = "Old R15",       id = "rbxassetid://94436206974808"   },
		{ name = "Void Rush",     id = "rbxassetid://119380285634530"  },
		{ name = "Animal",        id = "rbxassetid://102269417125238"  },
		{ name = "Retro",         id = "rbxassetid://107806791584829"  },
		{ name = "1x1x1x1",      id = "rbxassetid://138379855143069"  },
		{ name = "C00LKiDD",      id = "rbxassetid://116229217913998"  },
		{ name = "Sonic",         id = "rbxassetid://104074120169874"  },
		{ name = "Chud",          id = "rbxassetid://85887415033585"   },
		{ name = "West",          id = "rbxassetid://121350640829746"  },
		{ name = "Classic Sonic", id = "rbxassetid://137135247245806"  },
		{ name = "Freddy",        id = "rbxassetid://70636286183373"   },
		{ name = "Rewrite",       id = "rbxassetid://134540907860256"  },
		{ name = "Float",         id = "rbxassetid://84988218571738"   },
		{ name = "Chase",         id = "rbxassetid://82260970223217"   },
		{ name = "Tall",          id = "rbxassetid://134010853417610"  },
		{ name = "John Doe",      id = "rbxassetid://94467125495426"   },
		{ name = "Injured",       id = "rbxassetid://101924559363550"  },
		{ name = "Noli",          id = "rbxassetid://90175656540190"   },
	},
	punch = {
		{ name = "Default",                 combo = { "rbxassetid://80057445812313"   } },
		{ name = "Poorly Made",             combo = { "rbxassetid://129358357219637"  } },
		{ name = "Quad Punch",              combo = { "rbxassetid://139643944264511"  } },
		{ name = "Shedlesky Slash [BUGGED]",combo = { "rbxassetid://127792295696012"  } },
		{ name = "Slash",                   combo = { "rbxassetid://95046715221370"   } },
		{ name = "Spindash",                combo = { "rbxassetid://119889021060156"  } },
		{ name = "Jumpscare",               combo = { "rbxassetid://82436820061312"   } },
		{ name = "2011x Kill",              combo = { "rbxassetid://119673703676481"  } },
	},
	block = {
		{ name = "Default",        id = "rbxassetid://105310177683245"  },
		{ name = "Sans",           id = "rbxassetid://76366756431367"   },
		{ name = "Corrupt Nature", id = "rbxassetid://70981913426043"   },
		{ name = "Laugh",          id = "rbxassetid://77737003924429"   },
	},
}

local activeAnims = { idle=1, walk=1, run=1, punch=1, block=1 }

local LIVE = { idle=I_ID, walk=W_ID, run=R_ID, punch=P_ID, block=B_ID }

local preloadIds = {
	P_ID, B_ID, I_ID, W_ID, R_ID,
	E1_ID, C_ID, H_ID, BU_ID, MM_ID,
	S3_ID, CD_ID, UN_ID, DP_ID,
	FACE_IDLE_ID, FACE_EMOTE_ID
}

-- ─────────────────────────────────────────────────────────
-- ANIMATION STATE  (all declared here so resets are easy)
-- ─────────────────────────────────────────────────────────

local active        = true
local isSL          = false
local ca            = nil   -- current action track
local st            = {}    -- locomotion tracks {idle,walk,run}
local ir            = false -- is running
local isConga       = false
local headTrackRef  = nil
local faceTrackRef  = nil
local renderConn    = nil   -- single RenderStepped connection (replaced on respawn)
local animateConns  = {}
local charAddedConn = nil

local punchComboIndex = 1
local lastPunchTime   = 0
local COMBO_WINDOW    = 1.2

-- ─────────────────────────────────────────────────────────
-- LOADING SCREEN
-- ─────────────────────────────────────────────────────────

local loadGui = ins("ScreenGui")
loadGui.Name           = "ForsakenLoading"
loadGui.DisplayOrder   = 20
loadGui.ResetOnSpawn   = false
loadGui.IgnoreGuiInset = true
loadGui.Parent         = playerGui

local loadBg = ins("Frame")
loadBg.Size            = ud2(1,0,1,0)
loadBg.BackgroundColor3= c3(6,6,10)
loadBg.BorderSizePixel = 0
loadBg.Parent          = loadGui

local loadCard = ins("Frame")
loadCard.Size            = ud2(0,320,0,110)
loadCard.Position        = ud2(0.5,0,0.5,0)
loadCard.AnchorPoint     = v2(0.5,0.5)
loadCard.BackgroundColor3= c3(14,12,22)
loadCard.BorderSizePixel = 0
loadCard.Parent          = loadGui

local lcc = ins("UICorner"); lcc.CornerRadius = UDim.new(0,14); lcc.Parent = loadCard
local lcs = ins("UIStroke"); lcs.Color = c3(80,55,130); lcs.Thickness = 1.2; lcs.Transparency = 0.35; lcs.Parent = loadCard

local loadTitle = ins("TextLabel")
loadTitle.Size = ud2(1,0,0,28); loadTitle.Position = ud2(0,0,0,16)
loadTitle.BackgroundTransparency = 1; loadTitle.Text = "FORSAKEN"
loadTitle.TextColor3 = c3(255,255,255); loadTitle.Font = Enum.Font.GothamBlack
loadTitle.TextSize = 20; loadTitle.Parent = loadCard

local loadSub = ins("TextLabel")
loadSub.Size = ud2(1,0,0,18); loadSub.Position = ud2(0,0,0,42)
loadSub.BackgroundTransparency = 1; loadSub.Text = "LOADING ASSETS..."
loadSub.TextColor3 = c3(140,100,220); loadSub.Font = Enum.Font.GothamBold
loadSub.TextSize = 12; loadSub.Parent = loadCard

local barTrack = ins("Frame")
barTrack.Size = ud2(0,260,0,6); barTrack.Position = ud2(0.5,0,0,68)
barTrack.AnchorPoint = v2(0.5,0); barTrack.BackgroundColor3 = c3(30,26,44)
barTrack.BorderSizePixel = 0; barTrack.Parent = loadCard
local bttc = ins("UICorner"); bttc.CornerRadius = UDim.new(1,0); bttc.Parent = barTrack

local barFill = ins("Frame")
barFill.Size = ud2(0,0,1,0); barFill.BackgroundColor3 = c3(120,70,210)
barFill.BorderSizePixel = 0; barFill.Parent = barTrack
local bfc = ins("UICorner"); bfc.CornerRadius = UDim.new(1,0); bfc.Parent = barFill

local loadPct = ins("TextLabel")
loadPct.Size = ud2(1,0,0,16); loadPct.Position = ud2(0,0,0,82)
loadPct.BackgroundTransparency = 1; loadPct.Text = "0%"
loadPct.TextColor3 = c3(90,75,115); loadPct.Font = Enum.Font.Gotham
loadPct.TextSize = 11; loadPct.Parent = loadCard

local animObjects = {}
for _, id in ipairs(preloadIds) do
	local a = ins("Animation"); a.AnimationId = id; table.insert(animObjects, a)
end
for i, animObj in ipairs(animObjects) do
	ContentProvider:PreloadAsync({animObj})
	local progress = i / #animObjects
	TweenService:Create(barFill, TweenInfo.new(0.1, Enum.EasingStyle.Quad), {Size = ud2(progress,0,1,0)}):Play()
	loadSub.Text = "LOADING ASSETS  " .. math.floor(progress*100) .. "%"
	loadPct.Text = math.floor(progress*100) .. "%"
end

task.wait(0.2)
tw(loadCard, 0.35, {BackgroundTransparency=1})
tw(loadTitle, 0.25, {TextTransparency=1}); tw(loadSub, 0.25, {TextTransparency=1})
tw(loadPct, 0.25, {TextTransparency=1}); tw(barTrack, 0.25, {BackgroundTransparency=1})
tw(barFill, 0.25, {BackgroundTransparency=1}); tw(loadBg, 0.4, {BackgroundTransparency=1})
task.wait(0.45)
loadGui:Destroy()

-- ─────────────────────────────────────────────────────────
-- MAIN GUI ROOT
-- ─────────────────────────────────────────────────────────

local gui = ins("ScreenGui")
gui.Name = "ForsakenHUD"; gui.ResetOnSpawn = false
gui.IgnoreGuiInset = true; gui.ZIndexBehavior = Enum.ZIndexBehavior.Sibling
gui.Parent = playerGui

local blur = ins("BlurEffect"); blur.Size = 0; blur.Parent = Lighting

-- ─────────────────────────────────────────────────────────
-- HUD BARS  (top-left on desktop, top-center on mobile)
-- ─────────────────────────────────────────────────────────

local hudW  = mobScale(420, math.min(workspace.CurrentCamera.ViewportSize.X - 20, 380))
local hudH  = mobScale(88, 76)

local holder = ins("Frame")
holder.AnchorPoint = v2(0,1)
holder.Position    = ud2(0,-600,1,-20)  -- starts off-screen, animated in later
holder.Size        = ud2(0,hudW,0,hudH)
holder.BackgroundTransparency = 1
holder.Visible     = true
holder.Parent      = gui

if isMobile then
	holder.AnchorPoint = v2(0.5,1)
	holder.Position    = ud2(0.5,0,0,- 600)
end

local bg = ins("Frame")
bg.Size            = ud2(1,0,1,0)
bg.BackgroundColor3= c3(8,6,14)
bg.BackgroundTransparency = 0.25
bg.BorderSizePixel = 0
bg.Parent          = holder

local bgInner = ins("Frame")
bgInner.Size = ud2(1,-4,1,-4); bgInner.Position = ud2(0,2,0,2)
bgInner.BackgroundColor3 = c3(12,10,20); bgInner.BackgroundTransparency = 0.35
bgInner.BorderSizePixel = 0; bgInner.Parent = holder

ins("UICorner", bg).CornerRadius        = UDim.new(0,10)
ins("UICorner", bgInner).CornerRadius   = UDim.new(0,10)

local portraitSize = mobScale(72, 58)
local portraitHolder = ins("Frame")
portraitHolder.Size            = ud2(0,portraitSize,0,portraitSize)
portraitHolder.Position        = ud2(0,8,0.5,-portraitSize/2)
portraitHolder.BackgroundColor3= c3(15,12,25)
portraitHolder.BorderSizePixel = 0
portraitHolder.Parent          = holder

ins("UICorner", portraitHolder).CornerRadius = UDim.new(1,0)
local ring = ins("UIStroke"); ring.Color = c3(100,65,190); ring.Thickness = 2.5; ring.Parent = portraitHolder

local avatar = ins("ImageLabel")
avatar.BackgroundTransparency = 1; avatar.Size = ud2(1,-6,1,-6); avatar.Position = ud2(0,3,0,3)
avatar.Parent = portraitHolder
ins("UICorner", avatar).CornerRadius = UDim.new(1,0)

local ok, thumb = pcall(function()
	return Players:GetUserThumbnailAsync(lp.UserId, Enum.ThumbnailType.HeadShot, Enum.ThumbnailSize.Size420x420)
end)
avatar.Image = ok and thumb or ""

local nameLabel = ins("TextLabel")
nameLabel.BackgroundTransparency = 1
nameLabel.Position  = ud2(0,2,0,portraitSize + 4)
nameLabel.Size      = ud2(0,portraitSize+8,0,12)
nameLabel.Font      = Enum.Font.GothamBold
nameLabel.Text      = lp.DisplayName
nameLabel.TextColor3= c3(150,120,210)
nameLabel.TextSize  = mobScale(9, 8)
nameLabel.TextXAlignment = Enum.TextXAlignment.Center
nameLabel.Parent    = holder

local barXOffset = portraitSize + 22
local barW       = mobScale(230, hudW - barXOffset - 80)

local function makeForsakenBar(yOffset, iconText, iconColor, fillColor, lowColor)
	local icon = ins("TextLabel")
	icon.BackgroundTransparency = 1
	icon.Position   = ud2(0, portraitSize + 12, 0, yOffset)
	icon.Size       = ud2(0, 22, 0, 22)
	icon.Text       = iconText
	icon.TextScaled = true
	icon.Font       = Enum.Font.GothamBold
	icon.TextColor3 = iconColor
	icon.Parent     = holder

	local track = ins("Frame")
	track.Size            = ud2(0, barW, 0, mobScale(14,12))
	track.Position        = ud2(0, barXOffset + 22, 0, yOffset + 4)
	track.BackgroundColor3= c3(35,28,50)
	track.BorderSizePixel = 0
	track.Parent          = holder
	ins("UICorner", track).CornerRadius = UDim.new(0,2)

	local fill = ins("Frame")
	fill.Size            = ud2(1,0,1,0)
	fill.BackgroundColor3= fillColor
	fill.BorderSizePixel = 0
	fill.Parent          = track
	ins("UICorner", fill).CornerRadius = UDim.new(0,2)

	local shine = ins("Frame")
	shine.Size = ud2(1,0,0.35,0); shine.BackgroundColor3 = c3(255,255,255)
	shine.BackgroundTransparency = 0.82; shine.BorderSizePixel = 0
	shine.ZIndex = 2; shine.Parent = fill
	ins("UICorner", shine).CornerRadius = UDim.new(0,2)

	local valLabel = ins("TextLabel")
	valLabel.BackgroundTransparency = 1
	valLabel.Position   = ud2(1, 6, 0.5, -8)
	valLabel.Size       = ud2(0, 58, 0, 16)
	valLabel.Font       = Enum.Font.GothamBold
	valLabel.TextSize   = mobScale(13,11)
	valLabel.TextColor3 = c3(220,210,240)
	valLabel.Text       = "100/100"
	valLabel.TextXAlignment = Enum.TextXAlignment.Left
	valLabel.Parent     = track

	return fill, valLabel, fillColor, lowColor
end

local barY1 = mobScale(14, 10)
local barY2 = mobScale(44, 38)
local healthFill,  healthText,  hpNorm,  hpLow  = makeForsakenBar(barY1, "❤", c3(220,60,80),  c3(200,50,70),  c3(255,30,30))
local staminaFill, staminaText, stNorm,  stLow  = makeForsakenBar(barY2, "⚡", c3(200,200,200),c3(210,210,210),c3(255,80,80))

-- ─────────────────────────────────────────────────────────
-- WATERMARK
-- ─────────────────────────────────────────────────────────

local watermark = ins("TextLabel")
watermark.BackgroundTransparency = 1
watermark.Size     = ud2(1,0,0,18)
watermark.Position = ud2(0,0,1,-18)
watermark.Font     = Enum.Font.GothamBold
watermark.Text     = "OWNED BY SONICISNUMBER12  •  UI MADE BY FORSAKEN TEAM"
watermark.TextColor3 = c3(120,100,160)
watermark.TextSize = 10
watermark.TextXAlignment = Enum.TextXAlignment.Center
watermark.Parent   = gui

-- ─────────────────────────────────────────────────────────
-- ACTION GUI
-- ─────────────────────────────────────────────────────────

local sg = ins("ScreenGui")
sg.Name = "ActionGui"; sg.ResetOnSpawn = false; sg.Parent = playerGui

-- ─── button size helpers ────────────────────────────────

-- On mobile we use a full-width bottom action bar with large tap targets
-- On desktop we keep the original compact corner layout

local BTN_MAIN  = mobScale(ud2(0,130,0,130), ud2(0,72,0,72))  -- sprint (desktop big, mobile smaller)
local BTN_SMALL = mobScale(ud2(0,66,0,66),   ud2(0,64,0,64))

local function hint(p, t)
	if isMobile then return end  -- no hotkey hints on mobile
	local h = ins("TextLabel")
	h.Size = ud2(1,0,0,16); h.Position = ud2(0,0,0,-20)
	h.BackgroundTransparency = 1; h.Text = "[" .. t .. "]"
	h.TextColor3 = c3(200,185,230); h.TextStrokeTransparency = 0.6
	h.Font = Enum.Font.GothamBold; h.TextSize = 13; h.Parent = p
end

local function makeRoundBtn(parent, zIdx)
	local b = ins("Frame")
	b.BackgroundColor3 = c3(14,12,22); b.BackgroundTransparency = 0.15
	b.BorderSizePixel  = 0; b.ZIndex = zIdx or 1; b.Parent = parent
	ins("UICorner", b).CornerRadius = UDim.new(0, mobScale(10, 12))
	local s = ins("UIStroke"); s.Color = c3(80,55,130); s.Thickness = 1; s.Transparency = 0.4; s.Parent = b
	return b, s
end

-- ─── MOBILE LAYOUT: bottom bar ──────────────────────────

local mobileBar       -- container only created on mobile
local mobileSprint, mobileBlock, mobilePunch, mobileShiftlock, mobileEmote, mobileAnims

if isMobile then
	-- Semi-transparent bar pinned to the bottom of the screen
	mobileBar = ins("Frame")
	mobileBar.AnchorPoint     = v2(0.5, 1)
	mobileBar.Position        = ud2(0.5, 0, 1, -8)
	mobileBar.Size            = ud2(1, -16, 0, 86)
	mobileBar.BackgroundColor3= c3(8, 6, 14)
	mobileBar.BackgroundTransparency = 0.18
	mobileBar.BorderSizePixel = 0
	mobileBar.ZIndex          = 5
	mobileBar.Parent          = sg
	ins("UICorner", mobileBar).CornerRadius = UDim.new(0, 16)
	local mbs = ins("UIStroke"); mbs.Color = c3(70,50,110); mbs.Thickness = 1; mbs.Transparency = 0.35; mbs.Parent = mobileBar

	-- Horizontal list of equally-spaced buttons
	local barList = ins("UIListLayout")
	barList.FillDirection  = Enum.FillDirection.Horizontal
	barList.HorizontalAlignment = Enum.HorizontalAlignment.Center
	barList.VerticalAlignment   = Enum.VerticalAlignment.Center
	barList.Padding        = UDim.new(0, 8)
	barList.Parent         = mobileBar

	local barPad = ins("UIPadding")
	barPad.PaddingLeft = UDim.new(0,10); barPad.PaddingRight = UDim.new(0,10)
	barPad.Parent = mobileBar

	local function mobileActionBtn(label, color, accent)
		local btn = ins("TextButton")
		btn.Size              = ud2(0, 70, 0, 64)
		btn.BackgroundColor3  = color or c3(14,12,24)
		btn.BackgroundTransparency = 0.12
		btn.Text              = label
		btn.TextColor3        = accent or c3(210, 200, 240)
		btn.Font              = Enum.Font.GothamBlack
		btn.TextSize          = 12
		btn.BorderSizePixel   = 0
		btn.AutoButtonColor   = false
		btn.ZIndex            = 6
		btn.Parent            = mobileBar
		ins("UICorner", btn).CornerRadius = UDim.new(0, 12)
		local s = ins("UIStroke"); s.Color = accent or c3(80,55,130); s.Thickness = 1; s.Transparency = 0.4; s.Parent = btn
		btn.MouseButton1Down:Connect(function() tw(btn,0.08,{BackgroundTransparency=0}) end)
		btn.MouseButton1Up:Connect(function()   tw(btn,0.12,{BackgroundTransparency=0.12}) end)
		return btn
	end

	mobileSprint    = mobileActionBtn("🏃 RUN",    c3(14,22,14), c3(100,200,80))
	mobilePunch     = mobileActionBtn("👊 HIT",    c3(22,12,12), c3(210,70,70))
	mobileBlock     = mobileActionBtn("🛡 BLOCK",  c3(12,14,22), c3(70,110,220))
	mobileEmote     = mobileActionBtn("EMOTE",     c3(14,12,22), c3(180,130,230))
	mobileShiftlock = mobileActionBtn("🔒 SL",     c3(12,16,22), c3(100,180,255))
	mobileAnims     = mobileActionBtn("ANIMS",     c3(12,14,20), c3(140,200,255))
end

-- ─── DESKTOP LAYOUT: original corner buttons ────────────

local rb, slb, pb, bb  -- declared regardless; only used on desktop

if not isMobile then
	local function actionBtn(n, id, pos, size, hotkey)
		local b = ins("ImageButton")
		b.Name = n; b.Size = size; b.Position = pos
		b.Image = (typeof(id)=="string" and id:find("rbxassetid") and id) or "rbxassetid://"..tostring(id)
		b.BackgroundColor3 = c3(14,12,22); b.BackgroundTransparency = 0.15
		b.BorderSizePixel = 0; b.ImageColor3 = c3(255,255,255); b.Parent = sg
		ins("UICorner", b).CornerRadius = UDim.new(0,10)
		local bs = ins("UIStroke"); bs.Color = c3(80,55,130); bs.Thickness = 1; bs.Transparency = 0.4; bs.Parent = b
		b.MouseEnter:Connect(function()
			tw(b, 0.12, {BackgroundTransparency=0})
			playSound(SFX_CLICK, 0.12, 1.4)
		end)
		b.MouseLeave:Connect(function() tw(b, 0.12, {BackgroundTransparency=0.15}) end)
		b.MouseButton1Click:Connect(function() playSound(SFX_CLICK, 0.4, 1) end)
		if hotkey then hint(b, hotkey) end
		return b
	end

	rb  = actionBtn("Run",       89719285639088,  ud2(1,-20,1,-20),   BTN_MAIN,  "SHIFT"); rb.AnchorPoint  = v2(1,1)
	slb = actionBtn("ShiftLock", SL_ID,           ud2(1,-165,1,-20),  BTN_SMALL, "CTRL"); slb.AnchorPoint = v2(1,1)
	pb  = actionBtn("Punch",     113322091360332, ud2(1,-20,1,-165),  BTN_SMALL, "E");   pb.AnchorPoint  = v2(1,1)
	bb  = actionBtn("Block",     81303931345144,  ud2(1,-96,1,-165),  BTN_SMALL, "Q");   bb.AnchorPoint  = v2(1,1)
end

-- ─────────────────────────────────────────────────────────
-- DRAGGABLE EMOTE BUTTON  (desktop only)
-- ─────────────────────────────────────────────────────────

local eb
if not isMobile then
	eb = ins("TextButton")
	eb.Name = "EmoteToggle"; eb.Size = BTN_SMALL
	eb.Position = ud2(0, 20, 1, -165); eb.AnchorPoint = v2(0,1)
	eb.BackgroundColor3 = c3(14,12,22); eb.BackgroundTransparency = 0.15
	eb.Text = "EMOTE"; eb.TextColor3 = c3(200,185,230)
	eb.Font = Enum.Font.GothamBold; eb.TextSize = 14
	eb.BorderSizePixel = 0; eb.AutoButtonColor = false; eb.Parent = sg
	ins("UICorner", eb).CornerRadius = UDim.new(0,10)
	local ebs = ins("UIStroke"); ebs.Color = c3(80,55,130); ebs.Thickness = 1; ebs.Transparency = 0.4; ebs.Parent = eb

	local dragHint = ins("TextLabel")
	dragHint.BackgroundTransparency = 1; dragHint.Size = ud2(1,0,0,12)
	dragHint.Position = ud2(0,0,1,2); dragHint.Text = ":: drag ::"
	dragHint.TextColor3 = c3(100,80,140); dragHint.Font = Enum.Font.Gotham
	dragHint.TextSize = 10; dragHint.Parent = eb

	eb.MouseEnter:Connect(function()
		tw(eb, 0.12, {BackgroundTransparency=0})
		playSound(SFX_CLICK, 0.12, 1.4)
	end)
	eb.MouseLeave:Connect(function() tw(eb, 0.12, {BackgroundTransparency=0.15}) end)

	-- Drag logic
	local draggingEmote = false
	local dragStartPos
	local dragMoved = false

	eb.InputBegan:Connect(function(input)
		if input.UserInputType == Enum.UserInputType.MouseButton1 or
		   input.UserInputType == Enum.UserInputType.Touch then
			draggingEmote = true
			dragMoved = false
			local absPos = eb.AbsolutePosition
			local absSize = sg.AbsoluteSize
			dragStartPos = {
				x  = absPos.X / absSize.X,
				y  = absPos.Y / absSize.Y,
				mx = input.Position.X,
				my = input.Position.Y
			}
		end
	end)

	UserInputService.InputChanged:Connect(function(input)
		if not draggingEmote then return end
		if input.UserInputType == Enum.UserInputType.MouseMovement or
		   input.UserInputType == Enum.UserInputType.Touch then
			local dx = input.Position.X - dragStartPos.mx
			local dy = input.Position.Y - dragStartPos.my
			if math.sqrt(dx*dx + dy*dy) > 6 then
				dragMoved = true
				local screenSize = sg.AbsoluteSize
				local newX = math.clamp(dragStartPos.x + dx/screenSize.X, 0, 1-(eb.AbsoluteSize.X/screenSize.X))
				local newY = math.clamp(dragStartPos.y + dy/screenSize.Y, 0, 1-(eb.AbsoluteSize.Y/screenSize.Y))
				eb.AnchorPoint = v2(0,0)
				eb.Position = ud2(newX,0,newY,0)
			end
		end
	end)

	UserInputService.InputEnded:Connect(function(input)
		if input.UserInputType == Enum.UserInputType.MouseButton1 or
		   input.UserInputType == Enum.UserInputType.Touch then
			draggingEmote = false
		end
	end)

	eb.MouseButton1Click:Connect(function()
		if not dragMoved then
			ef.Visible = not ef.Visible
			if ef.Visible then animPanel.Visible = false end
			playSound(ef.Visible and SFX_OPEN or SFX_CLOSE, 0.3, 1)
		end
	end)
end

-- ─────────────────────────────────────────────────────────
-- EMOTE MENU
-- ─────────────────────────────────────────────────────────

-- position & size differ between mobile and desktop
local emoteMenuW = mobScale(210, math.min(workspace.CurrentCamera.ViewportSize.X - 20, 260))
local emoteMenuH = mobScale(270, 340)

local ef = ins("Frame")
ef.Name = "EmoteMenu"
ef.Size = ud2(0, emoteMenuW, 0, emoteMenuH)
ef.BackgroundColor3 = c3(10,9,16); ef.BackgroundTransparency = 0.08
ef.BorderSizePixel = 0; ef.Visible = false; ef.Parent = sg; ef.ZIndex = 10

ins("UICorner", ef).CornerRadius = UDim.new(0,12)
local efs = ins("UIStroke"); efs.Color = c3(80,55,130); efs.Thickness = 1.2; efs.Transparency = 0.4; efs.Parent = ef

if isMobile then
	-- centred just above the mobile bar
	ef.AnchorPoint = v2(0.5, 1)
	ef.Position    = ud2(0.5, 0, 1, -102)
else
	ef.Position    = ud2(0, 20, 1, -242); ef.AnchorPoint = v2(0,1)
end

local efTitle = ins("TextLabel")
efTitle.Size = ud2(1,0,0,28); efTitle.Position = ud2(0,0,0,4)
efTitle.BackgroundTransparency = 1; efTitle.Text = "EMOTES"
efTitle.TextColor3 = c3(160,130,220); efTitle.Font = Enum.Font.GothamBlack
efTitle.TextSize = 13; efTitle.ZIndex = 11; efTitle.Parent = ef

local sf = ins("ScrollingFrame")
sf.Size = ud2(1,-12,1,-38); sf.Position = ud2(0,6,0,32)
sf.BackgroundTransparency = 1; sf.CanvasSize = ud2(0,0,0,0)
sf.AutomaticCanvasSize = Enum.AutomaticSize.Y; sf.ScrollBarThickness = 3
sf.ScrollBarImageColor3 = c3(120,70,210); sf.ZIndex = 11; sf.Parent = ef
local uiList = ins("UIListLayout"); uiList.Padding = UDim.new(0,4); uiList.Parent = sf

-- Stop Emote button
local stopE = ins("TextButton")
stopE.Name = "StopEmote"
stopE.Size = ud2(0,160,0,mobScale(36,44))
stopE.BackgroundColor3 = c3(160,30,60); stopE.BackgroundTransparency = 0.1
stopE.Text = "STOP EMOTE"; stopE.TextColor3 = c3(255,255,255)
stopE.Font = Enum.Font.GothamBold; stopE.TextSize = mobScale(13,14)
stopE.BorderSizePixel = 0; stopE.AutoButtonColor = false
stopE.Visible = false; stopE.ZIndex = 10; stopE.Parent = sg

ins("UICorner", stopE).CornerRadius = UDim.new(0,8)
stopE.MouseEnter:Connect(function() tw(stopE,0.12,{BackgroundColor3=c3(200,40,70)}); playSound(SFX_CLICK,0.1,1.3) end)
stopE.MouseLeave:Connect(function() tw(stopE,0.12,{BackgroundColor3=c3(160,30,60)}) end)
stopE.MouseButton1Click:Connect(function() playSound(SFX_CLICK,0.4,0.9) end)

if isMobile then
	stopE.AnchorPoint = v2(0.5,1)
	stopE.Position    = ud2(0.5,0,1,-98)
	stopE.Size        = ud2(0,180,0,44)
else
	stopE.AnchorPoint = v2(0,1)
	stopE.Position    = ud2(0,20,1,-110)
end

-- ─────────────────────────────────────────────────────────
-- ANIMATION CUSTOMIZER BUTTON  (desktop corner / mobile bar)
-- ─────────────────────────────────────────────────────────

local animBtn
if not isMobile then
	animBtn = ins("TextButton")
	animBtn.Name = "AnimCustomizer"; animBtn.Size = BTN_SMALL
	animBtn.Position = ud2(0,96,1,-165); animBtn.AnchorPoint = v2(0,1)
	animBtn.BackgroundColor3 = c3(14,12,22); animBtn.BackgroundTransparency = 0.15
	animBtn.Text = "ANIMS"; animBtn.TextColor3 = c3(180,220,255)
	animBtn.Font = Enum.Font.GothamBold; animBtn.TextSize = 13
	animBtn.BorderSizePixel = 0; animBtn.AutoButtonColor = false; animBtn.Parent = sg
	ins("UICorner", animBtn).CornerRadius = UDim.new(0,10)
	local animBtnS = ins("UIStroke"); animBtnS.Color = c3(55,100,160); animBtnS.Thickness = 1; animBtnS.Transparency = 0.4; animBtnS.Parent = animBtn
	animBtn.MouseEnter:Connect(function() tw(animBtn,0.12,{BackgroundTransparency=0}); playSound(SFX_CLICK,0.12,1.4) end)
	animBtn.MouseLeave:Connect(function() tw(animBtn,0.12,{BackgroundTransparency=0.15}) end)
	animBtn.MouseButton1Click:Connect(function() playSound(SFX_CLICK,0.4,1) end)
end

-- ─────────────────────────────────────────────────────────
-- ANIMATION CUSTOMIZER PANEL
-- ─────────────────────────────────────────────────────────

local panelW = mobScale(380, math.min(workspace.CurrentCamera.ViewportSize.X - 20, 340))
local panelH = mobScale(440, math.min(workspace.CurrentCamera.ViewportSize.Y - 120, 420))

local animPanel = ins("Frame")
animPanel.Name = "AnimPanel"
animPanel.Size = ud2(0,panelW,0,panelH)
animPanel.Position = ud2(0.5,0,0.5,0); animPanel.AnchorPoint = v2(0.5,0.5)
animPanel.BackgroundColor3 = c3(10,9,18); animPanel.BackgroundTransparency = 0.04
animPanel.BorderSizePixel = 0; animPanel.Visible = false; animPanel.ZIndex = 60
animPanel.Parent = sg

ins("UICorner", animPanel).CornerRadius = UDim.new(0,14)
local aps = ins("UIStroke"); aps.Color = c3(55,100,170); aps.Thickness = 1.5; aps.Transparency = 0.3; aps.Parent = animPanel

local apTitleBar = ins("Frame")
apTitleBar.Size = ud2(1,0,0,42); apTitleBar.BackgroundColor3 = c3(14,12,26)
apTitleBar.BorderSizePixel = 0; apTitleBar.ZIndex = 61; apTitleBar.Parent = animPanel
ins("UICorner", apTitleBar).CornerRadius = UDim.new(0,14)

local apTitleBarFill = ins("Frame")
apTitleBarFill.Size = ud2(1,0,0,14); apTitleBarFill.Position = ud2(0,0,1,-14)
apTitleBarFill.BackgroundColor3 = c3(14,12,26); apTitleBarFill.BorderSizePixel = 0
apTitleBarFill.ZIndex = 61; apTitleBarFill.Parent = apTitleBar

local apAccent = ins("Frame")
apAccent.Size = ud2(0,340,0,2); apAccent.Position = ud2(0.5,0,0,42)
apAccent.AnchorPoint = v2(0.5,0); apAccent.BackgroundColor3 = c3(60,120,220)
apAccent.BorderSizePixel = 0; apAccent.ZIndex = 62; apAccent.Parent = animPanel
ins("UICorner", apAccent).CornerRadius = UDim.new(1,0)

local apTitle = ins("TextLabel")
apTitle.BackgroundTransparency = 1; apTitle.Size = ud2(1,-50,1,0)
apTitle.Position = ud2(0,16,0,0); apTitle.Text = "ANIMATION CUSTOMIZER"
apTitle.TextColor3 = c3(180,220,255); apTitle.Font = Enum.Font.GothamBlack
apTitle.TextSize = mobScale(15,13); apTitle.TextXAlignment = Enum.TextXAlignment.Left
apTitle.ZIndex = 62; apTitle.Parent = apTitleBar

local apCloseBtn = ins("TextButton")
apCloseBtn.Size = ud2(0,mobScale(26,34),0,mobScale(26,34))
apCloseBtn.Position = ud2(1,-40,0.5,-(mobScale(26,34)/2))
apCloseBtn.BackgroundColor3 = c3(160,30,60); apCloseBtn.BackgroundTransparency = 0.15
apCloseBtn.Text = "✕"; apCloseBtn.TextColor3 = c3(255,255,255)
apCloseBtn.Font = Enum.Font.GothamBold; apCloseBtn.TextSize = mobScale(13,15)
apCloseBtn.BorderSizePixel = 0; apCloseBtn.AutoButtonColor = false
apCloseBtn.ZIndex = 62; apCloseBtn.Parent = apTitleBar
ins("UICorner", apCloseBtn).CornerRadius = UDim.new(0,6)
apCloseBtn.MouseEnter:Connect(function() tw(apCloseBtn,0.1,{BackgroundColor3=c3(200,40,70)}); playSound(SFX_CLICK,0.1,1.3) end)
apCloseBtn.MouseLeave:Connect(function() tw(apCloseBtn,0.1,{BackgroundColor3=c3(160,30,60)}) end)
apCloseBtn.MouseButton1Click:Connect(function()
	playSound(SFX_CLOSE,0.3,1.1); animPanel.Visible = false
end)

-- Tab bar
local TAB_NAMES  = {"IDLE","WALK","RUN","PUNCH","BLOCK"}
local TAB_SLOTS  = {"idle","walk","run","punch","block"}
local TAB_COLORS = {
	idle  = c3(120,70,210), walk  = c3(60,180,120),
	run   = c3(220,140,40), punch = c3(210,60,80), block = c3(60,130,220),
}

local tabHolder = ins("Frame")
tabHolder.Size = ud2(1,-20,0,32); tabHolder.Position = ud2(0,10,0,50)
tabHolder.BackgroundTransparency = 1; tabHolder.ZIndex = 61; tabHolder.Parent = animPanel

local tabLayout = ins("UIListLayout")
tabLayout.FillDirection = Enum.FillDirection.Horizontal
tabLayout.Padding = UDim.new(0,3); tabLayout.Parent = tabHolder

local currentTab  = "idle"
local tabBtns     = {}
local slotFrames  = {}

local function switchTab(slot)
	currentTab = slot
	for s, f in pairs(slotFrames) do f.Visible = (s == slot) end
	for s, btn in pairs(tabBtns) do
		local col = TAB_COLORS[s]
		if s == slot then
			tw(btn,0.15,{BackgroundColor3=col,BackgroundTransparency=0.1})
			btn.TextColor3 = c3(255,255,255)
		else
			tw(btn,0.15,{BackgroundColor3=c3(20,18,32),BackgroundTransparency=0.2})
			btn.TextColor3 = c3(140,130,170)
		end
	end
end

-- calculate tab button width dynamically
local tabBtnW = math.floor((panelW - 20 - 4*4) / 5)

for i, name in ipairs(TAB_NAMES) do
	local slot = TAB_SLOTS[i]
	local tb = ins("TextButton")
	tb.Size = ud2(0, tabBtnW, 0, 30)
	tb.BackgroundColor3 = c3(20,18,32); tb.BackgroundTransparency = 0.2
	tb.Text = name; tb.TextColor3 = c3(140,130,170)
	tb.Font = Enum.Font.GothamBold; tb.TextSize = mobScale(10,9)
	tb.BorderSizePixel = 0; tb.AutoButtonColor = false; tb.ZIndex = 62; tb.Parent = tabHolder
	ins("UICorner", tb).CornerRadius = UDim.new(0,6)
	tb.MouseEnter:Connect(function() playSound(SFX_CLICK,0.08,1.5) end)
	tb.MouseButton1Click:Connect(function()
		playSound(SFX_CLICK,0.3,1.1); switchTab(slot)
	end)
	tabBtns[slot] = tb
end

local contentArea = ins("Frame")
contentArea.Size = ud2(1,-20,1,-100); contentArea.Position = ud2(0,10,0,92)
contentArea.BackgroundTransparency = 1; contentArea.ZIndex = 61; contentArea.Parent = animPanel

local function makeOptionBtn(parent, label, subLabel, zIdx)
	local rowH = mobScale(44, 50)
	local row = ins("TextButton")
	row.Size = ud2(1,0,0,rowH)
	row.BackgroundColor3 = c3(18,16,30); row.BackgroundTransparency = 0.1
	row.Text = ""; row.BorderSizePixel = 0; row.AutoButtonColor = false
	row.ZIndex = zIdx; row.Parent = parent
	ins("UICorner", row).CornerRadius = UDim.new(0,8)
	local rs = ins("UIStroke"); rs.Color = c3(50,45,75); rs.Thickness = 1; rs.Transparency = 0.5; rs.Parent = row

	local nameL = ins("TextLabel")
	nameL.BackgroundTransparency = 1; nameL.Size = ud2(1,-50,0,22)
	nameL.Position = ud2(0,12,0,mobScale(5,7)); nameL.Text = label
	nameL.TextColor3 = c3(210,200,240); nameL.Font = Enum.Font.GothamBold
	nameL.TextSize = mobScale(13,14); nameL.TextXAlignment = Enum.TextXAlignment.Left
	nameL.ZIndex = zIdx+1; nameL.Parent = row

	if subLabel and subLabel ~= "" then
		local subL = ins("TextLabel")
		subL.BackgroundTransparency = 1; subL.Size = ud2(1,-50,0,16)
		subL.Position = ud2(0,12,0,mobScale(24,28)); subL.Text = subLabel
		subL.TextColor3 = c3(100,90,130); subL.Font = Enum.Font.Gotham
		subL.TextSize = mobScale(10,11); subL.TextXAlignment = Enum.TextXAlignment.Left
		subL.ZIndex = zIdx+1; subL.Parent = row
	end

	local check = ins("TextLabel")
	check.BackgroundTransparency = 1; check.Size = ud2(0,30,1,0)
	check.Position = ud2(1,-36,0,0); check.Text = ""
	check.TextColor3 = c3(100,200,120); check.Font = Enum.Font.GothamBold
	check.TextSize = 16; check.ZIndex = zIdx+1; check.Parent = row

	row.MouseEnter:Connect(function() tw(row,0.1,{BackgroundColor3=c3(28,24,44)}); playSound(SFX_CLICK,0.08,1.4) end)
	row.MouseLeave:Connect(function() tw(row,0.1,{BackgroundColor3=c3(18,16,30)}) end)
	return row, check, rs
end

local activeLabels = {}

local function buildSlotFrame(slot, zOff)
	local frame = ins("ScrollingFrame")
	frame.Size = ud2(1,0,1,0); frame.BackgroundTransparency = 1
	frame.CanvasSize = ud2(0,0,0,0); frame.AutomaticCanvasSize = Enum.AutomaticSize.Y
	frame.ScrollBarThickness = 3; frame.ScrollBarImageColor3 = c3(60,120,220)
	frame.ZIndex = 61+zOff; frame.Visible = false; frame.Parent = contentArea

	local fLayout = ins("UIListLayout"); fLayout.Padding = UDim.new(0,5); fLayout.Parent = frame

	local activeLbl = ins("TextLabel")
	activeLbl.BackgroundTransparency = 1; activeLbl.Size = ud2(1,0,0,20)
	activeLbl.Text = "Active: " .. ANIM_LIBRARY[slot][activeAnims[slot]].name
	activeLbl.TextColor3 = TAB_COLORS[slot]; activeLbl.Font = Enum.Font.GothamBold
	activeLbl.TextSize = 11; activeLbl.TextXAlignment = Enum.TextXAlignment.Left
	activeLbl.ZIndex = 62+zOff; activeLbl.Parent = frame
	activeLabels[slot] = activeLbl

	local checkRefs = {}

	local function refreshChecks()
		for idx, refs in ipairs(checkRefs) do
			refs.check.Text   = (idx == activeAnims[slot]) and "✔" or ""
			refs.stroke.Color = (idx == activeAnims[slot]) and TAB_COLORS[slot] or c3(50,45,75)
			refs.stroke.Transparency = (idx == activeAnims[slot]) and 0.2 or 0.5
		end
		activeLbl.Text = "Active: " .. ANIM_LIBRARY[slot][activeAnims[slot]].name
	end

	for idx, entry in ipairs(ANIM_LIBRARY[slot]) do
		local sub = ""
		if entry.combo then
			sub = #entry.combo == 1 and "Single hit" or (#entry.combo .. "-hit combo")
		end
		local btn, check, stroke = makeOptionBtn(frame, entry.name, sub, 62+zOff)
		table.insert(checkRefs, { check=check, stroke=stroke })
		local capturedIdx = idx
		btn.MouseButton1Click:Connect(function()
			playSound(SFX_CLICK,0.35,1.1)
			activeAnims[slot] = capturedIdx
			if slot == "punch" then
				LIVE.punch = entry.combo and entry.combo[1] or entry.id
			else
				LIVE[slot] = entry.id
			end
			st = {}  -- force locomotion reload
			if slot == "punch" then punchComboIndex=1; lastPunchTime=0 end
			refreshChecks()
		end)
	end
	refreshChecks()
	return frame
end

for i, slot in ipairs(TAB_SLOTS) do
	slotFrames[slot] = buildSlotFrame(slot, i)
end
switchTab("idle")

-- Open/close panel
local animPanelOpen = false
local function toggleAnimPanel()
	animPanelOpen = not animPanelOpen
	animPanel.Visible = animPanelOpen
	if animPanelOpen then
		playSound(SFX_OPEN,0.3,1)
		ef.Visible = false
		if credPanel then credPanel.Visible = false end
	else
		playSound(SFX_CLOSE,0.3,1)
	end
end

if animBtn then
	animBtn.MouseButton1Click:Connect(toggleAnimPanel)
end
if mobileAnims then
	mobileAnims.MouseButton1Click:Connect(function()
		playSound(SFX_CLICK,0.4,1); toggleAnimPanel()
	end)
end

-- ─────────────────────────────────────────────────────────
-- CREDITS BUTTON + PANEL
-- ─────────────────────────────────────────────────────────

local credBtn = ins("TextButton")
credBtn.Size = ud2(0,90,0,28); credBtn.Position = ud2(0.5,0,0,14)
credBtn.AnchorPoint = v2(0.5,0); credBtn.BackgroundColor3 = c3(20,16,34)
credBtn.BackgroundTransparency = 0.1; credBtn.Text = "CREDITS"
credBtn.TextColor3 = c3(170,140,230); credBtn.Font = Enum.Font.GothamBold
credBtn.TextSize = 12; credBtn.BorderSizePixel = 0; credBtn.AutoButtonColor = false
credBtn.Parent = sg
ins("UICorner", credBtn).CornerRadius = UDim.new(0,8)
local cbs = ins("UIStroke"); cbs.Color = c3(80,55,130); cbs.Thickness = 1; cbs.Transparency = 0.4; cbs.Parent = credBtn
credBtn.MouseEnter:Connect(function() tw(credBtn,0.12,{BackgroundTransparency=0}); playSound(SFX_CLICK,0.1,1.4) end)
credBtn.MouseLeave:Connect(function() tw(credBtn,0.12,{BackgroundTransparency=0.1}) end)

local credPanel = ins("Frame")
credPanel.Size = ud2(0,320,0,210); credPanel.Position = ud2(0.5,0,0.5,0)
credPanel.AnchorPoint = v2(0.5,0.5); credPanel.BackgroundColor3 = c3(10,8,18)
credPanel.BackgroundTransparency = 0.05; credPanel.BorderSizePixel = 0
credPanel.Visible = false; credPanel.ZIndex = 50; credPanel.Parent = sg
ins("UICorner", credPanel).CornerRadius = UDim.new(0,14)
local cps = ins("UIStroke"); cps.Color = c3(90,60,150); cps.Thickness = 1.5; cps.Transparency = 0.3; cps.Parent = credPanel

local credAccent = ins("Frame")
credAccent.Size = ud2(0,260,0,2); credAccent.Position = ud2(0.5,0,0,42)
credAccent.AnchorPoint = v2(0.5,0); credAccent.BackgroundColor3 = c3(120,70,210)
credAccent.BorderSizePixel = 0; credAccent.ZIndex = 51; credAccent.Parent = credPanel
ins("UICorner", credAccent).CornerRadius = UDim.new(1,0)

local credTitle = ins("TextLabel")
credTitle.BackgroundTransparency = 1; credTitle.Size = ud2(1,0,0,36)
credTitle.Position = ud2(0,0,0,6); credTitle.Text = "CREDITS"
credTitle.TextColor3 = c3(255,255,255); credTitle.Font = Enum.Font.GothamBlack
credTitle.TextSize = 20; credTitle.ZIndex = 51; credTitle.Parent = credPanel

local credEntries = {
	{"OWNER",               "sonicisnumber12/whospierce", c3(200,160,255)},
	{"SPRINT, BLOCK & PUNCH UI", "Forsaken Team",         c3(170,220,255)},
	{"UI TOUCH UPS",        "Claude AI",                  c3(180,255,200)},
}

for i, entry in ipairs(credEntries) do
	local row = ins("Frame")
	row.Size = ud2(1,-32,0,30); row.Position = ud2(0,16,0,46+(i-1)*36)
	row.BackgroundTransparency = 1; row.ZIndex = 51; row.Parent = credPanel

	local roleLabel = ins("TextLabel")
	roleLabel.BackgroundTransparency = 1; roleLabel.Size = ud2(0.52,0,1,0)
	roleLabel.Text = entry[1]; roleLabel.TextColor3 = c3(130,110,160)
	roleLabel.Font = Enum.Font.GothamBold; roleLabel.TextSize = 11
	roleLabel.TextXAlignment = Enum.TextXAlignment.Left; roleLabel.ZIndex = 51; roleLabel.Parent = row

	local nameL = ins("TextLabel")
	nameL.BackgroundTransparency = 1; nameL.Size = ud2(0.48,0,1,0)
	nameL.Position = ud2(0.52,0,0,0); nameL.Text = entry[2]
	nameL.TextColor3 = entry[3]; nameL.Font = Enum.Font.GothamBold
	nameL.TextSize = 13; nameL.TextXAlignment = Enum.TextXAlignment.Right
	nameL.ZIndex = 51; nameL.Parent = row
end

local closeCredBtn = ins("TextButton")
closeCredBtn.Size = ud2(0,100,0,32); closeCredBtn.Position = ud2(0.5,0,1,-46)
closeCredBtn.AnchorPoint = v2(0.5,0); closeCredBtn.BackgroundColor3 = c3(100,60,190)
closeCredBtn.BackgroundTransparency = 0.1; closeCredBtn.Text = "CLOSE"
closeCredBtn.TextColor3 = c3(255,255,255); closeCredBtn.Font = Enum.Font.GothamBold
closeCredBtn.TextSize = 13; closeCredBtn.BorderSizePixel = 0; closeCredBtn.AutoButtonColor = false
closeCredBtn.ZIndex = 51; closeCredBtn.Parent = credPanel
ins("UICorner", closeCredBtn).CornerRadius = UDim.new(0,8)
closeCredBtn.MouseEnter:Connect(function() tw(closeCredBtn,0.12,{BackgroundColor3=c3(130,80,220)}); playSound(SFX_CLICK,0.1,1.3) end)
closeCredBtn.MouseLeave:Connect(function() tw(closeCredBtn,0.12,{BackgroundColor3=c3(100,60,190)}) end)
closeCredBtn.MouseButton1Click:Connect(function()
	playSound(SFX_CLOSE,0.3,1.1); credPanel.Visible = false
end)

credBtn.MouseButton1Click:Connect(function()
	playSound(SFX_OPEN,0.35,1)
	credPanel.Visible = not credPanel.Visible
	if credPanel.Visible then animPanel.Visible = false end
end)

-- ─────────────────────────────────────────────────────────
-- UNLOAD CONFIRM
-- ─────────────────────────────────────────────────────────

local xb = ins("ImageButton")
xb.Size = ud2(0,32,0,32); xb.Position = ud2(0.5,38,0,14)
xb.AnchorPoint = v2(0.5,0); xb.Image = "http://www.roblox.com/asset/?id=16201262"
xb.BackgroundColor3 = c3(14,12,22); xb.BackgroundTransparency = 0.2
xb.BorderSizePixel = 0; xb.Parent = sg
ins("UICorner", xb).CornerRadius = UDim.new(0,8)
xb.MouseEnter:Connect(function() playSound(SFX_CLICK,0.1,1.4) end)
xb.MouseButton1Click:Connect(function() playSound(SFX_CLICK,0.35,0.9) end)

local cf = ins("Frame")
cf.Size = ud2(0,300,0,170); cf.Position = ud2(0.5,-150,0.5,-85)
cf.BackgroundColor3 = c3(10,9,16); cf.BackgroundTransparency = 0.08
cf.BorderSizePixel = 0; cf.Visible = false; cf.ZIndex = 50; cf.Parent = sg
ins("UICorner", cf).CornerRadius = UDim.new(0,14)
local cfs2 = ins("UIStroke"); cfs2.Color = c3(80,55,130); cfs2.Thickness = 1.2; cfs2.Transparency = 0.35; cfs2.Parent = cf

local cl = ins("TextLabel")
cl.Size = ud2(1,-24,0,70); cl.Position = ud2(0,12,0,18)
cl.BackgroundTransparency = 1; cl.Text = "Stop larping as guest 1367?"
cl.TextColor3 = c3(200,185,230); cl.TextWrapped = true
cl.Font = Enum.Font.GothamBold; cl.TextSize = 16; cl.ZIndex = 51; cl.Parent = cf

local function confirmBtn(n, t, xpos, clr)
	local b = ins("TextButton")
	b.Name = n; b.Size = ud2(0,110,0,38); b.Position = ud2(0,xpos,0,104)
	b.BackgroundColor3 = clr; b.BackgroundTransparency = 0.1
	b.Text = t; b.TextColor3 = c3(255,255,255)
	b.Font = Enum.Font.GothamBold; b.TextSize = 15
	b.BorderSizePixel = 0; b.AutoButtonColor = false; b.ZIndex = 51; b.Parent = cf
	ins("UICorner", b).CornerRadius = UDim.new(0,8)
	b.MouseEnter:Connect(function() playSound(SFX_CLICK,0.1,1.3) end)
	b.MouseButton1Click:Connect(function() playSound(SFX_CLICK,0.4,1) end)
	return b
end

local bn = confirmBtn("No",  "No",  26,  c3(40,35,60))
local by = confirmBtn("Yes", "Yes", 164, c3(150,25,50))
bn.MouseEnter:Connect(function() tw(bn,0.12,{BackgroundColor3=c3(60,50,90)}) end)
bn.MouseLeave:Connect(function() tw(bn,0.12,{BackgroundColor3=c3(40,35,60)}) end)
by.MouseEnter:Connect(function() tw(by,0.12,{BackgroundColor3=c3(190,35,60)}) end)
by.MouseLeave:Connect(function() tw(by,0.12,{BackgroundColor3=c3(150,25,50)}) end)

-- ─────────────────────────────────────────────────────────
-- EMOTE BUTTONS
-- ─────────────────────────────────────────────────────────

local function addEmoteBtn(name, id)
	local btnH = mobScale(32, 40)
	local b = ins("TextButton")
	b.Size = ud2(1,0,0,btnH); b.BackgroundColor3 = c3(22,18,35)
	b.BackgroundTransparency = 0.1; b.Text = name
	b.TextColor3 = c3(200,185,230); b.Font = Enum.Font.GothamBold
	b.TextSize = mobScale(13,14); b.BorderSizePixel = 0
	b.AutoButtonColor = false; b.ZIndex = 12; b.Parent = sf
	ins("UICorner", b).CornerRadius = UDim.new(0,6)
	b.MouseEnter:Connect(function() tw(b,0.1,{BackgroundTransparency=0}); playSound(SFX_CLICK,0.1,1.3) end)
	b.MouseLeave:Connect(function() tw(b,0.1,{BackgroundTransparency=0.1}) end)
	b.MouseButton1Click:Connect(function()
		playSound(SFX_OPEN,0.3,1)
		playEmote(id)
	end)
end

-- ─────────────────────────────────────────────────────────
-- ANIMATION HELPERS
-- ─────────────────────────────────────────────────────────

local function loadAnim(animator, id)
	local anim = ins("Animation"); anim.AnimationId = id
	return animator:LoadAnimation(anim)
end

local function stopOthers(animator)
	for _, t in ipairs(animator:GetPlayingAnimationTracks()) do
		if t ~= headTrackRef and t ~= ca and t ~= faceTrackRef then t:Stop(0.25) end
	end
end

local function stopCurrent()
	if ca then ca:Stop(0.25); ca = nil end
end

local function updateFace(isEmoting, animator)
	if not animator then return end
	if isEmoting then
		if faceTrackRef then
			local ok2, fid = pcall(function() return faceTrackRef.Animation.AnimationId end)
			if ok2 and fid == FACE_IDLE_ID then faceTrackRef:AdjustWeight(0); faceTrackRef = nil end
		end
		if not faceTrackRef then
			local fa = ins("Animation"); fa.AnimationId = FACE_EMOTE_ID
			faceTrackRef = animator:LoadAnimation(fa)
			faceTrackRef.Priority = Enum.AnimationPriority.Action4; faceTrackRef.Looped = true
			faceTrackRef:Play(0.25)
		end
	else
		if faceTrackRef then
			local ok2, fid = pcall(function() return faceTrackRef.Animation.AnimationId end)
			if ok2 and fid == FACE_EMOTE_ID then faceTrackRef:AdjustWeight(0); faceTrackRef = nil end
		end
		if not faceTrackRef then
			local fa = ins("Animation"); fa.AnimationId = FACE_IDLE_ID
			faceTrackRef = animator:LoadAnimation(fa)
			faceTrackRef.Priority = Enum.AnimationPriority.Action4; faceTrackRef.Looped = true
			faceTrackRef:Play(0.25)
		end
	end
end

-- ─── SAFE ANIM ID READER ────────────────────────────────
-- Prevents errors when a track belongs to a destroyed animator

local function safeGetId(track)
	if not track then return nil end
	local ok2, id = pcall(function() return track.Animation.AnimationId end)
	return ok2 and id or nil
end

local function updateLocomotion(char, hum, animator)
	if not active or not char or not hum or not animator then return end
	if ca and ca.IsPlaying then return end  -- action (punch/block/emote) has control

	local rp = char:FindFirstChild("HumanoidRootPart")
	if not rp then return end

	local vel    = v3(rp.AssemblyLinearVelocity.X, 0, rp.AssemblyLinearVelocity.Z)
	local moving = vel.Magnitude > 0.5
	local state  = "idle"
	if moving then state = ir and "run" or "walk" end

	local liveIdle = LIVE.idle
	local liveWalk = LIVE.walk
	local liveRun  = LIVE.run

	-- Stop tracks that shouldn't be playing
	for k, t in pairs(st) do
		if k ~= state and t and t.IsPlaying then t:Stop(0.25) end
	end

	if state == "idle" then
		-- Reload if ID changed or track dead
		if st.idle and st.idle.IsPlaying and safeGetId(st.idle) ~= liveIdle then
			st.idle:Stop(0.25); st.idle = nil
		end
		if not st.idle or not st.idle.IsPlaying then
			stopOthers(animator)
			st.idle = loadAnim(animator, liveIdle)
			st.idle.Priority = Enum.AnimationPriority.Idle; st.idle:Play(0.25)
		end
		hum.WalkSpeed = 16

	elseif state == "walk" then
		if st.walk and st.walk.IsPlaying and safeGetId(st.walk) ~= liveWalk then
			st.walk:Stop(0.25); st.walk = nil
		end
		if not st.walk or not st.walk.IsPlaying then
			stopOthers(animator)
			st.walk = loadAnim(animator, liveWalk)
			st.walk.Priority = Enum.AnimationPriority.Movement; st.walk:Play(0.1)
		end
		st.walk:AdjustSpeed(1.2); hum.WalkSpeed = 12

	elseif state == "run" then
		if st.run and st.run.IsPlaying and safeGetId(st.run) ~= liveRun then
			st.run:Stop(0.25); st.run = nil
		end
		if not st.run or not st.run.IsPlaying then
			stopOthers(animator)
			st.run = loadAnim(animator, liveRun)
			st.run.Priority = Enum.AnimationPriority.Movement; st.run:Play(0.25)
			st.run:AdjustSpeed(2.4)
		end
		hum.WalkSpeed = 28
	end
end

-- ─────────────────────────────────────────────────────────
-- ACTIONS
-- ─────────────────────────────────────────────────────────

function playEmote(id)
	local char = lp.Character
	local hum  = char and char:FindFirstChildOfClass("Humanoid")
	local anim = hum  and hum:FindFirstChildOfClass("Animator")
	if not anim then return end
	ef.Visible = false; stopCurrent(); stopOthers(anim)
	local t = loadAnim(anim, id); ca = t
	t.Looped = true; t.Priority = Enum.AnimationPriority.Action3
	isConga = (id == C_ID)
	hum.WalkSpeed = isConga and 8 or 0
	t:Play(0.25); stopE.Visible = true; updateFace(true, anim)
	task.spawn(function()
		while ca == t and t.IsPlaying do stopOthers(anim); task.wait(0.1) end
	end)
end

local function clearEmote()
	stopCurrent(); isConga = false; stopE.Visible = false
	playSound(SFX_CLOSE, 0.3, 0.9)
	local char = lp.Character
	local hum  = char and char:FindFirstChildOfClass("Humanoid")
	local anim = hum  and hum:FindFirstChildOfClass("Animator")
	if hum  then hum.WalkSpeed = ir and 26 or 16 end
	if anim then updateFace(false, anim) end
end

-- ─────────────────────────────────────────────────────────
-- PUNCH
-- ─────────────────────────────────────────────────────────

local function doPunch()
	if stopE.Visible then return end
	local char = lp.Character
	local hum  = char and char:FindFirstChildOfClass("Humanoid")
	local rp   = char and char:FindFirstChild("HumanoidRootPart")
	local anim = hum  and hum:FindFirstChildOfClass("Animator")
	if not anim or not rp then return end

	local comboEntry = ANIM_LIBRARY.punch[activeAnims.punch]
	local combo      = comboEntry.combo or { comboEntry.id }
	local now        = tick()
	if now - lastPunchTime > COMBO_WINDOW then punchComboIndex = 1 end
	lastPunchTime = now

	local hitId = combo[punchComboIndex]
	punchComboIndex = punchComboIndex % #combo + 1

	stopCurrent(); stopOthers(anim)
	local t = loadAnim(anim, hitId); ca = t
	t.Priority = Enum.AnimationPriority.Action2

	if t.Length == 0 then t:GetPropertyChangedSignal("Length"):Wait() end

	local PUNCH_SPEED = 3.5
	hum.WalkSpeed = 2
	t:Play(0.08); t:AdjustSpeed(PUNCH_SPEED)
	updateFace(true, anim)

	local att = ins("Attachment", rp)
	local vl  = ins("LinearVelocity")
	vl.MaxForce       = 99999
	vl.VectorVelocity = rp.CFrame.LookVector * 78
	vl.Attachment0    = att; vl.Parent = rp
	Debris:AddItem(vl, 0.12); Debris:AddItem(att, 0.12)

	task.wait(t.Length / PUNCH_SPEED)
	if ca == t then
		t:Stop(0.2); ca = nil
		hum.WalkSpeed = ir and 26 or 16
		updateFace(false, anim)
	end
end

-- ─────────────────────────────────────────────────────────
-- BLOCK
-- ─────────────────────────────────────────────────────────

local function doBlock()
	if stopE.Visible then return end
	local char = lp.Character
	local hum  = char and char:FindFirstChildOfClass("Humanoid")
	local anim = hum  and hum:FindFirstChildOfClass("Animator")
	if not anim then return end
	stopCurrent(); stopOthers(anim)
	local t = loadAnim(anim, LIVE.block); ca = t
	t.Priority = Enum.AnimationPriority.Action2
	if t.Length == 0 then t:GetPropertyChangedSignal("Length"):Wait() end
	hum.WalkSpeed = 0.1; t:Play(0.1); t:AdjustSpeed(3)
	updateFace(true, anim)
	task.wait((t.Length * 0.3) / 3); t:AdjustSpeed(1); t.Stopped:Wait()
	task.wait(0.3)
	if ca == t then ca = nil; hum.WalkSpeed = ir and 26 or 16; updateFace(false, anim) end
end

-- ─────────────────────────────────────────────────────────
-- SPRINT
-- ─────────────────────────────────────────────────────────

local function setRun(state)
	ir = state
	if rb then rb.ImageColor3 = ir and c3(120,200,100) or c3(255,255,255) end
	if mobileSprint then
		mobileSprint.BackgroundColor3 = ir and c3(18,32,14) or c3(14,22,14)
	end
	local char = lp.Character
	local hum  = char and char:FindFirstChildOfClass("Humanoid")
	if hum and not (ca and ca.IsPlaying) then hum.WalkSpeed = ir and 26 or 16 end
end

-- ─────────────────────────────────────────────────────────
-- INPUT BINDINGS
-- ─────────────────────────────────────────────────────────

UserInputService.InputBegan:Connect(function(input, gp)
	if gp or not active then return end
	if input.KeyCode == Enum.KeyCode.LeftShift then     setRun(true)
	elseif input.KeyCode == Enum.KeyCode.E then          doPunch()
	elseif input.KeyCode == Enum.KeyCode.Q then          doBlock()
	elseif input.KeyCode == Enum.KeyCode.LeftControl then
		isSL = not isSL
		if slb then slb.ImageColor3 = isSL and c3(100,180,255) or c3(255,255,255) end
	end
end)

UserInputService.InputEnded:Connect(function(input, gp)
	if gp or not active then return end
	if input.KeyCode == Enum.KeyCode.LeftShift then setRun(false) end
end)

-- Desktop button bindings
if rb  then rb.MouseButton1Down:Connect(function() setRun(true) end)
          rb.MouseButton1Up:Connect(function()   setRun(false) end)
          rb.MouseLeave:Connect(function() if ir then setRun(false) end end) end
if slb then slb.MouseButton1Click:Connect(function()
	isSL = not isSL
	slb.ImageColor3 = isSL and c3(100,180,255) or c3(255,255,255)
	playSound(SFX_CLICK, 0.35, isSL and 1.1 or 0.9)
end) end
if pb  then pb.MouseButton1Click:Connect(doPunch) end
if bb  then bb.MouseButton1Click:Connect(doBlock) end

-- Mobile button bindings
if mobileSprint then
	mobileSprint.MouseButton1Down:Connect(function() setRun(true) end)
	mobileSprint.MouseButton1Up:Connect(function()   setRun(false) end)
	mobileSprint.TouchLongPress:Connect(function()   setRun(false) end)
end
if mobilePunch then mobilePunch.MouseButton1Click:Connect(doPunch) end
if mobileBlock then mobileBlock.MouseButton1Click:Connect(doBlock) end
if mobileShiftlock then
	mobileShiftlock.MouseButton1Click:Connect(function()
		isSL = not isSL
		mobileShiftlock.BackgroundColor3 = isSL and c3(14,20,28) or c3(12,16,22)
		playSound(SFX_CLICK,0.35,isSL and 1.1 or 0.9)
	end)
end
if mobileEmote then
	mobileEmote.MouseButton1Click:Connect(function()
		ef.Visible = not ef.Visible
		if ef.Visible then animPanel.Visible = false end
		playSound(ef.Visible and SFX_OPEN or SFX_CLOSE,0.3,1)
	end)
end

stopE.MouseButton1Click:Connect(clearEmote)
xb.MouseButton1Click:Connect(function() cf.Visible = true end)
bn.MouseButton1Click:Connect(function() cf.Visible = false end)

addEmoteBtn("California Girls", E1_ID)
addEmoteBtn("Conga!",           C_ID)
addEmoteBtn("Bag Up",           BU_ID)
addEmoteBtn("Metro Man",        MM_ID)
addEmoteBtn("Subject Theee",    S3_ID)
addEmoteBtn("Caramelldansen",   CD_ID)
addEmoteBtn("Unemployment",     UN_ID)
addEmoteBtn("Bang Bang!",       DP_ID)
addEmoteBtn("Stretching",       WW_ID)
addEmoteBtn("2011x Taunt",      X_ID)
addEmoteBtn("Low Cortisol",     LW_ID)
addEmoteBtn("S RANK!",          S_ID)
addEmoteBtn("Two time ritual",  RI_ID)

-- ─────────────────────────────────────────────────────────
-- TOP RIGHT HUD BUTTONS
-- ─────────────────────────────────────────────────────────

local hudConnections = {}
local hudHidden = false

local buttonHolder = ins("Frame")
buttonHolder.Size = ud2(0,264,0,40); buttonHolder.Position = ud2(1,60,0,20)
buttonHolder.BackgroundTransparency = 1; buttonHolder.Parent = gui

local bLayout = ins("UIListLayout")
bLayout.FillDirection = Enum.FillDirection.Horizontal
bLayout.HorizontalAlignment = Enum.HorizontalAlignment.Right
bLayout.Padding = UDim.new(0,8); bLayout.Parent = buttonHolder

local function makeHudBtn(text)
	local btn = ins("TextButton")
	btn.Size = ud2(0,122,0,34); btn.BackgroundColor3 = c3(20,18,28)
	btn.Text = text; btn.TextColor3 = c3(210,200,240)
	btn.Font = Enum.Font.GothamBold; btn.TextSize = 12
	btn.BorderSizePixel = 0; btn.AutoButtonColor = false
	ins("UICorner", btn).CornerRadius = UDim.new(0,8)
	local bs = ins("UIStroke"); bs.Color = c3(80,65,110); bs.Thickness = 1; bs.Transparency = 0.3; bs.Parent = btn
	btn.MouseEnter:Connect(function() tw(btn,0.15,{BackgroundColor3=c3(38,30,58)}); playSound(SFX_CLICK,0.1,1.4) end)
	btn.MouseLeave:Connect(function() tw(btn,0.15,{BackgroundColor3=c3(20,18,28)}) end)
	btn.MouseButton1Click:Connect(function() playSound(SFX_CLICK,0.4,1) end)
	return btn
end

local hideButton   = makeHudBtn("Hide HUD")
local unloadButton = makeHudBtn("Unload HUD")
hideButton.Parent = buttonHolder; unloadButton.Parent = buttonHolder

local hudHidePos    = isMobile and ud2(0.5,0,0,-500) or ud2(0,-500,1,-20)
local hudShowPos    = isMobile and ud2(0.5,0,0,16)   or ud2(0,18,1,-20)

table.insert(hudConnections, hideButton.MouseButton1Click:Connect(function()
	hudHidden = not hudHidden
	if hudHidden then
		tw(holder,0.35,{Position=hudHidePos}); hideButton.Text = "Show HUD"
	else
		tw(holder,0.5,{Position=hudShowPos}); hideButton.Text = "Hide HUD"
	end
end))

table.insert(hudConnections, unloadButton.MouseButton1Click:Connect(function()
	tw(holder,0.35,{Position=hudHidePos})
	tw(buttonHolder,0.3,{Position=ud2(1,60,0,20)})
	task.wait(0.4)
	for _, c in pairs(hudConnections) do c:Disconnect() end
	gui:Destroy()
end))

-- ─────────────────────────────────────────────────────────
-- SPLASH SCREEN
-- ─────────────────────────────────────────────────────────

local splashBG = ins("Frame")
splashBG.Size = ud2(1,0,1,0); splashBG.BackgroundColor3 = c3(6,6,10)
splashBG.BackgroundTransparency = 0; splashBG.ZIndex = 100; splashBG.Parent = gui

local splashW = mobScale(420, math.min(workspace.CurrentCamera.ViewportSize.X - 20, 360))
local splash = ins("Frame")
splash.AnchorPoint = v2(0.5,0.5); splash.Position = ud2(0.5,0,0.58,0)
splash.Size = ud2(0,splashW,0,268); splash.BackgroundColor3 = c3(14,12,22)
splash.BackgroundTransparency = 1; splash.BorderSizePixel = 0
splash.ZIndex = 101; splash.Parent = gui
ins("UICorner", splash).CornerRadius = UDim.new(0,14)
local sps = ins("UIStroke"); sps.Color = c3(90,60,150); sps.Thickness = 1.5; sps.Transparency = 0.3; sps.Parent = splash

local splashAccent = ins("Frame")
splashAccent.Size = ud2(0,0,0,2); splashAccent.Position = ud2(0.5,0,0,0)
splashAccent.AnchorPoint = v2(0.5,0); splashAccent.BackgroundColor3 = c3(130,80,230)
splashAccent.BorderSizePixel = 0; splashAccent.ZIndex = 102; splashAccent.Parent = splash
ins("UICorner", splashAccent).CornerRadius = UDim.new(1,0)

local function makeSplashLbl(text, size, color, yPos, font)
	local lbl = ins("TextLabel")
	lbl.BackgroundTransparency = 1; lbl.Position = ud2(0,0,0,yPos)
	lbl.Size = ud2(1,0,0,size+10); lbl.Font = font or Enum.Font.GothamBlack
	lbl.Text = text; lbl.TextColor3 = color; lbl.TextSize = size
	lbl.TextTransparency = 1; lbl.ZIndex = 102; lbl.Parent = splash
	return lbl
end

local titleLbl    = makeSplashLbl("FORSAKEN",              mobScale(44,38), c3(255,255,255), 36)
local subtitleLbl = makeSplashLbl("GUEST FE USE  •  R15",  14,              c3(140,90,230),  90, Enum.Font.GothamBold)
local versionLbl  = makeSplashLbl("v5.1  —  Fixed + Mobile UI", 11,        c3(90,75,115),  118, Enum.Font.Gotham)

local splashDiv = ins("Frame")
splashDiv.Size = ud2(0,0,0,1); splashDiv.Position = ud2(0.5,0,0,112)
splashDiv.AnchorPoint = v2(0.5,0); splashDiv.BackgroundColor3 = c3(60,50,85)
splashDiv.BackgroundTransparency = 1; splashDiv.BorderSizePixel = 0
splashDiv.ZIndex = 102; splashDiv.Parent = splash

local startBtn = ins("TextButton")
startBtn.AnchorPoint = v2(0.5,0); startBtn.Position = ud2(0.5,0,0,155)
startBtn.Size = ud2(0,mobScale(180,220),0,mobScale(46,54))
startBtn.BackgroundColor3 = c3(100,60,195); startBtn.BackgroundTransparency = 1
startBtn.Text = "START"; startBtn.TextColor3 = c3(255,255,255); startBtn.TextTransparency = 1
startBtn.Font = Enum.Font.GothamBlack; startBtn.TextSize = mobScale(15,17)
startBtn.BorderSizePixel = 0; startBtn.AutoButtonColor = false
startBtn.ZIndex = 102; startBtn.Parent = splash
ins("UICorner", startBtn).CornerRadius = UDim.new(0,10)
local sbtnS = ins("UIStroke"); sbtnS.Color = c3(160,110,255); sbtnS.Thickness = 1; sbtnS.Transparency = 0.4; sbtnS.Parent = startBtn

startBtn.MouseEnter:Connect(function()
	tw(startBtn,0.15,{BackgroundColor3=c3(120,78,215)})
	tw(startBtn,0.15,{Size=ud2(0,mobScale(190,230),0,mobScale(46,54))})
	playSound(SFX_CLICK,0.12,1.4)
end)
startBtn.MouseLeave:Connect(function()
	tw(startBtn,0.15,{BackgroundColor3=c3(100,60,195)})
	tw(startBtn,0.15,{Size=ud2(0,mobScale(180,220),0,mobScale(46,54))})
end)

task.defer(function()
	tw(blur,0.6,{Size=28})
	tw(splash,0.4,{Position=ud2(0.5,0,0.5,0),BackgroundTransparency=0})
	task.wait(0.3)
	tw(splashAccent,0.5,{Size=ud2(0,splashW-60,0,2)})
	task.wait(0.18)
	tw(titleLbl,0.35,{TextTransparency=0}); task.wait(0.1)
	tw(subtitleLbl,0.35,{TextTransparency=0}); task.wait(0.1)
	tw(splashDiv,0.35,{Size=ud2(0,splashW-60,0,1),BackgroundTransparency=0}); task.wait(0.08)
	tw(versionLbl,0.3,{TextTransparency=0}); task.wait(0.12)
	tw(startBtn,0.35,{TextTransparency=0,BackgroundTransparency=0})
end)

startBtn.MouseButton1Click:Connect(function()
	playSound(SFX_OPEN,0.5,1)
	tw(startBtn,0.1,{BackgroundColor3=c3(200,160,255)})
	task.wait(0.1)
	TweenService:Create(splash,TweenInfo.new(0.25,Enum.EasingStyle.Quint),{BackgroundTransparency=1,Position=ud2(0.5,0,0.44,0)}):Play()
	TweenService:Create(splashBG,TweenInfo.new(0.25,Enum.EasingStyle.Quint),{BackgroundTransparency=1}):Play()
	for _, lbl in ipairs({titleLbl,subtitleLbl,versionLbl}) do tw(lbl,0.2,{TextTransparency=1}) end
	tw(startBtn,0.2,{TextTransparency=1,BackgroundTransparency=1})
	tw(splashDiv,0.2,{BackgroundTransparency=1})
	tw(splashAccent,0.2,{BackgroundTransparency=1})
	tw(blur,0.7,{Size=0})
	task.wait(0.4)
	splashBG:Destroy(); splash:Destroy(); blur:Destroy()
	-- Animate HUD in
	if isMobile then
		tw(holder,0.55,{Position=ud2(0.5,0,0,16)})
	else
		tw(holder,0.55,{Position=ud2(0,18,1,-20)})
	end
	task.wait(0.15)
	tw(buttonHolder,0.45,{Position=ud2(1,-280,0,20)})
end)

-- ─────────────────────────────────────────────────────────
-- HUD BARS UPDATE
-- ─────────────────────────────────────────────────────────

local MAX_STAMINA = 100
local stamina = 100

local function tweenBar(bar, pct)
	TweenService:Create(bar,
		TweenInfo.new(0.13,Enum.EasingStyle.Sine),
		{Size=ud2(math.clamp(pct,0,1),0,1,0)}
	):Play()
end

local prevHp = -1
local function updateHealthBar()
	local char = lp.Character
	local hum  = char and char:FindFirstChildOfClass("Humanoid")
	if not hum then return end
	local hp, max = hum.Health, hum.MaxHealth
	if math.abs(hp - prevHp) < 0.1 then return end
	prevHp = hp
	local pct = hp / max
	tweenBar(healthFill, pct)
	healthText.Text = math.floor(hp) .. "/" .. math.floor(max)
	tw(healthFill,0.15,{BackgroundColor3 = pct < 0.3 and c3(255,30,30) or hpNorm})
end

local prevSt = -1
local function updateStaminaBar()
	local pct = stamina / MAX_STAMINA
	if math.abs(pct - prevSt) < 0.005 then return end
	prevSt = pct
	tweenBar(staminaFill, pct)
	staminaText.Text = math.floor(stamina) .. "/" .. MAX_STAMINA
	tw(staminaFill,0.15,{BackgroundColor3 = pct < 0.15 and stLow or stNorm})
end

-- ─────────────────────────────────────────────────────────
-- UNLOAD YES
-- ─────────────────────────────────────────────────────────

by.MouseButton1Click:Connect(function()
	active = false; getgenv().forsakenthingyguys = nil

	-- Stop the render loop
	if renderConn then renderConn:Disconnect(); renderConn = nil end

	local char = lp.Character
	if char then
		local hum  = char:FindFirstChildOfClass("Humanoid")
		local anim = hum and hum:FindFirstChildOfClass("Animator")
		if anim then for _, t in ipairs(anim:GetPlayingAnimationTracks()) do t:Stop(0) end end
		if hum then hum.WalkSpeed = 16; hum.JumpPower = 50; hum:SetStateEnabled(Enum.HumanoidStateType.Seated,true) end
		for _, s in ipairs(char:GetDescendants()) do
			if s:IsA("Script") and s.Name == "Animate" then s.Disabled=false; s.Enabled=false; s.Enabled=true; break end
		end
	end

	for _, c in pairs(animateConns) do c:Disconnect() end
	animateConns = {}
	if charAddedConn then charAddedConn:Disconnect(); charAddedConn = nil end
	sg:Destroy(); gui:Destroy()
end)

-- ─────────────────────────────────────────────────────────
-- CHARACTER MAIN  (all the bug fixes live here)
-- ─────────────────────────────────────────────────────────

local function watchAnimate(char)
	-- Disconnect old per-character animate watchers before adding new ones
	for _, c in pairs(animateConns) do c:Disconnect() end
	animateConns = {}

	local conn = char.DescendantAdded:Connect(function(d)
		if d:IsA("Script") and d.Name == "Animate" then d.Disabled = true end
	end)
	table.insert(animateConns, conn)

	for _, o in ipairs(char:GetDescendants()) do
		if o:IsA("Script") and o.Name == "Animate" then o.Disabled = true end
	end
end

local function main(char)
	local hum = char:WaitForChild("Humanoid")

	-- ── CRITICAL: reset all animation state for this new character ──
	if renderConn then renderConn:Disconnect(); renderConn = nil end
	ca            = nil
	st            = {}
	headTrackRef  = nil
	faceTrackRef  = nil
	isConga       = false
	ir            = false
	punchComboIndex = 1
	lastPunchTime   = 0
	-- ────────────────────────────────────────────────────────────────

	if hum.RigType == Enum.HumanoidRigType.R6 then
		sg.Enabled = false
		local wGui = ins("ScreenGui"); wGui.Name = "R6Warning"; wGui.Parent = playerGui
		local wBg  = ins("Frame"); wBg.Size = ud2(0,300,0,75); wBg.Position = ud2(0.5,0,0.15,0)
		wBg.AnchorPoint = v2(0.5,0.5); wBg.BackgroundColor3 = c3(10,9,16); wBg.BorderSizePixel = 0; wBg.Parent = wGui
		ins("UICorner", wBg).CornerRadius = UDim.new(0,10)
		local ws = ins("UIStroke"); ws.Color = c3(160,30,60); ws.Thickness = 1; ws.Parent = wBg
		local wTxt = ins("TextLabel"); wTxt.Size = ud2(1,0,1,0); wTxt.BackgroundTransparency = 1
		wTxt.Text = "R15 REQUIRED — R6 NOT SUPPORTED"; wTxt.TextColor3 = c3(255,60,80)
		wTxt.Font = Enum.Font.GothamBold; wTxt.TextSize = 16; wTxt.Parent = wBg
		task.spawn(function()
			task.wait(3); tw(wBg,1,{BackgroundTransparency=1}); tw(wTxt,1,{TextTransparency=1}); task.wait(1); wGui:Destroy()
		end)
		return
	else
		sg.Enabled = true
		local old = playerGui:FindFirstChild("R6Warning")
		if old then old:Destroy() end
	end

	local animator = hum:WaitForChild("Animator")

	-- Kill the default Animate script and stop all existing tracks
	for _, o in ipairs(char:GetDescendants()) do
		if o:IsA("Script") and o.Name == "Animate" then o.Disabled = true; break end
	end
	for _, t in ipairs(animator:GetPlayingAnimationTracks()) do t:Stop(0) end
	for _, t in ipairs(hum:GetPlayingAnimationTracks()) do t:Stop(0) end

	hum.JumpPower = 0; hum:SetStateEnabled(Enum.HumanoidStateType.Seated, false)

	-- Load initial idle
	local it = loadAnim(animator, LIVE.idle)
	it.Priority = Enum.AnimationPriority.Idle; it:Play(0.25); st.idle = it

	-- Head turn track
	local headAnim = ins("Animation"); headAnim.AnimationId = H_ID
	headTrackRef = animator:LoadAnimation(headAnim)
	headTrackRef.Priority = Enum.AnimationPriority.Action4
	headTrackRef:Play(0,1,0); headTrackRef:AdjustWeight(0.2)
	updateFace(false, animator)

	-- Per-frame state
	local currentYaw = 0; local targetYaw = 0
	local yawVelocity = 0; local yawAccel = 0; local prevTarget = 0

	-- ── Single RenderStepped connection stored at module scope ──────
	renderConn = RunService.RenderStepped:Connect(function(dt)
		if not active or not char or not char.Parent then
			renderConn:Disconnect(); renderConn = nil; return
		end

		updateLocomotion(char, hum, animator)
		updateHealthBar()
		stamina = math.clamp(stamina + (ir and -12 or 9)*dt, 0, MAX_STAMINA)
		updateStaminaBar()

		local rp = char:FindFirstChild("HumanoidRootPart")
		if not rp then return end

		local targetFov = (ir and hum.MoveDirection.Magnitude > 0) and 80 or 70
		camera.FieldOfView = camera.FieldOfView + (targetFov - camera.FieldOfView) * math.min(dt*2,1)

		if isSL then
			local cl = camera.CFrame.LookVector
			local targetCF = CFrame.lookAt(rp.Position, rp.Position + v3(cl.X,0,cl.Z))
			rp.CFrame = rp.CFrame:Lerp(targetCF, 0.5)
		elseif hum.MoveDirection.Magnitude > 0 then
			local targetCF = CFrame.lookAt(rp.Position, rp.Position + hum.MoveDirection)
			rp.CFrame = rp.CFrame:Lerp(targetCF, 0.1)
		end

		if isConga then hum:Move(rp.CFrame.LookVector) end

		if not (ca and ca.IsPlaying) then
			local camObj = rp.CFrame:VectorToObjectSpace(camera.CFrame.LookVector)
			targetYaw = math.clamp(-math.atan2(camObj.X,-camObj.Z), -math.rad(75), math.rad(75))
			local delta = targetYaw - currentYaw
			local pred  = (targetYaw - prevTarget) * 8
			prevTarget  = targetYaw
			yawAccel    = (yawAccel + (delta+pred)*12*dt) * 0.92
			yawVelocity = yawVelocity*0.98 + yawAccel*dt
			currentYaw  = currentYaw + yawVelocity*dt
			currentYaw  = currentYaw + (targetYaw-currentYaw)*math.min(dt*3,0.15)
		else
			yawAccel    = (yawAccel + (0-currentYaw)*12*dt) * 0.92
			yawVelocity = yawVelocity*0.98 + yawAccel*dt
			currentYaw  = currentYaw + yawVelocity*dt
			currentYaw  = currentYaw + (0-currentYaw)*math.min(dt*3,0.15)
		end

		local yawMapped = currentYaw < 0 and currentYaw + math.pi*2 or currentYaw
		if headTrackRef and headTrackRef.Length > 0 then
			headTrackRef.TimePosition = (yawMapped/(math.pi*2)) * headTrackRef.Length
		end
	end)
end

charAddedConn = lp.CharacterAdded:Connect(function(char)
	watchAnimate(char); main(char)
end)

if lp.Character then
	watchAnimate(lp.Character); task.spawn(main, lp.Character)
end

Features & Information

A custom FE animation script for R15 avatars. Includes visible animations that everyone in the server can see, along with custom running, punching, blocking, emotes, and more. you can fling you need to be using your own fling script for it.
#forsaken#fe#animations#tuff#guest

Community Comments (16)

oneballoneballMay 15, 2026

The pastebin got deleted it aint working guys

Replying to @oneball
wasitpiercewasitpierceMay 17, 2026

@oneball i can update and have the whole script

oneballoneballMay 19, 2026

can u fix the emote list being halfway cut on mobile?

Replying to @oneball
wasitpiercewasitpierceMay 26, 2026

@oneball this is mainly for pc, so idk about mobile support

Ripping_flippinRipping_flippinMay 21, 2026

Is this skidded? (just asking)

Replying to @Ripping_flippin
oneballoneballMay 22, 2026

@Ripping_flippin probably no but i personally think took some from gazes guest

Replying to @Ripping_flippin
wasitpiercewasitpierceMay 26, 2026

@Ripping_flippin the script used to make the animations arent mine, but everything else is made by me, used claude for gui touchup

GenuineTakabaGenuineTakabaMay 24, 2026

fix the stamina pls

Replying to @GenuineTakaba
wasitpiercewasitpierceMay 26, 2026

@GenuineTakaba stamina is for looks, doesnt do anything

Replying to @wasitpierce
GenuineTakabaGenuineTakabaMay 29, 2026

@wasitpierce can you add in the menu a settings to make and adjust the stamina along with ability cooldowns then???

Replying to @GenuineTakaba
GenuineTakabaGenuineTakabaMay 29, 2026

btw I mean make the stamina work

wasitpiercewasitpierceMay 26, 2026

hey guys quick reminder the animations will bug out and just flat out not work, planning to push out an update for fixing that sometime soon

IroncobeoIroncobeoJun 6, 2026

It's just stuck on the loading screen

GenuineTakabaGenuineTakabaJun 14, 2026

I'm back but just add stamina anyway dude

lovingjordan044lovingjordan044Jun 24, 2026

do emotes work for this script, or will they come out later

Replying to @lovingjordan044
wasitpiercewasitpierceJun 25, 2026

@lovingjordan044 they work, if not then im guessing im going to need to update this script, if ur using mobile its prob not gonna work right.

More Scripts for Universal Scripts