local _, ns = ...
local SlickUI = ns.SlickUI
local Theme = SlickUI.Theme
local COOLDOWN_SPELL_LIMIT = 50

local Options = {
	Pages = {},
	Tabs = {},
	SelectedUnit = "player",
	SelectedAuraUnit = "target",
	UnitSection = "personal",
	SelectedInformationBar = "top1",
	SelectedActionBar = "main",
}
SlickUI.Options = Options

function Options:EnablePermanentSettingsDrag()
	local settingsFrame = _G.SettingsPanel
	if not settingsFrame or not settingsFrame.SetMovable then return false end
	settingsFrame:SetMovable(true)
	settingsFrame:SetClampedToScreen(true)

	if self.SettingsDragHandle then
		self.SettingsDragHandle:Show()
		return true
	end

	-- Blizzard does not expose its Options title bar as a dependable drag
	-- surface. Add a narrow mouse-only layer over that bar so the complete
	-- Options window is always movable. The right edge remains clear for the
	-- native close button, and the search field below is not covered.
	local handle = CreateFrame("Frame", "SlickUISettingsPanelDragHandle", settingsFrame)
	handle:SetPoint("TOPLEFT", settingsFrame, "TOPLEFT", 8, -3)
	handle:SetPoint("TOPRIGHT", settingsFrame, "TOPRIGHT", -48, -3)
	handle:SetHeight(31)
	handle:SetFrameLevel(settingsFrame:GetFrameLevel() + 20)
	handle:EnableMouse(true)
	handle:RegisterForDrag("LeftButton")
	handle:SetScript("OnDragStart", function()
		if InCombatLockdown() then return end
		settingsFrame:SetMovable(true)
		settingsFrame:StartMoving()
	end)
	handle:SetScript("OnDragStop", function()
		settingsFrame:StopMovingOrSizing()
	end)
	handle:SetScript("OnEnter", function(self)
		GameTooltip:SetOwner(self, "ANCHOR_CURSOR")
		GameTooltip:SetText("Move Options", 1, 0.82, 0)
		GameTooltip:AddLine("Drag this title bar to move the complete control panel.", 1, 1, 1)
		GameTooltip:Show()
	end)
	handle:SetScript("OnLeave", GameTooltip_Hide)
	self.SettingsDragHandle = handle
	return true
end

local function DeepCopy(value, seen)
	if type(value) ~= "table" then return value end
	seen = seen or {}
	if seen[value] then return seen[value] end
	local copy = {}
	seen[value] = copy
	for key, child in pairs(value) do
		copy[DeepCopy(key, seen)] = DeepCopy(child, seen)
	end
	return copy
end

function Options:CreateResetBackup(reason)
	if type(SlickUIDB) ~= "table" then return false end
	SlickUIBackupDB = {
		version = SlickUI.Version,
		created = time(),
		reason = reason or "SlickUI reset",
		data = DeepCopy(SlickUIDB),
	}
	return true
end

function Options:HasResetBackup()
	return type(SlickUIBackupDB) == "table" and type(SlickUIBackupDB.data) == "table"
end

StaticPopupDialogs.SLICKUI_RESET_ALL_SETTINGS = {
	text = "Reset all SlickUI settings?\n\nEvery option and frame position will return to its default. SlickUI will save one recovery backup before reloading the interface.",
	button1 = "Reset Everything",
	button2 = CANCEL,
	OnAccept = function()
		if InCombatLockdown() then
			SlickUI:Print("Settings cannot be reset during combat. Try again after combat ends.")
			return
		end
		Options:CreateResetBackup("Reset All SlickUI Settings")
		SlickUIDB = nil
		ReloadUI()
	end,
	timeout = 0,
	whileDead = true,
	hideOnEscape = true,
	preferredIndex = 3,
}

StaticPopupDialogs.SLICKUI_RESTORE_SETTINGS_BACKUP = {
	text = "Restore the SlickUI settings saved before your last reset?\n\nThis replaces the current SlickUI settings and reloads the interface.",
	button1 = "Restore Backup",
	button2 = CANCEL,
	OnAccept = function()
		if InCombatLockdown() then
			SlickUI:Print("Settings cannot be restored during combat. Try again after combat ends.")
			return
		end
		if not Options:HasResetBackup() then
			SlickUI:Print("No reset backup is available.")
			return
		end
		SlickUIDB = DeepCopy(SlickUIBackupDB.data)
		ReloadUI()
	end,
	timeout = 0,
	whileDead = true,
	hideOnEscape = true,
	preferredIndex = 3,
}

local function CreateButton(parent, text, width)
	local button = CreateFrame("Button", nil, parent, "UIPanelButtonTemplate")
	button:SetSize(width or 150, 24)
	button:SetText(text)
	return button
end

local function CreateCheckbox(parent, text)
	local checkbox = CreateFrame("CheckButton", nil, parent, "UICheckButtonTemplate")
	checkbox:SetSize(26, 26)
	checkbox.Text:SetText(text)
	Theme:SetFont(checkbox.Text, 12)
	return checkbox
end

local function CallBlizzardSecurely(callback, ...)
	if type(callback) ~= "function" then return false end
	if type(securecallfunction) == "function" then
		return pcall(securecallfunction, callback, ...)
	end
	return pcall(callback, ...)
end

local function CreateCooldownSpellRows(scrollFrame, rowWidth)
	local rowHeight, rowStep = 24, 25
	local scrollChild = CreateFrame("Frame", nil, scrollFrame)
	scrollChild:SetSize(rowWidth, COOLDOWN_SPELL_LIMIT * rowStep)
	scrollFrame:SetScrollChild(scrollChild)

	local rows = {}
	for index = 1, COOLDOWN_SPELL_LIMIT do
		local row = CreateFrame("Frame", nil, scrollChild, "BackdropTemplate")
		row:SetSize(rowWidth, rowHeight)
		row:SetPoint("TOPLEFT", scrollChild, "TOPLEFT", 0, -((index - 1) * rowStep))
		local background = row:CreateTexture(nil, "BACKGROUND")
		background:SetAllPoints()
		background:SetColorTexture(0.06, 0.07, 0.09, 0.92)
		local icon = row:CreateTexture(nil, "ARTWORK")
		icon:SetSize(20, 20)
		icon:SetPoint("LEFT", 2, 0)
		icon:SetTexCoord(0.08, 0.92, 0.08, 0.92)
		row.Icon = icon
		local name = Theme:CreateText(row, 9)
		name:SetPoint("LEFT", icon, "RIGHT", 8, 0)
		name:SetPoint("RIGHT", row, "RIGHT", -68, 0)
		name:SetJustifyH("LEFT")
		row.Name = name
		local remove = CreateFrame("Button", nil, row, "UIPanelButtonTemplate")
		remove:SetSize(58, 18)
		remove:SetPoint("RIGHT", -4, 0)
		remove:SetText("Remove")
		remove:SetScript("OnClick", function()
			if row.SpellID then Options:RemoveCooldownSpell(row.SpellID) end
		end)
		row:Hide()
		rows[index] = row
	end
	return rows, scrollChild
end

local SliderIndex = 0
local function CreateSlider(parent, labelText, minimum, maximum, step, width, onChanged, formatter)
	SliderIndex = SliderIndex + 1
	local sliderName = "SlickUIOptionsSlider" .. SliderIndex
	local slider = CreateFrame("Slider", sliderName, parent, "OptionsSliderTemplate")
	slider:SetSize(width or 180, 16)
	slider:SetMinMaxValues(minimum, maximum)
	slider:SetValueStep(step)
	slider:SetObeyStepOnDrag(true)

	local label = Theme:CreateText(slider, 10)
	label:SetPoint("BOTTOMLEFT", slider, "TOPLEFT", 0, 4)
	label:SetText(labelText)
	label:SetTextColor(unpack(Theme.Colors.mutedText))
	slider.Label = label

	slider.Formatter = formatter or function(number) return tostring(math.floor(number + 0.5)) end
	slider.Minimum = minimum
	slider.Maximum = maximum
	slider.Step = step

	local value = CreateFrame("EditBox", nil, slider, "InputBoxTemplate")
	value:SetSize(52, 20)
	value:SetPoint("BOTTOMRIGHT", slider, "TOPRIGHT", 0, 2)
	value:SetAutoFocus(false)
	value:SetMaxLetters(7)
	value:SetJustifyH("CENTER")
	Theme:SetFont(value, 10)
	value:SetTextColor(unpack(Theme.Colors.goldText))
	slider.ValueEdit = value

	local low = _G[sliderName .. "Low"]
	local high = _G[sliderName .. "High"]
	local templateText = _G[sliderName .. "Text"]
	if low then low:Hide() end
	if high then high:Hide() end
	if templateText then templateText:Hide() end

	slider:SetScript("OnValueChanged", function(self, newValue)
		local rounded = math.floor(newValue / step + 0.5) * step
		if not self.ValueEdit:HasFocus() then
			self.ValueEdit:SetText(self.Formatter(rounded))
		end
		if not self.SuppressUpdate then
			onChanged(rounded)
		end
	end)

	local function CommitTypedValue(editBox)
		local typedValue = tonumber(editBox:GetText())
		if not typedValue then
			typedValue = slider:GetValue()
		end
		typedValue = math.max(slider.Minimum, math.min(slider.Maximum, typedValue))
		typedValue = math.floor(typedValue / slider.Step + 0.5) * slider.Step
		slider:SetValue(typedValue)
		editBox:SetText(slider.Formatter(typedValue))
		editBox:ClearFocus()
	end
	value:SetScript("OnEnterPressed", CommitTypedValue)
	value:SetScript("OnEditFocusLost", CommitTypedValue)
	value:SetScript("OnEscapePressed", function(self)
		self:SetText(slider.Formatter(slider:GetValue()))
		self:ClearFocus()
	end)
	return slider
end

local function SetSliderValue(slider, value)
	slider.SuppressUpdate = true
	slider:SetValue(value)
	slider.ValueEdit:SetText(slider.Formatter(value))
	slider.SuppressUpdate = false
end

local function CreateTab(parent, key, text, yOffset)
	local tab = CreateFrame("Button", nil, parent, "BackdropTemplate")
	tab:SetSize(118, 32)
	tab:SetPoint("TOPLEFT", 8, yOffset)
	tab:SetBackdrop({ bgFile = Theme.Texture, edgeFile = Theme.Texture, edgeSize = 1 })
	tab:SetBackdropColor(unpack(Theme.Colors.panelSoft))
	tab:SetBackdropBorderColor(unpack(Theme.Colors.borderMuted))

	local label = Theme:CreateText(tab, 12)
	label:SetPoint("LEFT", 12, 0)
	label:SetText(text)
	label:SetTextColor(unpack(Theme.Colors.mutedText))
	tab.Label = label
	tab.PageKey = key

	tab:SetScript("OnClick", function(self)
		Options:SelectPage(self.PageKey)
	end)
	tab:SetScript("OnEnter", function(self)
		if not self.Selected then
			self:SetBackdropBorderColor(unpack(Theme.Colors.border))
			self.Label:SetTextColor(unpack(Theme.Colors.text))
		end
	end)
	tab:SetScript("OnLeave", function(self)
		Options:RefreshTabs()
	end)

	Options.Tabs[key] = tab
	return tab
end

function Options:RefreshTabs()
	for key, tab in pairs(self.Tabs) do
		local selected = key == self.SelectedPage
		tab.Selected = selected
		if selected then
			tab:SetBackdropColor(0.18, 0.13, 0.035, 0.98)
			tab:SetBackdropBorderColor(unpack(Theme.Colors.border))
			tab.Label:SetTextColor(unpack(Theme.Colors.goldText))
		else
			tab:SetBackdropColor(unpack(Theme.Colors.panelSoft))
			tab:SetBackdropBorderColor(unpack(Theme.Colors.borderMuted))
			tab.Label:SetTextColor(unpack(Theme.Colors.mutedText))
		end
	end
end

function Options:SelectPage(pageKey)
	if not self.Pages[pageKey] then
		pageKey = "general"
	end

	self.SelectedPage = pageKey
	if SlickUI.DB then
		SlickUI.DB.optionsPage = pageKey
	end

	for key, page in pairs(self.Pages) do
		page:SetShown(key == pageKey)
	end
	self:RefreshTabs()
end

function Options:Refresh()
	if not self.Panel then
		return
	end

	self.Panel.SnapGridCheckbox:SetChecked(SlickUI.DB.editMode.snapToGrid)
	self.Panel.SnapFramesCheckbox:SetChecked(SlickUI.DB.editMode.snapToFrames)
	SetSliderValue(self.Panel.GridSizeSlider, SlickUI.DB.editMode.gridSize)
	self.Panel.CastbarCheckbox:SetChecked(SlickUI.DB.showCastbars)
	self.Panel.MinimapCheckbox:SetChecked(not SlickUI.DB.minimap.hide)
	self.Panel.QueueStatusCheckbox:SetChecked(SlickUI.DB.queueStatus.enabled)
	self.Panel.QuestTrackerCheckbox:SetChecked(SlickUI.DB.questTracker.enabled)
	self.Panel.GroupControlsCheckbox:SetChecked(SlickUI.DB.groupControls.enabled)
	if self.LustAnnounceCheckbox then
		self.LustAnnounceCheckbox:SetChecked(SlickUI.DB.groupControls.lustAnnounce == true)
	end
	if self.PullAnnounceCheckbox then
		self.PullAnnounceCheckbox:SetChecked(SlickUI.DB.groupControls.pullAnnounce == true)
	end
	if self.PinkPonyClubCheckbox then
		self.PinkPonyClubCheckbox:SetChecked(SlickUI.DB.cute and SlickUI.DB.cute.pinkPonyClub == true)
	end
	if self.AFKModeCheckbox then
		self.AFKModeCheckbox:SetChecked(SlickUI.DB.funStuff and SlickUI.DB.funStuff.afkMode == true)
	end
	self.Panel.FlightTimerCheckbox:SetChecked(SlickUI.DB.flightTimer.enabled)
	self.Panel.ChatCopyButtonCheckbox:SetChecked(SlickUI.DB.chatTools.showCopyButton ~= false)
	self.Panel.ReputationTrackerCheckbox:SetChecked(SlickUI.ReputationTracker:IsEnabled())
	self.Panel.SkipSeenCinematicsCheckbox:SetChecked(SlickUI.DB.cinematics.skipPreviouslySeen == true)
	self:RefreshGroupMarkerControls()
	self.Panel.ErrorCatcherCheckbox:SetChecked(SlickUI.DB.errorCatcher.enabled)
	self.Panel.ErrorSoundCheckbox:SetChecked(SlickUI.DB.errorCatcher.sound)
	self.Panel.ErrorSoundCheckbox:SetEnabled(SlickUI.DB.errorCatcher.enabled)
	if self.CraftingEnabledCheckbox then self.CraftingEnabledCheckbox:SetChecked(SlickUI.DB.crafting.enabled ~= false) end
	if self.CraftingStockTargetSlider then SetSliderValue(self.CraftingStockTargetSlider, tonumber(SlickUI.DB.crafting.stockTarget) or 3) end
	if self.Panel.RestoreSettingsButton then
		self.Panel.RestoreSettingsButton:SetEnabled(self:HasResetBackup())
	end
	self.Panel.EditModeButton:SetText(SlickUI.DB.locked and "Unlock & Move Frames" or "Lock & Finish Moving")
	self.Panel.SellGreysCheckbox:SetChecked(SlickUI.DB.vendorAutomation.sellGreys)
	self.Panel.AutoRepairCheckbox:SetChecked(SlickUI.DB.vendorAutomation.autoRepair)
	local repairMode = SlickUI.DB.vendorAutomation.repairFunds
	UIDropDownMenu_SetSelectedValue(self.Panel.RepairFundsDropdown, repairMode)
	UIDropDownMenu_SetText(self.Panel.RepairFundsDropdown, SlickUI.VendorAutomation.RepairFundLabels[repairMode] or "Personal Funds")
	self.Panel.RepairFundsDropdown:SetAlpha(SlickUI.DB.vendorAutomation.autoRepair and 1 or 0.48)
	if self.Panel.RepairFundsDropdown.Button then self.Panel.RepairFundsDropdown.Button:SetEnabled(SlickUI.DB.vendorAutomation.autoRepair) end
	self:SelectPage(SlickUI.DB.optionsPage or "general")
	self:RefreshUnitFrameControls()
	self:RefreshAuraControls()
	self:RefreshNameplateControls()
	self:RefreshActionBarControls()
	self:RefreshBagControls()
	self:RefreshAlertControls()
	self:RefreshClassBuffControls()
	self:RefreshMinimapButtonBarControls()
	self:RefreshQuestTrackerControls()
	self:RefreshInformationBarControls()
	self:RefreshSystemFontControls()
	self:RefreshPetsAndMountsControls()
end

function Options:CreateCraftingPage(parent)
	local page = CreateFrame("Frame", nil, parent)
	page:SetAllPoints()
	local title = Theme:CreateText(page, 18)
	title:SetPoint("TOPLEFT", 0, 0); title:SetText("Crafting"); title:SetTextColor(unpack(Theme.Colors.goldText))
	local description = Theme:CreateText(page, 10)
	description:SetPoint("TOPLEFT", title, "BOTTOMLEFT", 0, -8); description:SetPoint("RIGHT", page, "RIGHT", 0, 0)
	description:SetJustifyH("LEFT")
	description:SetText("Control SlickUI's crafting queue, Shopping List, Auction House assistance, and vendor-material helper.")
	description:SetTextColor(unpack(Theme.Colors.mutedText))
	local enabled = CreateCheckbox(page, "Enable crafting help")
	enabled:SetPoint("TOPLEFT", description, "BOTTOMLEFT", -4, -18)
	enabled:SetScript("OnClick", function(self)
		SlickUI.CraftingLoader:SetEnabled(self:GetChecked() == true)
		Options:Refresh()
	end)
	self.CraftingEnabledCheckbox = enabled
	local stockTarget = CreateSlider(page, "Default crafted-item stock target", 0, 20, 1, 240, function(value)
		SlickUI.DB.crafting.stockTarget = math.floor(value + 0.5)
		local planner = SlickUI.CraftingPlanner
		if planner and planner.DashboardWindow and planner.DashboardWindow:IsShown() then planner:RefreshDashboardRows() end
	end)
	stockTarget:SetPoint("TOPLEFT", enabled, "BOTTOMLEFT", 4, -42)
	self.CraftingStockTargetSlider = stockTarget
	local stockHelp = Theme:CreateText(page, 9)
	stockHelp:SetPoint("TOPLEFT", stockTarget, "BOTTOMLEFT", 0, -10); stockHelp:SetPoint("RIGHT", page, "RIGHT", -20, 0)
	stockHelp:SetJustifyH("LEFT"); stockHelp:SetText("Dashboard stock combines carried items, known bank inventory, and your latest owned-auction scan. The default target is 3."); stockHelp:SetTextColor(unpack(Theme.Colors.mutedText))
	self.Pages.crafting = page
end

