diff --git a/Docs/FadeUi.md b/Docs/FadeUi.md
new file mode 100644
index 0000000..1f9b356
--- /dev/null
+++ b/Docs/FadeUi.md
@@ -0,0 +1,47 @@
+# FadeUi
+
+Fade UI elements in and out using `TweenService`. Automatically traverses all descendant `GuiBase` elements and tweens their transparency properties.
+
+## Methods
+
+### `FadeOut`
+
+`FadeOut(ui: GuiBase, transparencyInfo: TweenInfo, waitForEnd: boolean)` → `void`
+
+Fades out all descendant `GuiBase` elements of the given UI container by tweening their transparency properties to `1`.
+
+Handles the following instance types:
+
+- `TextLabel`, `TextButton`, `TextBox`: tweens `BackgroundTransparency` and `TextTransparency`
+- `ImageLabel`, `ImageButton`: tweens `BackgroundTransparency` and `ImageTransparency`
+- `Frame`: tweens `BackgroundTransparency`
+
+#### Parameters
+
+| Name | Type | Required |
+| ---------------- | ----------- | -------- |
+| ui | `GuiBase` | Yes |
+| transparencyInfo | `TweenInfo` | Yes |
+| waitForEnd | `boolean` | Yes |
+
+---
+
+### `FadeIn`
+
+`FadeIn(ui: GuiBase, transparencyInfo: TweenInfo, waitForEnd: boolean)` → `void`
+
+Fades in all descendant `GuiBase` elements of the given UI container by tweening their transparency properties to `0`.
+
+Handles the following instance types:
+
+- `TextLabel`, `TextButton`, `TextBox`: tweens `BackgroundTransparency` and `TextTransparency`
+- `ImageLabel`, `ImageButton`: tweens `BackgroundTransparency` and `ImageTransparency`
+- `Frame`: tweens `BackgroundTransparency`
+
+#### Parameters
+
+| Name | Type | Required |
+| ---------------- | ----------- | -------- |
+| ui | `GuiBase` | Yes |
+| transparencyInfo | `TweenInfo` | Yes |
+| waitForEnd | `boolean` | Yes |
diff --git a/Docs/TweenSequence.md b/Docs/TweenSequence.md
new file mode 100644
index 0000000..9bc9160
--- /dev/null
+++ b/Docs/TweenSequence.md
@@ -0,0 +1,92 @@
+# TweenSequence
+
+Play a sequence of tweens, waits, parallel tween groups, and callbacks with support for pausing and resuming mid-sequence.
+
+## Types
+
+### `TweenSequenceType`
+
+```
+{ number | Tween | {Tween} | () -> () }
+```
+
+A sequence is an ordered array of items. Each item is processed one at a time (except parallel groups), in the order it appears. Supported item types:
+
+| Type | Behaviour |
+| ------------- | ----------------------------------------------------------------------------------------------- |
+| `number` | Waits for that many seconds before advancing. |
+| `Tween` | Plays the tween and waits for it to complete before advancing. |
+| `{Tween}` | Plays all tweens in parallel. Waits for the longest one to complete before advancing. |
+| `() -> ()` | Calls the function immediately and advances. No waiting. |
+
+---
+
+## Constructor
+
+### `Create`
+
+`Create(sequence: TweenSequenceType)` → `TweenSequence`
+
+Creates a new `TweenSequence` from the provided sequence. Does not begin playback automatically.
+
+#### Parameters
+
+| Name | Type | Required |
+| -------- | -------------------- | -------- |
+| sequence | `TweenSequenceType` | Yes |
+
+#### Returns
+
+| Type |
+| --------------- |
+| `TweenSequence` |
+
+#### Example
+
+```lua
+local seq = TweenSequence.Create({
+ tweenA, -- plays tweenA, waits for completion
+ 1.5, -- waits 1.5 seconds
+ { tweenB, tweenC }, -- plays tweenB and tweenC in parallel, waits for longest
+ function() print("done!") end,
+})
+seq:Play()
+```
+
+---
+
+## Properties
+
+### `Playing`
+
+`Playing: boolean`
+
+`true` while the sequence is actively running. Set to `false` when the sequence completes or is paused.
+
+---
+
+## Methods
+
+### `Play`
+
+`Play()` → `void`
+
+Plays the sequence from the beginning. Each item in the sequence is processed in order.
+
+---
+
+### `Pause`
+
+`Pause()` → `void`
+
+Pauses the sequence at the current step. Any actively playing `Tween` instances at that step are also paused. The elapsed time within the current step is retained for use by [`Resume`](#resume).
+
+---
+
+### `Resume`
+
+`Resume()` → `void`
+
+Resumes the sequence from where it was paused. Completes the remaining duration of the current step (accounting for elapsed time) before advancing to subsequent steps.
+
+Does nothing if the sequence is already playing or has not been started.
diff --git a/Modules/FadeUi.luau b/Modules/FadeUi.luau
new file mode 100644
index 0000000..39b1c2f
--- /dev/null
+++ b/Modules/FadeUi.luau
@@ -0,0 +1,55 @@
+--!optimize 2
+--!strict
+local module = {}
+
+local TweenService = game:GetService("TweenService")
+
+-- Fades out all descendant GuiBase elements of the given UI container.
+-- Tweens BackgroundTransparency, TextTransparency, and ImageTransparency to 1.
+-- Optionally waits for the tween to finish before returning.
+function module.FadeOut(ui: GuiBase, transparencyInfo: TweenInfo, waitForEnd: boolean)
+ local uisToFade = {}
+ for _, v in ipairs(ui:GetDescendants()) do
+ if v:IsA("GuiBase") then
+ table.insert(uisToFade, v)
+ end
+ end
+ for _, v in ipairs(uisToFade) do
+ if v:IsA("TextLabel") or v:IsA("TextButton") or v:IsA("TextBox") then
+ TweenService:Create(v, transparencyInfo, { BackgroundTransparency = 1, TextTransparency = 1 }):Play()
+ elseif v:IsA("ImageLabel") or v:IsA("ImageButton") then
+ TweenService:Create(v, transparencyInfo, { BackgroundTransparency = 1, ImageTransparency = 1 }):Play()
+ elseif v:IsA("Frame") then
+ TweenService:Create(v, transparencyInfo, { BackgroundTransparency = 1 }):Play()
+ end
+ end
+ if waitForEnd == true then
+ task.wait(transparencyInfo.Time)
+ end
+end
+
+-- Fades in all descendant GuiBase elements of the given UI container.
+-- Tweens BackgroundTransparency, TextTransparency, and ImageTransparency to 0.
+-- Optionally waits for the tween to finish before returning.
+function module.FadeIn(ui: GuiBase, transparencyInfo: TweenInfo, waitForEnd: boolean)
+ local uisToFade = {}
+ for _, v in ipairs(ui:GetDescendants()) do
+ if v:IsA("GuiBase") then
+ table.insert(uisToFade, v)
+ end
+ end
+ for _, v in ipairs(uisToFade) do
+ if v:IsA("TextLabel") or v:IsA("TextButton") or v:IsA("TextBox") then
+ TweenService:Create(v, transparencyInfo, { BackgroundTransparency = 0, TextTransparency = 0 }):Play()
+ elseif v:IsA("ImageLabel") or v:IsA("ImageButton") then
+ TweenService:Create(v, transparencyInfo, { BackgroundTransparency = 0, ImageTransparency = 0 }):Play()
+ elseif v:IsA("Frame") then
+ TweenService:Create(v, transparencyInfo, { BackgroundTransparency = 0 }):Play()
+ end
+ end
+ if waitForEnd == true then
+ task.wait(transparencyInfo.Time)
+ end
+end
+
+return module
diff --git a/Modules/TweenSequence.luau b/Modules/TweenSequence.luau
new file mode 100644
index 0000000..9733e31
--- /dev/null
+++ b/Modules/TweenSequence.luau
@@ -0,0 +1,201 @@
+--!optimize 2
+--!strict
+local RunService = game:GetService("RunService")
+
+type TweenSequenceItem = number | Tween | { Tween } | () -> ()
+export type TweenSequenceType = { TweenSequenceItem }
+
+type CurrentState = {
+ thread: thread?,
+ index: number,
+ elapsed: number,
+ heartbeat: RBXScriptConnection?,
+ activeTweenIndices: { number },
+}
+
+type TweenSequenceImpl = {
+ __index: TweenSequenceImpl,
+ __tostring: (self: TweenSequence) -> string,
+ new: (sequence: TweenSequenceType) -> TweenSequence,
+ _connectHeartbeat: (self: TweenSequence) -> (),
+ _disconnectHeartbeat: (self: TweenSequence) -> (),
+ _runItem: (self: TweenSequence, item: TweenSequenceItem) -> (),
+ _resumeItem: (self: TweenSequence, item: TweenSequenceItem) -> (),
+ Play: (self: TweenSequence) -> (),
+ Pause: (self: TweenSequence) -> (),
+ Resume: (self: TweenSequence) -> (),
+}
+
+export type TweenSequence = typeof(setmetatable(
+ {} :: {
+ _sequence: TweenSequenceType,
+ _current: CurrentState,
+ Playing: boolean,
+ },
+ {} :: TweenSequenceImpl
+ ))
+
+local TweenSequence = {} :: TweenSequenceImpl
+TweenSequence.__index = TweenSequence
+
+-- Creates a new TweenSequence from a sequence of items.
+-- Items can be number (wait), Tween, {Tween} (parallel), or () -> () (callback).
+function TweenSequence.new(sequence: TweenSequenceType): TweenSequence
+ local self = setmetatable({}, TweenSequence)
+ self._sequence = sequence
+ self.Playing = false
+ self._current = {
+ thread = nil,
+ index = 0,
+ elapsed = 0,
+ heartbeat = nil,
+ activeTweenIndices = {},
+ }
+ return self
+end
+
+function TweenSequence:_connectHeartbeat()
+ self._current.heartbeat = RunService.Heartbeat:Connect(function(dt: number)
+ self._current.elapsed += dt
+ end)
+end
+
+function TweenSequence:_disconnectHeartbeat()
+ if self._current.heartbeat then
+ self._current.heartbeat:Disconnect()
+ self._current.heartbeat = nil
+ end
+end
+
+function TweenSequence:_runItem(item: TweenSequenceItem)
+ if typeof(item) == "number" then
+ task.wait(item)
+ elseif typeof(item) == "table" then
+ self._current.activeTweenIndices = {}
+ for tweenIndex, tween in item do
+ tween:Play()
+ table.insert(self._current.activeTweenIndices, tweenIndex)
+ tween.Completed:Connect(function()
+ local idx = table.find(self._current.activeTweenIndices, tweenIndex)
+ if idx then
+ table.remove(self._current.activeTweenIndices, idx)
+ end
+ end)
+ end
+ local longest: Tween = item[1]
+ for _, tween in item do
+ if tween.TweenInfo.Time > longest.TweenInfo.Time then
+ longest = tween
+ end
+ end
+ longest.Completed:Wait()
+ elseif typeof(item) == "function" then
+ item()
+ else
+ item:Play()
+ item.Completed:Wait()
+ end
+end
+
+function TweenSequence:_resumeItem(item: TweenSequenceItem)
+ if typeof(item) == "number" then
+ local remaining = item - self._current.elapsed
+ if remaining > 0 then
+ task.wait(remaining)
+ end
+ elseif typeof(item) == "table" then
+ for _, tweenIndex in self._current.activeTweenIndices do
+ item[tweenIndex]:Play()
+ end
+ if #self._current.activeTweenIndices > 0 then
+ local longest: Tween = item[self._current.activeTweenIndices[1]]
+ for _, tweenIndex in self._current.activeTweenIndices do
+ local tween: Tween = item[tweenIndex]
+ if tween.TweenInfo.Time > longest.TweenInfo.Time then
+ longest = tween
+ end
+ end
+ longest.Completed:Wait()
+ end
+ else
+ local tween = item :: Tween
+ tween:Play()
+ tween.Completed:Wait()
+ end
+end
+
+-- Plays the sequence from the beginning.
+-- Sets Playing to true for the duration of playback.
+function TweenSequence:Play()
+ self._current.thread = task.spawn(function()
+ self.Playing = true
+ for index, item in self._sequence do
+ self._current.index = index
+ self._current.elapsed = 0
+ self._current.activeTweenIndices = {}
+ self:_connectHeartbeat()
+ self:_runItem(item)
+ self:_disconnectHeartbeat()
+ end
+ self.Playing = false
+ end)
+end
+
+-- Pauses the sequence at the current step.
+-- Pauses any actively playing Tween instances at that step.
+function TweenSequence:Pause()
+ self:_disconnectHeartbeat()
+ if self._current.thread then
+ task.cancel(self._current.thread)
+ self._current.thread = nil
+ end
+ local item = self._sequence[self._current.index]
+ if item then
+ if typeof(item) == "table" then
+ for _, tween in item do
+ if tween.PlaybackState == Enum.PlaybackState.Playing then
+ tween:Pause()
+ end
+ end
+ elseif typeof(item) ~= "number" and typeof(item) ~= "function" then
+ item:Pause()
+ end
+ end
+ self.Playing = false
+end
+
+-- Resumes the sequence from where it was paused.
+-- Completes the remaining duration of the current step before advancing.
+function TweenSequence:Resume()
+ if self._current.index == 0 or self.Playing then
+ return
+ end
+ self._current.thread = task.spawn(function()
+ self.Playing = true
+ local startIndex = self._current.index
+ self:_connectHeartbeat()
+ self:_resumeItem(self._sequence[startIndex])
+ self:_disconnectHeartbeat()
+ for index = startIndex + 1, #self._sequence do
+ self._current.index = index
+ self._current.elapsed = 0
+ self._current.activeTweenIndices = {}
+ self:_connectHeartbeat()
+ self:_runItem(self._sequence[index])
+ self:_disconnectHeartbeat()
+ end
+ self.Playing = false
+ end)
+end
+
+function TweenSequence:__tostring(): string
+ return ("TweenSequence(Playing=%s, step=%d/%d)"):format(
+ tostring(self.Playing),
+ self._current.index,
+ #self._sequence
+ )
+end
+
+return {
+ Create = TweenSequence.new,
+}