-- Services local TweenService = game:GetService("TweenService") local UserInputService = game:GetService("UserInputService") local TextService = game:GetService("TextService") local Players = game:GetService("Players") -- Utility: Dynamic Instance Constructor local function Create(className, properties) local instance = Instance.new(className) for property, value in pairs(properties) do instance[property] = value end return instance end -- Centralized Theme System local Theme = { Background = Color3.fromRGB(25, 25, 25), Elevated = Color3.fromRGB(35, 35, 35), Text = Color3.fromRGB(240, 240, 240), TextSecondary = Color3.fromRGB(180, 180, 180), Accent = Color3.fromRGB(0, 120, 215), Border = Color3.fromRGB(60, 60, 60), Hover = Color3.fromRGB(45, 45, 45), Font = Enum.Font.Gotham, TitleFont = Enum.Font.GothamBold, Padding = 10, CornerRadius = 8, UIScale = 0.75 } -- Janitor for Resource Management local Janitor = {} Janitor.__index = Janitor function Janitor.new() return setmetatable({_tasks = {}}, Janitor) end function Janitor:Add(task) table.insert(self._tasks, task) return task end function Janitor:Destroy() for _, task in ipairs(self._tasks) do if type(task) == "function" then task() elseif typeof(task) == "RBXScriptConnection" then task:Disconnect() elseif typeof(task) == "Instance" then task:Destroy() end end self._tasks = {} end -- Custom Signal Class local Signal = {} Signal.__index = Signal function Signal.new() return setmetatable({_connections = {}}, Signal) end function Signal:Connect(callback) local connection = {Connected = true} connection.Disconnect = function() connection.Connected = false for i, conn in ipairs(self._connections) do if conn == connection then table.remove(self._connections, i) break end end end connection.Callback = callback table.insert(self._connections, connection) return connection end function Signal:Fire(...) for _, connection in ipairs(self._connections) do if connection.Connected then task.spawn(connection.Callback, ...) end end end -- Paragraph Module local Paragraph = {} Paragraph.__index = Paragraph function Paragraph.new(config) local self = setmetatable({}, Paragraph) self._janitor = Janitor.new() self.OnClose = Signal.new() self._isVisible = true self._title = config.Title or "Information" self._content = config.Content or "This is a paragraph of text." self._width = config.Width or 400 self._maxHeight = config.MaxHeight or 500 self:_createUI(config) self:_setupInteractions() return self end function Paragraph:_createUI(config) -- Main Container self.Container = self._janitor:Add(Create("Frame", { Size = UDim2.new(0, self._width, 0, 100), Position = UDim2.new(0.5, 0, 0.5, 0), AnchorPoint = Vector2.new(0.5, 0.5), BackgroundColor3 = Theme.Elevated, BorderSizePixel = 0, Parent = config.Parent or Players.LocalPlayer:WaitForChild("PlayerGui"):WaitForChild("ScreenGui") or Create("ScreenGui", {Parent = Players.LocalPlayer:WaitForChild("PlayerGui")}) })) Create("UICorner", { CornerRadius = UDim.new(0, Theme.CornerRadius), Parent = self.Container }) Create("UIStroke", { Color = Theme.Border, Thickness = 1, Parent = self.Container }) Create("UIScale", { Scale = Theme.UIScale, Parent = self.Container }) -- Header Bar self.Header = Create("Frame", { Size = UDim2.new(1, 0, 0, 45), BackgroundColor3 = Theme.Background, BorderSizePixel = 0, Parent = self.Container }) Create("UICorner", { CornerRadius = UDim.new(0, Theme.CornerRadius), Parent = self.Header }) -- Title self.TitleLabel = Create("TextLabel", { Size = UDim2.new(1, -50, 1, 0), Position = UDim2.new(0, Theme.Padding, 0, 0), BackgroundTransparency = 1, Text = self._title, TextColor3 = Theme.Text, Font = Theme.TitleFont, TextSize = 16, TextXAlignment = Enum.TextXAlignment.Left, TextYAlignment = Enum.TextYAlignment.Center, Parent = self.Header }) -- Close Button self.CloseButton = Create("TextButton", { Size = UDim2.new(0, 35, 0, 35), Position = UDim2.new(1, -40, 0.5, 0), AnchorPoint = Vector2.new(0, 0.5), BackgroundColor3 = Theme.Background, BackgroundTransparency = 1, Text = "✕", TextColor3 = Theme.TextSecondary, Font = Theme.Font, TextSize = 18, Parent = self.Header }) Create("UICorner", { CornerRadius = UDim.new(0, 6), Parent = self.CloseButton }) -- Content Container with Scrolling self.ContentFrame = Create("ScrollingFrame", { Size = UDim2.new(1, -20, 1, -55), Position = UDim2.new(0, 10, 0, 50), BackgroundTransparency = 1, BorderSizePixel = 0, ScrollBarThickness = 4, ScrollBarImageColor3 = Theme.Border, CanvasSize = UDim2.new(0, 0, 0, 0), AutomaticCanvasSize = Enum.AutomaticSize.Y, Parent = self.Container }) -- Content Text self.ContentLabel = Create("TextLabel", { Size = UDim2.new(1, -10, 0, 0), AutomaticSize = Enum.AutomaticSize.Y, BackgroundTransparency = 1, Text = self._content, TextColor3 = Theme.Text, Font = Theme.Font, TextSize = 14, TextXAlignment = Enum.TextXAlignment.Left, TextYAlignment = Enum.TextYAlignment.Top, TextWrapped = true, RichText = true, LineHeight = 1.4, Parent = self.ContentFrame }) Create("UIPadding", { PaddingLeft = UDim.new(0, 5), PaddingRight = UDim.new(0, 5), PaddingTop = UDim.new(0, 5), PaddingBottom = UDim.new(0, 10), Parent = self.ContentFrame }) -- Auto-resize container based on content task.wait() self:_updateSize() end function Paragraph:_updateSize() local contentHeight = self.ContentLabel.AbsoluteSize.Y + 65 local finalHeight = math.min(contentHeight, self._maxHeight) TweenService:Create(self.Container, TweenInfo.new(0.3, Enum.EasingStyle.Quad, Enum.EasingDirection.Out), { Size = UDim2.new(0, self._width, 0, finalHeight) }):Play() end function Paragraph:_setupInteractions() -- Close button hover effect local hoverTween self._janitor:Add(self.CloseButton.MouseEnter:Connect(function() if hoverTween then hoverTween:Cancel() end hoverTween = TweenService:Create(self.CloseButton, TweenInfo.new(0.15, Enum.EasingStyle.Quad), { BackgroundTransparency = 0, BackgroundColor3 = Theme.Hover, TextColor3 = Theme.Text }) hoverTween:Play() end)) self._janitor:Add(self.CloseButton.MouseLeave:Connect(function() if hoverTween then hoverTween:Cancel() end hoverTween = TweenService:Create(self.CloseButton, TweenInfo.new(0.15, Enum.EasingStyle.Quad), { BackgroundTransparency = 1, TextColor3 = Theme.TextSecondary }) hoverTween:Play() end)) -- Close button click self._janitor:Add(self.CloseButton.MouseButton1Click:Connect(function() self:Close() end)) -- Make draggable self:_makeDraggable() end function Paragraph:_makeDraggable() local dragging, dragInput, dragStart, startPos self._janitor:Add(self.Header.InputBegan:Connect(function(input) if input.UserInputType == Enum.UserInputType.MouseButton1 or input.UserInputType == Enum.UserInputType.Touch then dragging = true dragStart = input.Position startPos = self.Container.Position input.Changed:Connect(function() if input.UserInputState == Enum.UserInputState.End then dragging = false end end) end end)) self._janitor:Add(self.Header.InputChanged:Connect(function(input) if input.UserInputType == Enum.UserInputType.MouseMovement or input.UserInputType == Enum.UserInputType.Touch then dragInput = input end end)) self._janitor:Add(UserInputService.InputChanged:Connect(function(input) if input == dragInput and dragging then local delta = input.Position - dragStart self.Container.Position = UDim2.new( startPos.X.Scale, startPos.X.Offset + delta.X, startPos.Y.Scale, startPos.Y.Offset + delta.Y ) end end)) end function Paragraph:SetTitle(newTitle) self._title = newTitle self.TitleLabel.Text = newTitle TweenService:Create(self.TitleLabel, TweenInfo.new(0.2, Enum.EasingStyle.Quad), { TextColor3 = Theme.Accent }):Play() task.delay(0.2, function() TweenService:Create(self.TitleLabel, TweenInfo.new(0.3, Enum.EasingStyle.Quad), { TextColor3 = Theme.Text }):Play() end) end function Paragraph:SetContent(newContent) self._content = newContent self.ContentLabel.Text = newContent task.wait() self:_updateSize() end function Paragraph:Toggle() self._isVisible = not self._isVisible self.Container.Visible = self._isVisible if self._isVisible then self:_playShowAnimation() end end function Paragraph:Show() self._isVisible = true self.Container.Visible = true self:_playShowAnimation() end function Paragraph:_playShowAnimation() self.Container.Size = UDim2.new(0, self._width, 0, 0) local contentHeight = self.ContentLabel.AbsoluteSize.Y + 65 local finalHeight = math.min(contentHeight, self._maxHeight) TweenService:Create(self.Container, TweenInfo.new(0.4, Enum.EasingStyle.Back, Enum.EasingDirection.Out), { Size = UDim2.new(0, self._width, 0, finalHeight) }):Play() end function Paragraph:Close() local tween = TweenService:Create(self.Container, TweenInfo.new(0.3, Enum.EasingStyle.Quad, Enum.EasingDirection.In), { Size = UDim2.new(0, self._width, 0, 0) }) tween:Play() tween.Completed:Connect(function() self._isVisible = false self.Container.Visible = false self.OnClose:Fire() end) end function Paragraph:Destroy() self._janitor:Destroy() end -- Example Usage local screenGui = Create("ScreenGui", { Parent = Players.LocalPlayer:WaitForChild("PlayerGui"), ResetOnSpawn = false }) local exampleContent = [[Welcome to the Professional Paragraph System! This UI component demonstrates several advanced features including rich text formatting, automatic content sizing, smooth animations, and responsive design patterns. Key Features: • Dynamic height adjustment based on content length • Smooth expand and collapse animations using TweenService • Draggable header for repositioning • Scrollable content area for lengthy paragraphs • Clean, professional dark theme design • Proper resource management with Janitor pattern The text supports bold formatting, italic text, and underlined content. You can also use custom colors to highlight important information. Try dragging this window around by clicking and holding the header bar. The close button in the top-right corner will smoothly animate the window closed when clicked.]] local paragraph = Paragraph.new({ Parent = screenGui, Title = "System Documentation", Content = exampleContent, Width = 450, MaxHeight = 550 }) -- Listen for close event paragraph.OnClose:Connect(function() print("Paragraph closed") end) -- Example: Toggle with keybind UserInputService.InputBegan:Connect(function(input, gameProcessed) if not gameProcessed and input.KeyCode == Enum.KeyCode.P then paragraph:Toggle() end end) -- Example: Update content dynamically task.delay(5, function() paragraph:SetTitle("Updated Information") paragraph:SetContent("This content was updated after 5 seconds to demonstrate the dynamic updating capabilities of the paragraph system. The container automatically resizes to fit the new content!") end)