function Options:RefreshPetsAndMountsControls()
	local controls = self.PetsAndMountsControls
	if not controls or not SlickUI.DB.petsAndMounts then return end
	local filters = SlickUI.DB.petsAndMounts.filters
	controls.Ground:SetChecked(filters.ground == true)
	controls.Flying:SetChecked(filters.flying == true)
	controls.Aquatic:SetChecked(filters.aquatic == true)
	controls.RideAlong:SetChecked(filters.rideAlong == true)
	controls.Count:SetText("Stored collected mounts: " .. #(SlickUI.DB.petsAndMounts.mountPool or {}))
end

function Options:CreatePetsAndMountsPage(parent)
	local page = CreateFrame("Frame", nil, parent)
	page:SetAllPoints()
	local title = Theme:CreateText(page, 18)
	title:SetPoint("TOPLEFT", 0, 0); title:SetText("Pets & Mounts"); title:SetTextColor(unpack(Theme.Colors.goldText))
	local description = Theme:CreateText(page, 10)
	description:SetPoint("TOPLEFT", title, "BOTTOMLEFT", 0, -8); description:SetPoint("RIGHT", page, "RIGHT", 0, 0)
	description:SetJustifyH("LEFT"); description:SetText("Summon a random companion or build a filtered pool of collected mounts. Mount filters are inclusive.")
	description:SetTextColor(unpack(Theme.Colors.mutedText))

	local pet = CreateButton(page, "Summon Random Pet", 190)
	pet:SetPoint("TOPLEFT", description, "BOTTOMLEFT", 0, -24)
	pet:SetScript("OnClick", function() SlickUI.PetsAndMounts:SummonRandomPet() end)
	local mount = CreateButton(page, "Summon Random Mount", 190)
	mount:SetPoint("LEFT", pet, "RIGHT", 18, 0)
	mount:SetScript("OnClick", function() SlickUI.PetsAndMounts:SummonRandomMount() end)

	local heading = Theme:CreateText(page, 15)
	heading:SetPoint("TOPLEFT", pet, "BOTTOMLEFT", 0, -28); heading:SetText("Mount Types"); heading:SetTextColor(unpack(Theme.Colors.goldText))
	local controls = {}
	local function AddFilter(label, key, x, y)
		local checkbox = CreateCheckbox(page, label)
		checkbox:SetPoint("TOPLEFT", heading, "BOTTOMLEFT", x, y)
		checkbox:SetScript("OnClick", function(self)
			SlickUI.DB.petsAndMounts.filters[key] = self:GetChecked() == true
			local module = SlickUI.PetsAndMounts
			module:QueueNextMount(module:GetEligibleMounts())
			module:RefreshMacroIcon()
		end)
		return checkbox
	end
	controls.Ground = AddFilter("Ground", "ground", 0, -12)
	controls.Flying = AddFilter("Flying", "flying", 190, -12)
	controls.Aquatic = AddFilter("Aquatic", "aquatic", 0, -50)
	controls.RideAlong = AddFilter("Ride Along / Passengers", "rideAlong", 190, -50)

	local rebuild = CreateButton(page, "Rebuild Stored Mounts", 190)
	rebuild:SetPoint("TOPLEFT", heading, "BOTTOMLEFT", 0, -96)
	rebuild:SetScript("OnClick", function() SlickUI.PetsAndMounts:RebuildMountPool(false) end)
	local macro = CreateButton(page, "Pick Up SlickMount Macro", 230)
	macro:SetPoint("LEFT", rebuild, "RIGHT", 18, 0)
	macro:SetScript("OnClick", function() SlickUI.PetsAndMounts:CreateOrUpdateMacro() end)
	local count = Theme:CreateText(page, 10)
	count:SetPoint("TOPLEFT", rebuild, "BOTTOMLEFT", 0, -14); count:SetTextColor(unpack(Theme.Colors.mutedText))
	controls.Count = count
	local hint = Theme:CreateText(page, 10)
	hint:SetPoint("TOPLEFT", count, "BOTTOMLEFT", 0, -15); hint:SetPoint("RIGHT", page, "RIGHT", 0, 0); hint:SetJustifyH("LEFT")
	hint:SetText("SlickMount chooses from the stored collected pool and rechecks usability when pressed. If no boxes are checked, every usable collected mount is eligible. Rebuild after learning new mounts.")
	hint:SetTextColor(unpack(Theme.Colors.mutedText))
	self.PetsAndMountsControls = controls
	self.Pages.petsmounts = page
end

function Options:CreateFlightExportWindow()
	if self.FlightExportWindow then return self.FlightExportWindow end
	local window = CreateFrame("Frame", "SlickUIFlightRouteExport", UIParent, "BackdropTemplate")
	window:SetSize(760, 480)
	window:SetPoint("CENTER")
	window:SetFrameStrata("DIALOG")
	window:SetClampedToScreen(true)
	Theme:CreatePanel(window)
	Theme:CreateHeaderIcon(window, 36, 14, -9)
	local title = Theme:CreateText(window, 18)
	title:SetPoint("TOPLEFT", 56, -16)
	title:SetText("SlickUI Flight Route Export")
	title:SetTextColor(unpack(Theme.Colors.goldText))
	local close = CreateFrame("Button", nil, window, "UIPanelCloseButton")
	close:SetPoint("TOPRIGHT", -4, -4)
	local help = Theme:CreateText(window, 11)
	help:SetPoint("TOPLEFT", title, "BOTTOMLEFT", 0, -8)
	help:SetText("Click Select All, press Ctrl+C, then paste the route data into the development chat.")
	help:SetTextColor(unpack(Theme.Colors.mutedText))
	window.Help = help
	local scroll = CreateFrame("ScrollFrame", nil, window, "UIPanelScrollFrameTemplate")
	scroll:SetPoint("TOPLEFT", 18, -70)
	scroll:SetPoint("BOTTOMRIGHT", -38, 52)
	local edit = CreateFrame("EditBox", nil, scroll)
	edit:SetMultiLine(true)
	edit:SetAutoFocus(false)
	edit:SetWidth(688)
	edit:SetHeight(330)
	edit:SetTextInsets(6, 6, 6, 6)
	Theme:SetFont(edit, 11, "")
	edit:SetTextColor(unpack(Theme.Colors.text))
	edit:SetScript("OnEscapePressed", function(self) self:ClearFocus() end)
	scroll:SetScrollChild(edit)
	window.EditBox = edit
	local selectAll = CreateButton(window, "Select All for Copy", 190)
	selectAll:SetPoint("BOTTOMLEFT", 18, 16)
	selectAll:SetScript("OnClick", function() edit:SetFocus(); edit:HighlightText() end)
	window:Hide()
	self.FlightExportWindow = window
	return window
end

function Options:OpenFlightExport()
	local window = self:CreateFlightExportWindow()
	local text = SlickUI.FlightTimer:BuildExportText()
	text = text ~= "" and text or "-- No learned flight routes are available yet."
	local _, lineBreaks = string.gsub(text, "\n", "\n")
	window.EditBox:SetText(text)
	window.EditBox:SetCursorPosition(0)
	window.EditBox:SetHeight(math.max(330, ((lineBreaks or 0) + 2) * 15))
	window.Help:SetFormattedText("%d learned routes / %d export characters. Click Select All, press Ctrl+C, then paste into the development chat.", SlickUI.FlightTimer:GetRouteCount(), string.len(text))
	window:Show()
	window:Raise()
	window.EditBox:GetParent():SetVerticalScroll(0)
end

function Options:CreateDiagnosticExportWindow()
	if self.DiagnosticExportWindow then return self.DiagnosticExportWindow end
	local window = CreateFrame("Frame", "SlickUIDiagnosticExport", UIParent, "BackdropTemplate")
	window:SetSize(860, 560)
	window:SetPoint("CENTER")
	window:SetFrameStrata("DIALOG")
	window:SetClampedToScreen(true)
	Theme:CreatePanel(window)
	Theme:CreateHeaderIcon(window, 36, 14, -9)
	local title = Theme:CreateText(window, 18)
	title:SetPoint("TOPLEFT", 56, -16)
	title:SetText("SlickUI Diagnostic Journal")
	title:SetTextColor(unpack(Theme.Colors.goldText))
	local close = CreateFrame("Button", nil, window, "UIPanelCloseButton")
	close:SetPoint("TOPRIGHT", -4, -4)
	local help = Theme:CreateText(window, 11)
	help:SetPoint("TOPLEFT", title, "BOTTOMLEFT", 0, -8)
	help:SetText("Click Select All, press Ctrl+C, then paste the journal into the development chat.")
	help:SetTextColor(unpack(Theme.Colors.mutedText))
	local scroll = CreateFrame("ScrollFrame", nil, window, "UIPanelScrollFrameTemplate")
	scroll:SetPoint("TOPLEFT", 18, -70)
	scroll:SetPoint("BOTTOMRIGHT", -38, 52)
	local edit = CreateFrame("EditBox", nil, scroll)
	edit:SetMultiLine(true)
	edit:SetAutoFocus(false)
	edit:SetWidth(788)
	edit:SetHeight(410)
	edit:SetTextInsets(6, 6, 6, 6)
	Theme:SetFont(edit, 10, "")
	edit:SetTextColor(unpack(Theme.Colors.text))
	edit:SetScript("OnEscapePressed", function(self) self:ClearFocus() end)
	scroll:SetScrollChild(edit)
	window.EditBox = edit
	local selectAll = CreateButton(window, "Select All for Copy", 190)
	selectAll:SetPoint("BOTTOMLEFT", 18, 16)
	selectAll:SetScript("OnClick", function() edit:SetFocus(); edit:HighlightText() end)
	window:Hide()
	self.DiagnosticExportWindow = window
	return window
end

function Options:OpenDiagnosticExport()
	local window = self:CreateDiagnosticExportWindow()
	local text = SlickUI:BuildDiagnosticExport()
	local _, lineBreaks = string.gsub(text, "\n", "\n")
	window.EditBox:SetText(text)
	window.EditBox:SetCursorPosition(0)
	window.EditBox:SetHeight(math.max(410, ((lineBreaks or 0) + 2) * 14))
	window:Show()
	window:Raise()
	window.EditBox:GetParent():SetVerticalScroll(0)
end

function Options:CreateDeveloperPage(parent, panel)
	local page = CreateFrame("Frame", nil, parent)
	page:SetAllPoints()
	local scroll = CreateFrame("ScrollFrame", nil, page, "UIPanelScrollFrameTemplate")
	scroll:SetPoint("TOPLEFT", 0, 0)
	scroll:SetPoint("BOTTOMRIGHT", -22, 0)
	scroll:EnableMouseWheel(true)
	local body = CreateFrame("Frame", nil, scroll)
	body:SetSize(430, 720)
	scroll:SetScrollChild(body)
	scroll:SetScript("OnMouseWheel", function(self, delta)
		self:SetVerticalScroll(math.max(0, math.min(self:GetVerticalScrollRange(), self:GetVerticalScroll() - delta * 45)))
	end)
	local title = Theme:CreateText(body, 18)
	title:SetPoint("TOPLEFT", 0, 0)
	title:SetText("Developer")
	title:SetTextColor(unpack(Theme.Colors.goldText))
	local description = Theme:CreateText(body, 10)
	description:SetPoint("TOPLEFT", title, "BOTTOMLEFT", 0, -8)
	description:SetPoint("RIGHT", body, "RIGHT", -12, 0)
	description:SetJustifyH("LEFT")
	description:SetText("Local diagnostic and data-export tools. These tools do not upload or transmit information automatically.")
	description:SetTextColor(unpack(Theme.Colors.mutedText))
	local heading = Theme:CreateText(body, 14)
	heading:SetPoint("TOPLEFT", description, "BOTTOMLEFT", 0, -24)
	heading:SetText("Flight Path Collection")
	heading:SetTextColor(unpack(Theme.Colors.goldText))
	local routeCount = Theme:CreateText(body, 11)
	routeCount:SetPoint("TOPLEFT", heading, "BOTTOMLEFT", 0, -10)
	routeCount:SetTextColor(unpack(Theme.Colors.text))
	local routeStatus = Theme:CreateText(body, 11)
	routeStatus:SetPoint("TOPLEFT", routeCount, "BOTTOMLEFT", 0, -7)
	local export = CreateButton(body, "Copy Flight Data", 180)
	export:SetPoint("TOPLEFT", routeStatus, "BOTTOMLEFT", 0, -14)
	export:SetScript("OnClick", function() Options:OpenFlightExport() end)
	local note = Theme:CreateText(body, 10)
	note:SetPoint("TOPLEFT", export, "BOTTOMLEFT", 0, -10)
	note:SetPoint("RIGHT", body, "RIGHT", -12, 0)
	note:SetJustifyH("LEFT")
	note:SetText("SlickUI learns a route after a completed taxi flight longer than 10 seconds. Repeated trips improve its average. Exporting does not erase local data.")
	note:SetTextColor(unpack(Theme.Colors.mutedText))
	local diagnosticHeading = Theme:CreateText(body, 14)
	diagnosticHeading:SetPoint("TOPLEFT", note, "BOTTOMLEFT", 0, -28)
	diagnosticHeading:SetText("Diagnostic Journal")
	diagnosticHeading:SetTextColor(unpack(Theme.Colors.goldText))
	local diagnosticNote = Theme:CreateText(body, 10)
	diagnosticNote:SetPoint("TOPLEFT", diagnosticHeading, "BOTTOMLEFT", 0, -8)
	diagnosticNote:SetPoint("RIGHT", body, "RIGHT", -12, 0)
	diagnosticNote:SetJustifyH("LEFT")
	diagnosticNote:SetText("Stores the newest 250 SlickUI lifecycle and error entries locally. The journal is written to SavedVariables on logout or UI reload.")
	diagnosticNote:SetTextColor(unpack(Theme.Colors.mutedText))
	local diagnosticExport = CreateButton(body, "Copy Diagnostic Journal", 220)
	diagnosticExport:SetPoint("TOPLEFT", diagnosticNote, "BOTTOMLEFT", 0, -14)
	diagnosticExport:SetScript("OnClick", function() Options:OpenDiagnosticExport() end)
	local errorHeading = Theme:CreateText(body, 14)
	errorHeading:SetPoint("TOPLEFT", diagnosticExport, "BOTTOMLEFT", 0, -28)
	errorHeading:SetText("Error Catcher")
	errorHeading:SetTextColor(unpack(Theme.Colors.goldText))
	local errorNote = Theme:CreateText(body, 10)
	errorNote:SetPoint("TOPLEFT", errorHeading, "BOTTOMLEFT", 0, -8)
	errorNote:SetPoint("RIGHT", body, "RIGHT", -12, 0)
	errorNote:SetJustifyH("LEFT")
	errorNote:SetText("Review, copy, or clear Lua errors captured by SlickUI without opening Blizzard's blocking error popup.")
	errorNote:SetTextColor(unpack(Theme.Colors.mutedText))
	local errorCatcher = CreateCheckbox(body, "Catch Lua errors without the blocking popup")
	errorCatcher:SetPoint("TOPLEFT", errorNote, "BOTTOMLEFT", -4, -12)
	errorCatcher:SetScript("OnClick", function(self)
		SlickUI.ErrorCatcher:SetEnabled(self:GetChecked())
		Options:Refresh()
	end)
	panel.ErrorCatcherCheckbox = errorCatcher
	local errorSound = CreateCheckbox(body, "Play an alert sound when an error is caught")
	errorSound:SetPoint("TOPLEFT", errorCatcher, "BOTTOMLEFT", 0, -8)
	errorSound:SetScript("OnClick", function(self)
		SlickUI.DB.errorCatcher.sound = self:GetChecked() == true
		Options:Refresh()
	end)
	panel.ErrorSoundCheckbox = errorSound
	local viewErrors = CreateButton(body, "View / Copy Captured Errors", 210)
	viewErrors:SetPoint("TOPLEFT", errorSound, "BOTTOMLEFT", 4, -14)
	viewErrors:SetScript("OnClick", function() SlickUI.ErrorCatcher:Show() end)
	page:SetScript("OnShow", function()
		routeCount:SetText(string.format("Locally learned routes: %d", SlickUI.FlightTimer:GetRouteCount()))
		local newRoutes = SlickUI.FlightTimer:GetNewRouteCount()
		if newRoutes > 0 then
			routeStatus:SetFormattedText("New Routes Add: %d", newRoutes)
			routeStatus:SetTextColor(0.25, 1.00, 0.45, 1)
		else
			routeStatus:SetText("Up To Date")
			routeStatus:SetTextColor(unpack(Theme.Colors.text))
		end
	end)
	self.Pages.developer = page
end

local CompanionAddons = {
	{
		folder = "Eat-Me",
		name = "Eat Me",
		icon = "Interface\\AddOns\\SlickUI\\Media\\Icons\\Companions\\EatMe",
		description = "Smart food, drink, flask, and buff management with quick consumable macros.",
		url = "https://www.curseforge.com/wow/addons/eat-me",
	},
	{
		folder = "OneButtonWonder",
		name = "One Button Wonder",
		icon = "Interface\\AddOns\\SlickUI\\Media\\Icons\\Companions\\OneButtonWonder",
		description = "Build a personal one-button cast sequence directly from your spellbook.",
		url = "https://www.curseforge.com/wow/addons/one-button-wonder",
	},
	{
		folder = "Quest-Count",
		name = "Quest Count",
		icon = "Interface\\AddOns\\SlickUI\\Media\\Icons\\Companions\\QuestCount",
		description = "Shows how many quest-log slots remain and remembers each character's capacity.",
		url = "https://www.curseforge.com/wow/addons/quest-count",
	},
	{
		folder = "SpellPilot",
		name = "Spell Pilot",
		icon = "Interface\\AddOns\\SlickUI\\Media\\Icons\\Companions\\SpellPilot",
		description = "Live interrupt, utility, pet, defense, and healing assistance.",
		url = "https://www.curseforge.com/wow/addons/spell-pilot",
	},
}

local function GetCompanionVersion(folder)
	local getMetadata = C_AddOns and C_AddOns.GetAddOnMetadata or GetAddOnMetadata
	if not getMetadata then return nil end
	return getMetadata(folder, "Version")
end

function Options:CreateExternalLinkWindow()
	if self.ExternalLinkWindow then return self.ExternalLinkWindow end
	local window = CreateFrame("Frame", "SlickUIExternalLinkWindow", UIParent, "BackdropTemplate")
	window:SetSize(560, 150)
	window:SetPoint("CENTER")
	window:SetFrameStrata("DIALOG")
	window:SetClampedToScreen(true)
	Theme:CreatePanel(window)

	local title = Theme:CreateText(window, 17)
	title:SetPoint("TOPLEFT", 18, -16)
	title:SetTextColor(unpack(Theme.Colors.goldText))
	window.Title = title

	local help = Theme:CreateText(window, 10)
	help:SetPoint("TOPLEFT", title, "BOTTOMLEFT", 0, -8)
	help:SetText("Press Ctrl+C to copy this CurseForge address, then paste it into your browser.")
	help:SetTextColor(unpack(Theme.Colors.mutedText))

	local edit = CreateFrame("EditBox", nil, window, "BackdropTemplate")
	edit:SetPoint("TOPLEFT", 18, -72)
	edit:SetPoint("TOPRIGHT", -18, -72)
	edit:SetHeight(30)
	edit:SetAutoFocus(false)
	edit:SetTextInsets(8, 8, 0, 0)
	edit:SetBackdrop({ bgFile = Theme.Texture, edgeFile = Theme.Texture, edgeSize = 1 })
	edit:SetBackdropColor(unpack(Theme.Colors.panelSoft))
	edit:SetBackdropBorderColor(unpack(Theme.Colors.borderMuted))
	Theme:SetFont(edit, 11, "")
	edit:SetTextColor(unpack(Theme.Colors.text))
	edit:SetScript("OnEscapePressed", function(self) self:ClearFocus(); window:Hide() end)
	edit:SetScript("OnEditFocusGained", function(self) self:HighlightText() end)
	window.EditBox = edit

	local close = CreateButton(window, "Close", 100)
	close:SetPoint("BOTTOMRIGHT", -18, 14)
	close:SetScript("OnClick", function() window:Hide() end)
	window:Hide()
	self.ExternalLinkWindow = window
	return window
end

function Options:ShowExternalLink(addonName, url)
	local window = self:CreateExternalLinkWindow()
	window.Title:SetText(addonName .. " on CurseForge")
	window.EditBox:SetText(url)
	window:Show()
	window.EditBox:SetFocus()
	window.EditBox:HighlightText()
end

function Options:CreateAboutPage(parent)
	local page = CreateFrame("Frame", nil, parent)
	page:SetAllPoints()
	local scroll = CreateFrame("ScrollFrame", nil, page, "UIPanelScrollFrameTemplate")
	scroll:SetPoint("TOPLEFT", 0, 0)
	scroll:SetPoint("BOTTOMRIGHT", -22, 0)
	scroll:EnableMouseWheel(true)
	local body = CreateFrame("Frame", nil, scroll)
	body:SetSize(450, 790)
	scroll:SetScrollChild(body)
	scroll:SetScript("OnMouseWheel", function(self, delta)
		self:SetVerticalScroll(math.max(0, math.min(self:GetVerticalScrollRange(), self:GetVerticalScroll() - delta * 45)))
	end)
	local cards = {}

	local title = Theme:CreateText(body, 18)
	title:SetPoint("TOPLEFT", 0, 0)
	title:SetText("About SlickUI")
	title:SetTextColor(unpack(Theme.Colors.goldText))

	local version = Theme:CreateText(body, 11)
	version:SetPoint("TOPLEFT", title, "BOTTOMLEFT", 0, -7)
	version:SetText("SlickUI v" .. SlickUI.Version .. "  |  Tarlac / Bono Studios")
	version:SetTextColor(unpack(Theme.Colors.text))

	local description = Theme:CreateText(body, 10)
	description:SetPoint("TOPLEFT", version, "BOTTOMLEFT", 0, -7)
	description:SetPoint("RIGHT", body, "RIGHT", -8, 0)
	description:SetJustifyH("LEFT")
	description:SetText("A clean, modern interface suite built for World of Warcraft. These companion addons are separate, optional downloads.")
	description:SetTextColor(unpack(Theme.Colors.mutedText))

	local heading = Theme:CreateText(body, 14)
	heading:SetPoint("TOPLEFT", description, "BOTTOMLEFT", 0, -18)
	heading:SetText("More addons from us")
	heading:SetTextColor(unpack(Theme.Colors.goldText))

	local previous
	for _, info in ipairs(CompanionAddons) do
		local card = CreateFrame("Frame", nil, body, "BackdropTemplate")
		card:SetSize(450, 86)
		if previous then
			card:SetPoint("TOPLEFT", previous, "BOTTOMLEFT", 0, -8)
		else
			card:SetPoint("TOPLEFT", heading, "BOTTOMLEFT", 0, -10)
		end
		card:SetBackdrop({ bgFile = Theme.Texture, edgeFile = Theme.Texture, edgeSize = 1 })
		card:SetBackdropColor(unpack(Theme.Colors.panelSoft))
		card:SetBackdropBorderColor(unpack(Theme.Colors.borderMuted))

		local icon = card:CreateTexture(nil, "ARTWORK")
		icon:SetSize(58, 58)
		icon:SetPoint("LEFT", 12, 0)
		icon:SetTexture(info.icon)
		icon:SetTexCoord(0.07, 0.93, 0.07, 0.93)

		local name = Theme:CreateText(card, 13)
		name:SetPoint("TOPLEFT", icon, "TOPRIGHT", 12, -1)
		name:SetText(info.name)
		name:SetTextColor(unpack(Theme.Colors.goldText))

		local status = Theme:CreateText(card, 9)
		status:SetPoint("TOPRIGHT", -12, -8)
		card.Status = status
		card.Info = info

		local details = Theme:CreateText(card, 9)
		details:SetPoint("TOPLEFT", name, "BOTTOMLEFT", 0, -5)
		details:SetPoint("RIGHT", card, "RIGHT", -150, 0)
		details:SetJustifyH("LEFT")
		details:SetText(info.description)
		details:SetTextColor(unpack(Theme.Colors.mutedText))

		local link = CreateButton(card, "CurseForge Link", 128)
		link:SetPoint("BOTTOMRIGHT", -10, 10)
		link:SetScript("OnClick", function() Options:ShowExternalLink(info.name, info.url) end)

		previous = card
		cards[#cards + 1] = card
	end

	local reportHeading = Theme:CreateText(body, 14)
	reportHeading:SetPoint("TOPLEFT", previous, "BOTTOMLEFT", 0, -22)
	reportHeading:SetText("Report a Bug")
	reportHeading:SetTextColor(unpack(Theme.Colors.goldText))

	local reportPanel = CreateFrame("Frame", nil, body, "BackdropTemplate")
	reportPanel:SetSize(450, 190)
	reportPanel:SetPoint("TOPLEFT", reportHeading, "BOTTOMLEFT", 0, -9)
	reportPanel:SetBackdrop({ bgFile = Theme.Texture, edgeFile = Theme.Texture, edgeSize = 1 })
	reportPanel:SetBackdropColor(unpack(Theme.Colors.panelSoft))
	reportPanel:SetBackdropBorderColor(unpack(Theme.Colors.borderMuted))

	local instructions = Theme:CreateText(reportPanel, 10)
	instructions:SetPoint("TOPLEFT", 14, -12)
	instructions:SetPoint("RIGHT", reportPanel, "RIGHT", -14, 0)
	instructions:SetJustifyH("LEFT")
	instructions:SetText("1. Reproduce the problem and note what you were doing.\n2. Click Open Error Report (or type /slick errors).\n3. Click Select All for Copy, then press Ctrl+C.\n4. Open the CurseForge comments link, describe the problem, and paste the full report. Screenshots and reproduction steps help us fix it faster.")
	instructions:SetTextColor(unpack(Theme.Colors.text))

	local privacy = Theme:CreateText(reportPanel, 9)
	privacy:SetPoint("TOPLEFT", instructions, "BOTTOMLEFT", 0, -9)
	privacy:SetPoint("RIGHT", reportPanel, "RIGHT", -14, 0)
	privacy:SetJustifyH("LEFT")
	privacy:SetText("Reports are never uploaded automatically. You choose exactly what is copied and posted.")
	privacy:SetTextColor(unpack(Theme.Colors.mutedText))

	local openReport = CreateButton(reportPanel, "Open Error Report", 156)
	openReport:SetPoint("BOTTOMLEFT", 14, 12)
	openReport:SetScript("OnClick", function()
		if SlickUI.ErrorCatcher then SlickUI.ErrorCatcher:Show() end
	end)

	local reportLink = CreateButton(reportPanel, "CurseForge Comments", 166)
	reportLink:SetPoint("LEFT", openReport, "RIGHT", 10, 0)
	reportLink:SetScript("OnClick", function()
		Options:ShowExternalLink("SlickUI Bug Reports", "https://www.curseforge.com/wow/addons/slickui/comments")
	end)

	page:SetScript("OnShow", function()
		for _, child in ipairs(cards) do
			if child.Info and child.Status then
				local installedVersion = GetCompanionVersion(child.Info.folder)
				if installedVersion then
					child.Status:SetText("Installed v" .. installedVersion)
					child.Status:SetTextColor(0.25, 1, 0.45)
				else
					child.Status:SetText("Not installed")
					child.Status:SetTextColor(unpack(Theme.Colors.mutedText))
				end
			end
		end
		scroll:SetVerticalScroll(0)
	end)

	self.Pages.about = page
end

function Options:RefreshBagControls()
	local controls = self.BagControls
	if not controls or not SlickUI.DB or not SlickUI.Bags then return end
	local settings = SlickUI.DB.bags
	controls.Enabled:SetChecked(settings.enabled)
	controls.BankEnabled:SetChecked(settings.bankEnabled)
	controls.Unified:SetEnabled(settings.mode ~= "unified")
	controls.Categories:SetEnabled(settings.mode ~= "categories")
	controls.Mode:SetText(settings.mode == "categories" and "Current layout: Category sections" or "Current layout: Single bag")
	SetSliderValue(controls.ButtonSize, settings.buttonSize)
	SetSliderValue(controls.Spacing, settings.spacing)
	SetSliderValue(controls.Columns, settings.columns)
	SetSliderValue(controls.Scale, settings.scale)
	controls.Open:SetEnabled(settings.enabled)
end

function Options:CreateBagsPage(parent)
	local page = CreateFrame("Frame", nil, parent)
	page:SetAllPoints()
	local controls = {}
	self.BagControls = controls

	local heading = Theme:CreateText(page, 17)
	heading:SetPoint("TOPLEFT", 0, 0)
	heading:SetText("Bags")
	heading:SetTextColor(unpack(Theme.Colors.goldText))

	local note = Theme:CreateText(page, 10)
	note:SetPoint("TOPLEFT", heading, "BOTTOMLEFT", 0, -7)
	note:SetPoint("RIGHT", page, "RIGHT", 0, 0)
	note:SetJustifyH("LEFT")
	note:SetText("Use matching SlickUI windows for carried bags, character-bank tabs, and Warband-bank tabs. Collapsible side rails expose equipped bags and purchased bank tabs.")
	note:SetTextColor(unpack(Theme.Colors.mutedText))

	local function Apply(field, value)
		if not SlickUI:IsAvailableOutOfCombat("Bag changes") then Options:RefreshBagControls() return end
		SlickUI.DB.bags[field] = value
		SlickUI.Bags:ApplySettings()
		Options:RefreshBagControls()
	end

	local enabled = CreateCheckbox(page, "Enable SlickUI Bags")
	enabled:SetPoint("TOPLEFT", note, "BOTTOMLEFT", -4, -15)
	enabled:SetScript("OnClick", function(self)
		Apply("enabled", self:GetChecked() == true)
		if not self:GetChecked() then SlickUI.Bags:Hide() end
	end)
	controls.Enabled = enabled

	local bankEnabled = CreateCheckbox(page, "Enable SlickUI Bank")
	bankEnabled:SetPoint("LEFT", enabled, "RIGHT", 190, 0)
	bankEnabled:SetScript("OnClick", function(self)
		Apply("bankEnabled", self:GetChecked() == true)
		if not self:GetChecked() and SlickUI.Bank then SlickUI.Bank:Hide(false) end
	end)
	controls.BankEnabled = bankEnabled

	local layoutTitle = Theme:CreateText(page, 14)
	layoutTitle:SetPoint("TOPLEFT", enabled, "BOTTOMLEFT", 4, -18)
	layoutTitle:SetText("Layout")
	layoutTitle:SetTextColor(unpack(Theme.Colors.goldText))

	local unified = CreateButton(page, "Single Bag", 150)
	unified:SetPoint("TOPLEFT", layoutTitle, "BOTTOMLEFT", 0, -9)
	unified:SetScript("OnClick", function() SlickUI.Bags:SetMode("unified") end)
	controls.Unified = unified

	local categories = CreateButton(page, "Category Sections", 150)
	categories:SetPoint("LEFT", unified, "RIGHT", 12, 0)
	categories:SetScript("OnClick", function() SlickUI.Bags:SetMode("categories") end)
	controls.Categories = categories

	local mode = Theme:CreateText(page, 10)
	mode:SetPoint("TOPLEFT", unified, "BOTTOMLEFT", 2, -8)
	mode:SetTextColor(unpack(Theme.Colors.mutedText))
	controls.Mode = mode

	local sizingTitle = Theme:CreateText(page, 14)
	sizingTitle:SetPoint("TOPLEFT", mode, "BOTTOMLEFT", -2, -22)
	sizingTitle:SetText("Size & Spacing")
	sizingTitle:SetTextColor(unpack(Theme.Colors.goldText))

	local buttonSize = CreateSlider(page, "Item size", 28, 56, 1, 190, function(value) Apply("buttonSize", value) end)
	buttonSize:SetPoint("TOPLEFT", sizingTitle, "BOTTOMLEFT", 0, -30)
	controls.ButtonSize = buttonSize

	local spacing = CreateSlider(page, "Item spacing", 0, 12, 1, 190, function(value) Apply("spacing", value) end)
	spacing:SetPoint("TOPLEFT", buttonSize, "BOTTOMLEFT", 0, -32)
	controls.Spacing = spacing

	local columns = CreateSlider(page, "Columns", 6, 16, 1, 190, function(value) Apply("columns", value) end)
	columns:SetPoint("TOPLEFT", sizingTitle, "BOTTOMLEFT", 235, -30)
	controls.Columns = columns

	local scale = CreateSlider(page, "Window scale", 0.70, 1.35, 0.05, 190, function(value) Apply("scale", value) end, function(value) return string.format("%.2f", value) end)
	scale:SetPoint("TOPLEFT", columns, "BOTTOMLEFT", 0, -32)
	controls.Scale = scale

	local open = CreateButton(page, "Open SlickUI Bags", 190)
	open:SetPoint("TOPLEFT", spacing, "BOTTOMLEFT", 0, -36)
	open:SetScript("OnClick", function() SlickUI.Bags:Show() end)
	controls.Open = open

	local reset = CreateButton(page, "Restore Bag Defaults", 190)
	reset:SetPoint("LEFT", open, "RIGHT", 45, 0)
	reset:SetScript("OnClick", function()
		if not SlickUI:IsAvailableOutOfCombat("Bag reset") then return end
		for field, value in pairs(SlickUI.Defaults.bags) do
			if type(value) == "table" then
				local copied = {}
				for key, nestedValue in pairs(value) do copied[key] = nestedValue end
				SlickUI.DB.bags[field] = copied
			else
				SlickUI.DB.bags[field] = value
			end
		end
		SlickUI.Bags:Hide()
		SlickUI.Bags:ApplySettings()
		Options:RefreshBagControls()
	end)

	local warning = Theme:CreateText(page, 10)
	warning:SetPoint("TOPLEFT", open, "BOTTOMLEFT", 0, -20)
	warning:SetPoint("RIGHT", page, "RIGHT", 0, 0)
	warning:SetJustifyH("LEFT")
	warning:SetText("If another bag replacement is enabled, disable one of them if both windows respond to the bag key. Use /slickbags as a direct test command.")
	warning:SetTextColor(1, 0.72, 0.18)

	self.Pages.bags = page
end

function Options:RefreshClassBuffControls()
	local controls = self.ClassBuffControls
	if not controls or not SlickUI.DB or not SlickUI.ClassBuffs then return end
	local settings = SlickUI.DB.classBuffs
	controls.Enabled:SetChecked(settings.enabled ~= false)
	controls.SelfOnly:SetChecked(settings.trackSelfOnly ~= false)
	controls.SelfOnly:SetEnabled(settings.enabled ~= false)
	local _, className = UnitClass("player")
	local specName = "Current Specialization"
	if GetSpecialization and GetSpecializationInfo then
		local index = GetSpecialization()
		if type(index) == "number" then
			local _, name = GetSpecializationInfo(index)
			if type(name) == "string" then specName = name end
		end
	end
	controls.Context:SetText((className or "Current Class") .. " - " .. specName)
	local available = SlickUI.ClassBuffs:GetAvailableBuffs()
	for index, row in ipairs(controls.Rows) do
		local buff = available[index]
		if buff then
			row.SpellID = buff.spellID
			row.Icon:SetTexture(buff.icon or "Interface\\Icons\\INV_Misc_QuestionMark")
			row.Name:SetText(buff.name .. (buff.scope == "player" and "  |cff67d5ff(Personal)|r" or ""))
			row.Checkbox:SetChecked(SlickUI.ClassBuffs:IsTracked(buff.spellID))
			row.Checkbox:SetEnabled(settings.enabled ~= false)
			row:Show()
		else
			row.SpellID = nil
			row:Hide()
		end
	end
	controls.Empty:SetShown(#available == 0)
	local procControls = self.ClassProcControls
	if procControls and SlickUI.ClassProcAlerts and SlickUI.DB.classProcs then
		procControls.Enabled:SetChecked(SlickUI.DB.classProcs.enabled ~= false)
		local procs = SlickUI.ClassProcAlerts:GetAvailableProcs()
		for index, row in ipairs(procControls.Rows) do
			local proc = procs[index]
			if proc then
				local procSettings = SlickUI.ClassProcAlerts:GetSettings(proc.auraID)
				row.AuraID = proc.auraID
				row.Icon:SetTexture(proc.icon or "Interface\\Icons\\INV_Misc_QuestionMark")
				row.Name:SetText(proc.name)
				row.Enabled:SetChecked(procSettings.enabled ~= false)
				row.ShowIcon:SetChecked(procSettings.icon ~= false)
				row.Sound:SetChecked(procSettings.sound ~= false)
				row.Enabled:SetEnabled(SlickUI.DB.classProcs.enabled ~= false)
				row.ShowIcon:SetEnabled(SlickUI.DB.classProcs.enabled ~= false and procSettings.enabled ~= false)
				row.Sound:SetEnabled(SlickUI.DB.classProcs.enabled ~= false and procSettings.enabled ~= false)
				row.Test:SetEnabled(SlickUI.DB.classProcs.enabled ~= false and procSettings.enabled ~= false)
				row:Show()
			else
				row.AuraID = nil; row:Hide()
			end
		end
		procControls.Empty:SetShown(#procs == 0)
	end
	local announceControls = self.ClassAnnouncementControls
	if announceControls and SlickUI.ClassAnnouncements and SlickUI.DB.classAnnouncements then
		local announceSettings = SlickUI.DB.classAnnouncements
		announceControls.Enabled:SetChecked(announceSettings.enabled == true)
		local abilities = SlickUI.ClassAnnouncements:GetAvailableAbilities()
		for index, row in ipairs(announceControls.Rows) do
			local ability = abilities[index]
			if ability then
				row.SpellID = ability.spellID
				row.Icon:SetTexture(ability.icon or "Interface\\Icons\\INV_Misc_QuestionMark")
				row.Name:SetText(ability.name)
				row.Checkbox:SetChecked(SlickUI.ClassAnnouncements:IsAbilityEnabled(ability.spellID))
				row.Checkbox:SetEnabled(announceSettings.enabled == true)
				row:Show()
			else
				row.SpellID = nil; row:Hide()
			end
		end
		announceControls.Empty:SetShown(#abilities == 0)
	end
end

function Options:RefreshAlertControls()
	local controls = self.AlertControls
	if not controls or not SlickUI.DB then return end
	local groundHazard = SlickUI.DB.alerts.groundHazard
	local groundSupported = SlickUI.GroundHazardAlerts:IsSupported()
	controls.GroundHazardHeading:SetText("Standing in Stupid")
	controls.GroundHazardEnabled:SetChecked(groundSupported and groundHazard.enabled)
	controls.GroundHazardEnabled:SetEnabled(groundSupported)
	controls.GroundHazardSound:SetChecked(groundHazard.sound)
	controls.GroundHazardSound:SetEnabled(groundSupported and groundHazard.enabled)
	controls.GroundHazardNote:SetText(SlickUI.GroundHazardAlerts:GetDetectionMode() == "combat-log"
		and "Environmental fire and lava alert immediately. Non-aura periodic damage and rapidly repeating direct ground damage are detected conservatively."
		or "WoW 12 adaptive detection warns after closely repeated non-physical damage to your character. The warning renews only while damage continues. This safe fallback avoids restricted combat-log APIs; isolated spell hits and ordinary melee are ignored.")
	local interrupt = SlickUI.DB.alerts.interrupt
	controls.InterruptEnabled:SetChecked(interrupt.enabled)
	controls.InterruptSound:SetChecked(interrupt.sound)
	controls.InterruptSound:SetEnabled(interrupt.enabled)
	controls.InterruptAnnounce:SetChecked(interrupt.announce == true)
	local interruptCooldowns = SlickUI.DB.interruptCooldowns
	if controls.InterruptCooldownEnabled then controls.InterruptCooldownEnabled:SetChecked(interruptCooldowns.enabled ~= false) end
	if controls.InterruptCooldownAlpha then controls.InterruptCooldownAlpha:SetValue(interruptCooldowns.backgroundAlpha or 0.65) end
	local groupDeath = SlickUI.DB.alerts.groupDeath
	controls.GroupDeathSound:SetChecked(groupDeath.sound)
	local settings = SlickUI.DB.alerts.cleanse
	controls.CleanseEnabled:SetChecked(settings.enabled)
	controls.CleanseSound:SetChecked(settings.sound)
	controls.CleanseIcon:SetChecked(settings.showIcon ~= false)
	controls.OnlyWhenReady:SetChecked(settings.onlyWhenReady)
	controls.CleanseSound:SetEnabled(settings.enabled)
	controls.CleanseIcon:SetEnabled(settings.enabled)
	controls.OnlyWhenReady:SetEnabled(settings.enabled)
	local cooldowns = SlickUI.DB.alerts.cooldowns
	controls.CooldownEnabled:SetChecked(cooldowns.enabled)
	controls.CooldownSound:SetChecked(cooldowns.sound)
	controls.CooldownSound:SetEnabled(cooldowns.enabled)
	local _, specName = SlickUI.Alerts:GetCurrentSpecialization()
	if controls.AbilityHeading then controls.AbilityHeading:SetText("Cooldown Ready — " .. specName) end
	self:RefreshCooldownSpellRows()
end

function Options:RefreshCooldownSpellRows()
	local controls = self.AlertControls
	if not SlickUI.DB then return end
	local spells = SlickUI.Alerts:GetTrackedCooldownSpells()
	for _, rows in pairs({ controls and controls.CooldownRows, self.CooldownPickerRows }) do
		if rows then
			for index, row in ipairs(rows) do
				local spellID = spells[index]
				if spellID then
					local name, icon = SlickUI.Alerts:GetSpellDisplay(spellID)
					row.SpellID = spellID
					row.Icon:SetTexture(icon or "Interface\\Icons\\INV_Misc_QuestionMark")
					row.Name:SetText(name or ("Spell " .. spellID))
					row:Show()
				else
					row.SpellID = nil
					row:Hide()
				end
			end
		end
	end
	local contentHeight = math.max(1, #spells) * 25
	for _, scrollChild in pairs({
		controls and controls.CooldownScrollChild,
		self.CooldownPickerScrollChild,
	}) do
		if scrollChild then scrollChild:SetHeight(contentHeight) end
	end
end

function Options:AddCooldownSpellFromCursor()
	local cursorType, first, second, third, fourth = GetCursorInfo()
	if cursorType == "action" and type(first) == "number" then
		local actionType, actionID = GetActionInfo(first)
		if actionType == "spell" then
			cursorType, first, second, third, fourth = "spell", actionID, nil, nil, nil
		end
	end
	if cursorType ~= "spell" then return false end
	-- Retail spellbook cursors include the spell ID after the book index/type.
	-- Action-bar cursors expose it in the first value instead.
	for _, candidate in pairs({ third, fourth, first, second }) do
		if type(candidate) == "number" and self:AddCooldownSpell(candidate) then
			-- Clearing a spell cursor refreshes every Blizzard action button. Run
			-- that native operation through the secure boundary so protected
			-- cooldown values are not passed to SetCooldown from addon execution.
			if type(securecallfunction) == "function" and type(ClearCursor) == "function" then
				pcall(securecallfunction, ClearCursor)
			end
			return true
		end
	end
	return false
end

function Options:AddCooldownSpell(spellID)
	spellID = tonumber(spellID)
	if not spellID then return false end
	local name = SlickUI.Alerts:GetSpellDisplay(spellID)
	if not name or not SlickUI.Alerts:IsKnownSpell(spellID) then
		SlickUI:Print("That spell is not learned in the current specialization.")
		return false
	end
	local spells = SlickUI.Alerts:GetTrackedCooldownSpells()
	for _, existing in ipairs(spells) do
		if existing == spellID then
			SlickUI:Print(name .. " is already tracked.")
			return false
		end
	end
	if #spells >= COOLDOWN_SPELL_LIMIT then
		SlickUI:Print("Cooldown alerts currently support up to " .. COOLDOWN_SPELL_LIMIT .. " tracked spells.")
		return false
	end
	table.insert(spells, spellID)
	SlickUI.Alerts:ResetCooldownTracking()
	self:RefreshCooldownSpellRows()
	SlickUI:Print("Tracking " .. name .. ".")
	return true
end

function Options:RemoveCooldownSpell(spellID)
	local spells = SlickUI.Alerts:GetTrackedCooldownSpells()
	for index = #spells, 1, -1 do
		if spells[index] == spellID then table.remove(spells, index) end
	end
	SlickUI.Alerts:ResetCooldownTracking()
	self:RefreshCooldownSpellRows()
end

function Options:CreateCooldownSpellPicker()
	if self.CooldownPicker then return self.CooldownPicker end
	local picker = CreateFrame("Frame", "SlickUICooldownSpellPicker", UIParent, "BackdropTemplate")
	picker:SetSize(350, 430)
	picker:SetPoint("CENTER", UIParent, "CENTER", 420, 0)
	picker:SetFrameStrata("DIALOG")
	picker:SetClampedToScreen(true)
	picker:SetMovable(true)
	picker:EnableMouse(true)
	picker:RegisterForDrag("LeftButton")
	picker:SetScript("OnDragStart", picker.StartMoving)
	picker:SetScript("OnDragStop", picker.StopMovingOrSizing)
	Theme:CreatePanel(picker)
	picker:Hide()
	-- Do not add the picker to UISpecialFrames. Blizzard closes every special
	-- window when the player opens the native Spellbook, which made pressing P
	-- immediately dismiss this independent picker. Its X button remains the
	-- explicit, predictable close control.

	local title = Theme:CreateText(picker, 16)
	title:SetPoint("TOPLEFT", 16, -14)
	title:SetText("Cooldown Spell Picker")
	title:SetTextColor(unpack(Theme.Colors.goldText))
	local close = CreateFrame("Button", nil, picker, "UIPanelCloseButton")
	close:SetPoint("TOPRIGHT", -3, -3)

	local help = Theme:CreateText(picker, 9)
	help:SetPoint("TOPLEFT", title, "BOTTOMLEFT", 0, -8)
	help:SetPoint("RIGHT", picker, "RIGHT", -14, 0)
	help:SetJustifyH("LEFT")
	help:SetText("Press P to open the Spellbook, then drag any learned spell onto this square.")
	help:SetTextColor(unpack(Theme.Colors.mutedText))

	local drop = CreateFrame("Button", nil, picker, "BackdropTemplate")
	drop:SetSize(58, 58)
	drop:SetPoint("TOPLEFT", help, "BOTTOMLEFT", 0, -12)
	Theme:CreatePanel(drop)
	local dropIcon = drop:CreateTexture(nil, "ARTWORK")
	dropIcon:SetPoint("TOPLEFT", 5, -5)
	dropIcon:SetPoint("BOTTOMRIGHT", -5, 5)
	dropIcon:SetTexture("Interface\\AddOns\\SlickUI\\Media\\Icons\\SlickUI_Icon.png")
	dropIcon:SetTexCoord(0.08, 0.92, 0.08, 0.92)
	drop:SetScript("OnReceiveDrag", function() Options:AddCooldownSpellFromCursor() end)
	drop:SetScript("OnMouseUp", function() Options:AddCooldownSpellFromCursor() end)
	local dropText = Theme:CreateText(picker, 10)
	dropText:SetPoint("LEFT", drop, "RIGHT", 10, 0)
	dropText:SetText("Drop spell here")

	local spellScroll = CreateFrame("ScrollFrame", nil, picker, "UIPanelScrollFrameTemplate")
	spellScroll:SetPoint("TOPLEFT", picker, "TOPLEFT", 16, -130)
	spellScroll:SetPoint("BOTTOMRIGHT", picker, "BOTTOMRIGHT", -34, 16)
	self.CooldownPickerRows, self.CooldownPickerScrollChild = CreateCooldownSpellRows(spellScroll, 292)
	self.CooldownPickerScroll = spellScroll

	self.CooldownPicker = picker
	return picker
end

function Options:OpenCooldownSpellPicker()
	if not SlickUI:IsAvailableOutOfCombat("Cooldown Spell Picker") then return false end

	local settingsWasOpen = SettingsPanel and SettingsPanel:IsShown()
	if settingsWasOpen then
		-- Settings is a managed modal panel. Hiding the frame directly leaves its
		-- fullscreen input blocker active, which prevents the Spellbook and other
		-- panels from receiving clicks until the next UI reload. Exit through the
		-- panel mixin so Blizzard releases the modal state and commits the canvas.
		local closed = false
		if type(SettingsPanel.ExitWithCommit) == "function" then
			closed = CallBlizzardSecurely(SettingsPanel.ExitWithCommit, SettingsPanel, true)
		end
		if not closed and type(HideUIPanel) == "function" then
			CallBlizzardSecurely(HideUIPanel, SettingsPanel)
		end
	end

	local function OpenPicker()
		if SettingsPanel and SettingsPanel:IsShown() then
			-- Wait until Blizzard has completed its deferred panel transition.
			C_Timer.After(0.05, OpenPicker)
			return
		end
		if GameMenuFrame and GameMenuFrame:IsShown() and type(HideUIPanel) == "function" then
			CallBlizzardSecurely(HideUIPanel, GameMenuFrame)
		end

		local picker = self:CreateCooldownSpellPicker()
		-- Never load, toggle, anchor to, or otherwise touch Blizzard's Spellbook
		-- from addon execution. In WoW 12 that taints its protected spell buttons,
		-- causing CastSpellBookItem and cooldown-rendering errors when a player
		-- naturally clicks a spell. The player opens it with the native P binding.
		picker:ClearAllPoints()
		picker:SetPoint("CENTER", UIParent, "CENTER", 420, 0)
		self:RefreshCooldownSpellRows()
		picker:Show()
		picker:Raise()
		SlickUI:Print("Press P to open the Spellbook. It remains fully usable beside the picker.")
	end

	-- A managed Settings panel needs one frame to release its mouse/keyboard
	-- capture before another top-level panel is opened.
	C_Timer.After(settingsWasOpen and 0.10 or 0, OpenPicker)
	return true
end

function Options:CreateAlertsPage(parent)
	local page = CreateFrame("Frame", nil, parent)
	page:SetAllPoints()

	local pageScroll = CreateFrame("ScrollFrame", nil, page, "UIPanelScrollFrameTemplate")
	pageScroll:SetPoint("TOPLEFT", 0, 0)
	pageScroll:SetPoint("BOTTOMRIGHT", -22, 0)
	pageScroll:EnableMouseWheel(true)
	pageScroll:SetScript("OnMouseWheel", function(self, delta)
		local maximum = math.max(0, self:GetVerticalScrollRange())
		self:SetVerticalScroll(math.max(0, math.min(maximum, self:GetVerticalScroll() - (delta * 36))))
	end)

	local body = CreateFrame("Frame", nil, pageScroll)
	body:SetHeight(1120)
	body:SetWidth(420)
	pageScroll:SetScrollChild(body)
	page:SetScript("OnSizeChanged", function(_, width)
		body:SetWidth(math.max(360, width - 28))
	end)
	page.AlertScroll = pageScroll
	page.AlertBody = body
	local controls = {}
	self.AlertControls = controls

	local heading = Theme:CreateText(body, 17)
	heading:SetPoint("TOPLEFT", 0, 0)
	heading:SetText("Alerts")
	heading:SetTextColor(unpack(Theme.Colors.goldText))

	local description = Theme:CreateText(body, 10)
	description:SetPoint("TOPLEFT", heading, "BOTTOMLEFT", 0, -8)
	description:SetPoint("RIGHT", body, "RIGHT", -8, 0)
	description:SetJustifyH("LEFT")
	description:SetText("Audio assistance for actionable combat conditions. Alerts use WoW's current class/spec rules and avoid repeating while the same condition remains active.")
	description:SetTextColor(unpack(Theme.Colors.mutedText))

	local interruptHeading = Theme:CreateText(body, 13)
	interruptHeading:SetPoint("TOPLEFT", description, "BOTTOMLEFT", 0, -22)
	interruptHeading:SetText("Interrupt Ready")
	interruptHeading:SetTextColor(unpack(Theme.Colors.goldText))

	local interruptEnabled = CreateCheckbox(body, "Show an interrupt icon for interruptible target casts")
	interruptEnabled:SetPoint("TOPLEFT", interruptHeading, "BOTTOMLEFT", -4, -8)
	interruptEnabled:SetScript("OnClick", function(self)
		SlickUI.DB.alerts.interrupt.enabled = self:GetChecked() == true
		SlickUI.InterruptAlerts:RefreshSpell()
		Options:RefreshAlertControls()
	end)
	controls.InterruptEnabled = interruptEnabled

	local interruptSound = CreateCheckbox(body, "Play Danger Buzzer when an interrupt is ready")
	interruptSound:SetPoint("TOPLEFT", interruptEnabled, "BOTTOMLEFT", 0, -5)
	interruptSound:SetScript("OnClick", function(self)
		SlickUI.DB.alerts.interrupt.sound = self:GetChecked() == true
		Options:RefreshAlertControls()
	end)
	controls.InterruptSound = interruptSound

	local interruptAnnounce = CreateCheckbox(body, "Announce successful interrupts to the group")
	interruptAnnounce:SetPoint("TOPLEFT", interruptSound, "BOTTOMLEFT", 0, -5)
	interruptAnnounce:SetScript("OnClick", function(self)
		SlickUI.DB.alerts.interrupt.announce = self:GetChecked() == true
		Options:RefreshAlertControls()
	end)
	controls.InterruptAnnounce = interruptAnnounce

	local interruptCooldownEnabled = CreateCheckbox(body, "Track party and raid interrupt cooldowns")
	interruptCooldownEnabled:SetPoint("TOPLEFT", interruptAnnounce, "BOTTOMLEFT", 0, -5)
	interruptCooldownEnabled:SetScript("OnClick", function(self)
		SlickUI.DB.interruptCooldowns.enabled = self:GetChecked() == true
		Options:RefreshAlertControls()
	end)
	controls.InterruptCooldownEnabled = interruptCooldownEnabled

	local interruptCooldownAlpha = CreateSlider(body, "Floating-window background", 0, 1, 0.05, 220, function(value)
		if SlickUI.InterruptCooldowns then
			SlickUI.InterruptCooldowns:SetBackgroundAlpha(value)
		else
			SlickUI.DB.interruptCooldowns.backgroundAlpha = value
		end
	end, function(value) return string.format("%d%%", value * 100) end)
	interruptCooldownAlpha:SetPoint("TOPLEFT", interruptCooldownEnabled, "BOTTOMLEFT", 4, -30)
	controls.InterruptCooldownAlpha = interruptCooldownAlpha

	local testInterrupt = CreateButton(body, "Test Interrupt Sound", 180)
	testInterrupt:SetPoint("TOPLEFT", interruptCooldownAlpha, "BOTTOMLEFT", 0, -20)
	testInterrupt:SetScript("OnClick", function()
		SlickUI.InterruptAlerts:PlaySound(true)
	end)

	local groupDeathSound = CreateCheckbox(body, "Play Man Down when a party or raid member dies")
	groupDeathSound:SetPoint("TOPLEFT", testInterrupt, "BOTTOMLEFT", -4, -12)
	groupDeathSound:SetScript("OnClick", function(self)
		SlickUI.DB.alerts.groupDeath.sound = self:GetChecked() == true
		Options:RefreshAlertControls()
	end)
	controls.GroupDeathSound = groupDeathSound

	local groundHeading = Theme:CreateText(body, 13)
	groundHeading:SetPoint("TOPLEFT", groupDeathSound, "BOTTOMLEFT", 4, -22)
	groundHeading:SetText("Standing in Stupid")
	groundHeading:SetTextColor(unpack(Theme.Colors.goldText))
	controls.GroundHazardHeading = groundHeading

	local groundEnabled = CreateCheckbox(body, "Alert while standing in harmful ground effects")
	groundEnabled:SetPoint("TOPLEFT", groundHeading, "BOTTOMLEFT", -4, -8)
	groundEnabled:SetScript("OnClick", function(self)
		SlickUI.DB.alerts.groundHazard.enabled = self:GetChecked() == true
		SlickUI.GroundHazardAlerts:Refresh()
		Options:RefreshAlertControls()
	end)
	controls.GroundHazardEnabled = groundEnabled

	local groundSound = CreateCheckbox(body, "Loop the SlickUI Double Buzzer while danger continues")
	groundSound:SetPoint("TOPLEFT", groundEnabled, "BOTTOMLEFT", 0, -5)
	groundSound:SetScript("OnClick", function(self)
		SlickUI.DB.alerts.groundHazard.sound = self:GetChecked() == true
		Options:RefreshAlertControls()
	end)
	controls.GroundHazardSound = groundSound

	local testGround = CreateButton(body, "Test Danger Warning", 180)
	testGround:SetPoint("TOPLEFT", groundSound, "BOTTOMLEFT", 4, -14)
	testGround:SetScript("OnClick", function()
		SlickUI.GroundHazardAlerts:StartPreview()
	end)

	local groundNote = Theme:CreateText(body, 9)
	groundNote:SetPoint("TOPLEFT", testGround, "BOTTOMLEFT", 0, -12)
	groundNote:SetPoint("RIGHT", body, "RIGHT", -8, 0)
	groundNote:SetJustifyH("LEFT")
	groundNote:SetTextColor(unpack(Theme.Colors.mutedText))
	controls.GroundHazardNote = groundNote

	local cleanseHeading = Theme:CreateText(body, 13)
	cleanseHeading:SetPoint("TOPLEFT", groundNote, "BOTTOMLEFT", 0, -22)
	cleanseHeading:SetText("Cleanse Ready")
	cleanseHeading:SetTextColor(unpack(Theme.Colors.goldText))

	local cleanseEnabled = CreateCheckbox(body, "Alert when a removable debuff is detected")
	cleanseEnabled:SetPoint("TOPLEFT", cleanseHeading, "BOTTOMLEFT", -4, -8)
	cleanseEnabled:SetScript("OnClick", function(self)
		SlickUI.DB.alerts.cleanse.enabled = self:GetChecked() == true
		SlickUI.Alerts:Refresh()
		Options:RefreshAlertControls()
	end)
	controls.CleanseEnabled = cleanseEnabled

	local cleanseSound = CreateCheckbox(body, "Play the original SlickUI cleanse sound")
	cleanseSound:SetPoint("TOPLEFT", cleanseEnabled, "BOTTOMLEFT", 0, -5)
	cleanseSound:SetScript("OnClick", function(self)
		SlickUI.DB.alerts.cleanse.sound = self:GetChecked() == true
		Options:RefreshAlertControls()
	end)
	controls.CleanseSound = cleanseSound

	local cleanseIcon = CreateCheckbox(body, "Show cleanse-ready icon")
	cleanseIcon:SetPoint("TOPLEFT", cleanseSound, "BOTTOMLEFT", 0, -5)
	cleanseIcon:SetScript("OnClick", function(self)
		SlickUI.DB.alerts.cleanse.showIcon = self:GetChecked() == true
		SlickUI.Alerts:Refresh()
		Options:RefreshAlertControls()
	end)
	controls.CleanseIcon = cleanseIcon

	local onlyReady = CreateCheckbox(body, "Only alert when a cleanse ability is off cooldown")
	onlyReady:SetPoint("TOPLEFT", cleanseIcon, "BOTTOMLEFT", 0, -5)
	onlyReady:SetScript("OnClick", function(self)
		SlickUI.DB.alerts.cleanse.onlyWhenReady = self:GetChecked() == true
		SlickUI.Alerts:Refresh()
		Options:RefreshAlertControls()
	end)
	controls.OnlyWhenReady = onlyReady

	local testCleanse = CreateButton(body, "Test Cleanse Sound", 180)
	testCleanse:SetPoint("TOPLEFT", onlyReady, "BOTTOMLEFT", 4, -14)
	testCleanse:SetScript("OnClick", function()
		SlickUI.Alerts:PlayCleanseSound()
	end)

	local cleanseNote = Theme:CreateText(body, 9)
	cleanseNote:SetPoint("TOPLEFT", testCleanse, "BOTTOMLEFT", 0, -12)
	cleanseNote:SetPoint("RIGHT", body, "RIGHT", -8, 0)
	cleanseNote:SetJustifyH("LEFT")
	cleanseNote:SetText("Monitors you, party members, and raid members. SlickUI matches each debuff type to the current class/spec's learned cleanse spell and, when requested, that specific spell's cooldown.")
	cleanseNote:SetTextColor(unpack(Theme.Colors.mutedText))

	local abilityHeading = Theme:CreateText(body, 13)
	abilityHeading:SetPoint("TOPLEFT", cleanseNote, "BOTTOMLEFT", 0, -24)
	abilityHeading:SetText("Cooldown Ready")
	abilityHeading:SetTextColor(unpack(Theme.Colors.goldText))
	controls.AbilityHeading = abilityHeading

	local cooldownEnabled = CreateCheckbox(body, "Enable tracked-spell cooldown alerts")
	cooldownEnabled:SetPoint("TOPLEFT", abilityHeading, "BOTTOMLEFT", -4, -8)
	cooldownEnabled:SetScript("OnClick", function(self)
		SlickUI.DB.alerts.cooldowns.enabled = self:GetChecked() == true
		SlickUI.Alerts:ResetCooldownTracking()
		Options:RefreshAlertControls()
	end)
	controls.CooldownEnabled = cooldownEnabled

	local cooldownSound = CreateCheckbox(body, "Play the original SlickUI ability-ready sound")
	cooldownSound:SetPoint("TOPLEFT", cooldownEnabled, "BOTTOMLEFT", 0, -5)
	cooldownSound:SetScript("OnClick", function(self)
		SlickUI.DB.alerts.cooldowns.sound = self:GetChecked() == true
		Options:RefreshAlertControls()
	end)
	controls.CooldownSound = cooldownSound

	local abilityNote = Theme:CreateText(body, 9)
	abilityNote:SetPoint("TOPLEFT", cooldownSound, "BOTTOMLEFT", 0, -10)
	abilityNote:SetWidth(260)
	abilityNote:SetJustifyH("LEFT")
	abilityNote:SetText("Drag any learned spell from the spellbook or an action bar onto the square. SlickUI alerts only after a real cooldown-to-ready transition. Up to 50 spells can be tracked.")
	abilityNote:SetTextColor(unpack(Theme.Colors.mutedText))

	local drop = CreateFrame("Button", nil, body, "BackdropTemplate")
	drop:SetSize(62, 62)
	drop:SetPoint("TOPLEFT", abilityNote, "BOTTOMLEFT", 4, -14)
	Theme:CreatePanel(drop)
	local dropIcon = drop:CreateTexture(nil, "ARTWORK")
	dropIcon:SetPoint("TOPLEFT", 5, -5)
	dropIcon:SetPoint("BOTTOMRIGHT", -5, 5)
	dropIcon:SetTexture("Interface\\AddOns\\SlickUI\\Media\\Icons\\SlickUI_Icon.png")
	dropIcon:SetTexCoord(0.08, 0.92, 0.08, 0.92)
	drop.Icon = dropIcon
	local dropText = Theme:CreateText(body, 10)
	dropText:SetPoint("TOPLEFT", drop, "TOPRIGHT", 12, -4)
	dropText:SetText("Click for picker, then press P for Spellbook")
	dropText:SetTextColor(unpack(Theme.Colors.text))
	local function AcceptCooldownCursor()
		local cursorType = GetCursorInfo()
		if cursorType then
			Options:AddCooldownSpellFromCursor()
		else
			Options:OpenCooldownSpellPicker()
		end
	end
	drop:SetScript("OnReceiveDrag", AcceptCooldownCursor)
	drop:SetScript("OnMouseUp", AcceptCooldownCursor)
	controls.CooldownDrop = drop

	local testAbility = CreateButton(body, "Test Swoop Alert", 180)
	testAbility:SetPoint("TOPLEFT", dropText, "BOTTOMLEFT", -4, -10)
	testAbility:SetScript("OnClick", function()
		local spellID = SlickUI.Alerts:GetTrackedCooldownSpells()[1]
		if spellID then
			SlickUI.Alerts:ShowCooldownReady(spellID, true)
		else
			SlickUI:Print("Add a tracked spell first, then use Test Swoop Alert.")
		end
	end)

	local spellScroll = CreateFrame("ScrollFrame", nil, body, "UIPanelScrollFrameTemplate")
	spellScroll:SetPoint("TOPLEFT", drop, "BOTTOMLEFT", 0, -16)
	spellScroll:SetPoint("RIGHT", body, "RIGHT", -28, 0)
	-- Ten 25-pixel rows remain visible while longer per-specialization lists
	-- scroll independently from the Alerts page itself.
	spellScroll:SetHeight(250)
	controls.CooldownRows, controls.CooldownScrollChild = CreateCooldownSpellRows(spellScroll, 560)
	spellScroll:SetScript("OnSizeChanged", function(self, width)
		local rowWidth = math.max(290, width - 4)
		controls.CooldownScrollChild:SetWidth(rowWidth)
		for _, row in ipairs(controls.CooldownRows) do
			row:SetWidth(rowWidth)
		end
	end)
	controls.CooldownScroll = spellScroll

	self.Pages.alerts = page
end

function Options:CreateClassBuffsPage(parent)
	local container = CreateFrame("Frame", nil, parent)
	container:SetAllPoints()
	local scroll = CreateFrame("ScrollFrame", nil, container, "UIPanelScrollFrameTemplate")
	scroll:SetPoint("TOPLEFT", 0, 0)
	scroll:SetPoint("BOTTOMRIGHT", -26, 0)
	scroll:EnableMouseWheel(true)
	local page = CreateFrame("Frame", nil, scroll)
	page:SetSize(820, 1600)
	scroll:SetScrollChild(page)
	scroll:SetScript("OnSizeChanged", function(_, width)
		page:SetWidth(math.max(760, width - 4))
	end)
	scroll:SetScript("OnMouseWheel", function(self, delta)
		local maximum = math.max(0, page:GetHeight() - self:GetHeight())
		self:SetVerticalScroll(math.max(0, math.min(maximum, self:GetVerticalScroll() - delta * 48)))
	end)
	local controls = { Rows = {} }
	self.ClassBuffControls = controls

	local heading = Theme:CreateText(page, 17)
	heading:SetPoint("TOPLEFT", 0, 0)
	heading:SetText("Class")
	heading:SetTextColor(unpack(Theme.Colors.goldText))

	local description = Theme:CreateText(page, 10)
	description:SetPoint("TOPLEFT", heading, "BOTTOMLEFT", 0, -8)
	description:SetPoint("RIGHT", page, "RIGHT", -8, 0)
	description:SetJustifyH("LEFT")
	description:SetText("Checks learned group buffs and optional personal maintenance buffs for the current class and specialization. SlickUI reminds you when an enabled buff is missing.")
	description:SetTextColor(unpack(Theme.Colors.mutedText))

	local context = Theme:CreateText(page, 13)
	context:SetPoint("TOPLEFT", description, "BOTTOMLEFT", 0, -22)
	context:SetTextColor(unpack(Theme.Colors.goldText))
	controls.Context = context

	local enabled = CreateCheckbox(page, "Enable class-buff reminders")
	enabled:SetPoint("TOPLEFT", context, "BOTTOMLEFT", -4, -10)
	enabled:SetScript("OnClick", function(self)
		SlickUI.DB.classBuffs.enabled = self:GetChecked() == true
		SlickUI.ClassBuffs:Refresh()
		Options:RefreshClassBuffControls()
	end)
	controls.Enabled = enabled

	local selfOnly = CreateCheckbox(page, "Track self-only buffs")
	selfOnly:SetPoint("LEFT", enabled, "LEFT", 270, 0)
	selfOnly:SetScript("OnClick", function(self)
		SlickUI.DB.classBuffs.trackSelfOnly = self:GetChecked() == true
		SlickUI.ClassBuffs:Refresh()
		Options:RefreshClassBuffControls()
	end)
	controls.SelfOnly = selfOnly

	local listHeading = Theme:CreateText(page, 12)
	listHeading:SetPoint("TOPLEFT", enabled, "BOTTOMLEFT", 4, -20)
	listHeading:SetText("Helpful buffs to track")
	listHeading:SetTextColor(unpack(Theme.Colors.goldText))

	for index = 1, 7 do
		local row = CreateFrame("Frame", nil, page, "BackdropTemplate")
		row:SetSize(410, 38)
		row:SetPoint("TOPLEFT", listHeading, "BOTTOMLEFT", 0, -8 - ((index - 1) * 42))
		row:SetBackdrop({ bgFile = Theme.Texture, edgeFile = Theme.Texture, edgeSize = 1 })
		row:SetBackdropColor(unpack(Theme.Colors.panelSoft))
		row:SetBackdropBorderColor(unpack(Theme.Colors.borderMuted))
		local checkbox = CreateCheckbox(row, "")
		checkbox:SetPoint("LEFT", 4, 0)
		checkbox:SetScript("OnClick", function(self)
			if row.SpellID then
				SlickUI.ClassBuffs:SetTracked(row.SpellID, self:GetChecked() == true)
			end
		end)
		row.Checkbox = checkbox
		local icon = row:CreateTexture(nil, "ARTWORK")
		icon:SetSize(28, 28)
		icon:SetPoint("LEFT", checkbox, "RIGHT", 4, 0)
		icon:SetTexCoord(0.08, 0.92, 0.08, 0.92)
		row.Icon = icon
		local name = Theme:CreateText(row, 11)
		name:SetPoint("LEFT", icon, "RIGHT", 10, 0)
		name:SetPoint("RIGHT", row, "RIGHT", -8, 0)
		name:SetJustifyH("LEFT")
		name:SetTextColor(unpack(Theme.Colors.text))
		row.Name = name
		row:Hide()
		controls.Rows[index] = row
	end

	local empty = Theme:CreateText(page, 11)
	empty:SetPoint("TOPLEFT", listHeading, "BOTTOMLEFT", 0, -16)
	empty:SetPoint("RIGHT", page, "RIGHT", -8, 0)
	empty:SetJustifyH("LEFT")
	empty:SetText("No supported class buff is learned in the current specialization.")
	empty:SetTextColor(unpack(Theme.Colors.mutedText))
	controls.Empty = empty

	local procControls = { Rows = {} }
	self.ClassProcControls = procControls
	local procHeading = Theme:CreateText(page, 13)
	procHeading:SetPoint("TOPLEFT", listHeading, "BOTTOMLEFT", 0, -326)
	procHeading:SetText("Passive Proc Alerts")
	procHeading:SetTextColor(unpack(Theme.Colors.goldText))
	local procDescription = Theme:CreateText(page, 10)
	procDescription:SetPoint("TOPLEFT", procHeading, "BOTTOMLEFT", 0, -8)
	procDescription:SetWidth(410); procDescription:SetJustifyH("LEFT")
	procDescription:SetText("Shows an icon and/or plays a sound when an actionable class proc becomes active.")
	procDescription:SetTextColor(unpack(Theme.Colors.mutedText))
	local procEnabled = CreateCheckbox(page, "Enable passive proc alerts")
	procEnabled:SetPoint("TOPLEFT", procDescription, "BOTTOMLEFT", -4, -10)
	procEnabled:SetScript("OnClick", function(self)
		SlickUI.DB.classProcs.enabled = self:GetChecked() == true
		SlickUI.ClassProcAlerts:Refresh(); Options:RefreshClassBuffControls()
	end)
	procControls.Enabled = procEnabled
	for index = 1, 5 do
		local row = CreateFrame("Frame", nil, page, "BackdropTemplate")
		row:SetSize(410, 82); row:SetPoint("TOPLEFT", procEnabled, "BOTTOMLEFT", 4, -10 - ((index - 1) * 88))
		row:SetBackdrop({ bgFile = Theme.Texture, edgeFile = Theme.Texture, edgeSize = 1 })
		row:SetBackdropColor(unpack(Theme.Colors.panelSoft)); row:SetBackdropBorderColor(unpack(Theme.Colors.borderMuted))
		local icon = row:CreateTexture(nil, "ARTWORK")
		icon:SetSize(36, 36); icon:SetPoint("TOPLEFT", 10, -10); icon:SetTexCoord(0.08, 0.92, 0.08, 0.92); row.Icon = icon
		local name = Theme:CreateText(row, 12)
		name:SetPoint("TOPLEFT", icon, "TOPRIGHT", 10, -1); name:SetPoint("RIGHT", row, "RIGHT", -8, 0); name:SetJustifyH("LEFT"); row.Name = name
		local enabledProc = CreateCheckbox(row, "Alert")
		enabledProc:SetPoint("TOPLEFT", icon, "BOTTOMLEFT", -4, -6); row.Enabled = enabledProc
		local showIcon = CreateCheckbox(row, "Show icon")
		showIcon:SetPoint("LEFT", enabledProc, "LEFT", 92, 0); row.ShowIcon = showIcon
		local sound = CreateCheckbox(row, "Play sound")
		sound:SetPoint("LEFT", showIcon, "LEFT", 112, 0); row.Sound = sound
		local test = CreateButton(row, "Test", 58)
		test:SetPoint("RIGHT", row, "RIGHT", -8, -18); row.Test = test
		local function Save(field, value)
			if not row.AuraID then return end
			SlickUI.ClassProcAlerts:GetSettings(row.AuraID)[field] = value
			SlickUI.ClassProcAlerts:Refresh(); Options:RefreshClassBuffControls()
		end
		enabledProc:SetScript("OnClick", function(self) Save("enabled", self:GetChecked() == true) end)
		showIcon:SetScript("OnClick", function(self) Save("icon", self:GetChecked() == true) end)
		sound:SetScript("OnClick", function(self) Save("sound", self:GetChecked() == true) end)
		test:SetScript("OnClick", function() if row.AuraID then SlickUI.ClassProcAlerts:Preview(row.AuraID) end end)
		row:Hide(); procControls.Rows[index] = row
	end
	local procEmpty = Theme:CreateText(page, 11)
	procEmpty:SetPoint("TOPLEFT", procEnabled, "BOTTOMLEFT", 4, -18); procEmpty:SetWidth(410); procEmpty:SetJustifyH("LEFT")
	procEmpty:SetText("No supported passive proc alerts are available for the current specialization.")
	procEmpty:SetTextColor(unpack(Theme.Colors.mutedText)); procControls.Empty = procEmpty

	local announceControls = { Rows = {} }
	self.ClassAnnouncementControls = announceControls
	local announceHeading = Theme:CreateText(page, 13)
	announceHeading:SetPoint("TOPLEFT", procEnabled, "BOTTOMLEFT", 4, -470)
	announceHeading:SetText("Group Utility Announcements")
	announceHeading:SetTextColor(unpack(Theme.Colors.goldText))
	local announceDescription = Theme:CreateText(page, 10)
	announceDescription:SetPoint("TOPLEFT", announceHeading, "BOTTOMLEFT", 0, -8)
	announceDescription:SetWidth(520); announceDescription:SetJustifyH("LEFT")
	announceDescription:SetText("Announces successful casts of selected crowd-control and group-utility abilities to instance, raid, or party chat. Nothing is sent while solo.")
	announceDescription:SetTextColor(unpack(Theme.Colors.mutedText))
	local announceEnabled = CreateCheckbox(page, "Enable group utility announcements")
	announceEnabled:SetPoint("TOPLEFT", announceDescription, "BOTTOMLEFT", -4, -10)
	announceEnabled:SetScript("OnClick", function(self)
		SlickUI.DB.classAnnouncements.enabled = self:GetChecked() == true
		Options:RefreshClassBuffControls()
	end)
	announceControls.Enabled = announceEnabled
	for index = 1, 8 do
		local row = CreateFrame("Frame", nil, page, "BackdropTemplate")
		row:SetSize(410, 38)
		row:SetPoint("TOPLEFT", announceEnabled, "BOTTOMLEFT", 4, -10 - ((index - 1) * 42))
		row:SetBackdrop({ bgFile = Theme.Texture, edgeFile = Theme.Texture, edgeSize = 1 })
		row:SetBackdropColor(unpack(Theme.Colors.panelSoft)); row:SetBackdropBorderColor(unpack(Theme.Colors.borderMuted))
		local checkbox = CreateCheckbox(row, "")
		checkbox:SetPoint("LEFT", 4, 0); row.Checkbox = checkbox
		local icon = row:CreateTexture(nil, "ARTWORK")
		icon:SetSize(28, 28); icon:SetPoint("LEFT", checkbox, "RIGHT", 4, 0); icon:SetTexCoord(0.08, 0.92, 0.08, 0.92); row.Icon = icon
		local name = Theme:CreateText(row, 11)
		name:SetPoint("LEFT", icon, "RIGHT", 10, 0); name:SetPoint("RIGHT", row, "RIGHT", -8, 0); name:SetJustifyH("LEFT"); row.Name = name
		checkbox:SetScript("OnClick", function(self)
			if row.SpellID then SlickUI.ClassAnnouncements:SetAbilityEnabled(row.SpellID, self:GetChecked() == true) end
		end)
		row:Hide(); announceControls.Rows[index] = row
	end
	local announceEmpty = Theme:CreateText(page, 11)
	announceEmpty:SetPoint("TOPLEFT", announceEnabled, "BOTTOMLEFT", 4, -18)
	announceEmpty:SetWidth(520); announceEmpty:SetJustifyH("LEFT")
	announceEmpty:SetText("No supported group utility ability is learned in the current specialization.")
	announceEmpty:SetTextColor(unpack(Theme.Colors.mutedText)); announceControls.Empty = announceEmpty

	local note = Theme:CreateText(page, 9)
	note:SetPoint("BOTTOMLEFT", page, "BOTTOMLEFT", 0, 8)
	note:SetPoint("RIGHT", page, "RIGHT", -8, 0)
	note:SetJustifyH("LEFT")
	note:SetText("Out-of-range, disconnected, dead, and unreadable protected units are ignored to prevent false reminders. The reminder clears automatically after everyone nearby is buffed.")
	note:SetTextColor(unpack(Theme.Colors.mutedText))

	self.Pages.classbuffs = container
end

function Options:RefreshGroupMarkerControls()
	if not self.GroupMarkerDropdowns or not SlickUI.GroupMarkers then return end
	for _, assignment in ipairs(SlickUI.GroupMarkers.Assignments) do
		local dropdown = self.GroupMarkerDropdowns[assignment.key]
		if dropdown then
			local marker = tonumber(SlickUI.DB.groupMarkers[assignment.key]) or 0
			UIDropDownMenu_SetSelectedValue(dropdown, marker)
			UIDropDownMenu_SetText(dropdown, SlickUI.GroupMarkers:GetMarkerLabel(marker))
		end
	end
end

function Options:CreateGroupPage(parent)
	local page = CreateFrame("Frame", nil, parent)
	page:SetAllPoints()

	local heading = Theme:CreateText(page, 17)
	heading:SetPoint("TOPLEFT", 0, 0)
	heading:SetText("Group")
	heading:SetTextColor(unpack(Theme.Colors.goldText))

	local description = Theme:CreateText(page, 11)
	description:SetPoint("TOPLEFT", heading, "BOTTOMLEFT", 0, -8)
	description:SetPoint("RIGHT", page, "RIGHT", -8, 0)
	description:SetJustifyH("LEFT")
	description:SetText("Configure tank and healer markers, then apply them with secure clicks.")
	description:SetTextColor(unpack(Theme.Colors.mutedText))

	local tankHeading = Theme:CreateText(page, 13)
	tankHeading:SetPoint("TOPLEFT", description, "BOTTOMLEFT", 0, -24)
	tankHeading:SetText("Tanks")
	tankHeading:SetTextColor(unpack(Theme.Colors.goldText))

	local healerHeading = Theme:CreateText(page, 13)
	healerHeading:SetPoint("TOPLEFT", tankHeading, "TOPLEFT", 230, 0)
	healerHeading:SetText("Healers")
	healerHeading:SetTextColor(unpack(Theme.Colors.goldText))

	self.GroupMarkerDropdowns = {}
	local roleRows = { TANK = 0, HEALER = 0 }
	for _, assignment in ipairs(SlickUI.GroupMarkers.Assignments) do
		local assignmentKey = assignment.key
		local columnHeading = assignment.role == "TANK" and tankHeading or healerHeading
		local row = roleRows[assignment.role]
		roleRows[assignment.role] = row + 1
		local label = Theme:CreateText(page, 11)
		label:SetPoint("TOPLEFT", columnHeading, "BOTTOMLEFT", 0, -18 - row * 64)
		label:SetText(assignment.label)
		label:SetTextColor(unpack(Theme.Colors.text))

		local dropdown = CreateFrame("Frame", nil, page, "UIDropDownMenuTemplate")
		dropdown:SetPoint("TOPLEFT", label, "BOTTOMLEFT", -16, -2)
		UIDropDownMenu_SetWidth(dropdown, 170)
		UIDropDownMenu_Initialize(dropdown, function()
			for marker = 0, 8 do
				local markerValue = marker
				local info = UIDropDownMenu_CreateInfo()
				info.text = SlickUI.GroupMarkers:GetMarkerLabel(markerValue)
				info.value = markerValue
				info.checked = (tonumber(SlickUI.DB.groupMarkers[assignmentKey]) or 0) == markerValue
				info.func = function()
					SlickUI.DB.groupMarkers[assignmentKey] = markerValue
					Options:RefreshGroupMarkerControls()
				end
				UIDropDownMenu_AddButton(info)
			end
		end)
		self.GroupMarkerDropdowns[assignmentKey] = dropdown
	end

	local apply = CreateButton(page, "Apply Group Marks", 190)
	apply:SetPoint("TOPLEFT", tankHeading, "BOTTOMLEFT", 0, -178)
	apply:SetScript("OnClick", function()
		if SlickUI.GroupMarkers then SlickUI.GroupMarkers:Apply(true) end
	end)

	local note = Theme:CreateText(page, 10)
	note:SetPoint("TOPLEFT", apply, "BOTTOMLEFT", 0, -16)
	note:SetPoint("RIGHT", page, "RIGHT", -8, 0)
	note:SetJustifyH("LEFT")
	note:SetText("Patch 12.0 requires one secure click per marker. Raid marking requires leader or assistant permission. Selecting None disables that role slot. Duplicate choices move that marker to the later role.")
	note:SetTextColor(unpack(Theme.Colors.mutedText))

	local announceHeading = Theme:CreateText(page, 13)
	announceHeading:SetPoint("TOPLEFT", note, "BOTTOMLEFT", 0, -26)
	announceHeading:SetText("Lust Announce")
	announceHeading:SetTextColor(unpack(Theme.Colors.goldText))

	local lustAnnounce = CreateCheckbox(page, "Enable Lust Announce")
	lustAnnounce:SetPoint("TOPLEFT", announceHeading, "BOTTOMLEFT", -4, -10)
	lustAnnounce:SetChecked(SlickUI.DB.groupControls.lustAnnounce == true)
	lustAnnounce:SetScript("OnClick", function(self)
		SlickUI.DB.groupControls.lustAnnounce = self:GetChecked() == true
	end)
	self.LustAnnounceCheckbox = lustAnnounce

	local announceNote = Theme:CreateText(page, 10)
	announceNote:SetPoint("TOPLEFT", lustAnnounce, "BOTTOMLEFT", 4, -8)
	announceNote:SetPoint("RIGHT", page, "RIGHT", -8, 0)
	announceNote:SetJustifyH("LEFT")
	announceNote:SetText("Announces Bloodlust, Heroism, Time Warp, Primal Rage, Fury of the Aspects, Harrier's Cry, and drums once to instance, raid, or party chat.")
	announceNote:SetTextColor(unpack(Theme.Colors.mutedText))

	local pullHeading = Theme:CreateText(page, 13)
	pullHeading:SetPoint("TOPLEFT", announceNote, "BOTTOMLEFT", 0, -24)
	pullHeading:SetText("Pull Announce")
	pullHeading:SetTextColor(unpack(Theme.Colors.goldText))

	local pullAnnounce = CreateCheckbox(page, "Enable boss Pull Announce")
	pullAnnounce:SetPoint("TOPLEFT", pullHeading, "BOTTOMLEFT", -4, -10)
	pullAnnounce:SetChecked(SlickUI.DB.groupControls.pullAnnounce == true)
	pullAnnounce:SetScript("OnClick", function(self)
		SlickUI.DB.groupControls.pullAnnounce = self:GetChecked() == true
	end)
	self.PullAnnounceCheckbox = pullAnnounce

	local pullNote = Theme:CreateText(page, 10)
	pullNote:SetPoint("TOPLEFT", pullAnnounce, "BOTTOMLEFT", 4, -8)
	pullNote:SetPoint("RIGHT", page, "RIGHT", -8, 0)
	pullNote:SetJustifyH("LEFT")
	pullNote:SetText("For dungeon and raid bosses only, announces the first group member detected entering combat. Trash pulls are ignored.")
	pullNote:SetTextColor(unpack(Theme.Colors.mutedText))

	self.Pages.group = page
end

function Options:CreateCutePage(parent)
	local container = CreateFrame("Frame", nil, parent)
	container:SetAllPoints()

	local scroll = CreateFrame("ScrollFrame", nil, container, "UIPanelScrollFrameTemplate")
	scroll:SetPoint("TOPLEFT", 0, 0)
	scroll:SetPoint("BOTTOMRIGHT", -22, 0)
	scroll:EnableMouseWheel(true)
	scroll:SetScript("OnMouseWheel", function(self, delta)
		local maximum = math.max(0, self:GetVerticalScrollRange())
		self:SetVerticalScroll(math.max(0, math.min(maximum, self:GetVerticalScroll() - delta * 38)))
	end)

	local page = CreateFrame("Frame", nil, scroll)
	page:SetSize(430, 700)
	scroll:SetScrollChild(page)
	container:SetScript("OnSizeChanged", function(_, width)
		page:SetWidth(math.max(400, width - 28))
	end)

	local heading = Theme:CreateText(page, 19)
	heading:SetPoint("TOPLEFT", 0, 0)
	heading:SetText("Fun Stuff")
	heading:SetTextColor(1.00, 0.42, 0.75, 1)

	local description = Theme:CreateText(page, 11)
	description:SetPoint("TOPLEFT", heading, "BOTTOMLEFT", 0, -8)
	description:SetPoint("RIGHT", page, "RIGHT", -8, 0)
	description:SetJustifyH("LEFT")
	description:SetText("Playful SlickUI themes and charming little extras live here. This page is scrollable from the very beginning, ready for more.")
	description:SetTextColor(unpack(Theme.Colors.mutedText))

	local ponyHeading = Theme:CreateText(page, 15)
	ponyHeading:SetPoint("TOPLEFT", description, "BOTTOMLEFT", 0, -28)
	ponyHeading:SetText("Pink Pony Club")
	ponyHeading:SetTextColor(1.00, 0.42, 0.75, 1)

	local pony = CreateCheckbox(page, "Join the Pink Pony Club")
	pony:SetPoint("TOPLEFT", ponyHeading, "BOTTOMLEFT", -4, -12)
	pony:SetChecked(SlickUI.DB.cute and SlickUI.DB.cute.pinkPonyClub == true)
	pony:SetScript("OnClick", function(self)
		if SlickUI.CuteTheme then SlickUI.CuteTheme:SetPinkPonyClub(self:GetChecked() == true) end
		Options:RefreshTabs()
	end)
	self.PinkPonyClubCheckbox = pony

	local note = Theme:CreateText(page, 11)
	note:SetPoint("TOPLEFT", pony, "BOTTOMLEFT", 4, -10)
	note:SetPoint("RIGHT", page, "RIGHT", -8, 0)
	note:SetJustifyH("LEFT")
	note:SetText("Swaps SlickUI's golden window trim for candy pink and gives every information bar a rich berry-pink background. Uncheck it anytime to return to the classic colors.")
	note:SetTextColor(unpack(Theme.Colors.mutedText))

	local preview = CreateFrame("Frame", nil, page, "BackdropTemplate")
	preview:SetPoint("TOPLEFT", note, "BOTTOMLEFT", 0, -28)
	preview:SetPoint("RIGHT", page, "RIGHT", -10, 0)
	preview:SetHeight(92)
	preview:SetBackdrop({ bgFile = Theme.Texture, edgeFile = Theme.Texture, edgeSize = 2 })
	preview:SetBackdropColor(0.24, 0.035, 0.13, 0.96)
	Theme:RegisterAccentFrame(preview)

	local heart = Theme:CreateText(preview, 24)
	heart:SetPoint("LEFT", 18, 0)
	heart:SetText("♥")
	heart:SetTextColor(1.00, 0.32, 0.72, 1)
	local previewTitle = Theme:CreateText(preview, 15)
	previewTitle:SetPoint("LEFT", heart, "RIGHT", 14, 10)
	previewTitle:SetText("Pink Pony Club")
	previewTitle:SetTextColor(1.00, 0.62, 0.84, 1)
	local previewText = Theme:CreateText(preview, 10)
	previewText:SetPoint("TOPLEFT", previewTitle, "BOTTOMLEFT", 0, -8)
	previewText:SetText("Cute, bright, and still easy to read.")
	previewText:SetTextColor(unpack(Theme.Colors.text))

	local afkHeading = Theme:CreateText(page, 15)
	afkHeading:SetPoint("TOPLEFT", preview, "BOTTOMLEFT", 0, -34)
	afkHeading:SetText("AFK Mode")
	afkHeading:SetTextColor(1.00, 0.42, 0.75, 1)

	local afkMode = CreateCheckbox(page, "Enable AFK Mode")
	afkMode:SetPoint("TOPLEFT", afkHeading, "BOTTOMLEFT", -4, -12)
	afkMode:SetChecked(SlickUI.DB.funStuff and SlickUI.DB.funStuff.afkMode == true)
	afkMode:SetScript("OnClick", function(self)
		if SlickUI.AFKMode then SlickUI.AFKMode:SetEnabled(self:GetChecked() == true) end
	end)
	self.AFKModeCheckbox = afkMode

	local afkNote = Theme:CreateText(page, 11)
	afkNote:SetPoint("TOPLEFT", afkMode, "BOTTOMLEFT", 4, -10)
	afkNote:SetPoint("RIGHT", page, "RIGHT", -8, 0)
	afkNote:SetJustifyH("LEFT")
	afkNote:SetText("When your character goes AFK, SlickUI opens a full-screen scene selected randomly from the available collection. First scene: your character sleeping beside a cozy campfire.")
	afkNote:SetTextColor(unpack(Theme.Colors.mutedText))

	self.Pages.cute = container
end

function Options:CreateGeneralPage(parent, panel)
	local container = CreateFrame("Frame", nil, parent)
	container:SetAllPoints()
	local scroll = CreateFrame("ScrollFrame", nil, container, "UIPanelScrollFrameTemplate")
	scroll:SetPoint("TOPLEFT", 0, 0)
	scroll:SetPoint("BOTTOMRIGHT", -22, 0)
	scroll:EnableMouseWheel(true)
	scroll:SetScript("OnMouseWheel", function(self, delta)
		local maximum = math.max(0, self:GetVerticalScrollRange())
		self:SetVerticalScroll(math.max(0, math.min(maximum, self:GetVerticalScroll() - (delta * 38))))
	end)
	local page = CreateFrame("Frame", nil, scroll)
	page:SetHeight(740)
	page:SetWidth(430)
	scroll:SetScrollChild(page)
	container:SetScript("OnSizeChanged", function(_, width)
		page:SetWidth(math.max(400, width - 28))
	end)

	local heading = Theme:CreateText(page, 17)
	heading:SetPoint("TOPLEFT", 0, 0)
	heading:SetText("General")
	heading:SetTextColor(unpack(Theme.Colors.goldText))

	local description = Theme:CreateText(page, 11)
	description:SetPoint("TOPLEFT", heading, "BOTTOMLEFT", 0, -8)
	description:SetPoint("RIGHT", page, "RIGHT", -8, 0)
	description:SetJustifyH("LEFT")
	description:SetText("Core SlickUI display and positioning controls.")
	description:SetTextColor(unpack(Theme.Colors.mutedText))

	local snapGrid = CreateCheckbox(page, "Snap to edit grid")
	snapGrid:SetPoint("TOPLEFT", description, "BOTTOMLEFT", -4, -18)
	snapGrid:SetScript("OnClick", function(self)
		SlickUI.Movers:SetGridEnabled(self:GetChecked() == true)
		Options:Refresh()
	end)
	panel.SnapGridCheckbox = snapGrid

	local snapFrames = CreateCheckbox(page, "Snap to nearby SlickUI frames")
	snapFrames:SetPoint("TOPLEFT", snapGrid, "BOTTOMLEFT", 0, -4)
	snapFrames:SetScript("OnClick", function(self)
		SlickUI.Movers:SetFrameSnapEnabled(self:GetChecked() == true)
		Options:Refresh()
	end)
	panel.SnapFramesCheckbox = snapFrames

	local gridSize = CreateSlider(page, "Edit-grid size", 8, 100, 1, 180, function(value)
		SlickUI.Movers:SetGridSize(value)
	end)
	gridSize:SetPoint("TOPLEFT", snapGrid, "TOPLEFT", 245, -15)
	panel.GridSizeSlider = gridSize

	local vendorHeading = Theme:CreateText(page, 13)
	vendorHeading:SetPoint("TOPLEFT", gridSize, "BOTTOMLEFT", 0, -34)
	vendorHeading:SetText("Vendor Automation")
	vendorHeading:SetTextColor(unpack(Theme.Colors.goldText))

	local sellGreys = CreateCheckbox(page, "Sell grey items automatically")
	sellGreys:SetPoint("TOPLEFT", vendorHeading, "BOTTOMLEFT", -4, -8)
	sellGreys:SetScript("OnClick", function(self)
		SlickUI.DB.vendorAutomation.sellGreys = self:GetChecked() == true
		Options:Refresh()
	end)
	panel.SellGreysCheckbox = sellGreys

	local autoRepair = CreateCheckbox(page, "Repair items automatically")
	autoRepair:SetPoint("TOPLEFT", sellGreys, "BOTTOMLEFT", 0, -6)
	autoRepair:SetScript("OnClick", function(self)
		SlickUI.DB.vendorAutomation.autoRepair = self:GetChecked() == true
		Options:Refresh()
	end)
	panel.AutoRepairCheckbox = autoRepair

	local repairLabel = Theme:CreateText(page, 10)
	repairLabel:SetPoint("TOPLEFT", autoRepair, "BOTTOMLEFT", 4, -10)
	repairLabel:SetText("Repair funding")
	repairLabel:SetTextColor(unpack(Theme.Colors.mutedText))

	local repairFunds = CreateFrame("Frame", "SlickUIRepairFundsDropdown", page, "UIDropDownMenuTemplate")
	repairFunds:SetPoint("TOPLEFT", repairLabel, "BOTTOMLEFT", -16, -3)
	UIDropDownMenu_SetWidth(repairFunds, 180)
	UIDropDownMenu_Initialize(repairFunds, function()
		for _, mode in ipairs({ "personal", "guild", "guildFirst", "personalFirst" }) do
			local selectedMode = mode
			local info = UIDropDownMenu_CreateInfo()
			info.text = SlickUI.VendorAutomation.RepairFundLabels[selectedMode]
			info.value = selectedMode
			info.checked = SlickUI.DB.vendorAutomation.repairFunds == selectedMode
			info.func = function()
				SlickUI.DB.vendorAutomation.repairFunds = selectedMode
				Options:Refresh()
			end
			UIDropDownMenu_AddButton(info)
		end
	end)
	panel.RepairFundsDropdown = repairFunds

	local castbars = CreateCheckbox(page, "Show SlickUI cast bars")
	castbars:SetPoint("TOPLEFT", snapFrames, "BOTTOMLEFT", 0, -10)
	castbars:SetScript("OnClick", function(self)
		SlickUI:SetCastbarsEnabled(self:GetChecked())
		Options:Refresh()
	end)
	panel.CastbarCheckbox = castbars

	local minimap = CreateCheckbox(page, "Show minimap button")
	minimap:SetPoint("TOPLEFT", castbars, "BOTTOMLEFT", 0, -10)
	minimap:SetScript("OnClick", function(self)
		if not SlickUI:IsAvailableOutOfCombat("Minimap-button changes") then
			Options:Refresh()
			return
		end
		SlickUI.MinimapButton:SetShown(self:GetChecked())
		Options:Refresh()
	end)
	panel.MinimapCheckbox = minimap

	local queueStatus = CreateCheckbox(page, "Show LFG / LFR queue timer")
	queueStatus:SetPoint("TOPLEFT", minimap, "BOTTOMLEFT", 0, -10)
	queueStatus:SetScript("OnClick", function(self)
		SlickUI.DB.queueStatus.enabled = self:GetChecked() == true
		if SlickUI.QueueStatus then SlickUI.QueueStatus:Refresh() end
		Options:Refresh()
	end)
	panel.QueueStatusCheckbox = queueStatus

	local questTracker = CreateCheckbox(page, "Use SlickUI scrollable quest tracker")
	questTracker:SetPoint("TOPLEFT", queueStatus, "BOTTOMLEFT", 0, -10)
	questTracker:SetScript("OnClick", function(self)
		if SlickUI.QuestTracker then SlickUI.QuestTracker:SetEnabled(self:GetChecked()) end
		Options:Refresh()
	end)
	panel.QuestTrackerCheckbox = questTracker

	local groupControls = CreateCheckbox(page, "Show raid / group controls")
	-- Leave room for the repair-funding label and dropdown, while keeping the
	-- final option in this column clear of the action-button row below it.
	groupControls:SetPoint("TOPLEFT", autoRepair, "BOTTOMLEFT", 0, -54)
	groupControls:SetScript("OnClick", function(self)
		if SlickUI.GroupControls then SlickUI.GroupControls:SetEnabled(self:GetChecked() == true) end
		Options:Refresh()
	end)
	panel.GroupControlsCheckbox = groupControls

	local flightTimer = CreateCheckbox(page, "Show flight timer while traveling")
	flightTimer:SetPoint("TOPLEFT", groupControls, "BOTTOMLEFT", 0, -8)
	flightTimer:SetScript("OnClick", function(self)
		if SlickUI.FlightTimer then SlickUI.FlightTimer:SetEnabled(self:GetChecked() == true) end
		Options:Refresh()
	end)
	panel.FlightTimerCheckbox = flightTimer

	local chatCopyButton = CreateCheckbox(page, "Show Copy button on chat windows")
	chatCopyButton:SetPoint("TOPLEFT", flightTimer, "BOTTOMLEFT", 0, -8)
	chatCopyButton:SetScript("OnClick", function(self)
		SlickUI.DB.chatTools.showCopyButton = self:GetChecked() == true
		if SlickUI.ChatTools then SlickUI.ChatTools:RefreshButtonVisibility() end
		Options:Refresh()
	end)
	panel.ChatCopyButtonCheckbox = chatCopyButton

	local reputationTracker = CreateCheckbox(page, "Track the most recently gained reputation")
	reputationTracker:SetPoint("TOPLEFT", questTracker, "BOTTOMLEFT", 0, -10)
	reputationTracker:SetScript("OnClick", function(self)
		if SlickUI.ReputationTracker then SlickUI.ReputationTracker:SetEnabled(self:GetChecked() == true) end
		Options:Refresh()
	end)
	panel.ReputationTrackerCheckbox = reputationTracker

	local skipSeenCinematics = CreateCheckbox(page, "Skip previously seen cutscenes")
	skipSeenCinematics:SetPoint("TOPLEFT", reputationTracker, "BOTTOMLEFT", 0, -10)
	skipSeenCinematics:SetScript("OnClick", function(self)
		SlickUI.DB.cinematics.skipPreviouslySeen = self:GetChecked() == true
		Options:Refresh()
	end)
	panel.SkipSeenCinematicsCheckbox = skipSeenCinematics

	local resetCinematicHistory = CreateButton(page, "Reset Cutscene History", 180)
	resetCinematicHistory:SetPoint("TOPLEFT", skipSeenCinematics, "BOTTOMLEFT", 4, -8)
	resetCinematicHistory:SetScript("OnClick", function()
		if SlickUI.Cinematics then SlickUI.Cinematics:ResetHistory() end
	end)

	local resetAll = CreateButton(page, "Reset All SlickUI Settings", 210)
	resetAll:SetScript("OnClick", function()
		if not SlickUI:IsAvailableOutOfCombat("Settings reset") then return end
		StaticPopup_Show("SLICKUI_RESET_ALL_SETTINGS")
	end)

	local reset = CreateButton(page, "Reset Frame Positions", 180)
	-- The right column extends two rows below reputation tracking. Reserve that
	-- height before starting the shared action area so neither column overlaps it.
	reset:SetPoint("TOPLEFT", resetCinematicHistory, "BOTTOMLEFT", 0, -42)
	reset:SetScript("OnClick", function()
		SlickUI.Movers:ResetPositions()
		Options:Refresh()
	end)

	local editMode = CreateButton(page, "Unlock & Move Frames", 190)
	editMode:SetPoint("LEFT", reset, "RIGHT", 14, 0)
	editMode:SetScript("OnClick", function()
		SlickUI.Movers:SetLocked(not SlickUI.DB.locked)
		Options:Refresh()
	end)
	panel.EditModeButton = editMode

	local restoreSettings = CreateButton(page, "Restore Previous Settings", 210)
	restoreSettings:SetPoint("TOPLEFT", reset, "BOTTOMLEFT", 0, -12)
	resetAll:SetPoint("LEFT", restoreSettings, "RIGHT", 14, 0)
	restoreSettings:SetScript("OnClick", function()
		if not SlickUI:IsAvailableOutOfCombat("Settings restore") then return end
		if not Options:HasResetBackup() then
			SlickUI:Print("No reset backup is available yet.")
			return
		end
		StaticPopup_Show("SLICKUI_RESTORE_SETTINGS_BACKUP")
	end)
	panel.RestoreSettingsButton = restoreSettings

	local note = Theme:CreateText(page, 10)
	note:SetPoint("BOTTOMLEFT", 0, 4)
	note:SetPoint("RIGHT", page, "RIGHT", -8, 0)
	note:SetJustifyH("LEFT")
	note:SetText("Unlocking enters SlickUI Edit Mode, hides Settings, and leaves Close Edit Mode and Hidden Frames controls on screen. Visible top-level Blizzard and addon frames receive movers automatically. Right-click the minimap button to enter or close Edit Mode quickly.")
	note:SetTextColor(unpack(Theme.Colors.mutedText))

	self.Pages.general = container
end

local AuraUnitLabels = {
	player = "Player",
	target = "Target",
	focus = "Focus",
	pet = "Pet",
	targettarget = "Target of Target",
	boss = "Boss",
}

function Options:RefreshAuraControls()
	local controls = self.AuraControls
	if not controls or not SlickUI.DB then return end
	local unitSettings = SlickUI.DB.unitFrames.auras[self.SelectedAuraUnit]
	controls.Heading:SetText(AuraUnitLabels[self.SelectedAuraUnit] .. " Buffs & Debuffs")
	for unit, button in pairs(controls.UnitButtons) do
		button:SetEnabled(unit ~= self.SelectedAuraUnit)
	end
	for auraType, column in pairs(controls.Columns) do
		local settings = unitSettings[auraType]
		column.Enabled:SetChecked(settings.enabled)
		if column.KnownSpellsOnly then
			column.KnownSpellsOnly:SetChecked(settings.knownSpellsOnly == true)
			column.IncludeConsumables:SetChecked(settings.includeConsumables == true)
			column.IncludeConsumables:SetEnabled(settings.knownSpellsOnly == true)
		end
		UIDropDownMenu_SetSelectedValue(column.Position, settings.position)
		UIDropDownMenu_SetText(column.Position, ({ ABOVE = "Above", BELOW = "Below", LEFT = "Left", RIGHT = "Right" })[settings.position])
		SetSliderValue(column.IconSize, settings.iconSize)
		SetSliderValue(column.Spacing, settings.spacing)
		SetSliderValue(column.MaxIcons, settings.maxIcons)
	end
end

function Options:CreateAurasPage(parent)
	local page = CreateFrame("Frame", nil, parent)
	page:SetAllPoints()
	local controls = { UnitButtons = {}, Columns = {} }
	self.AuraControls = controls

	local heading = Theme:CreateText(page, 17)
	heading:SetPoint("TOPLEFT", 0, 0)
	heading:SetText("Target Buffs & Debuffs")
	heading:SetTextColor(unpack(Theme.Colors.goldText))
	controls.Heading = heading

	local note = Theme:CreateText(page, 10)
	note:SetPoint("TOPLEFT", heading, "BOTTOMLEFT", 0, -7)
	note:SetPoint("RIGHT", page, "RIGHT", 0, 0)
	note:SetJustifyH("LEFT")
	note:SetText("Configure independent aura rows. Known-spell filtering checks this character's spellbook; recognized consumables can be included separately.")
	note:SetTextColor(unpack(Theme.Colors.mutedText))

	local previous
	for _, unit in ipairs({ "player", "target", "focus", "pet", "targettarget", "boss" }) do
		local selectedUnit = unit
		local button = CreateButton(page, selectedUnit == "targettarget" and "ToT" or AuraUnitLabels[selectedUnit], 70)
		if previous then
			button:SetPoint("LEFT", previous, "RIGHT", 6, 0)
		else
			button:SetPoint("TOPLEFT", note, "BOTTOMLEFT", 0, -12)
		end
		button:SetScript("OnClick", function()
			Options.SelectedAuraUnit = selectedUnit
			Options:RefreshAuraControls()
		end)
		controls.UnitButtons[selectedUnit] = button
		previous = button
	end

	local function UpdateAura(auraType, field, value)
		if not SlickUI:IsAvailableOutOfCombat("Aura-display changes") then
			Options:RefreshAuraControls()
			return
		end
		SlickUI.DB.unitFrames.auras[Options.SelectedAuraUnit][auraType][field] = value
		SlickUI:ApplyAuraSettings(Options.SelectedAuraUnit)
		Options:RefreshAuraControls()
	end

	for index, auraType in ipairs({ "buffs", "debuffs" }) do
		local selectedAuraType = auraType
		local column = {}
		controls.Columns[selectedAuraType] = column
		local container = CreateFrame("Frame", nil, page, "BackdropTemplate")
		container:SetSize(220, 280)
		container:SetPoint("TOPLEFT", note, "BOTTOMLEFT", (index - 1) * 235, -54)
		container:SetBackdrop({ bgFile = Theme.Texture, edgeFile = Theme.Texture, edgeSize = 1 })
		container:SetBackdropColor(unpack(Theme.Colors.panelSoft))
		container:SetBackdropBorderColor(unpack(Theme.Colors.borderMuted))

		local title = Theme:CreateText(container, 14)
		title:SetPoint("TOPLEFT", 14, -12)
		title:SetText(selectedAuraType == "buffs" and "Buffs" or "Debuffs")
		title:SetTextColor(unpack(Theme.Colors.goldText))

		local enabled = CreateCheckbox(container, "Show " .. selectedAuraType)
		enabled:SetPoint("TOPLEFT", title, "BOTTOMLEFT", -4, -7)
		enabled:SetScript("OnClick", function(self) UpdateAura(selectedAuraType, "enabled", self:GetChecked() == true) end)
		column.Enabled = enabled
		local positionAnchor = enabled
		if selectedAuraType == "buffs" then
			local knownSpellsOnly = CreateCheckbox(container, "Known class spells only")
			Theme:SetFont(knownSpellsOnly.Text, 10)
			knownSpellsOnly:SetPoint("TOPLEFT", enabled, "BOTTOMLEFT", 0, -3)
			knownSpellsOnly:SetScript("OnClick", function(self) UpdateAura("buffs", "knownSpellsOnly", self:GetChecked() and true or false) end)
			column.KnownSpellsOnly = knownSpellsOnly

			local includeConsumables = CreateCheckbox(container, "Include consumables")
			Theme:SetFont(includeConsumables.Text, 10)
			includeConsumables:SetPoint("TOPLEFT", knownSpellsOnly, "BOTTOMLEFT", 0, -2)
			includeConsumables:SetScript("OnClick", function(self) UpdateAura("buffs", "includeConsumables", self:GetChecked() and true or false) end)
			column.IncludeConsumables = includeConsumables
			positionAnchor = includeConsumables
		end

		local positionLabel = Theme:CreateText(container, 10)
		positionLabel:SetPoint("TOPLEFT", positionAnchor, "BOTTOMLEFT", 4, -7)
		positionLabel:SetText("Position")
		positionLabel:SetTextColor(unpack(Theme.Colors.mutedText))

		local dropdownName = selectedAuraType == "buffs" and "SlickUIBuffPositionDropdown" or "SlickUIDebuffPositionDropdown"
		local dropdown = CreateFrame("Frame", dropdownName, container, "UIDropDownMenuTemplate")
		dropdown:SetPoint("TOPLEFT", positionLabel, "BOTTOMLEFT", -16, 4)
		UIDropDownMenu_SetWidth(dropdown, 115)
		UIDropDownMenu_Initialize(dropdown, function()
			for _, choice in ipairs({ "ABOVE", "BELOW", "LEFT", "RIGHT" }) do
				local selected = choice
				local label = ({ ABOVE = "Above", BELOW = "Below", LEFT = "Left", RIGHT = "Right" })[selected]
				local info = UIDropDownMenu_CreateInfo()
				info.text = label
				info.value = selected
				info.checked = SlickUI.DB.unitFrames.auras[Options.SelectedAuraUnit][selectedAuraType].position == selected
				info.func = function() UpdateAura(selectedAuraType, "position", selected) end
				UIDropDownMenu_AddButton(info)
			end
		end)
		column.Position = dropdown

		local iconSize = CreateSlider(container, "Icon size", 14, 40, 1, 174, function(value) UpdateAura(selectedAuraType, "iconSize", value) end)
		iconSize:SetPoint("TOPLEFT", positionLabel, "BOTTOMLEFT", 4, -48)
		column.IconSize = iconSize
		local spacing = CreateSlider(container, "Icon spacing", 0, 10, 1, 174, function(value) UpdateAura(selectedAuraType, "spacing", value) end)
		spacing:SetPoint("TOPLEFT", iconSize, "BOTTOMLEFT", 0, -34)
		column.Spacing = spacing
		local maxIcons = CreateSlider(container, "Maximum icons", 1, 16, 1, 174, function(value) UpdateAura(selectedAuraType, "maxIcons", value) end)
		maxIcons:SetPoint("TOPLEFT", spacing, "BOTTOMLEFT", 0, -34)
		column.MaxIcons = maxIcons
	end

	self.Pages.auras = page
end

function Options:RefreshNameplateControls()
	local controls = self.NameplateControls
	if not controls or not SlickUI.DB then return end
	local settings = SlickUI.DB.nameplates
	controls.Enabled:SetChecked(settings.enabled)
	controls.Enemies:SetChecked(settings.showEnemies)
	controls.FriendlyPlayers:SetChecked(settings.showFriendlyPlayers)
	controls.FriendlyNPCs:SetChecked(settings.showFriendlyNPCs)
	controls.FriendlyPets:SetChecked(settings.showFriendlyPets)
	controls.ClassColorPlayerNames:SetChecked(settings.classColorPlayerNames)
	controls.Castbar:SetChecked(settings.showCastbar)
	controls.TargetHighlight:SetChecked(settings.showTargetHighlight)
	controls.EnlargeTarget:SetChecked(settings.enlargeTarget)
	controls.Threat:SetChecked(settings.showThreat)
	controls.Quest:SetChecked(settings.showQuest)
	controls.StackNameplates:SetChecked(settings.stackNameplates)
	settings.display = settings.display or { left = "classification", center = "none", right = "healthPercent" }
	for slotKey, dropdown in pairs(controls.DisplayDropdowns or {}) do
		local selected = settings.display[slotKey] or "none"
		local choice = SlickUI.PlayerDisplayChoiceByKey[selected] or SlickUI.PlayerDisplayChoiceByKey.none
		UIDropDownMenu_SetSelectedValue(dropdown, selected)
		UIDropDownMenu_SetText(dropdown, choice.label)
	end
	SetSliderValue(controls.Width, settings.width)
	SetSliderValue(controls.Height, settings.height)
	SetSliderValue(controls.Scale, settings.scale)
	SetSliderValue(controls.TargetScale, settings.targetScale)
	SetSliderValue(controls.FontSize, settings.fontSize)
	SetSliderValue(controls.CastbarHeight, settings.castbarHeight)
	SetSliderValue(controls.HorizontalSpacing, settings.horizontalSpacing)
	SetSliderValue(controls.VerticalSpacing, settings.verticalSpacing)
	controls.PreviewButton:SetText(SlickUI.Nameplates.PreviewVisible and "Hide Test Nameplates" or "Show Test Nameplates")
end

function Options:CreateNameplatesPage(parent)
	local page = CreateFrame("Frame", nil, parent)
	page:SetAllPoints()
	local controls = {}
	self.NameplateControls = controls

	local heading = Theme:CreateText(page, 17)
	heading:SetPoint("TOPLEFT", 0, 0)
	heading:SetText("Nameplates")
	heading:SetTextColor(unpack(Theme.Colors.goldText))

	local note = Theme:CreateText(page, 11)
	note:SetPoint("TOPLEFT", heading, "BOTTOMLEFT", 0, -7)
	note:SetPoint("RIGHT", page, "RIGHT", 0, 0)
	note:SetJustifyH("LEFT")
	note:SetText("Legacy feature: SlickUI Nameplates are no longer being actively updated. Blizzard has significantly improved its native nameplates, so we recommend using Blizzard Nameplates instead. These controls remain available for existing users.")
	note:SetTextColor(1, 0.62, 0.28)

	local function Update(field, value)
		if not SlickUI:IsAvailableOutOfCombat("Nameplate changes") then
			Options:RefreshNameplateControls()
			return
		end
		SlickUI.DB.nameplates[field] = value
		if field == "showFriendlyPlayers" then
			SlickUI.DB.nameplates.blizzardFriendlyMode = value and "bars" or "names"
			SlickUI.DB.nameplates.blizzardFriendlyModeInitialized = true
		end
		SlickUI.Nameplates:ApplySettings()
		Options:RefreshNameplateControls()
	end

	local visibility = CreateFrame("Frame", nil, page, "BackdropTemplate")
	visibility:SetSize(220, 410)
	visibility:SetPoint("TOPLEFT", note, "BOTTOMLEFT", 0, -20)
	visibility:SetBackdrop({ bgFile = Theme.Texture, edgeFile = Theme.Texture, edgeSize = 1 })
	visibility:SetBackdropColor(unpack(Theme.Colors.panelSoft))
	visibility:SetBackdropBorderColor(unpack(Theme.Colors.borderMuted))

	local visibilityTitle = Theme:CreateText(visibility, 14)
	visibilityTitle:SetPoint("TOPLEFT", 14, -12)
	visibilityTitle:SetText("Visibility & Indicators")
	visibilityTitle:SetTextColor(unpack(Theme.Colors.goldText))

	local checkboxDefinitions = {
		{ "Enabled", "Enable nameplates", "enabled" },
		{ "Enemies", "Enemies", "showEnemies" },
		{ "FriendlyPlayers", "Friendly health bars", "showFriendlyPlayers" },
		{ "FriendlyNPCs", "Friendly NPCs", "showFriendlyNPCs" },
		{ "FriendlyPets", "Friendly pets", "showFriendlyPets" },
		{ "ClassColorPlayerNames", "Class Colors", "classColorPlayerNames" },
		{ "Castbar", "Cast bars", "showCastbar" },
		{ "TargetHighlight", "Target highlight", "showTargetHighlight" },
		{ "EnlargeTarget", "Enlarge target", "enlargeTarget" },
		{ "Threat", "Threat color", "showThreat" },
		{ "Quest", "Quest icon", "showQuest" },
		{ "StackNameplates", "Stack nearby", "stackNameplates" },
	}
	local columnAnchors = {}
	for index, definition in ipairs(checkboxDefinitions) do
		local controlKey, label, field = unpack(definition)
		local checkbox = CreateCheckbox(visibility, label)
		Theme:SetFont(checkbox.Text, 9)
		local column = index <= 5 and 1 or 2
		if columnAnchors[column] then
			checkbox:SetPoint("TOPLEFT", columnAnchors[column], "BOTTOMLEFT", 0, -2)
		else
			checkbox:SetPoint("TOPLEFT", visibilityTitle, "BOTTOMLEFT", column == 1 and -4 or 102, -7)
		end
		checkbox:SetScript("OnClick", function(self) Update(field, self:GetChecked() == true) end)
		controls[controlKey] = checkbox
		columnAnchors[column] = checkbox
	end

	local displayTitle = Theme:CreateText(visibility, 11)
	displayTitle:SetPoint("TOPLEFT", visibility, "TOPLEFT", 14, -210)
	displayTitle:SetText("Nameplate Text")
	displayTitle:SetTextColor(unpack(Theme.Colors.goldText))

	controls.DisplayDropdowns = {}
	local displayRows = {
		{ key = "left", label = "Left", y = -234 },
		{ key = "center", label = "Center", y = -276 },
		{ key = "right", label = "Right", y = -318 },
	}
	for _, row in ipairs(displayRows) do
		local slotKey = row.key
		local label = Theme:CreateText(visibility, 9)
		label:SetPoint("TOPLEFT", visibility, "TOPLEFT", 14, row.y)
		label:SetText(row.label)
		label:SetTextColor(unpack(Theme.Colors.mutedText))

		local dropdown = CreateFrame("Frame", "SlickUINameplate" .. slotKey .. "DisplayDropdown", visibility, "UIDropDownMenuTemplate")
		dropdown:SetPoint("LEFT", label, "LEFT", 48, -1)
		UIDropDownMenu_SetWidth(dropdown, 112)
		UIDropDownMenu_Initialize(dropdown, function()
			for _, displayChoice in ipairs(SlickUI.PlayerDisplayChoices) do
				local choiceKey = displayChoice.key
				local info = UIDropDownMenu_CreateInfo()
				info.text = displayChoice.label
				info.value = choiceKey
				info.checked = (SlickUI.DB.nameplates.display or {})[slotKey] == choiceKey
				info.func = function()
					if not SlickUI:IsAvailableOutOfCombat("Nameplate changes") then Options:RefreshNameplateControls() return end
					SlickUI.DB.nameplates.display = SlickUI.DB.nameplates.display or {}
					SlickUI.DB.nameplates.display[slotKey] = choiceKey
					SlickUI.Nameplates:ApplySettings()
					Options:RefreshNameplateControls()
				end
				UIDropDownMenu_AddButton(info)
			end
		end)
		controls.DisplayDropdowns[slotKey] = dropdown
	end

	local preview = CreateButton(visibility, "Show Test Nameplates", 180)
	preview:SetPoint("BOTTOM", visibility, "BOTTOM", 0, 8)
	preview:SetScript("OnClick", function()
		if not SlickUI:IsAvailableOutOfCombat("Nameplate preview") then return end
		SlickUI.Nameplates:TogglePreview()
		Options:RefreshNameplateControls()
	end)
	controls.PreviewButton = preview

	local sizing = CreateFrame("Frame", nil, page, "BackdropTemplate")
	sizing:SetSize(220, 410)
	sizing:SetPoint("TOPLEFT", visibility, "TOPRIGHT", 15, 0)
	sizing:SetBackdrop({ bgFile = Theme.Texture, edgeFile = Theme.Texture, edgeSize = 1 })
	sizing:SetBackdropColor(unpack(Theme.Colors.panelSoft))
	sizing:SetBackdropBorderColor(unpack(Theme.Colors.borderMuted))

	local sizingTitle = Theme:CreateText(sizing, 14)
	sizingTitle:SetPoint("TOPLEFT", 14, -12)
	sizingTitle:SetText("Size & Spacing")
	sizingTitle:SetTextColor(unpack(Theme.Colors.goldText))

	local width = CreateSlider(sizing, "Plate width", 90, 240, 1, 174, function(value) Update("width", value) end)
	width:SetPoint("TOPLEFT", sizingTitle, "BOTTOMLEFT", 0, -30)
	controls.Width = width
	local height = CreateSlider(sizing, "Health height", 8, 30, 1, 174, function(value) Update("height", value) end)
	height:SetPoint("TOPLEFT", width, "BOTTOMLEFT", 0, -28)
	controls.Height = height
	local scale = CreateSlider(sizing, "Scale", 0.6, 1.5, 0.05, 174, function(value) Update("scale", value) end, function(value) return string.format("%.2f", value) end)
	scale:SetPoint("TOPLEFT", height, "BOTTOMLEFT", 0, -28)
	controls.Scale = scale
	local targetScale = CreateSlider(sizing, "Target scale", 1, 2, 0.05, 174, function(value) Update("targetScale", value) end, function(value) return string.format("%.2f", value) end)
	targetScale:SetPoint("TOPLEFT", scale, "BOTTOMLEFT", 0, -28)
	controls.TargetScale = targetScale
	local fontSize = CreateSlider(sizing, "Font size", 8, 18, 1, 174, function(value) Update("fontSize", value) end)
	fontSize:SetPoint("TOPLEFT", targetScale, "BOTTOMLEFT", 0, -28)
	controls.FontSize = fontSize
	local castbarHeight = CreateSlider(sizing, "Cast-bar height", 6, 20, 1, 174, function(value) Update("castbarHeight", value) end)
	castbarHeight:SetPoint("TOPLEFT", fontSize, "BOTTOMLEFT", 0, -28)
	controls.CastbarHeight = castbarHeight
	local horizontalSpacing = CreateSlider(sizing, "Horizontal spacing", 0.4, 1.5, 0.05, 174, function(value) Update("horizontalSpacing", value) end, function(value) return string.format("%.2f", value) end)
	horizontalSpacing:SetPoint("TOPLEFT", castbarHeight, "BOTTOMLEFT", 0, -28)
	controls.HorizontalSpacing = horizontalSpacing
	local verticalSpacing = CreateSlider(sizing, "Vertical spacing", 0.4, 2, 0.05, 174, function(value) Update("verticalSpacing", value) end, function(value) return string.format("%.2f", value) end)
	verticalSpacing:SetPoint("TOPLEFT", horizontalSpacing, "BOTTOMLEFT", 0, -28)
	controls.VerticalSpacing = verticalSpacing

	self.Pages.nameplates = page
end

local InformationBarLabels = {
	top1 = "Top 1",
	top2 = "Top 2",
	bottom1 = "Bottom 1",
	bottom2 = "Bottom 2",
}

function Options:RefreshInformationBarControls()
	local controls = self.InformationBarControls
	if not controls or not SlickUI.DB then return end
	local information = SlickUI.DB.informationBars
	local selected = self.SelectedInformationBar
	local bar = information.bars[selected]
	controls.Heading:SetText("Information Bars - " .. InformationBarLabels[selected])
	for id, button in pairs(controls.BarButtons) do button:SetEnabled(id ~= selected) end
	controls.Enabled:SetChecked(bar.enabled)
	controls.Locked:SetChecked(bar.locked)
	controls.AdjustScreen:SetChecked(information.adjustScreen)
	SetSliderValue(controls.Height, bar.height)
	SetSliderValue(controls.Opacity, bar.opacity)
	SetSliderValue(controls.Snap, information.snap)
	for key, checkbox in pairs(controls.BlockChecks) do
		local block = information.blocks[key]
		checkbox:SetChecked(block and block.enabled and block.bar == selected)
	end
end

function Options:RebuildInformationBlockChecks()
	local controls = self.InformationBarControls
	if not controls or not controls.BlockContent then return end

	local keys = {}
	for key in pairs(SlickUI.InformationBars.Definitions) do keys[#keys + 1] = key end
	table.sort(keys, function(left, right)
		local a, b = SlickUI.InformationBars.Definitions[left], SlickUI.InformationBars.Definitions[right]
		if a.isExternal ~= b.isExternal then return not a.isExternal end
		return string.lower(a.label) < string.lower(b.label)
	end)

	for key, checkbox in pairs(controls.BlockChecks) do
		checkbox:SetShown(SlickUI.InformationBars.Definitions[key] ~= nil)
	end
	for index, key in ipairs(keys) do
		local selectedKey = key
		local definition = SlickUI.InformationBars.Definitions[key]
		local checkbox = controls.BlockChecks[key]
		if not checkbox then
			checkbox = CreateCheckbox(controls.BlockContent, definition.label)
			checkbox:SetScript("OnClick", function(self)
				SlickUI.InformationBars:SetBlockOnBar(selectedKey, Options.SelectedInformationBar, self:GetChecked() == true)
				Options:RefreshInformationBarControls()
			end)
			controls.BlockChecks[key] = checkbox
		else
			checkbox.Text:SetText(definition.label)
		end
		checkbox:ClearAllPoints()
		local column = (index - 1) % 2
		local row = math.floor((index - 1) / 2)
		checkbox:SetPoint("TOPLEFT", controls.BlockContent, "TOPLEFT", column * 215, -row * 30)
		checkbox:Show()
	end
	controls.BlockContent:SetHeight(math.max(90, math.ceil(#keys / 2) * 30))
end

function Options:CreateInformationBarsPage(parent)
	local page = CreateFrame("Frame", nil, parent)
	page:SetAllPoints()
	local controls = { BarButtons = {}, BlockChecks = {} }
	self.InformationBarControls = controls

	local heading = Theme:CreateText(page, 17)
	heading:SetPoint("TOPLEFT", 0, 0)
	heading:SetTextColor(unpack(Theme.Colors.goldText))
	controls.Heading = heading

	local note = Theme:CreateText(page, 10)
	note:SetPoint("TOPLEFT", heading, "BOTTOMLEFT", 0, -7)
	note:SetPoint("RIGHT", page, "RIGHT", 0, 0)
	note:SetJustifyH("LEFT")
	note:SetText("Create up to two bars on each screen edge. Unlock a bar with its right-side lock, then drag data blocks onto 20-pixel snap points; gaps are preserved.")
	note:SetTextColor(unpack(Theme.Colors.mutedText))

	local previous
	for _, id in ipairs({ "top1", "top2", "bottom1", "bottom2" }) do
		local selectedID = id
		local button = CreateButton(page, InformationBarLabels[id], 100)
		if previous then
			button:SetPoint("LEFT", previous, "RIGHT", 7, 0)
		else
			button:SetPoint("TOPLEFT", note, "BOTTOMLEFT", 0, -12)
		end
		button:SetScript("OnClick", function()
			Options.SelectedInformationBar = selectedID
			Options:RefreshInformationBarControls()
		end)
		controls.BarButtons[id] = button
		previous = button
	end

	local enabled = CreateCheckbox(page, "Enable selected bar")
	enabled:SetPoint("TOPLEFT", controls.BarButtons.top1, "BOTTOMLEFT", -4, -14)
	enabled:SetScript("OnClick", function(self)
		if not SlickUI:IsAvailableOutOfCombat("Information-bar changes") then Options:RefreshInformationBarControls() return end
		SlickUI.InformationBars:SetBarField(Options.SelectedInformationBar, "enabled", self:GetChecked() == true)
		Options:RefreshInformationBarControls()
	end)
	controls.Enabled = enabled

	local locked = CreateCheckbox(page, "Lock selected bar")
	locked:SetPoint("LEFT", enabled, "RIGHT", 190, 0)
	locked:SetScript("OnClick", function(self)
		SlickUI.DB.informationBars.bars[Options.SelectedInformationBar].locked = self:GetChecked() == true
		SlickUI.InformationBars:RefreshLocks()
		Options:RefreshInformationBarControls()
	end)
	controls.Locked = locked

	local adjustScreen = CreateCheckbox(page, "Automatically keep the interface clear of enabled bars")
	adjustScreen:SetPoint("TOPLEFT", enabled, "BOTTOMLEFT", 0, -7)
	adjustScreen:SetScript("OnClick", function(self)
		if not SlickUI:IsAvailableOutOfCombat("Screen-inset changes") then Options:RefreshInformationBarControls() return end
		SlickUI.DB.informationBars.adjustScreen = self:GetChecked() == true
		SlickUI.InformationBars:ApplyScreenInsets()
		Options:RefreshInformationBarControls()
	end)
	controls.AdjustScreen = adjustScreen

	local height = CreateSlider(page, "Bar height", 18, 48, 1, 180, function(value)
		if not SlickUI:IsAvailableOutOfCombat("Information-bar sizing") then Options:RefreshInformationBarControls() return end
		SlickUI.InformationBars:SetBarField(Options.SelectedInformationBar, "height", value)
	end)
	height:SetPoint("TOPLEFT", adjustScreen, "BOTTOMLEFT", 4, -34)
	controls.Height = height

	local opacity = CreateSlider(page, "Bar opacity", 0.10, 1.00, 0.05, 180, function(value)
		SlickUI.InformationBars:SetBarField(Options.SelectedInformationBar, "opacity", value)
	end, function(value) return string.format("%d%%", value * 100) end)
	opacity:SetPoint("LEFT", height, "RIGHT", 55, 0)
	controls.Opacity = opacity

	local snap = CreateSlider(page, "Snap spacing", 10, 50, 5, 180, function(value)
		SlickUI.DB.informationBars.snap = value
		SlickUI.InformationBars:LayoutBlocks()
	end, function(value) return string.format("%d px", value) end)
	snap:SetPoint("TOPLEFT", height, "BOTTOMLEFT", 0, -36)
	controls.Snap = snap

	local dataHeading = Theme:CreateText(page, 13)
	dataHeading:SetPoint("TOPLEFT", snap, "BOTTOMLEFT", -4, -20)
	dataHeading:SetText("Available data blocks")
	dataHeading:SetTextColor(unpack(Theme.Colors.goldText))

	local externalNote = Theme:CreateText(page, 9)
	externalNote:SetPoint("LEFT", dataHeading, "RIGHT", 8, 0)
	externalNote:SetText("Includes detected LibDataBroker addon plugins")
	externalNote:SetTextColor(unpack(Theme.Colors.mutedText))

	local scroll = CreateFrame("ScrollFrame", nil, page, "UIPanelScrollFrameTemplate")
	scroll:SetPoint("TOPLEFT", dataHeading, "BOTTOMLEFT", 0, -8)
	scroll:SetPoint("BOTTOMRIGHT", page, "BOTTOMRIGHT", -28, 34)
	local blockContent = CreateFrame("Frame", nil, scroll)
	blockContent:SetWidth(420)
	blockContent:SetHeight(90)
	scroll:SetScrollChild(blockContent)
	controls.BlockScroll = scroll
	controls.BlockContent = blockContent
	self:RebuildInformationBlockChecks()

	local reset = CreateButton(page, "Reset Information Layout", 190)
	reset:SetPoint("BOTTOMRIGHT", page, "BOTTOMRIGHT", 0, 0)
	reset:SetScript("OnClick", function()
		if not SlickUI:IsAvailableOutOfCombat("Information-bar reset") then return end
		SlickUI.InformationBars:ResetLayout()
		Options:RefreshInformationBarControls()
	end)

	self.Pages.informationbars = page
end

function Options:RefreshActionBarControls()
	local controls = self.ActionBarControls
	if not controls or not SlickUI.DB or not SlickUI.ActionBars then return end
	local settings = SlickUI.DB.actionBars
	local selected = self.SelectedActionBar or settings.selectedBar or "main"
	if not SlickUI.ActionBars.Definitions[selected] then selected = "main" end
	self.SelectedActionBar = selected
	settings.selectedBar = selected
	local bar = settings.bars[selected]
	local definition = SlickUI.ActionBars.Definitions[selected]

	controls.Enabled:SetChecked(settings.enabled)
	controls.HideHotkeys:SetChecked(settings.hideHotkeys)
	controls.HideMacroNames:SetChecked(settings.hideMacroNames)
	controls.HideCounts:SetChecked(settings.hideCounts)
	controls.ShowSelected:SetChecked(bar.shown ~= false)
	controls.SelectedLabel:SetText(definition.label .. " Layout")
	UIDropDownMenu_SetSelectedValue(controls.BarDropdown, selected)
	UIDropDownMenu_SetText(controls.BarDropdown, definition.label)
	SetSliderValue(controls.ButtonSize, bar.buttonSize)
	SetSliderValue(controls.Spacing, bar.spacing)
	SetSliderValue(controls.ButtonsPerRow, bar.buttonsPerRow)
	SetSliderValue(controls.NumberOfSlots, bar.numberOfSlots or (selected == "pet" and 10 or 12))
	SetSliderValue(controls.Scale, bar.scale)
	SetSliderValue(controls.Opacity, bar.opacity)
	controls.LockButton:SetText(SlickUI.DB.locked and "Unlock All Frames" or "Lock All Frames")
end

function Options:CreateActionBarsPage(parent)
	local page = CreateFrame("Frame", nil, parent)
	page:SetAllPoints()
	local controls = {}
	self.ActionBarControls = controls

	local heading = Theme:CreateText(page, 17)
	heading:SetPoint("TOPLEFT", 0, 0)
	heading:SetText("Action Bars")
	heading:SetTextColor(unpack(Theme.Colors.goldText))

	local note = Theme:CreateText(page, 10)
	note:SetPoint("TOPLEFT", heading, "BOTTOMLEFT", 0, -7)
	note:SetPoint("RIGHT", page, "RIGHT", 0, 0)
	note:SetJustifyH("LEFT")
	note:SetText("Moves and scales Blizzard's native bar containers while leaving protected action buttons untouched. This prevents 12.0.7 cooldown-value taint errors.")
	note:SetTextColor(unpack(Theme.Colors.mutedText))

	local function ApplyGlobal(field, value)
		if not SlickUI:IsAvailableOutOfCombat("Action-bar changes") then Options:RefreshActionBarControls() return end
		SlickUI.DB.actionBars[field] = value
		SlickUI.ActionBars:ApplySettings()
		Options:RefreshActionBarControls()
	end

	local enabled = CreateCheckbox(page, "Enable SlickUI action-bar layout")
	enabled:SetPoint("TOPLEFT", note, "BOTTOMLEFT", -4, -13)
	enabled:SetScript("OnClick", function(self) ApplyGlobal("enabled", self:GetChecked() == true) end)
	controls.Enabled = enabled

	local hideHotkeys = CreateCheckbox(page, "Hide hotkeys")
	hideHotkeys:SetPoint("TOPLEFT", enabled, "BOTTOMLEFT", 0, -2)
	hideHotkeys:SetScript("OnClick", function(self) ApplyGlobal("hideHotkeys", self:GetChecked() == true) end)
	controls.HideHotkeys = hideHotkeys

	local hideNames = CreateCheckbox(page, "Hide macro names")
	hideNames:SetPoint("LEFT", hideHotkeys, "LEFT", 150, 0)
	hideNames:SetScript("OnClick", function(self) ApplyGlobal("hideMacroNames", self:GetChecked() == true) end)
	controls.HideMacroNames = hideNames

	local hideCounts = CreateCheckbox(page, "Hide stack counts")
	hideCounts:SetPoint("LEFT", hideNames, "LEFT", 160, 0)
	hideCounts:SetScript("OnClick", function(self) ApplyGlobal("hideCounts", self:GetChecked() == true) end)
	controls.HideCounts = hideCounts
	hideHotkeys:Hide()
	hideNames:Hide()
	hideCounts:Hide()

	local barLabel = Theme:CreateText(page, 11)
	barLabel:SetPoint("TOPLEFT", enabled, "BOTTOMLEFT", 4, -18)
	barLabel:SetText("Bar to configure")
	barLabel:SetTextColor(unpack(Theme.Colors.mutedText))

	local dropdown = CreateFrame("Frame", "SlickUIActionBarSelectionDropdown", page, "UIDropDownMenuTemplate")
	dropdown:SetPoint("TOPLEFT", barLabel, "BOTTOMLEFT", -16, -3)
	UIDropDownMenu_SetWidth(dropdown, 180)
	UIDropDownMenu_Initialize(dropdown, function()
		for _, barKey in ipairs(SlickUI.ActionBars.Order) do
			local selectedKey = barKey
			local definition = SlickUI.ActionBars.Definitions[barKey]
			local info = UIDropDownMenu_CreateInfo()
			info.text = definition.label
			info.value = selectedKey
			info.checked = Options.SelectedActionBar == selectedKey
			info.func = function()
				Options.SelectedActionBar = selectedKey
				SlickUI.DB.actionBars.selectedBar = selectedKey
				Options:RefreshActionBarControls()
			end
			UIDropDownMenu_AddButton(info)
		end
	end)
	controls.BarDropdown = dropdown

	local selectedLabel = Theme:CreateText(page, 14)
	selectedLabel:SetPoint("TOPLEFT", dropdown, "BOTTOMLEFT", 18, -15)
	selectedLabel:SetTextColor(unpack(Theme.Colors.goldText))
	controls.SelectedLabel = selectedLabel

	local showSelected = CreateCheckbox(page, "Show selected bar")
	showSelected:SetPoint("TOPLEFT", selectedLabel, "BOTTOMLEFT", -4, -7)
	showSelected:SetScript("OnClick", function(self)
		if not SlickUI.ActionBars:SetBarShown(Options.SelectedActionBar, self:GetChecked() == true) then
			Options:RefreshActionBarControls()
			return
		end
		Options:RefreshActionBarControls()
	end)
	controls.ShowSelected = showSelected

	local function UpdateSelected(field, value)
		if not SlickUI:IsAvailableOutOfCombat("Action-bar layout") then Options:RefreshActionBarControls() return end
		SlickUI.DB.actionBars.bars[Options.SelectedActionBar][field] = value
		SlickUI.ActionBars:ApplySettings()
		Options:RefreshActionBarControls()
	end

	local buttonSize = CreateSlider(page, "Button size", 24, 64, 1, 190, function(value) UpdateSelected("buttonSize", value) end)
	buttonSize:SetPoint("TOPLEFT", showSelected, "BOTTOMLEFT", 4, -31)
	controls.ButtonSize = buttonSize
	local spacing = CreateSlider(page, "Button spacing", 0, 14, 1, 190, function(value) UpdateSelected("spacing", value) end)
	spacing:SetPoint("TOPLEFT", buttonSize, "BOTTOMLEFT", 0, -31)
	controls.Spacing = spacing
	local perRow = CreateSlider(page, "Buttons per row", 1, 12, 1, 190, function(value) UpdateSelected("buttonsPerRow", value) end)
	perRow:SetPoint("TOPLEFT", spacing, "BOTTOMLEFT", 0, -31)
	controls.ButtonsPerRow = perRow

	local scale = CreateSlider(page, "Button scale", 0.60, 1.50, 0.05, 190, function(value) UpdateSelected("scale", value) end, function(value) return string.format("%.2f", value) end)
	scale:SetPoint("TOPLEFT", showSelected, "BOTTOMLEFT", 239, -31)
	controls.Scale = scale
	local opacity = CreateSlider(page, "Bar opacity", 0.20, 1, 0.05, 190, function(value) UpdateSelected("opacity", value) end, function(value) return string.format("%.2f", value) end)
	opacity:SetPoint("TOPLEFT", scale, "BOTTOMLEFT", 0, -31)
	controls.Opacity = opacity
	local numberOfSlots = CreateSlider(page, "Number of slots", 1, 12, 1, 190, function(value)
		local maximum = Options.SelectedActionBar == "pet" and 10 or 12
		UpdateSelected("numberOfSlots", math.min(maximum, value))
	end)
	numberOfSlots:SetPoint("TOPLEFT", opacity, "BOTTOMLEFT", 0, -31)
	controls.NumberOfSlots = numberOfSlots

	local lockButton = CreateButton(page, "Unlock All Frames", 190)
	lockButton:SetPoint("TOPLEFT", numberOfSlots, "BOTTOMLEFT", -239, -32)
	lockButton:SetScript("OnClick", function()
		if not SlickUI:IsAvailableOutOfCombat("Action-bar locking") then return end
		SlickUI.Movers:SetLocked(not SlickUI.DB.locked)
		Options:Refresh()
	end)
	controls.LockButton = lockButton

	local reset = CreateButton(page, "Reset Selected Bar", 190)
	reset:SetPoint("LEFT", lockButton, "RIGHT", 49, 0)
	reset:SetScript("OnClick", function()
		if not SlickUI:IsAvailableOutOfCombat("Action-bar reset") then return end
		local key = Options.SelectedActionBar
		local defaults = SlickUI.Defaults.actionBars.bars[key]
		local bar = SlickUI.DB.actionBars.bars[key]
		for field, value in pairs(defaults) do bar[field] = value end
		SlickUI.ActionBars:ResetBar(key)
		SlickUI.ActionBars:ApplySettings()
		Options:RefreshActionBarControls()
	end)
	local warning = Theme:CreateText(page, 9)
	warning:SetPoint("TOPLEFT", lockButton, "BOTTOMLEFT", 0, -14)
	warning:SetPoint("RIGHT", page, "RIGHT", 0, 0)
	warning:SetJustifyH("LEFT")
	warning:SetText("SlickUI safely controls each bar's button size, scale, spacing, buttons per row, opacity, placement, snapping, and visibility. Changes require being outside combat.")
	warning:SetTextColor(unpack(Theme.Colors.mutedText))

	self.Pages.actionbars = page
end

function Options:RefreshSystemFontControls()
	local controls = self.SystemFontControls
	if not controls or not SlickUI.DB then return end
	local settings = SlickUI.DB.systemFonts
	controls.Enabled:SetChecked(settings.enabled)
	controls.General:SetChecked(settings.general)
	controls.Chat:SetChecked(settings.chat)
	controls.TooltipsAndQuests:SetChecked(settings.tooltipsAndQuests)
	controls.NumbersAndCombat:SetChecked(settings.numbersAndCombat)
	controls.General:SetEnabled(settings.enabled)
	controls.Chat:SetEnabled(settings.enabled)
	controls.TooltipsAndQuests:SetEnabled(settings.enabled)
	controls.NumbersAndCombat:SetEnabled(settings.enabled)
	SetSliderValue(controls.SizeOffset, settings.sizeOffset or 0)
	controls.SizeOffset:SetEnabled(settings.enabled)
	SetSliderValue(controls.SlickUIScale, settings.slickUIScale or 1)
end

function Options:CreateSystemFontsPage(parent)
	local page = CreateFrame("Frame", nil, parent)
	page:SetAllPoints()
	local controls = {}
	self.SystemFontControls = controls

	local heading = Theme:CreateText(page, 17)
	heading:SetPoint("TOPLEFT", 0, 0)
	heading:SetText("System Fonts")
	heading:SetTextColor(unpack(Theme.Colors.goldText))

	local note = Theme:CreateText(page, 10)
	note:SetPoint("TOPLEFT", heading, "BOTTOMLEFT", 0, -7)
	note:SetPoint("RIGHT", page, "RIGHT", 0, 0)
	note:SetJustifyH("LEFT")
	note:SetText("Apply SlickUI's clean font styling to Blizzard-managed interface text. Original font paths, sizes, and outlines are preserved so the setting can be disabled safely.")
	note:SetTextColor(unpack(Theme.Colors.mutedText))

	local slickUIHeading = Theme:CreateText(page, 13)
	slickUIHeading:SetPoint("TOPLEFT", note, "BOTTOMLEFT", 0, -18)
	slickUIHeading:SetText("SlickUI interface text")
	slickUIHeading:SetTextColor(unpack(Theme.Colors.goldText))

	local slickUIScale = CreateSlider(page, "All SlickUI font sizes", 0.75, 2.50, 0.05, 240, function(value)
		SlickUI.DB.systemFonts.slickUIScale = value
		Theme:RefreshFonts()
	end, function(value)
		return string.format("%.2f", value)
	end)
	slickUIScale:SetPoint("TOPLEFT", slickUIHeading, "BOTTOMLEFT", 4, -25)
	controls.SlickUIScale = slickUIScale

	local slickUINote = Theme:CreateText(page, 9)
	slickUINote:SetPoint("TOPLEFT", slickUIScale, "BOTTOMLEFT", -4, -12)
	slickUINote:SetPoint("RIGHT", page, "RIGHT", 0, 0)
	slickUINote:SetJustifyH("LEFT")
	slickUINote:SetText("Scales all SlickUI titles, tables, buttons, windows, unit text, and information-bar widgets together. Type an exact value in the number box and press Enter.")
	slickUINote:SetTextColor(unpack(Theme.Colors.mutedText))

	local function ApplySetting(field, checked)
		SlickUI.DB.systemFonts[field] = checked == true
		SlickUI.SystemFonts:Apply()
		Options:RefreshSystemFontControls()
	end

	local enabled = CreateCheckbox(page, "Enable SlickUI system fonts")
	enabled:SetPoint("TOPLEFT", slickUINote, "BOTTOMLEFT", -4, -22)
	enabled:SetScript("OnClick", function(self) ApplySetting("enabled", self:GetChecked()) end)
	controls.Enabled = enabled

	local categoryHeading = Theme:CreateText(page, 13)
	categoryHeading:SetPoint("TOPLEFT", enabled, "BOTTOMLEFT", 4, -18)
	categoryHeading:SetText("Blizzard font categories")
	categoryHeading:SetTextColor(unpack(Theme.Colors.goldText))

	local general = CreateCheckbox(page, "General interface text")
	general:SetPoint("TOPLEFT", categoryHeading, "BOTTOMLEFT", -4, -8)
	general:SetScript("OnClick", function(self) ApplySetting("general", self:GetChecked()) end)
	controls.General = general

	local chat = CreateCheckbox(page, "Chat and social text")
	chat:SetPoint("TOPLEFT", general, "BOTTOMLEFT", 0, -4)
	chat:SetScript("OnClick", function(self) ApplySetting("chat", self:GetChecked()) end)
	controls.Chat = chat

	local tooltips = CreateCheckbox(page, "Tooltips and quest text")
	tooltips:SetPoint("TOPLEFT", chat, "BOTTOMLEFT", 0, -4)
	tooltips:SetScript("OnClick", function(self) ApplySetting("tooltipsAndQuests", self:GetChecked()) end)
	controls.TooltipsAndQuests = tooltips

	local numbers = CreateCheckbox(page, "Numbers and combat text")
	numbers:SetPoint("TOPLEFT", tooltips, "BOTTOMLEFT", 0, -4)
	numbers:SetScript("OnClick", function(self) ApplySetting("numbersAndCombat", self:GetChecked()) end)
	controls.NumbersAndCombat = numbers

	local sizeOffset = CreateSlider(page, "Font-size adjustment", -2, 4, 1, 210, function(value)
		SlickUI.DB.systemFonts.sizeOffset = value
		SlickUI.SystemFonts:Apply()
	end, function(value)
		if value > 0 then return "+" .. value end
		return tostring(value)
	end)
	sizeOffset:SetPoint("TOPLEFT", numbers, "BOTTOMLEFT", 4, -38)
	controls.SizeOffset = sizeOffset

	local reapply = CreateButton(page, "Reapply System Fonts", 180)
	reapply:SetPoint("TOPLEFT", sizeOffset, "BOTTOMLEFT", -4, -30)
	reapply:SetScript("OnClick", function() SlickUI.SystemFonts:Apply() end)

	local limitation = Theme:CreateText(page, 9)
	limitation:SetPoint("TOPLEFT", reapply, "BOTTOMLEFT", 0, -14)
	limitation:SetPoint("RIGHT", page, "RIGHT", 0, 0)
	limitation:SetJustifyH("LEFT")
	limitation:SetText("SlickUI styles Blizzard's shared font objects. Other addons that deliberately assign their own fonts remain in control of their text.")
	limitation:SetTextColor(unpack(Theme.Colors.mutedText))

	self.Pages.systemfonts = page
end

local UnitLabels = {
	player = "Player",
	target = "Target",
	focus = "Focus",
	pet = "Pet",
	targettarget = "Target of Target",
	boss = "Boss",
	group = "Group",
}

local function GetDisplayButtonText(unit)
	return unit == "targettarget" and "ToT Display" or UnitLabels[unit] .. " Display"
end

local function ApplySelectedDisplay(slotKey)
	if Options.SelectedUnit == "group" then
		SlickUI.GroupFrames:ApplyDisplaySettings(slotKey)
	else
		SlickUI:ApplyFrameDisplaySettings(Options.SelectedUnit, slotKey)
	end
end

function Options:RefreshUnitFrameControls()
	local controls = self.UnitControls
	if not controls or not SlickUI.DB then
		return
	end

	local config = SlickUI.DB.unitFrames
	local frame = config.frames[self.SelectedUnit] or config.frames.player
	controls.SelectedLabel:SetText(UnitLabels[self.SelectedUnit])
	controls.DisplayButton:SetText(GetDisplayButtonText(self.SelectedUnit))
	controls.DisplayHeading:SetText(UnitLabels[self.SelectedUnit] .. " Information Layout")
	controls.Enabled:SetChecked(frame.enabled)
	controls.ClassColors:SetChecked(config.classColors)
	SetSliderValue(controls.Width, frame.width)
	SetSliderValue(controls.Height, frame.height)
	SetSliderValue(controls.Scale, frame.scale)
	SetSliderValue(controls.PowerHeight, frame.powerHeight)
	local supportsCastbar = self.SelectedUnit == "player" or self.SelectedUnit == "target" or self.SelectedUnit == "focus"
	controls.CastbarHeading:SetShown(supportsCastbar)
	controls.CastbarMode:SetShown(supportsCastbar)
	controls.CastbarWidth:SetShown(supportsCastbar)
	controls.CastbarHeight:SetShown(supportsCastbar)
	if supportsCastbar then
		local mode = frame.castbarMode or "ATTACHED"
		UIDropDownMenu_SetSelectedValue(controls.CastbarMode, mode)
		UIDropDownMenu_SetText(controls.CastbarMode, ({ ATTACHED = "Attached below frame", IN_FRAME = "Inside frame", DETACHED = "Move separately" })[mode] or "Attached below frame")
		SetSliderValue(controls.CastbarWidth, frame.castbarWidth or frame.width)
		SetSliderValue(controls.CastbarHeight, frame.castbarHeight or 18)
		controls.CastbarWidth:SetEnabled(mode ~= "IN_FRAME")
		controls.CastbarHeight:SetEnabled(mode ~= "IN_FRAME")
	end
	local isBoss = self.SelectedUnit == "boss"
	controls.PersonalSpacing:SetShown(not isBoss)
	controls.RowSpacing.Label:SetText(isBoss and "Boss frame spacing" or "Vertical row spacing")
	SetSliderValue(controls.PersonalSpacing, config.personalSpacing)
	SetSliderValue(controls.RowSpacing, isBoss and frame.spacing or config.rowSpacing)
	controls.ArrangeButton:SetShown(not isBoss)

	for unit, button in pairs(controls.UnitButtons) do
		button:SetEnabled(unit ~= self.SelectedUnit)
	end
	for unit, button in pairs(controls.DisplayUnitButtons) do
		button:SetEnabled(unit ~= self.SelectedUnit)
	end

	if controls.PlayerDisplayDropdowns then
		local display = config.frameDisplays[self.SelectedUnit]
		for slotKey, dropdown in pairs(controls.PlayerDisplayDropdowns) do
			local selected = display[slotKey] or "none"
			local choice = SlickUI.PlayerDisplayChoiceByKey[selected] or SlickUI.PlayerDisplayChoiceByKey.none
			UIDropDownMenu_SetSelectedValue(dropdown, choice.key)
			UIDropDownMenu_SetText(dropdown, choice.label)
		end
	end
	if controls.PlayerDisplaySizeEdits then
		local fontSizes = config.frameDisplayFontSizes[self.SelectedUnit]
		for slotKey, editBox in pairs(controls.PlayerDisplaySizeEdits) do
			if not editBox:HasFocus() then
				editBox:SetText(tostring(fontSizes[slotKey] or 9))
			end
		end
	end

	local group = config.group
	controls.PartyEnabled:SetChecked(group.partyEnabled)
	controls.RaidEnabled:SetChecked(group.raidEnabled)
	controls.HideBlizzardFrames:SetChecked(group.hideBlizzardFrames)
	controls.MainTankEnabled:SetChecked(group.mainTankEnabled)
	controls.MainAssistEnabled:SetChecked(group.mainAssistEnabled)
	controls.ShowIncomingHealing:SetChecked(group.showIncomingHealing)
	controls.ShowAbsorbShields:SetChecked(group.showAbsorbShields)
	controls.ShowHealAbsorbs:SetChecked(group.showHealAbsorbs)
	controls.CapOverhealing:SetChecked(group.capOverhealing)
	controls.ShowDispellableDebuffs:SetChecked(group.showDispellableDebuffs)
	controls.ShowPersonalHealingEffects:SetChecked(group.showPersonalHealingEffects)
	controls.ShowGroupCastbar:SetChecked(group.showCastbar)
	controls.Adaptive:SetChecked(group.adaptive)
	controls.Vertical:SetChecked(group.vertical)
	controls.ShowSolo:SetChecked(group.showSolo)
	controls.GroupClassColors:SetChecked(group.classColors)
	SetSliderValue(controls.GroupWidth, group.width)
	SetSliderValue(controls.GroupHeight, group.height)
	SetSliderValue(controls.GroupPowerHeight, group.powerHeight)
	SetSliderValue(controls.GroupSpacing, group.spacing)
	SetSliderValue(controls.MembersPerRow, group.membersPerRow)
	controls.MembersPerRow.Label:SetText(group.vertical and "Members per column" or "Members per row")
	SetSliderValue(controls.MinimumScale, group.minimumScale)
	if controls.PreviewDropdown then
		UIDropDownMenu_SetSelectedValue(controls.PreviewDropdown, group.previewCount)
		UIDropDownMenu_SetText(controls.PreviewDropdown, group.previewCount .. " members")
	end
	if controls.AnchorDropdown then
		UIDropDownMenu_SetSelectedValue(controls.AnchorDropdown, group.anchor)
		local anchorText = group.anchor == "LEFT" and "Left" or group.anchor == "RIGHT" and "Right" or "Center"
		UIDropDownMenu_SetText(controls.AnchorDropdown, anchorText)
	end
	if controls.PreviewButton then
		controls.PreviewButton:SetText(SlickUI.GroupFrames.PreviewVisible and "Hide Test Frames" or "Show Test Frames")
	end
end

function Options:ShowUnitSection(section)
	if section ~= "group" and section ~= "display" then
		section = "personal"
	end
	if section == "personal" and self.SelectedUnit == "group" then
		self.SelectedUnit = "player"
	end
	self.UnitSection = section
	local controls = self.UnitControls
	if not controls then return end
	controls.PersonalPanel:SetShown(self.UnitSection == "personal")
	controls.GroupPanel:SetShown(self.UnitSection == "group")
	controls.DisplayPanel:SetShown(self.UnitSection == "display")
	controls.PersonalButton:SetEnabled(self.UnitSection ~= "personal")
	controls.GroupButton:SetEnabled(self.UnitSection ~= "group")
	controls.DisplayButton:SetEnabled(self.UnitSection ~= "display")
	self:RefreshUnitFrameControls()
end

local function CanChangeUnitFrames()
	return SlickUI:IsAvailableOutOfCombat("Unit-frame changes")
end

function Options:CreateUnitFramesPage(parent)
	local page = CreateFrame("Frame", nil, parent)
	page:SetAllPoints()
	local controls = { UnitButtons = {}, DisplayUnitButtons = {} }
	self.UnitControls = controls

	local heading = Theme:CreateText(page, 17)
	heading:SetPoint("TOPLEFT", 0, 0)
	heading:SetText("Unit Frames")
	heading:SetTextColor(unpack(Theme.Colors.goldText))

	local personalButton = CreateButton(page, "Personal Frames", 145)
	personalButton:SetPoint("TOPLEFT", heading, "BOTTOMLEFT", 0, -10)
	personalButton:SetScript("OnClick", function() Options:ShowUnitSection("personal") end)
	controls.PersonalButton = personalButton

	local groupButton = CreateButton(page, "Party / Raid", 145)
	groupButton:SetPoint("LEFT", personalButton, "RIGHT", 8, 0)
	groupButton:SetScript("OnClick", function() Options:ShowUnitSection("group") end)
	controls.GroupButton = groupButton

	local displayButton = CreateButton(page, "Player Display", 145)
	displayButton:SetPoint("LEFT", groupButton, "RIGHT", 8, 0)
	displayButton:SetScript("OnClick", function() Options:ShowUnitSection("display") end)
	controls.DisplayButton = displayButton

	local personal = CreateFrame("Frame", nil, page)
	personal:SetPoint("TOPLEFT", personalButton, "BOTTOMLEFT", 0, -14)
	personal:SetPoint("BOTTOMRIGHT", page, "BOTTOMRIGHT", 0, 0)
	controls.PersonalPanel = personal

	local selectedLabel = Theme:CreateText(personal, 13)
	selectedLabel:SetPoint("TOPLEFT", 0, 0)
	selectedLabel:SetTextColor(unpack(Theme.Colors.goldText))
	controls.SelectedLabel = selectedLabel

	local unitOrder = { "player", "target", "focus", "pet", "targettarget", "boss", "group" }
	local previous
	for _, unit in ipairs(unitOrder) do
		local selectedUnit = unit
		local buttonText = selectedUnit == "targettarget" and "ToT" or UnitLabels[selectedUnit]
		local button = CreateButton(personal, buttonText, selectedUnit == "pet" and 50 or 60)
		if previous then
			button:SetPoint("LEFT", previous, "RIGHT", 5, 0)
		else
			button:SetPoint("TOPLEFT", selectedLabel, "BOTTOMLEFT", 0, -8)
		end
		button:SetScript("OnClick", function()
			Options.SelectedUnit = selectedUnit
			if selectedUnit == "group" then
				Options:ShowUnitSection("display")
			else
				Options:RefreshUnitFrameControls()
			end
		end)
		controls.UnitButtons[selectedUnit] = button
		previous = button
	end

	local enabled = CreateCheckbox(personal, "Enable selected frame")
	enabled:SetPoint("TOPLEFT", controls.UnitButtons.player, "BOTTOMLEFT", -4, -10)
	enabled:SetScript("OnClick", function(self)
		if not CanChangeUnitFrames() then Options:RefreshUnitFrameControls() return end
		SlickUI.DB.unitFrames.frames[Options.SelectedUnit].enabled = self:GetChecked() == true
		SlickUI:ApplyUnitFrameSettings(Options.SelectedUnit)
		Options:RefreshUnitFrameControls()
	end)
	controls.Enabled = enabled

	local classColors = CreateCheckbox(personal, "Use class-colored health")
	classColors:SetPoint("LEFT", enabled, "RIGHT", 145, 0)
	classColors:SetScript("OnClick", function(self)
		if not CanChangeUnitFrames() then Options:RefreshUnitFrameControls() return end
		SlickUI.DB.unitFrames.classColors = self:GetChecked() == true
		SlickUI:ApplyAllUnitFrameSettings()
		Options:RefreshUnitFrameControls()
	end)
	controls.ClassColors = classColors

	local function UpdatePersonal(field, value, arrange)
		if not CanChangeUnitFrames() then Options:RefreshUnitFrameControls() return end
		SlickUI.DB.unitFrames.frames[Options.SelectedUnit][field] = value
		SlickUI:ApplyUnitFrameSettings(Options.SelectedUnit)
		if arrange then SlickUI:ArrangePersonalFrames() end
	end

	local width = CreateSlider(personal, "Width", 80, 400, 5, 180, function(value) UpdatePersonal("width", value, false) end)
	width:SetPoint("TOPLEFT", enabled, "BOTTOMLEFT", 4, -34)
	controls.Width = width
	local height = CreateSlider(personal, "Height", 24, 100, 2, 180, function(value) UpdatePersonal("height", value, false) end)
	height:SetPoint("TOPLEFT", width, "BOTTOMLEFT", 0, -34)
	controls.Height = height
	local scale = CreateSlider(personal, "Scale", 0.50, 1.50, 0.05, 180, function(value) UpdatePersonal("scale", value, false) end, function(value) return string.format("%.2f", value) end)
	scale:SetPoint("TOPLEFT", height, "BOTTOMLEFT", 0, -34)
	controls.Scale = scale

	local power = CreateSlider(personal, "Power bar height", 2, 20, 1, 180, function(value) UpdatePersonal("powerHeight", value, false) end)
	power:SetPoint("TOPLEFT", width, "TOPRIGHT", 55, 0)
	controls.PowerHeight = power
	local spacing = CreateSlider(personal, "Horizontal spacing", 0, 100, 2, 180, function(value)
		if not CanChangeUnitFrames() then Options:RefreshUnitFrameControls() return end
		SlickUI.DB.unitFrames.personalSpacing = value
		SlickUI:ArrangePersonalFrames()
	end)
	spacing:SetPoint("TOPLEFT", power, "BOTTOMLEFT", 0, -34)
	controls.PersonalSpacing = spacing
	local rowSpacing = CreateSlider(personal, "Vertical row spacing", 0, 60, 2, 180, function(value)
		if not CanChangeUnitFrames() then Options:RefreshUnitFrameControls() return end
		if Options.SelectedUnit == "boss" then
			SlickUI.DB.unitFrames.frames.boss.spacing = value
			SlickUI:ApplyUnitFrameSettings("boss")
		else
			SlickUI.DB.unitFrames.rowSpacing = value
			SlickUI:ArrangePersonalFrames()
		end
	end)
	rowSpacing:SetPoint("TOPLEFT", spacing, "BOTTOMLEFT", 0, -34)
	controls.RowSpacing = rowSpacing

	local castbarHeading = Theme:CreateText(personal, 13)
	castbarHeading:SetPoint("TOPLEFT", rowSpacing, "BOTTOMLEFT", 0, -28)
	castbarHeading:SetText("Cast Bar")
	castbarHeading:SetTextColor(unpack(Theme.Colors.goldText))
	controls.CastbarHeading = castbarHeading

	local castbarMode = CreateFrame("Frame", "SlickUIUnitCastbarModeDropdown", personal, "UIDropDownMenuTemplate")
	castbarMode:SetPoint("TOPLEFT", castbarHeading, "BOTTOMLEFT", -16, 2)
	UIDropDownMenu_SetWidth(castbarMode, 160)
	UIDropDownMenu_Initialize(castbarMode, function()
		for _, choice in ipairs({
			{ value = "ATTACHED", label = "Attached below frame" },
			{ value = "IN_FRAME", label = "Inside frame" },
			{ value = "DETACHED", label = "Move separately" },
		}) do
			local selected = choice
			local info = UIDropDownMenu_CreateInfo()
			info.text = selected.label
			info.value = selected.value
			info.checked = (SlickUI.DB.unitFrames.frames[Options.SelectedUnit].castbarMode or "ATTACHED") == selected.value
			info.func = function()
				if not CanChangeUnitFrames() then Options:RefreshUnitFrameControls() return end
				SlickUI.DB.unitFrames.frames[Options.SelectedUnit].castbarMode = selected.value
				SlickUI:ApplyUnitFrameSettings(Options.SelectedUnit)
				Options:RefreshUnitFrameControls()
			end
			UIDropDownMenu_AddButton(info)
		end
	end)
	controls.CastbarMode = castbarMode

	local castbarWidth = CreateSlider(personal, "Cast-bar width", 80, 400, 5, 180, function(value) UpdatePersonal("castbarWidth", value, false) end)
	castbarWidth:SetPoint("TOPLEFT", castbarMode, "BOTTOMLEFT", 20, -18)
	controls.CastbarWidth = castbarWidth
	local castbarHeight = CreateSlider(personal, "Cast-bar height", 8, 32, 1, 180, function(value) UpdatePersonal("castbarHeight", value, false) end)
	castbarHeight:SetPoint("TOPLEFT", castbarWidth, "BOTTOMLEFT", 0, -34)
	controls.CastbarHeight = castbarHeight

	local arrange = CreateButton(personal, "Arrange with Even Spacing", 205)
	arrange:SetPoint("BOTTOMRIGHT", personal, "BOTTOMRIGHT", 0, 0)
	arrange:SetScript("OnClick", function() SlickUI:ArrangePersonalFrames() end)
	controls.ArrangeButton = arrange

	local display = CreateFrame("Frame", nil, page)
	display:SetPoint("TOPLEFT", personalButton, "BOTTOMLEFT", 0, -14)
	display:SetPoint("BOTTOMRIGHT", page, "BOTTOMRIGHT", 0, 0)
	controls.DisplayPanel = display
	controls.PlayerDisplayDropdowns = {}
	controls.PlayerDisplaySizeEdits = {}

	local displayHeading = Theme:CreateText(display, 13)
	displayHeading:SetPoint("TOPLEFT", 0, 0)
	displayHeading:SetText("Selected Frame Information Layout")
	displayHeading:SetTextColor(unpack(Theme.Colors.goldText))
	controls.DisplayHeading = displayHeading

	local displayNote = Theme:CreateText(display, 10)
	displayNote:SetPoint("TOPLEFT", displayHeading, "BOTTOMLEFT", 0, -7)
	displayNote:SetPoint("RIGHT", display, "RIGHT", 0, 0)
	displayNote:SetJustifyH("LEFT")
	displayNote:SetText("Choose what appears in each fixed frame position. Group settings apply to every party and raid member.")
	displayNote:SetTextColor(unpack(Theme.Colors.mutedText))

	local displayUnitOrder = { "player", "target", "focus", "pet", "targettarget", "boss", "group" }
	local displayPrevious
	for _, unit in ipairs(displayUnitOrder) do
		local selectedUnit = unit
		local width = selectedUnit == "pet" and 50 or 60
		local text = selectedUnit == "targettarget" and "ToT" or UnitLabels[selectedUnit]
		local button = CreateButton(display, text, width)
		if displayPrevious then
			button:SetPoint("LEFT", displayPrevious, "RIGHT", 5, 0)
		else
			button:SetPoint("TOPLEFT", displayNote, "BOTTOMLEFT", 0, -12)
		end
		button:SetScript("OnClick", function()
			Options.SelectedUnit = selectedUnit
			Options:RefreshUnitFrameControls()
		end)
		controls.DisplayUnitButtons[selectedUnit] = button
		displayPrevious = button
	end

	local slotLabels = {
		topLeft = "Top Left",
		topCenter = "Top Center",
		topRight = "Top Right",
		middleLeft = "Middle Left",
		middleCenter = "Middle Center",
		middleRight = "Middle Right",
		bottomLeft = "Bottom Left",
		bottomCenter = "Bottom Center",
		bottomRight = "Bottom Right",
	}
	local slotRows = {
		{ "topLeft", "topCenter", "topRight" },
		{ "middleLeft", "middleCenter", "middleRight" },
		{ "bottomLeft", "bottomCenter", "bottomRight" },
	}
	local columnOffsets = { 0, 152, 304 }

	for rowIndex, row in ipairs(slotRows) do
		for columnIndex, slotKey in ipairs(row) do
			local selectedSlot = slotKey
			local label = Theme:CreateText(display, 10)
			label:SetPoint("TOPLEFT", displayNote, "BOTTOMLEFT", columnOffsets[columnIndex], -58 - ((rowIndex - 1) * 70))
			label:SetText(slotLabels[selectedSlot])
			label:SetTextColor(unpack(Theme.Colors.goldText))

			local sizeLabel = Theme:CreateText(display, 9)
			sizeLabel:SetPoint("LEFT", label, "LEFT", 78, 0)
			sizeLabel:SetText("Size")
			sizeLabel:SetTextColor(unpack(Theme.Colors.mutedText))

			local sizeEdit = CreateFrame("EditBox", nil, display, "InputBoxTemplate")
			sizeEdit:SetSize(34, 18)
			sizeEdit:SetPoint("LEFT", label, "LEFT", 105, 0)
			sizeEdit:SetAutoFocus(false)
			sizeEdit:SetMaxLetters(2)
			sizeEdit:SetJustifyH("CENTER")
			Theme:SetFont(sizeEdit, 9)
			sizeEdit:SetTextColor(unpack(Theme.Colors.goldText))
			local function CommitFontSize(editBox)
				local unit = Options.SelectedUnit
				local fontSizes = SlickUI.DB.unitFrames.frameDisplayFontSizes[unit]
				local current = fontSizes[selectedSlot] or 9
				local size = tonumber(editBox:GetText()) or current
				size = math.max(7, math.min(16, math.floor(size + 0.5)))
				if CanChangeUnitFrames() then
					fontSizes[selectedSlot] = size
					ApplySelectedDisplay(selectedSlot)
				end
				editBox:SetText(tostring(fontSizes[selectedSlot] or current))
				editBox:ClearFocus()
			end
			sizeEdit:SetScript("OnEnterPressed", CommitFontSize)
			sizeEdit:SetScript("OnEditFocusLost", CommitFontSize)
			sizeEdit:SetScript("OnEscapePressed", function(self)
				local fontSizes = SlickUI.DB.unitFrames.frameDisplayFontSizes[Options.SelectedUnit]
				self:SetText(tostring(fontSizes[selectedSlot] or 9))
				self:ClearFocus()
			end)
			controls.PlayerDisplaySizeEdits[selectedSlot] = sizeEdit

			local dropdownName = "SlickUIPlayerDisplay" .. selectedSlot .. "Dropdown"
			local dropdown = CreateFrame("Frame", dropdownName, display, "UIDropDownMenuTemplate")
			dropdown:SetPoint("TOPLEFT", label, "BOTTOMLEFT", -16, 3)
			UIDropDownMenu_SetWidth(dropdown, 120)
			UIDropDownMenu_Initialize(dropdown, function()
				for _, displayChoice in ipairs(SlickUI.PlayerDisplayChoices) do
					local choiceKey = displayChoice.key
					local choiceLabel = displayChoice.label
					local info = UIDropDownMenu_CreateInfo()
					info.text = choiceLabel
					info.value = choiceKey
					info.checked = SlickUI.DB.unitFrames.frameDisplays[Options.SelectedUnit][selectedSlot] == choiceKey
					info.func = function()
						if not CanChangeUnitFrames() then Options:RefreshUnitFrameControls() return end
						SlickUI.DB.unitFrames.frameDisplays[Options.SelectedUnit][selectedSlot] = choiceKey
						ApplySelectedDisplay(selectedSlot)
						Options:RefreshUnitFrameControls()
					end
					UIDropDownMenu_AddButton(info)
				end
			end)
			controls.PlayerDisplayDropdowns[selectedSlot] = dropdown
		end
	end

	local restoreDisplay = CreateButton(display, "Restore Suggested Layout", 205)
	restoreDisplay:SetPoint("TOPRIGHT", display, "TOPRIGHT", 0, 4)
	restoreDisplay:SetScript("OnClick", function()
		if not CanChangeUnitFrames() then Options:RefreshUnitFrameControls() return end
		local unit = Options.SelectedUnit
		local defaults = SlickUI.Defaults.unitFrames.frameDisplays[unit]
		for slotKey, value in pairs(defaults) do
			SlickUI.DB.unitFrames.frameDisplays[unit][slotKey] = value
		end
		for slotKey, value in pairs(SlickUI.Defaults.unitFrames.frameDisplayFontSizes[unit]) do
			SlickUI.DB.unitFrames.frameDisplayFontSizes[unit][slotKey] = value
		end
		ApplySelectedDisplay()
		Options:RefreshUnitFrameControls()
	end)

	local group = CreateFrame("Frame", nil, page)
	group:SetPoint("TOPLEFT", personalButton, "BOTTOMLEFT", 0, -14)
	group:SetPoint("BOTTOMRIGHT", page, "BOTTOMRIGHT", 0, 0)
	controls.GroupPanel = group

	local groupNote = Theme:CreateText(group, 10)
	groupNote:SetPoint("TOPLEFT", 0, 0)
	groupNote:SetPoint("RIGHT", group, "RIGHT", 0, 0)
	groupNote:SetJustifyH("LEFT")
	groupNote:SetText("The dock fills sideways first, then adds shallow rows. Adaptive scaling keeps large raids below the character area. Unlock frames to drag the dock.")
	groupNote:SetTextColor(unpack(Theme.Colors.mutedText))

	local function UpdateGroup(field, value)
		if not CanChangeUnitFrames() then Options:RefreshUnitFrameControls() return end
		SlickUI.DB.unitFrames.group[field] = value
		SlickUI.GroupFrames:ApplySettings()
	end

	local partyEnabled = CreateCheckbox(group, "Enable party frames")
	partyEnabled:SetPoint("TOPLEFT", groupNote, "BOTTOMLEFT", -4, -10)
	partyEnabled:SetScript("OnClick", function(self) UpdateGroup("partyEnabled", self:GetChecked() == true) end)
	controls.PartyEnabled = partyEnabled
	local raidEnabled = CreateCheckbox(group, "Enable raid frames")
	raidEnabled:SetPoint("TOPLEFT", groupNote, "BOTTOMLEFT", 216, -10)
	raidEnabled:SetScript("OnClick", function(self) UpdateGroup("raidEnabled", self:GetChecked() == true) end)
	controls.RaidEnabled = raidEnabled
	local hideBlizzardFrames = CreateCheckbox(group, "Hide Blizzard frames")
	Theme:SetFont(hideBlizzardFrames.Text, 10)
	hideBlizzardFrames:SetPoint("TOPLEFT", partyEnabled, "BOTTOMLEFT", 0, -4)
	hideBlizzardFrames:SetScript("OnClick", function(self)
		if not CanChangeUnitFrames() then Options:RefreshUnitFrameControls() return end
		SlickUI.DB.unitFrames.group.hideBlizzardFrames = self:GetChecked() == true
		SlickUI.GroupFrames:ApplyBlizzardFrameVisibility()
		Options:RefreshUnitFrameControls()
	end)
	controls.HideBlizzardFrames = hideBlizzardFrames
	local adaptive = CreateCheckbox(group, "Adaptive raid scaling")
	adaptive:SetPoint("TOPLEFT", raidEnabled, "BOTTOMLEFT", 0, -4)
	adaptive:SetScript("OnClick", function(self) UpdateGroup("adaptive", self:GetChecked() == true) end)
	controls.Adaptive = adaptive
	local vertical = CreateCheckbox(group, "Vertical layout")
	vertical:SetPoint("TOPLEFT", hideBlizzardFrames, "BOTTOMLEFT", 0, -4)
	vertical:SetScript("OnClick", function(self) UpdateGroup("vertical", self:GetChecked() == true) end)
	controls.Vertical = vertical
	local groupClass = CreateCheckbox(group, "Use class-colored health")
	groupClass:SetPoint("TOPLEFT", adaptive, "BOTTOMLEFT", 0, -4)
	groupClass:SetScript("OnClick", function(self) UpdateGroup("classColors", self:GetChecked() == true) end)
	controls.GroupClassColors = groupClass
	local showSolo = CreateCheckbox(group, "Show self while solo")
	showSolo:SetPoint("TOPLEFT", vertical, "BOTTOMLEFT", 0, -4)
	showSolo:SetScript("OnClick", function(self) UpdateGroup("showSolo", self:GetChecked() == true) end)
	controls.ShowSolo = showSolo
	local mainTankEnabled = CreateCheckbox(group, "Main Tank window")
	Theme:SetFont(mainTankEnabled.Text, 10)
	mainTankEnabled:SetPoint("TOPLEFT", groupClass, "BOTTOMLEFT", 0, -4)
	mainTankEnabled:SetScript("OnClick", function(self) UpdateGroup("mainTankEnabled", self:GetChecked() == true) end)
	controls.MainTankEnabled = mainTankEnabled
	local mainAssistEnabled = CreateCheckbox(group, "Main Assist window")
	Theme:SetFont(mainAssistEnabled.Text, 10)
	mainAssistEnabled:SetPoint("TOPLEFT", showSolo, "BOTTOMLEFT", 0, -4)
	mainAssistEnabled:SetScript("OnClick", function(self) UpdateGroup("mainAssistEnabled", self:GetChecked() == true) end)
	controls.MainAssistEnabled = mainAssistEnabled
	local showDispellableDebuffs = CreateCheckbox(group, "Dispel outline + center icon")
	Theme:SetFont(showDispellableDebuffs.Text, 10)
	showDispellableDebuffs:SetScript("OnClick", function(self) UpdateGroup("showDispellableDebuffs", self:GetChecked() == true) end)
	controls.ShowDispellableDebuffs = showDispellableDebuffs

	local showIncomingHealing = CreateCheckbox(group, "Incoming heals")
	Theme:SetFont(showIncomingHealing.Text, 10)
	showIncomingHealing:SetPoint("TOPLEFT", mainTankEnabled, "BOTTOMLEFT", 0, -4)
	showIncomingHealing:SetScript("OnClick", function(self) UpdateGroup("showIncomingHealing", self:GetChecked() == true) end)
	controls.ShowIncomingHealing = showIncomingHealing
	local showAbsorbShields = CreateCheckbox(group, "Absorb shields")
	Theme:SetFont(showAbsorbShields.Text, 10)
	showAbsorbShields:SetPoint("TOPLEFT", mainAssistEnabled, "BOTTOMLEFT", 0, -4)
	showAbsorbShields:SetScript("OnClick", function(self) UpdateGroup("showAbsorbShields", self:GetChecked() == true) end)
	controls.ShowAbsorbShields = showAbsorbShields
	local showHealAbsorbs = CreateCheckbox(group, "Heal absorbs")
	Theme:SetFont(showHealAbsorbs.Text, 10)
	showHealAbsorbs:SetPoint("TOPLEFT", showIncomingHealing, "BOTTOMLEFT", 0, -4)
	showHealAbsorbs:SetScript("OnClick", function(self) UpdateGroup("showHealAbsorbs", self:GetChecked() == true) end)
	controls.ShowHealAbsorbs = showHealAbsorbs
	local capOverhealing = CreateCheckbox(group, "Cap overhealing")
	Theme:SetFont(capOverhealing.Text, 10)
	capOverhealing:SetPoint("TOPLEFT", showAbsorbShields, "BOTTOMLEFT", 0, -4)
	capOverhealing:SetScript("OnClick", function(self) UpdateGroup("capOverhealing", self:GetChecked() == true) end)
	controls.CapOverhealing = capOverhealing
	showDispellableDebuffs:SetPoint("TOPLEFT", capOverhealing, "BOTTOMLEFT", 0, -4)
	local showPersonalHealingEffects = CreateCheckbox(group, "Healing-effects info bar")
	Theme:SetFont(showPersonalHealingEffects.Text, 10)
	showPersonalHealingEffects:SetPoint("TOPLEFT", showHealAbsorbs, "BOTTOMLEFT", 0, -4)
	showPersonalHealingEffects:SetScript("OnClick", function(self) UpdateGroup("showPersonalHealingEffects", self:GetChecked() == true) end)
	controls.ShowPersonalHealingEffects = showPersonalHealingEffects
	local showGroupCastbar = CreateCheckbox(group, "Cast bars in info bar")
	Theme:SetFont(showGroupCastbar.Text, 10)
	showGroupCastbar:SetPoint("TOPLEFT", showPersonalHealingEffects, "BOTTOMLEFT", 0, -4)
	showGroupCastbar:SetScript("OnClick", function(self) UpdateGroup("showCastbar", self:GetChecked() == true) end)
	controls.ShowGroupCastbar = showGroupCastbar

	local groupWidth = CreateSlider(group, "Frame width", 70, 180, 5, 180, function(value) UpdateGroup("width", value) end)
	groupWidth:SetPoint("TOPLEFT", groupNote, "BOTTOMLEFT", 4, -284)
	controls.GroupWidth = groupWidth
	local groupHeight = CreateSlider(group, "Frame height", 20, 60, 2, 180, function(value) UpdateGroup("height", value) end)
	groupHeight:SetPoint("TOPLEFT", groupWidth, "BOTTOMLEFT", 0, -10)
	controls.GroupHeight = groupHeight
	local groupPower = CreateSlider(group, "Power bar height", 2, 12, 1, 180, function(value) UpdateGroup("powerHeight", value) end)
	groupPower:SetPoint("TOPLEFT", groupHeight, "BOTTOMLEFT", 0, -10)
	controls.GroupPowerHeight = groupPower

	local groupSpacing = CreateSlider(group, "Frame spacing", 0, 20, 1, 180, function(value) UpdateGroup("spacing", value) end)
	groupSpacing:SetPoint("TOPLEFT", groupNote, "BOTTOMLEFT", 224, -284)
	controls.GroupSpacing = groupSpacing
	local members = CreateSlider(group, "Members per row", 5, 10, 1, 180, function(value) UpdateGroup("membersPerRow", value) end)
	members:SetPoint("TOPLEFT", groupSpacing, "BOTTOMLEFT", 0, -10)
	controls.MembersPerRow = members
	local minimumScale = CreateSlider(group, "Minimum raid scale", 0.45, 0.80, 0.01, 180, function(value) UpdateGroup("minimumScale", value) end, function(value) return string.format("%.2f", value) end)
	minimumScale:SetPoint("TOPLEFT", members, "BOTTOMLEFT", 0, -10)
	controls.MinimumScale = minimumScale

	local anchorLabel = Theme:CreateText(group, 10)
	anchorLabel:SetPoint("BOTTOMLEFT", 0, 34)
	anchorLabel:SetText("Growth anchor")
	anchorLabel:SetTextColor(unpack(Theme.Colors.goldText))

	local anchorDropdown = CreateFrame("Frame", "SlickUIGroupGrowthAnchorDropdown", group, "UIDropDownMenuTemplate")
	anchorDropdown:SetPoint("BOTTOMLEFT", -16, -2)
	UIDropDownMenu_SetWidth(anchorDropdown, 82)
	UIDropDownMenu_Initialize(anchorDropdown, function()
		for _, choice in ipairs({ "LEFT", "CENTER", "RIGHT" }) do
			local anchorValue = choice
			local anchorText = anchorValue == "LEFT" and "Left" or anchorValue == "RIGHT" and "Right" or "Center"
			local info = UIDropDownMenu_CreateInfo()
			info.text = anchorText
			info.value = anchorValue
			info.checked = SlickUI.DB.unitFrames.group.anchor == anchorValue
			info.func = function()
				SlickUI.DB.unitFrames.group.anchor = anchorValue
				UIDropDownMenu_SetSelectedValue(anchorDropdown, anchorValue)
				UIDropDownMenu_SetText(anchorDropdown, anchorText)
				SlickUI.GroupFrames:ApplySettings()
				Options:RefreshUnitFrameControls()
			end
			UIDropDownMenu_AddButton(info)
		end
	end)
	controls.AnchorDropdown = anchorDropdown

	local previewLabel = Theme:CreateText(group, 10)
	previewLabel:SetPoint("BOTTOMLEFT", 132, 34)
	previewLabel:SetText("Test group size")
	previewLabel:SetTextColor(unpack(Theme.Colors.goldText))

	local previewDropdown = CreateFrame("Frame", "SlickUIGroupPreviewSizeDropdown", group, "UIDropDownMenuTemplate")
	previewDropdown:SetPoint("BOTTOMLEFT", 116, -2)
	UIDropDownMenu_SetWidth(previewDropdown, 92)
	UIDropDownMenu_Initialize(previewDropdown, function()
		for _, choice in ipairs({ 5, 10, 20, 30, 40 }) do
			local memberCount = choice
			local info = UIDropDownMenu_CreateInfo()
			info.text = memberCount .. " members"
			info.value = memberCount
			info.checked = SlickUI.DB.unitFrames.group.previewCount == memberCount
			info.func = function()
				SlickUI.DB.unitFrames.group.previewCount = memberCount
				UIDropDownMenu_SetSelectedValue(previewDropdown, memberCount)
				UIDropDownMenu_SetText(previewDropdown, memberCount .. " members")
				if SlickUI.GroupFrames.PreviewVisible then
					SlickUI.GroupFrames:ShowPreview(memberCount)
				end
				Options:RefreshUnitFrameControls()
			end
			UIDropDownMenu_AddButton(info)
		end
	end)
	controls.PreviewDropdown = previewDropdown

	local previewButton = CreateButton(group, "Show Test Frames", 160)
	previewButton:SetPoint("BOTTOMRIGHT", group, "BOTTOMRIGHT", 0, 2)
	previewButton:SetScript("OnClick", function()
		if not CanChangeUnitFrames() then return end
		SlickUI.GroupFrames:TogglePreview(SlickUI.DB.unitFrames.group.previewCount)
		Options:RefreshUnitFrameControls()
	end)
	controls.PreviewButton = previewButton

	self.Pages.unitframes = page
	self:ShowUnitSection("personal")
end

function Options:RefreshMinimapButtonBarControls()
	local controls = self.MinimapButtonBarControls
	if not controls or not SlickUI.DB or not SlickUI.MinimapButtonBar then return end
	local settings = SlickUI.DB.minimapButtonBar
	controls.Enabled:SetChecked(settings.enabled)
	controls.Horizontal:SetEnabled(settings.orientation ~= "HORIZONTAL")
	controls.Vertical:SetEnabled(settings.orientation ~= "VERTICAL")
	for count = 1, 3 do
		controls.Rows[count]:SetEnabled(settings.rows ~= count)
	end
	SetSliderValue(controls.Length, settings.length)
	SetSliderValue(controls.ButtonSize, settings.buttonSize)
	SetSliderValue(controls.Spacing, settings.spacing)
	controls.Locked:SetChecked(settings.locked)
	local direction = settings.orientation == "VERTICAL" and "vertical" or "horizontal"
	local bandWord = settings.orientation == "VERTICAL" and "column" or "row"
	if settings.rows ~= 1 then bandWord = bandWord .. "s" end
	local count = #SlickUI.MinimapButtonBar.Buttons
	local capacity = SlickUI.MinimapButtonBar:GetCapacity()
	controls.Status:SetText(string.format("%d button%s found. %s layout, %d %s, %d visible position%s per %s.%s",
		count, count == 1 and "" or "s", direction, settings.rows, bandWord,
		capacity, capacity == 1 and "" or "s", capacity < count and "page" or "bar",
		settings.collapsed and " Bar is currently collapsed." or ""))
end

function Options:CreateMinimapButtonBarPage(parent)
	local page = CreateFrame("Frame", nil, parent)
	page:SetAllPoints()
	local controls = { Rows = {} }
	self.MinimapButtonBarControls = controls

	local heading = Theme:CreateText(page, 17)
	heading:SetPoint("TOPLEFT", 0, 0)
	heading:SetText("Minimap Buttons")
	heading:SetTextColor(unpack(Theme.Colors.goldText))

	local note = Theme:CreateText(page, 10)
	note:SetPoint("TOPLEFT", heading, "BOTTOMLEFT", 0, -7)
	note:SetPoint("RIGHT", page, "RIGHT", 0, 0)
	note:SetJustifyH("LEFT")
	note:SetText("Organizes addon minimap buttons without replacing their original clicks or tooltips. Scroll the mouse wheel over the bar or any organized icon to page through overflow buttons.")
	note:SetTextColor(unpack(Theme.Colors.mutedText))

	local function Apply(field, value)
		if not SlickUI:IsAvailableOutOfCombat("Minimap button changes") then
			Options:RefreshMinimapButtonBarControls()
			return
		end
		SlickUI.DB.minimapButtonBar[field] = value
		SlickUI.MinimapButtonBar:ApplySettings()
		Options:RefreshMinimapButtonBarControls()
	end

	local enabled = CreateCheckbox(page, "Enable Minimap Button Bar")
	enabled:SetPoint("TOPLEFT", note, "BOTTOMLEFT", -4, -15)
	enabled:SetScript("OnClick", function(self) Apply("enabled", self:GetChecked() == true) end)
	controls.Enabled = enabled

	local layoutTitle = Theme:CreateText(page, 14)
	layoutTitle:SetPoint("TOPLEFT", enabled, "BOTTOMLEFT", 4, -17)
	layoutTitle:SetText("Direction")
	layoutTitle:SetTextColor(unpack(Theme.Colors.goldText))

	local horizontal = CreateButton(page, "Horizontal", 145)
	horizontal:SetPoint("TOPLEFT", layoutTitle, "BOTTOMLEFT", 0, -8)
	horizontal:SetScript("OnClick", function() Apply("orientation", "HORIZONTAL") end)
	controls.Horizontal = horizontal

	local vertical = CreateButton(page, "Vertical", 145)
	vertical:SetPoint("LEFT", horizontal, "RIGHT", 12, 0)
	vertical:SetScript("OnClick", function() Apply("orientation", "VERTICAL") end)
	controls.Vertical = vertical

	local rowsTitle = Theme:CreateText(page, 14)
	rowsTitle:SetPoint("TOPLEFT", horizontal, "BOTTOMLEFT", 0, -20)
	rowsTitle:SetText("Rows / Columns")
	rowsTitle:SetTextColor(unpack(Theme.Colors.goldText))

	local previous
	for count = 1, 3 do
		local button = CreateButton(page, tostring(count), 90)
		if previous then
			button:SetPoint("LEFT", previous, "RIGHT", 10, 0)
		else
			button:SetPoint("TOPLEFT", rowsTitle, "BOTTOMLEFT", 0, -8)
		end
		button:SetScript("OnClick", function() Apply("rows", count) end)
		controls.Rows[count] = button
		previous = button
	end

	local sizeTitle = Theme:CreateText(page, 14)
	sizeTitle:SetPoint("TOPLEFT", controls.Rows[1], "BOTTOMLEFT", 0, -22)
	sizeTitle:SetText("Capacity & Size")
	sizeTitle:SetTextColor(unpack(Theme.Colors.goldText))

	local length = CreateSlider(page, "Buttons per row / column", 3, 20, 1, 220, function(value) Apply("length", value) end)
	length:SetPoint("TOPLEFT", sizeTitle, "BOTTOMLEFT", 0, -30)
	controls.Length = length

	local buttonSize = CreateSlider(page, "Button size", 22, 42, 1, 220, function(value) Apply("buttonSize", value) end)
	buttonSize:SetPoint("TOPLEFT", length, "BOTTOMLEFT", 0, -34)
	controls.ButtonSize = buttonSize

	local spacing = CreateSlider(page, "Button spacing", 0, 10, 1, 220, function(value) Apply("spacing", value) end)
	spacing:SetPoint("TOPLEFT", buttonSize, "BOTTOMLEFT", 0, -34)
	controls.Spacing = spacing

	local locked = CreateCheckbox(page, "Lock Minimap Button Bar")
	locked:SetPoint("TOPLEFT", spacing, "BOTTOMLEFT", -4, -23)
	locked:SetScript("OnClick", function(self) Apply("locked", self:GetChecked() == true) end)
	controls.Locked = locked

	local refresh = CreateButton(page, "Refresh Buttons", 160)
	refresh:SetPoint("TOPLEFT", locked, "BOTTOMLEFT", 4, -13)
	refresh:SetScript("OnClick", function()
		SlickUI.MinimapButtonBar:Refresh()
		Options:RefreshMinimapButtonBarControls()
	end)

	local reset = CreateButton(page, "Reset Bar", 130)
	reset:SetPoint("LEFT", refresh, "RIGHT", 12, 0)
	reset:SetScript("OnClick", function()
		if not SlickUI:IsAvailableOutOfCombat("Minimap button reset") then return end
		local settings = SlickUI.DB.minimapButtonBar
		settings.orientation = "HORIZONTAL"
		settings.rows = 1
		settings.length = 8
		settings.buttonSize = 31
		settings.spacing = 2
		settings.locked = true
		settings.collapsed = false
		settings.x = 500
		settings.y = 250
		SlickUI.MinimapButtonBar:ApplySettings()
		Options:RefreshMinimapButtonBarControls()
	end)

	local status = Theme:CreateText(page, 10)
	status:SetPoint("TOPLEFT", refresh, "BOTTOMLEFT", 0, -14)
	status:SetPoint("RIGHT", page, "RIGHT", 0, 0)
	status:SetJustifyH("LEFT")
	status:SetTextColor(unpack(Theme.Colors.mutedText))
	controls.Status = status

	self.Pages.minimapbuttons = page
end

function Options:RefreshQuestTrackerControls()
	local controls = self.QuestTrackerControls
	if not controls or not SlickUI.DB then return end
	local settings = SlickUI.DB.questTracker
	controls.Enabled:SetChecked(settings.enabled)
	controls.ShowBorder:SetChecked(settings.showBorder)
	controls.ShowHeader:SetChecked(settings.showHeader)
	controls.GroupByZone:SetChecked(settings.groupByZone)
	controls.CurrentZoneOnly:SetChecked(settings.currentZoneOnly)
	controls.BlizzardCurrentZoneTracking:SetChecked(settings.blizzardCurrentZoneTracking)
	controls.BlizzardCurrentZoneTracking:SetEnabled(not settings.enabled)
	local presetLabels = { small = "Small", normal = "Normal", large = "Large" }
	UIDropDownMenu_SetSelectedValue(controls.TextSizePreset, settings.textSizePreset or "normal")
	UIDropDownMenu_SetText(controls.TextSizePreset, presetLabels[settings.textSizePreset or "normal"] or "Custom")
	SetSliderValue(controls.Width, settings.width)
	SetSliderValue(controls.Height, settings.height)
	SetSliderValue(controls.Scale, settings.scale)
	SetSliderValue(controls.BackgroundAlpha, settings.backgroundAlpha)
	SetSliderValue(controls.HeaderFont, settings.headerFontSize)
	SetSliderValue(controls.QuestFont, settings.questFontSize)
	SetSliderValue(controls.ObjectiveFont, settings.objectiveFontSize)
	SetSliderValue(controls.ScrollStep, settings.scrollStep)
end

function Options:CreateQuestTrackerPage(parent)
	local page = CreateFrame("Frame", nil, parent)
	page:SetAllPoints()
	local scroll = CreateFrame("ScrollFrame", nil, page, "UIPanelScrollFrameTemplate")
	scroll:SetPoint("TOPLEFT", 0, 0)
	scroll:SetPoint("BOTTOMRIGHT", -22, 0)
	scroll:EnableMouseWheel(true)
	local body = CreateFrame("Frame", nil, scroll)
	body:SetSize(430, 790)
	scroll:SetScrollChild(body)
	page:SetScript("OnSizeChanged", function(_, width)
		body:SetWidth(math.max(400, width - 28))
	end)
	scroll:SetScript("OnMouseWheel", function(self, delta)
		self:SetVerticalScroll(math.max(0, math.min(self:GetVerticalScrollRange(), self:GetVerticalScroll() - delta * 45)))
	end)
	local controls = {}
	self.QuestTrackerControls = controls

	local heading = Theme:CreateText(body, 17)
	heading:SetPoint("TOPLEFT", 0, 0)
	heading:SetText("Quest Tracker")
	heading:SetTextColor(unpack(Theme.Colors.goldText))

	local note = Theme:CreateText(body, 10)
	note:SetPoint("TOPLEFT", heading, "BOTTOMLEFT", 0, -7)
	note:SetPoint("RIGHT", body, "RIGHT", -10, 0)
	note:SetJustifyH("LEFT")
	note:SetText("Choose the supported Blizzard tracker filter or use SlickUI's paused replacement tracker below.")
	note:SetTextColor(unpack(Theme.Colors.mutedText))

	local function Apply(field, value)
		SlickUI.DB.questTracker[field] = value
		if field == "headerFontSize" or field == "questFontSize" or field == "objectiveFontSize" then
			SlickUI.DB.questTracker.textSizePreset = "custom"
		end
		if SlickUI.QuestTracker then SlickUI.QuestTracker:ApplySettings() end
		Options:RefreshQuestTrackerControls()
	end

	local blizzardTitle = Theme:CreateText(body, 14)
	blizzardTitle:SetPoint("TOPLEFT", note, "BOTTOMLEFT", 0, -16)
	blizzardTitle:SetText("Blizzard Quest Tracker")
	blizzardTitle:SetTextColor(unpack(Theme.Colors.goldText))

	local blizzardCurrentZone = CreateCheckbox(body, "Track current-zone quests only")
	blizzardCurrentZone:SetPoint("TOPLEFT", blizzardTitle, "BOTTOMLEFT", -4, -8)
	blizzardCurrentZone:SetScript("OnClick", function(self)
		if SlickUI.BlizzardZoneTracking then SlickUI.BlizzardZoneTracking:SetEnabled(self:GetChecked() == true) end
		Options:RefreshQuestTrackerControls()
	end)
	controls.BlizzardCurrentZoneTracking = blizzardCurrentZone

	local blizzardNote = Theme:CreateText(body, 10)
	blizzardNote:SetPoint("TOPLEFT", blizzardCurrentZone, "BOTTOMLEFT", 4, -5)
	blizzardNote:SetPoint("RIGHT", body, "RIGHT", -10, 0)
	blizzardNote:SetJustifyH("LEFT")
	blizzardNote:SetText("Uses Blizzard's tracker and automatically updates its watched quests when you change zones.")
	blizzardNote:SetTextColor(unpack(Theme.Colors.mutedText))

	local pausedTitle = Theme:CreateText(body, 14)
	pausedTitle:SetPoint("TOPLEFT", blizzardNote, "BOTTOMLEFT", 0, -20)
	pausedTitle:SetText("SlickUI Quest Tracker - Development On Hold")
	pausedTitle:SetTextColor(1, 0.35, 0.18)

	local pausedNote = Theme:CreateText(body, 10)
	pausedNote:SetPoint("TOPLEFT", pausedTitle, "BOTTOMLEFT", 0, -7)
	pausedNote:SetPoint("RIGHT", body, "RIGHT", -10, 0)
	pausedNote:SetJustifyH("LEFT")
	pausedNote:SetText("This replacement tracker is being kept for existing users, but active development is paused. It may not support every Blizzard objective type or future patch change. Use at your own risk.")
	pausedNote:SetTextColor(1, 0.62, 0.28)

	local enabled = CreateCheckbox(body, "Enable SlickUI Quest Tracker")
	enabled:SetPoint("TOPLEFT", pausedNote, "BOTTOMLEFT", -4, -14)
	enabled:SetScript("OnClick", function(self)
		if SlickUI.QuestTracker then SlickUI.QuestTracker:SetEnabled(self:GetChecked() == true) end
		Options:RefreshQuestTrackerControls()
	end)
	controls.Enabled = enabled

	local showBorder = CreateCheckbox(body, "Show gold window border")
	showBorder:SetPoint("TOPLEFT", enabled, "BOTTOMLEFT", 0, -6)
	showBorder:SetScript("OnClick", function(self) Apply("showBorder", self:GetChecked() == true) end)
	controls.ShowBorder = showBorder

	local showHeader = CreateCheckbox(body, "Show SlickUI Quest Tracker header")
	showHeader:SetPoint("TOPLEFT", enabled, "TOPLEFT", 270, 0)
	showHeader:SetScript("OnClick", function(self) Apply("showHeader", self:GetChecked() == true) end)
	controls.ShowHeader = showHeader

	local groupByZone = CreateCheckbox(body, "Group quests by zone")
	groupByZone:SetPoint("TOPLEFT", showHeader, "BOTTOMLEFT", 0, -6)
	groupByZone:SetScript("OnClick", function(self) Apply("groupByZone", self:GetChecked() == true) end)
	controls.GroupByZone = groupByZone

	local currentZoneOnly = CreateCheckbox(body, "Only show current zone quests")
	currentZoneOnly:SetPoint("TOPLEFT", groupByZone, "BOTTOMLEFT", 0, -6)
	currentZoneOnly:SetScript("OnClick", function(self) Apply("currentZoneOnly", self:GetChecked() == true) end)
	controls.CurrentZoneOnly = currentZoneOnly

	local textSizeLabel = Theme:CreateText(body, 11)
	textSizeLabel:SetPoint("TOPLEFT", showBorder, "BOTTOMLEFT", 4, -14)
	textSizeLabel:SetText("Text size")
	textSizeLabel:SetTextColor(unpack(Theme.Colors.mutedText))

	local textSizePreset = CreateFrame("Frame", "SlickUIQuestTrackerTextSizeDropdown", body, "UIDropDownMenuTemplate")
	textSizePreset:SetPoint("TOPLEFT", textSizeLabel, "BOTTOMLEFT", -16, -2)
	UIDropDownMenu_SetWidth(textSizePreset, 135)
	local presets = {
		{ value = "small", label = "Small", header = 13, quest = 10, objective = 9 },
		{ value = "normal", label = "Normal", header = 15, quest = 12, objective = 10 },
		{ value = "large", label = "Large", header = 18, quest = 15, objective = 13 },
	}
	UIDropDownMenu_Initialize(textSizePreset, function()
		for _, preset in ipairs(presets) do
			local choice = preset
			local info = UIDropDownMenu_CreateInfo()
			info.text = choice.label
			info.value = choice.value
			info.checked = SlickUI.DB.questTracker.textSizePreset == choice.value
			info.func = function()
				local settings = SlickUI.DB.questTracker
				settings.textSizePreset = choice.value
				settings.headerFontSize = choice.header
				settings.questFontSize = choice.quest
				settings.objectiveFontSize = choice.objective
				if SlickUI.QuestTracker then SlickUI.QuestTracker:ApplySettings() end
				Options:RefreshQuestTrackerControls()
			end
			UIDropDownMenu_AddButton(info)
		end
	end)
	controls.TextSizePreset = textSizePreset

	local sizeTitle = Theme:CreateText(body, 14)
	sizeTitle:SetPoint("TOPLEFT", textSizePreset, "BOTTOMLEFT", 16, -14)
	sizeTitle:SetText("Window")
	sizeTitle:SetTextColor(unpack(Theme.Colors.goldText))

	local width = CreateSlider(body, "Width", 250, 600, 10, 190, function(value) Apply("width", value) end)
	width:SetPoint("TOPLEFT", sizeTitle, "BOTTOMLEFT", 0, -28); controls.Width = width
	local height = CreateSlider(body, "Height", 250, 900, 10, 190, function(value) Apply("height", value) end)
	height:SetPoint("LEFT", width, "RIGHT", 30, 0); controls.Height = height
	local scale = CreateSlider(body, "Scale", 0.6, 1.5, 0.05, 190, function(value) Apply("scale", value) end, function(value) return string.format("%.2f", value) end)
	scale:SetPoint("TOPLEFT", width, "BOTTOMLEFT", 0, -40); controls.Scale = scale
	local alpha = CreateSlider(body, "Background opacity", 0, 1, 0.05, 190, function(value) Apply("backgroundAlpha", value) end, function(value) return string.format("%d%%", value * 100) end)
	alpha:SetPoint("LEFT", scale, "RIGHT", 30, 0); controls.BackgroundAlpha = alpha

	local textTitle = Theme:CreateText(body, 14)
	textTitle:SetPoint("TOPLEFT", scale, "BOTTOMLEFT", 0, -42)
	textTitle:SetText("Text")
	textTitle:SetTextColor(unpack(Theme.Colors.goldText))
	local headerFont = CreateSlider(body, "Section headers", 10, 24, 1, 190, function(value) Apply("headerFontSize", value) end)
	headerFont:SetPoint("TOPLEFT", textTitle, "BOTTOMLEFT", 0, -28); controls.HeaderFont = headerFont
	local questFont = CreateSlider(body, "Quest titles", 8, 20, 1, 190, function(value) Apply("questFontSize", value) end)
	questFont:SetPoint("LEFT", headerFont, "RIGHT", 30, 0); controls.QuestFont = questFont
	local objectiveFont = CreateSlider(body, "Objectives", 8, 18, 1, 190, function(value) Apply("objectiveFontSize", value) end)
	objectiveFont:SetPoint("TOPLEFT", headerFont, "BOTTOMLEFT", 0, -40); controls.ObjectiveFont = objectiveFont
	local scrollStep = CreateSlider(body, "Mouse-wheel speed", 15, 120, 5, 190, function(value) Apply("scrollStep", value) end)
	scrollStep:SetPoint("LEFT", objectiveFont, "RIGHT", 30, 0); controls.ScrollStep = scrollStep

	local reset = CreateButton(body, "Restore Quest Tracker Defaults", 230)
	reset:SetPoint("TOPLEFT", objectiveFont, "BOTTOMLEFT", 0, -14)
	reset:SetScript("OnClick", function()
		for field, value in pairs(SlickUI.Defaults.questTracker) do SlickUI.DB.questTracker[field] = value end
		if SlickUI.QuestTracker then
			SlickUI.QuestTracker:SetEnabled(SlickUI.DB.questTracker.enabled)
			SlickUI.QuestTracker:ApplySettings()
		end
		Options:RefreshQuestTrackerControls()
	end)

	local hint = Theme:CreateText(body, 10)
	hint:SetPoint("TOPLEFT", reset, "BOTTOMLEFT", 0, -15)
	hint:SetPoint("RIGHT", body, "RIGHT", -10, 0)
	hint:SetJustifyH("LEFT")
	hint:SetText("Move the tracker with Unlock & Move Frames. Overflow is handled by its scrollbar and mouse wheel.")
	hint:SetTextColor(unpack(Theme.Colors.mutedText))

	self.Pages.questtracker = page
end

function Options:Create()
	if self.Panel then
		return self.Panel
	end

	local panel = CreateFrame("Frame", "SlickUIOptionsPanel")
	panel.name = "SlickUI"

	local content = CreateFrame("Frame", nil, panel, "BackdropTemplate")
	content:SetPoint("TOPLEFT", 16, -16)
	-- The Party/Raid page has three columns of frame and prediction controls.
	-- Give the card enough vertical room for the sliders and bottom test tools
	-- instead of allowing them to collide at the former compact height.
	-- The taller Nameplates controls include target scaling plus all spacing
	-- sliders. Keep both inner cards and their bottom controls inside the main
	-- SlickUI border with comfortable padding.
	content:SetSize(650, 580)
	content:SetBackdrop({ bgFile = Theme.Texture, edgeFile = Theme.Texture, edgeSize = 1 })
	content:SetBackdropColor(unpack(Theme.Colors.panel))
	content:SetBackdropBorderColor(unpack(Theme.Colors.border))
	Theme:RegisterAccentFrame(content)

	Theme:CreateHeaderIcon(content, 38, 16, -10)
	local title = Theme:CreateText(content, 20)
	title:SetPoint("TOPLEFT", 62, -16)
	title:SetText("SlickUI")
	title:SetTextColor(unpack(Theme.Colors.goldText))

	local version = Theme:CreateText(content, 11)
	version:SetPoint("LEFT", title, "RIGHT", 10, 0)
	version:SetText("v" .. SlickUI.Version)
	version:SetTextColor(unpack(Theme.Colors.mutedText))

	local description = Theme:CreateText(content, 11)
	description:SetPoint("TOPLEFT", title, "BOTTOMLEFT", 0, -10)
	description:SetPoint("RIGHT", content, "RIGHT", -18, 0)
	description:SetJustifyH("LEFT")
	description:SetText("Clean, modern interface controls. Configuration changes are locked during combat.")
	description:SetTextColor(unpack(Theme.Colors.mutedText))

	-- SlickUI is displayed inside Blizzard's managed Settings panel.  Keep a
	-- dedicated drag surface over our otherwise non-interactive header so the
	-- complete control panel can always be moved out of the way while arranging
	-- UI elements.  Page controls remain untouched, and there is intentionally
	-- no lock state for this handle.
	local dragHandle = CreateFrame("Frame", nil, content)
	dragHandle:SetPoint("TOPLEFT", 8, -6)
	dragHandle:SetPoint("TOPRIGHT", -8, -6)
	dragHandle:SetHeight(68)
	dragHandle:EnableMouse(true)
	dragHandle:RegisterForDrag("LeftButton")
	dragHandle:SetScript("OnDragStart", function()
		if InCombatLockdown() then return end
		local settingsFrame = SettingsPanel or panel:GetParent()
		if settingsFrame and settingsFrame.SetMovable and settingsFrame.StartMoving then
			settingsFrame:SetMovable(true)
			settingsFrame:SetClampedToScreen(true)
			settingsFrame:StartMoving()
		end
	end)
	dragHandle:SetScript("OnDragStop", function()
		local settingsFrame = SettingsPanel or panel:GetParent()
		if settingsFrame and settingsFrame.StopMovingOrSizing then
			settingsFrame:StopMovingOrSizing()
		end
	end)
	dragHandle:SetScript("OnEnter", function(self)
		GameTooltip:SetOwner(self, "ANCHOR_CURSOR")
		GameTooltip:SetText("Move SlickUI Options", 1, 0.82, 0)
		GameTooltip:AddLine("Drag this header to move the control panel.", 1, 1, 1)
		GameTooltip:Show()
	end)
	dragHandle:SetScript("OnLeave", GameTooltip_Hide)

	local divider = content:CreateTexture(nil, "BORDER")
	divider:SetColorTexture(unpack(Theme.Colors.borderMuted))
	divider:SetPoint("TOPLEFT", 150, -82)
	divider:SetPoint("BOTTOMLEFT", 150, 16)
	divider:SetWidth(1)

	local sidebarScroll = CreateFrame("ScrollFrame", nil, content)
	sidebarScroll:SetPoint("TOPLEFT", 10, -82)
	sidebarScroll:SetPoint("BOTTOMLEFT", 10, 16)
	sidebarScroll:SetWidth(132)
	sidebarScroll:EnableMouseWheel(true)
	local sidebar = CreateFrame("Frame", nil, sidebarScroll)
	sidebar:SetSize(132, 740)
	sidebarScroll:SetScrollChild(sidebar)
	sidebarScroll:SetScript("OnMouseWheel", function(self, delta)
		self:SetVerticalScroll(math.max(0, math.min(self:GetVerticalScrollRange(), self:GetVerticalScroll() - delta * 38)))
	end)

	local sections = Theme:CreateText(sidebar, 11)
	sections:SetPoint("TOPLEFT", 8, 0)
	sections:SetText("SECTIONS")
	sections:SetTextColor(unpack(Theme.Colors.mutedText))

	CreateTab(sidebar, "general", "General", -24)
	CreateTab(sidebar, "unitframes", "Unit Frames", -62)
	CreateTab(sidebar, "auras", "Buffs / Debuffs", -100)
	CreateTab(sidebar, "nameplates", "Nameplates", -138)
	CreateTab(sidebar, "actionbars", "Action Bars", -176)
	CreateTab(sidebar, "bags", "Bags", -214)
	CreateTab(sidebar, "alerts", "Alerts", -252)
	CreateTab(sidebar, "group", "Group", -290)
	CreateTab(sidebar, "classbuffs", "Class", -328)
	CreateTab(sidebar, "minimapbuttons", "Minimap Buttons", -366)
	CreateTab(sidebar, "informationbars", "Information Bars", -404)
	CreateTab(sidebar, "systemfonts", "System Fonts", -442)
	CreateTab(sidebar, "questtracker", "Quest Tracker", -480)
	CreateTab(sidebar, "petsmounts", "Pets & Mounts", -518)
	CreateTab(sidebar, "crafting", "Crafting", -556)
	CreateTab(sidebar, "cute", "Fun Stuff", -594)
	CreateTab(sidebar, "developer", "Developer", -632)
	CreateTab(sidebar, "about", "About", -670)

	local pageHost = CreateFrame("Frame", nil, content)
	pageHost:SetPoint("TOPLEFT", 170, -86)
	pageHost:SetPoint("BOTTOMRIGHT", -18, 18)
	self:CreateGeneralPage(pageHost, panel)
	self:CreateUnitFramesPage(pageHost)
	self:CreateAurasPage(pageHost)
	self:CreateNameplatesPage(pageHost)
	self:CreateActionBarsPage(pageHost)
	self:CreateBagsPage(pageHost)
	self:CreateAlertsPage(pageHost)
	self:CreateGroupPage(pageHost)
	self:CreateClassBuffsPage(pageHost)
	self:CreateMinimapButtonBarPage(pageHost)
	self:CreateInformationBarsPage(pageHost)
	self:CreateSystemFontsPage(pageHost)
	self:CreateQuestTrackerPage(pageHost)
	self:CreatePetsAndMountsPage(pageHost)
	self:CreateCraftingPage(pageHost)
	self:CreateCutePage(pageHost)
	self:CreateDeveloperPage(pageHost, panel)
	self:CreateAboutPage(pageHost)

	panel:SetScript("OnShow", function()
		Options:EnablePermanentSettingsDrag()
		Options:Refresh()
	end)
	panel:SetScript("OnHide", function()
		if SlickUI.GroupFrames then
			SlickUI.GroupFrames:HidePreview()
		end
		if SlickUI.Nameplates then
			SlickUI.Nameplates:HidePreview()
		end
		if Options.OpenedDirectly then
			Options.OpenedDirectly = false
			local function DismissReturnedGameMenu()
				if GameMenuFrame and GameMenuFrame:IsShown() then
					HideUIPanel(GameMenuFrame)
				end
			end
			-- Blizzard restores the Escape menu after the Settings panel's own
			-- OnHide sequence. Check immediately and again after that deferred
			-- restoration so a directly opened SlickUI panel returns to gameplay.
			DismissReturnedGameMenu()
			C_Timer.After(0, DismissReturnedGameMenu)
			C_Timer.After(0.10, DismissReturnedGameMenu)
		end
	end)

	self.Panel = panel
	self.Content = content
	self:SelectPage("general")
	return panel
end

function Options:Initialize()
	if self.Category then
		return
	end

	local panel = self:Create()
	self:EnablePermanentSettingsDrag()
	if Settings and Settings.RegisterCanvasLayoutCategory and Settings.RegisterAddOnCategory then
		self.Category = Settings.RegisterCanvasLayoutCategory(panel, "SlickUI")
		Settings.RegisterAddOnCategory(self.Category)
	elseif InterfaceOptions_AddCategory then
		InterfaceOptions_AddCategory(panel)
	end
end

function Options:Open()
	if not SlickUI:IsAvailableOutOfCombat("SlickUI options") then
		return false
	end

	if not self.Category then
		self:Initialize()
	end
	self:EnablePermanentSettingsDrag()
	self.OpenedDirectly = true

	if Settings and Settings.OpenToCategory and self.Category then
		Settings.OpenToCategory(self.Category:GetID())
		return true
	elseif InterfaceOptionsFrame_OpenToCategory and self.Panel then
		InterfaceOptionsFrame_OpenToCategory(self.Panel)
		InterfaceOptionsFrame_OpenToCategory(self.Panel)
		return true
	end

	SlickUI:Print("The AddOns settings panel is not available yet.")
	return false
end

function Options:Toggle()
	return self:Open()
end

SlickUI:RegisterModule("Options", Options)

SLASH_SLICKUI1 = "/slick"
SlashCmdList.SLICKUI = function(message)
	local command = string.lower(strtrim(message or ""))
	if command == "unlock" then
		SlickUI.Movers:SetLocked(false)
	elseif command == "lock" then
		SlickUI.Movers:SetLocked(true)
	elseif command == "reset" then
		SlickUI.Movers:ResetPositions()
	elseif command == "repairbars" then
		SlickUI.ActionBars:RepairActiveEditModeLayout()
	elseif command == "errors" or command == "error" then
		SlickUI.ErrorCatcher:Show()
	elseif command == "fishing" or command == "fish" then
		SlickUI.Fishing:OpenLedger()
	else
		Options:Open()
	end
end
