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

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)
	self.Panel.FlightTimerCheckbox:SetChecked(SlickUI.DB.flightTimer.enabled)
	self.Panel.ErrorCatcherCheckbox:SetChecked(SlickUI.DB.errorCatcher.enabled)
	self.Panel.ErrorSoundCheckbox:SetChecked(SlickUI.DB.errorCatcher.sound)
	self.Panel.ErrorSoundCheckbox:SetEnabled(SlickUI.DB.errorCatcher.enabled)
	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()
end

function Options:CreateFlightExportWindow()
	if self.FlightExportWindow then return self.FlightExportWindow end
	local window = CreateFrame("Frame", "SlickUIFlightRouteExport", UIParent, "BackdropTemplate")
	window:SetSize(720, 520)
	window:SetPoint("CENTER")
	window:SetFrameStrata("DIALOG")
	window:SetClampedToScreen(true)
	Theme:CreatePanel(window)
	local title = Theme:CreateText(window, 18)
	title:SetPoint("TOPLEFT", 18, -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))
	local scroll = CreateFrame("ScrollFrame", nil, window, "UIPanelScrollFrameTemplate")
	scroll:SetPoint("TOPLEFT", 18, -70)
	scroll:SetPoint("BOTTOMRIGHT", -36, 54)
	local edit = CreateFrame("EditBox", nil, scroll)
	edit:SetPoint("TOPLEFT", scroll, "TOPLEFT", 0, 0)
	edit:SetMultiLine(true)
	edit:SetAutoFocus(false)
	edit:SetWidth(650)
	edit:SetHeight(390)
	edit:SetTextInsets(6, 6, 6, 6)
	edit:SetJustifyH("LEFT")
	edit:SetJustifyV("TOP")
	edit:SetFrameLevel(scroll:GetFrameLevel() + 2)
	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:SetHeight(math.max(390, ((lineBreaks or 0) + 1) * 14 + 12))
	window.EditBox:SetText(text)
	window:Show()
	window.EditBox:GetParent():SetVerticalScroll(0)
	window.EditBox:SetCursorPosition(0)
end

