How i can make my module for animation text better?

Now is 3 am, and i wan’t to make something funny, i made a slingshot, and more.
Back to the problem, i made a module that animate the text, like, here’s the function from my module - animationText:TypeText("There's <RAGE>animation</RAGE>! And it's Very <MAGIC>magical</MAGIC>!") and i have an error - Connection is not a supported attribute type, i wan’t to fix that to make my module better, but this error don’t make a bug, just to make a completed my first working module, here’s the module -

local TweenService = game:GetService("TweenService")
local RunService = game:GetService("RunService")

local TextAnimation = {}
TextAnimation.__index = TextAnimation

local LETTER_WIDTH = 15
local LETTER_HEIGHT = 50
local TEXT_SIZE = 20
local FONT = Font.new("rbxasset://fonts/families/Montserrat.json", Enum.FontWeight.ExtraBold, Enum.FontStyle.Normal)

function TextAnimation.new(options)
	local self = setmetatable({}, TextAnimation)
	self.adornee = options.adornee
	self.Pack = script:FindFirstChild("Pack")
	if not self.Pack then
		warn('Folder "Pack", is not valid member of this model!!!')
	end
	return self
end

local function parseText(text)
	local segments = {}
	local currentIndex = 1
	while true do
		local rageStart, rageEnd = string.find(text, "<RAGE>", currentIndex)
		local magicStart, magicEnd = string.find(text, "<MAGIC>", currentIndex)
		local startIdx, endIdx, tagType
		if rageStart and (not magicStart or rageStart < magicStart) then
			startIdx, endIdx, tagType = rageStart, rageEnd, "RAGE"
		elseif magicStart then
			startIdx, endIdx, tagType = magicStart, magicEnd, "MAGIC"
		end

		if startIdx then
			if startIdx > currentIndex then
				table.insert(segments, {text = string.sub(text, currentIndex, startIdx - 1), effect = "Normal"})
			end
			local closeTag = "</" .. tagType .. ">"
			local closeStart, closeEnd = string.find(text, closeTag, endIdx + 1)
			if closeStart then
				local tagText = string.sub(text, endIdx + 1, closeStart - 1)
				table.insert(segments, {text = tagText, effect = tagType})
				currentIndex = closeEnd + 1
			else
				local tagText = string.sub(text, endIdx + 1)
				table.insert(segments, {text = tagText, effect = tagType})
				break
			end
		else
			local remaining = string.sub(text, currentIndex)
			if remaining ~= "" then
				table.insert(segments, {text = remaining, effect = "Normal"})
			end
			break
		end
	end
	return segments
end

local function applyEffects(effectTemplate, parentGui)
	if effectTemplate then
		for _, effect in ipairs(effectTemplate:GetChildren()) do
			local clone = effect:Clone()
			clone.Parent = parentGui
		end
	end
end

local function vanishLetter(letterGui)
	local vanishDelay = 1.5
	local vanishDuration = 0.3
	delay(vanishDelay, function()
		local tweenInfo = TweenInfo.new(vanishDuration, Enum.EasingStyle.Sine, Enum.EasingDirection.Out)
		local tweenLetter = TweenService:Create(letterGui, tweenInfo, {TextTransparency = 1})
		tweenLetter:Play()
		tweenLetter.Completed:Connect(function()
			letterGui:Destroy()
		end)
		for _, child in ipairs(letterGui:GetDescendants()) do
			if child:IsA("UIStroke") then
				local tweenChild = TweenService:Create(child, tweenInfo, {Transparency = 1})
				tweenChild:Play()
			end
		end
	end)
end

local function animateNormalLetter(letterGui, targetPos)
	local tweenInfo = TweenInfo.new(0.15, Enum.EasingStyle.Sine, Enum.EasingDirection.Out)
	local properties = {Position = targetPos, TextTransparency = 0}
	local tween = TweenService:Create(letterGui, tweenInfo, properties)
	tween:Play()
	tween.Completed:Connect(function()
		vanishLetter(letterGui)
	end)
end