function Options:CreateDeveloperPage(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, 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 export = CreateButton(body, "Copy Flight Data", 180)
	export:SetPoint("TOPLEFT", routeCount, "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))
	page:SetScript("OnShow", function()
		routeCount:SetText(string.format("Locally learned routes: %d", SlickUI.FlightTimer:GetRouteCount()))
	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)
	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)
			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)
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)
	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.OnlyWhenReady:SetChecked(settings.onlyWhenReady)
	controls.CleanseSound: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(1010)
	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 testInterrupt = CreateButton(body, "Test Interrupt Sound", 180)
	testInterrupt:SetPoint("TOPLEFT", interruptSound, "BOTTOMLEFT", 4, -12)
	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 onlyReady = CreateCheckbox(body, "Only alert when a cleanse ability is off cooldown")
	onlyReady:SetPoint("TOPLEFT", cleanseSound, "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 page = CreateFrame("Frame", nil, parent)
	page:SetAllPoints()
	local controls = { Rows = {} }
	self.ClassBuffControls = controls

	local heading = Theme:CreateText(page, 17)
	heading:SetPoint("TOPLEFT", 0, 0)
	heading:SetText("Class Buffs")
	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 the current class and specialization for learned group buffs. SlickUI reminds you when you or a nearby, living group member is missing an enabled buff.")
	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 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 group-wide class buff is learned in the current specialization.")
	empty:SetTextColor(unpack(Theme.Colors.mutedText))
	controls.Empty = empty

	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 = page
end

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

	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, 205)
	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")
	groupControls:SetPoint("TOPLEFT", gridSize, "TOPLEFT", 245, 0)
	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 errorCatcher = CreateCheckbox(page, "Catch Lua errors without the blocking popup")
	errorCatcher:SetPoint("TOPLEFT", questTracker, "BOTTOMLEFT", 0, -10)
	errorCatcher:SetScript("OnClick", function(self)
		SlickUI.ErrorCatcher:SetEnabled(self:GetChecked())
		Options:Refresh()
	end)
	panel.ErrorCatcherCheckbox = errorCatcher

	local errorSound = CreateCheckbox(page, "Play an alert sound when an error is caught")
	errorSound:SetPoint("TOPLEFT", errorCatcher, "BOTTOMLEFT", 0, -10)
	errorSound:SetScript("OnClick", function(self)
		SlickUI.DB.errorCatcher.sound = self:GetChecked() == true
		Options:Refresh()
	end)
	panel.ErrorSoundCheckbox = errorSound

	local viewErrors = CreateButton(page, "View / Copy Captured Errors", 210)
	viewErrors:SetPoint("TOPLEFT", errorSound, "BOTTOMLEFT", 4, -16)
	viewErrors:SetScript("OnClick", function()
		SlickUI.ErrorCatcher:Show()
	end)

	local reset = CreateButton(page, "Reset Frame Positions", 180)
	reset:SetPoint("TOPLEFT", viewErrors, "BOTTOMLEFT", 0, -12)
	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 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 = page
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, 10)
	note:SetPoint("TOPLEFT", heading, "BOTTOMLEFT", 0, -7)
	note:SetPoint("RIGHT", page, "RIGHT", 0, 0)
	note:SetJustifyH("LEFT")
	note:SetText("Configure readable friendly and enemy plates. Protected threat and quest values are ignored safely when WoW restricts access.")
	note:SetTextColor(unpack(Theme.Colors.mutedText))

	local function Update(field, value)
		if not SlickUI:IsAvailableOutOfCombat("Nameplate changes") then
			Options:RefreshNameplateControls()
			return
		end
		SlickUI.DB.nameplates[field] = value
		SlickUI.Nameplates:ApplySettings()
		Options:RefreshNameplateControls()
	end

	local visibility = CreateFrame("Frame", nil, page, "BackdropTemplate")
	visibility:SetSize(220, 410)
	visibility:SetPoint("TOPLEFT", note, "BOTTOMLEFT", 0, -14)
	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 players", "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)
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 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", note, "BOTTOMLEFT", -4, -18)
	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 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.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 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 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)
	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 controls = {}
	self.QuestTrackerControls = controls

	local heading = Theme:CreateText(page, 17)
	heading:SetPoint("TOPLEFT", 0, 0)
	heading:SetText("Quest Tracker")
	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("Configure SlickUI's scrollable Campaign and Quests tracker. Changes apply immediately outside combat.")
	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 enabled = CreateCheckbox(page, "Enable SlickUI Quest Tracker")
	enabled:SetPoint("TOPLEFT", note, "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(page, "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(page, "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(page, "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 textSizeLabel = Theme:CreateText(page, 11)
	textSizeLabel:SetPoint("TOPLEFT", showBorder, "BOTTOMLEFT", 4, -14)
	textSizeLabel:SetText("Text size")
	textSizeLabel:SetTextColor(unpack(Theme.Colors.mutedText))

	local textSizePreset = CreateFrame("Frame", "SlickUIQuestTrackerTextSizeDropdown", page, "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(page, 14)
	sizeTitle:SetPoint("TOPLEFT", textSizePreset, "BOTTOMLEFT", 16, -14)
	sizeTitle:SetText("Window")
	sizeTitle:SetTextColor(unpack(Theme.Colors.goldText))

	local width = CreateSlider(page, "Width", 250, 600, 10, 190, function(value) Apply("width", value) end)
	width:SetPoint("TOPLEFT", sizeTitle, "BOTTOMLEFT", 0, -28); controls.Width = width
	local height = CreateSlider(page, "Height", 250, 900, 10, 190, function(value) Apply("height", value) end)
	height:SetPoint("LEFT", width, "RIGHT", 30, 0); controls.Height = height
	local scale = CreateSlider(page, "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(page, "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(page, 14)
	textTitle:SetPoint("TOPLEFT", scale, "BOTTOMLEFT", 0, -42)
	textTitle:SetText("Text")
	textTitle:SetTextColor(unpack(Theme.Colors.goldText))
	local headerFont = CreateSlider(page, "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(page, "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(page, "Objectives", 8, 18, 1, 190, function(value) Apply("objectiveFontSize", value) end)
	objectiveFont:SetPoint("TOPLEFT", headerFont, "BOTTOMLEFT", 0, -40); controls.ObjectiveFont = objectiveFont
	local scrollStep = CreateSlider(page, "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(page, "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(page, 10)
	hint:SetPoint("TOPLEFT", reset, "BOTTOMLEFT", 0, -15)
	hint:SetPoint("RIGHT", page, "RIGHT", 0, 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))

	local title = Theme:CreateText(content, 20)
	title:SetPoint("TOPLEFT", 18, -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))

	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, 578)
	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, "classbuffs", "Class Buffs", -290)
	CreateTab(sidebar, "minimapbuttons", "Minimap Buttons", -328)
	CreateTab(sidebar, "informationbars", "Information Bars", -366)
	CreateTab(sidebar, "systemfonts", "System Fonts", -404)
	CreateTab(sidebar, "questtracker", "Quest Tracker", -442)
	CreateTab(sidebar, "developer", "Developer", -480)
	CreateTab(sidebar, "about", "About", -518)

	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:CreateClassBuffsPage(pageHost)
	self:CreateMinimapButtonBarPage(pageHost)
	self:CreateInformationBarsPage(pageHost)
	self:CreateSystemFontsPage(pageHost)
	self:CreateQuestTrackerPage(pageHost)
	self:CreateDeveloperPage(pageHost)
	self:CreateAboutPage(pageHost)

	panel:SetScript("OnShow", function()
		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()
	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.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