local function animateRageLetter(letterGui, startPos, targetPos)
	local totalDuration = 0.45
	local elapsed = 0
	local amplitude = 10
	local connection
	connection = RunService.Heartbeat:Connect(function(delta)
		elapsed = elapsed + delta
		local progress = math.min(elapsed / totalDuration, 1)
		local newX = startPos.X.Offset + (targetPos.X.Offset - startPos.X.Offset) * progress
		local newY = startPos.Y.Offset + (targetPos.Y.Offset - startPos.Y.Offset) * progress
		local currentAmplitude = amplitude * (1 - progress)
		local offsetX = math.random(-currentAmplitude, currentAmplitude)
		local offsetY = math.random(-currentAmplitude, currentAmplitude)
		letterGui.Position = UDim2.new(0, newX + offsetX, 0, newY + offsetY)
		letterGui.TextTransparency = 1 - progress
		if progress >= 1 then
			connection:Disconnect()
			letterGui.Position = targetPos
			letterGui.TextTransparency = 0
			vanishLetter(letterGui)
		end
	end)
end

local TweenService = game:GetService("TweenService")
local RunService = game:GetService("RunService")

local function animateMagicLetter(letterGui, startPos, targetPos)
	local tweenInfo = TweenInfo.new(0.15, Enum.EasingStyle.Sine, Enum.EasingDirection.Out)
	local tween = TweenService:Create(letterGui, tweenInfo, {Position = targetPos, TextTransparency = 0})
	tween:Play()
	tween.Completed:Connect(function()
		local basePos = targetPos
		local speed = 6
		local radius = 3
		local startTime = tick()
		local connection
		connection = RunService.Heartbeat:Connect(function()
			local t = tick() - startTime
			local offsetX = math.cos(t * speed) * radius
			local offsetY = math.sin(t * speed) * radius
			letterGui.Position = UDim2.new(0, basePos.X.Offset + offsetX, 0, basePos.Y.Offset + offsetY)
		end)
		letterGui:SetAttribute("MagicConn", connection)
	end)
	vanishLetter(letterGui)
end

function TextAnimation:TypeText(fullText)
	self.adornee:ClearAllChildren()
	local segments = parseText(fullText)
	local letterDelay = 0.05
	local currentX = 0
	local yPosition = 0
	local globalIndex = 0
	for _, segment in ipairs(segments) do
		local effectFolderName = segment.effect
		local effectTemplate = self.Pack and self.Pack:FindFirstChild(effectFolderName)
		if not effectTemplate then
			warn("The effect " .. effectFolderName .. " cannot find in the Pack!")
		end
		for i = 1, #segment.text do
			globalIndex = globalIndex + 1
			local char = string.sub(segment.text, i, i)
			if segment.effect == "RAGE" then
				char = string.upper(char)
			end
			local letterGui = Instance.new("TextLabel")
			letterGui.Size = UDim2.new(0, LETTER_WIDTH, 0, LETTER_HEIGHT)
			letterGui.TextColor3 = Color3.fromRGB(255, 255, 255)
			letterGui.FontFace = FONT
			letterGui.BackgroundTransparency = 1
			letterGui.TextSize = TEXT_SIZE
			letterGui.Text = char
			letterGui.TextTransparency = 1
			letterGui.Parent = self.adornee
			applyEffects(effectTemplate, letterGui)
			local startPos = UDim2.new(0, currentX, 0, yPosition - 16)
			letterGui.Position = startPos
			local targetPos = UDim2.new(0, currentX, 0, yPosition)
			task.spawn(function()
				if segment.effect == "RAGE" then
					animateRageLetter(letterGui, startPos, targetPos)
				elseif segment.effect == "MAGIC" then
					animateMagicLetter(letterGui, startPos, targetPos)
				else
					animateNormalLetter(letterGui, targetPos)
				end
			end)
			currentX = currentX + LETTER_WIDTH
			wait(letterDelay)
		end
	end
end

return TextAnimation

Anyway’s y’all can take it, i just wanna to try to make a some like unity text animations.