--------------------------------------------------------------------------------
-- S-Frames: In-Game Settings Panel (ConfigUI.lua)
-- Pages:
-- 1) UI Settings (scrollable)
-- 2) Bag Settings
--------------------------------------------------------------------------------

SFrames.ConfigUI = SFrames.ConfigUI or {}
SFrames.ConfigUI.ready = true

local PANEL_WIDTH = 700
local PANEL_HEIGHT = 560

local BAG_DEFAULTS = {
    enable = true,
    columns = 10,
    bagSpacing = 0,
    scale = 1,
    sellGrey = true,
    bankColumns = 12,
    bankSpacing = 0,
    bankScale = 1,
}

local widgetId = 0

local function NextWidgetName(prefix)
    widgetId = widgetId + 1
    return "SFramesConfig" .. prefix .. tostring(widgetId)
end

local function Clamp(value, minValue, maxValue)
    if value < minValue then return minValue end
    if value > maxValue then return maxValue end
    return value
end

local SOFT_THEME = SFrames.ActiveTheme

local function EnsureSoftBackdrop(frame)
    if not frame then return end
    if frame.sfSoftBackdrop then return end
    frame:SetBackdrop({
        bgFile   = "Interface\\Tooltips\\UI-Tooltip-Background",
        edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
        tile = true, tileSize = 16, edgeSize = 14,
        insets = { left = 3, right = 3, top = 3, bottom = 3 },
    })
    frame.sfSoftBackdrop = true
end

-- Thin 1px border backdrop, suitable for small frames like checkbox boxes
local function EnsureThinBackdrop(frame)
    if not frame then return end
    if frame.sfSoftBackdrop then return end
    if SFrames and SFrames.CreateBackdrop then
        SFrames:CreateBackdrop(frame)
    elseif frame.SetBackdrop then
        frame:SetBackdrop({
            bgFile   = "Interface\\Buttons\\WHITE8X8",
            edgeFile = "Interface\\Buttons\\WHITE8X8",
            tile = false, edgeSize = 1,
            insets = { left = 1, right = 1, top = 1, bottom = 1 },
        })
    end
    frame.sfSoftBackdrop = true
end

local function HideTexture(tex)
    if not tex then return end
    if tex.SetTexture then tex:SetTexture(nil) end
    tex:Hide()
end

local function StyleButton(btn)
    if not btn or btn.sfSoftStyled then return btn end
    btn.sfSoftStyled = true

    -- Apply ESC menu style round backdrop
    btn:SetBackdrop({
        bgFile   = "Interface\\Tooltips\\UI-Tooltip-Background",
        edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
        tile = true, tileSize = 16, edgeSize = 14,
        insets = { left = 3, right = 3, top = 3, bottom = 3 },
    })
    HideTexture(btn.GetNormalTexture and btn:GetNormalTexture())
    HideTexture(btn.GetPushedTexture and btn:GetPushedTexture())
    HideTexture(btn.GetHighlightTexture and btn:GetHighlightTexture())
    HideTexture(btn.GetDisabledTexture and btn:GetDisabledTexture())

    local name = btn:GetName() or ""
    for _, suffix in ipairs({ "Left", "Right", "Middle" }) do
        local tex = _G[name .. suffix]
        if tex then tex:SetAlpha(0) tex:Hide() end
    end

    local function SetVisual(state)
        local bg = SOFT_THEME.buttonBg
        local border = SOFT_THEME.buttonBorder
        local text = SOFT_THEME.buttonText

        if btn.sfSoftActive then
            bg = SOFT_THEME.buttonActiveBg
            border = SOFT_THEME.buttonActiveBorder
            text = SOFT_THEME.buttonActiveText
        elseif state == "hover" then
            bg = SOFT_THEME.buttonHoverBg
            border = (SFrames.ActiveTheme and SFrames.ActiveTheme.btnHoverBd) or { 0.80, 0.48, 0.64, 0.98 }
            text = SOFT_THEME.buttonActiveText
        elseif state == "down" then
            bg = SOFT_THEME.buttonDownBg
        elseif state == "disabled" then
            bg = SOFT_THEME.buttonDisabledBg
            text = SOFT_THEME.buttonDisabledText
        end

        if btn.SetBackdropColor then
            btn:SetBackdropColor(bg[1], bg[2], bg[3], bg[4])
        end
        if btn.SetBackdropBorderColor then
            btn:SetBackdropBorderColor(border[1], border[2], border[3], border[4])
        end
        local fs = btn.GetFontString and btn:GetFontString()
        if fs then
            fs:SetTextColor(text[1], text[2], text[3])
        end
    end

    btn.RefreshVisual = function()
        if btn.sfSoftActive then
            SetVisual("active")
        elseif btn.IsEnabled and not btn:IsEnabled() then
            SetVisual("disabled")
        else
            SetVisual("normal")
        end
    end

    local oldEnter = btn:GetScript("OnEnter")
    local oldLeave = btn:GetScript("OnLeave")
    local oldDown = btn:GetScript("OnMouseDown")
    local oldUp = btn:GetScript("OnMouseUp")

    btn:SetScript("OnEnter", function()
        if oldEnter then oldEnter() end
        if this.IsEnabled and this:IsEnabled() and not this.sfSoftActive then
            SetVisual("hover")
        end
    end)
    btn:SetScript("OnLeave", function()
        if oldLeave then oldLeave() end
        if this.IsEnabled and this:IsEnabled() and not this.sfSoftActive then
            SetVisual("normal")
        end
    end)
    btn:SetScript("OnMouseDown", function()
        if oldDown then oldDown() end
        if this.IsEnabled and this:IsEnabled() and not this.sfSoftActive then
            SetVisual("down")
        end
    end)
    btn:SetScript("OnMouseUp", function()
        if oldUp then oldUp() end
        if this.IsEnabled and this:IsEnabled() and not this.sfSoftActive then
            SetVisual("hover")
        end
    end)

    btn:RefreshVisual()
    return btn
end

local function AddBtnIcon(btn, iconKey, size, align)
    if not (SFrames and SFrames.CreateIcon) then return end
    local sz = size or 12
    local gap = 3
    local ico = SFrames:CreateIcon(btn, iconKey, sz)
    ico:SetDrawLayer("OVERLAY")
    btn.nanamiIcon = ico
    local fs = btn.GetFontString and btn:GetFontString()
    if fs then
        fs:ClearAllPoints()
        if align == "left" then
            ico:SetPoint("LEFT", btn, "LEFT", 8, 0)
            fs:SetPoint("LEFT", ico, "RIGHT", gap, 0)
            fs:SetPoint("RIGHT", btn, "RIGHT", -4, 0)
            fs:SetJustifyH("LEFT")
        else
            fs:SetPoint("CENTER", btn, "CENTER", (sz + gap) / 2, 0)
            ico:SetPoint("RIGHT", fs, "LEFT", -gap, 0)
        end
    else
        ico:SetPoint("CENTER", btn, "CENTER", 0, 0)
    end
end

local function StyleCheckBox(cb)
    if not cb or cb.sfSoftStyled then return cb end
    cb.sfSoftStyled = true

    -- Hide ALL default CheckButton textures including the checked texture
    HideTexture(cb.GetNormalTexture and cb:GetNormalTexture())
    HideTexture(cb.GetPushedTexture and cb:GetPushedTexture())
    HideTexture(cb.GetHighlightTexture and cb:GetHighlightTexture())
    HideTexture(cb.GetDisabledTexture and cb:GetDisabledTexture())
    if cb.SetCheckedTexture then cb:SetCheckedTexture("") end
    if cb.SetDisabledCheckedTexture then cb:SetDisabledCheckedTexture("") end

    -- Round-corner box (tooltip border = soft rounded edges)
    local box = CreateFrame("Frame", nil, cb)
    box:SetPoint("TOPLEFT",     cb, "TOPLEFT",     0, 0)
    box:SetPoint("BOTTOMRIGHT", cb, "BOTTOMRIGHT",  0, 0)
    box:SetFrameLevel(cb:GetFrameLevel() + 1)
    box:SetBackdrop({
        bgFile   = "Interface\\Tooltips\\UI-Tooltip-Background",
        edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
        tile = true, tileSize = 16, edgeSize = 10,
        insets = { left = 2, right = 2, top = 2, bottom = 2 },
    })
    cb.sfBox = box

    local function ApplyState(isChecked)
        if isChecked then
            box:SetBackdropColor(
                SOFT_THEME.checkFill[1], SOFT_THEME.checkFill[2],
                SOFT_THEME.checkFill[3], 0.88)
            box:SetBackdropBorderColor(
                SOFT_THEME.checkFill[1], SOFT_THEME.checkFill[2],
                SOFT_THEME.checkFill[3], 1)
        else
            box:SetBackdropColor(
                SOFT_THEME.checkBg[1], SOFT_THEME.checkBg[2],
                SOFT_THEME.checkBg[3], SOFT_THEME.checkBg[4])
            box:SetBackdropBorderColor(
                SOFT_THEME.checkBorder[1], SOFT_THEME.checkBorder[2],
                SOFT_THEME.checkBorder[3], SOFT_THEME.checkBorder[4])
        end
    end
    cb.sfApplyState = ApplyState

    -- Text label: push it away from the box with a left margin
    local label = _G[cb:GetName() .. "Text"]
    if label then
        label:ClearAllPoints()
        label:SetPoint("LEFT", cb, "RIGHT", 4, 0)
        label:SetFont(SFrames:GetFont(), 11, "OUTLINE")
        label:SetJustifyH("LEFT")
        label:SetTextColor(SOFT_THEME.text[1], SOFT_THEME.text[2], SOFT_THEME.text[3])
    end

    -- Hover: brighten border when unchecked
    cb:SetScript("OnEnter", function()
        if not this.sfChecked then
            if this.sfBox then
                this.sfBox:SetBackdropBorderColor(
                    SOFT_THEME.checkHoverBorder[1], SOFT_THEME.checkHoverBorder[2],
                    SOFT_THEME.checkHoverBorder[3], SOFT_THEME.checkHoverBorder[4])
            end
        end
    end)
    cb:SetScript("OnLeave", function()
        if not this.sfChecked then
            if this.sfBox then
                this.sfBox:SetBackdropBorderColor(
                    SOFT_THEME.checkBorder[1], SOFT_THEME.checkBorder[2],
                    SOFT_THEME.checkBorder[3], SOFT_THEME.checkBorder[4])
            end
        end
    end)

    ApplyState(false)
    return cb
end

local function StyleSlider(slider, low, high, text)
    if not slider or slider.sfSoftStyled then return slider end
    slider.sfSoftStyled = true

    local regions = { slider:GetRegions() }
    for i = 1, table.getn(regions) do
        local region = regions[i]
        if region and region.GetObjectType and region:GetObjectType() == "Texture" then
            region:SetTexture(nil)
        end
    end

    local track = slider:CreateTexture(nil, "BACKGROUND")
    track:SetTexture("Interface\\Buttons\\WHITE8X8")
    track:SetPoint("LEFT",  slider, "LEFT",  0, 0)
    track:SetPoint("RIGHT", slider, "RIGHT", 0, 0)
    track:SetHeight(4)
    track:SetVertexColor(SOFT_THEME.sliderTrack[1], SOFT_THEME.sliderTrack[2], SOFT_THEME.sliderTrack[3], SOFT_THEME.sliderTrack[4])
    slider.sfTrack = track

    local fill = slider:CreateTexture(nil, "ARTWORK")
    fill:SetTexture("Interface\\Buttons\\WHITE8X8")
    fill:SetPoint("LEFT", track, "LEFT", 0, 0)
    fill:SetPoint("TOP", track, "TOP", 0, 0)
    fill:SetPoint("BOTTOM", track, "BOTTOM", 0, 0)
    fill:SetWidth(1)
    fill:SetVertexColor(SOFT_THEME.sliderFill[1], SOFT_THEME.sliderFill[2], SOFT_THEME.sliderFill[3], SOFT_THEME.sliderFill[4])
    slider.sfFill = fill

    if slider.SetThumbTexture then
        slider:SetThumbTexture("Interface\\Buttons\\WHITE8X8")
    end
    local thumb = slider.GetThumbTexture and slider:GetThumbTexture()
    if thumb then
        thumb:SetWidth(8)
        thumb:SetHeight(16)
        thumb:SetVertexColor(SOFT_THEME.sliderThumb[1], SOFT_THEME.sliderThumb[2], SOFT_THEME.sliderThumb[3], SOFT_THEME.sliderThumb[4])
    end

    local function UpdateFill()
        local minValue, maxValue = slider:GetMinMaxValues()
        local value = slider:GetValue() or minValue
        local pct = 0
        if maxValue > minValue then
            pct = (value - minValue) / (maxValue - minValue)
        end
        pct = Clamp(pct, 0, 1)
        local width = math.floor((slider:GetWidth() or 1) * pct + 0.5)
        if width < 1 then width = 1 end
        slider.sfFill:SetWidth(width)
    end

    local oldChanged = slider:GetScript("OnValueChanged")
    slider:SetScript("OnValueChanged", function()
        if oldChanged then oldChanged() end
        UpdateFill()
    end)
    UpdateFill()

    if low then
        low:SetTextColor(SOFT_THEME.dimText[1], SOFT_THEME.dimText[2], SOFT_THEME.dimText[3])
        low:ClearAllPoints()
        low:SetPoint("TOPLEFT", slider, "BOTTOMLEFT", 0, 0)
    end
    if high then
        high:SetTextColor(SOFT_THEME.dimText[1], SOFT_THEME.dimText[2], SOFT_THEME.dimText[3])
        high:ClearAllPoints()
        high:SetPoint("TOPRIGHT", slider, "BOTTOMRIGHT", 0, 0)
    end
    if text then
        text:SetTextColor(SOFT_THEME.text[1], SOFT_THEME.text[2], SOFT_THEME.text[3])
        text:ClearAllPoints()
        text:SetPoint("BOTTOM", slider, "TOP", 0, 2)
    end
    return slider
end

local function StyleScrollBar(slider, sliderName)
    if not slider or slider.sfSoftStyled then return slider end
    slider.sfSoftStyled = true

    slider:SetWidth(12)
    local regions = { slider:GetRegions() }
    for i = 1, table.getn(regions) do
        local region = regions[i]
        if region and region.GetObjectType and region:GetObjectType() == "Texture" then
            region:SetTexture(nil)
        end
    end

    local track = slider:CreateTexture(nil, "BACKGROUND")
    track:SetTexture("Interface\\Buttons\\WHITE8X8")
    track:SetPoint("TOPLEFT", slider, "TOPLEFT", 3, 0)
    track:SetPoint("BOTTOMRIGHT", slider, "BOTTOMRIGHT", -3, 0)
    track:SetVertexColor(0.18, 0.19, 0.22, 0.9)

    if slider.SetThumbTexture then
        slider:SetThumbTexture("Interface\\Buttons\\WHITE8X8")
    end
    local thumb = slider.GetThumbTexture and slider:GetThumbTexture()
    if thumb then
        thumb:SetWidth(8)
        thumb:SetHeight(20)
        thumb:SetVertexColor(SOFT_THEME.scrollThumb[1], SOFT_THEME.scrollThumb[2], SOFT_THEME.scrollThumb[3], SOFT_THEME.scrollThumb[4] or 0.95)
    end

    local upBtn = _G[sliderName .. "ScrollUpButton"]
    local downBtn = _G[sliderName .. "ScrollDownButton"]
    if upBtn then upBtn:Hide() end
    if downBtn then downBtn:Hide() end
    return slider
end

local function EnsureDB()
    if not SFramesDB then SFramesDB = {} end

    if SFramesDB.showLevel == nil then SFramesDB.showLevel = true end
    if SFramesDB.classColorHealth == nil then SFramesDB.classColorHealth = true end
    if SFramesDB.enableMerchant == nil then SFramesDB.enableMerchant = true end
    if SFramesDB.enableQuestUI == nil then SFramesDB.enableQuestUI = true end
    if SFramesDB.enableQuestLogSkin == nil then SFramesDB.enableQuestLogSkin = true end
    if SFramesDB.enableTrainer == nil then SFramesDB.enableTrainer = true end
    if SFramesDB.enableSpellBook == nil then SFramesDB.enableSpellBook = true end
    if SFramesDB.enableTradeSkill == nil then SFramesDB.enableTradeSkill = true end
    if SFramesDB.spellBookHighestOnly == nil then SFramesDB.spellBookHighestOnly = false end
    if SFramesDB.spellBookAutoReplace == nil then SFramesDB.spellBookAutoReplace = false end
    if SFramesDB.enableSocial == nil then SFramesDB.enableSocial = true end
    if SFramesDB.enableInspect == nil then SFramesDB.enableInspect = true end
    if SFramesDB.enableFlightMap == nil then SFramesDB.enableFlightMap = true end
    if SFramesDB.enablePetStable == nil then SFramesDB.enablePetStable = true end
    if SFramesDB.enableMail == nil then SFramesDB.enableMail = true end
    if type(SFramesDB.spellBookScale) ~= "number" then SFramesDB.spellBookScale = 1 end
    if type(SFramesDB.socialScale) ~= "number" then SFramesDB.socialScale = 1 end

    if type(SFramesDB.playerFrameScale) ~= "number" then SFramesDB.playerFrameScale = 1 end
    if type(SFramesDB.playerFrameWidth) ~= "number" then SFramesDB.playerFrameWidth = 220 end
    if type(SFramesDB.playerPortraitWidth) ~= "number" then SFramesDB.playerPortraitWidth = 50 end
    if type(SFramesDB.playerHealthHeight) ~= "number" then SFramesDB.playerHealthHeight = 38 end
    if type(SFramesDB.playerPowerHeight) ~= "number" then SFramesDB.playerPowerHeight = 9 end
    if SFramesDB.playerShowClass == nil then SFramesDB.playerShowClass = true end
    if SFramesDB.playerShowClassIcon == nil then SFramesDB.playerShowClassIcon = true end
    if SFramesDB.playerShowPortrait == nil then SFramesDB.playerShowPortrait = true end
    if type(SFramesDB.playerFrameAlpha) ~= "number" then SFramesDB.playerFrameAlpha = 1 end
    if type(SFramesDB.playerNameFontSize) ~= "number" then SFramesDB.playerNameFontSize = 10 end
    if type(SFramesDB.playerValueFontSize) ~= "number" then SFramesDB.playerValueFontSize = 10 end

    if type(SFramesDB.targetFrameScale) ~= "number" then SFramesDB.targetFrameScale = 1 end
    if type(SFramesDB.targetFrameWidth) ~= "number" then SFramesDB.targetFrameWidth = 220 end
    if type(SFramesDB.targetPortraitWidth) ~= "number" then SFramesDB.targetPortraitWidth = 50 end
    if type(SFramesDB.targetHealthHeight) ~= "number" then SFramesDB.targetHealthHeight = 38 end
    if type(SFramesDB.targetPowerHeight) ~= "number" then SFramesDB.targetPowerHeight = 9 end
    if SFramesDB.targetShowClass == nil then SFramesDB.targetShowClass = true end
    if SFramesDB.targetShowClassIcon == nil then SFramesDB.targetShowClassIcon = true end
    if SFramesDB.targetShowPortrait == nil then SFramesDB.targetShowPortrait = true end
    if type(SFramesDB.targetFrameAlpha) ~= "number" then SFramesDB.targetFrameAlpha = 1 end
    if type(SFramesDB.targetNameFontSize) ~= "number" then SFramesDB.targetNameFontSize = 10 end
    if type(SFramesDB.targetValueFontSize) ~= "number" then SFramesDB.targetValueFontSize = 10 end
    if SFramesDB.targetDistanceEnabled == nil then SFramesDB.targetDistanceEnabled = true end
    if type(SFramesDB.targetDistanceScale) ~= "number" then SFramesDB.targetDistanceScale = 1 end

    if SFramesDB.showPetFrame == nil then SFramesDB.showPetFrame = true end
    if type(SFramesDB.petFrameScale) ~= "number" then SFramesDB.petFrameScale = 1 end

    if SFramesDB.partyLayout ~= "horizontal" and SFramesDB.partyLayout ~= "vertical" then
        SFramesDB.partyLayout = "vertical"
    end
    if type(SFramesDB.partyFrameScale) ~= "number" then SFramesDB.partyFrameScale = 1 end
    if type(SFramesDB.partyFrameWidth) ~= "number" then SFramesDB.partyFrameWidth = 150 end
    if type(SFramesDB.partyFrameHeight) ~= "number" then SFramesDB.partyFrameHeight = 35 end
    if type(SFramesDB.partyPortraitWidth) ~= "number" then SFramesDB.partyPortraitWidth = 33 end
    if type(SFramesDB.partyHealthHeight) ~= "number" then SFramesDB.partyHealthHeight = 22 end
    if type(SFramesDB.partyPowerHeight) ~= "number" then SFramesDB.partyPowerHeight = 10 end
    if type(SFramesDB.partyHorizontalGap) ~= "number" then SFramesDB.partyHorizontalGap = 8 end
    if type(SFramesDB.partyVerticalGap) ~= "number" then SFramesDB.partyVerticalGap = 30 end
    if type(SFramesDB.partyNameFontSize) ~= "number" then SFramesDB.partyNameFontSize = 10 end
    if type(SFramesDB.partyValueFontSize) ~= "number" then SFramesDB.partyValueFontSize = 10 end
    if SFramesDB.partyShowBuffs == nil then SFramesDB.partyShowBuffs = true end
    if SFramesDB.partyShowDebuffs == nil then SFramesDB.partyShowDebuffs = true end

    if SFramesDB.raidLayout ~= "horizontal" and SFramesDB.raidLayout ~= "vertical" then
        SFramesDB.raidLayout = "horizontal"
    end
    if type(SFramesDB.raidFrameScale) ~= "number" then SFramesDB.raidFrameScale = 1 end
    if type(SFramesDB.raidFrameWidth) ~= "number" then SFramesDB.raidFrameWidth = 60 end
    if type(SFramesDB.raidFrameHeight) ~= "number" then SFramesDB.raidFrameHeight = 40 end
    if type(SFramesDB.raidHealthHeight) ~= "number" then SFramesDB.raidHealthHeight = 31 end
    if type(SFramesDB.raidHorizontalGap) ~= "number" then SFramesDB.raidHorizontalGap = 2 end
    if type(SFramesDB.raidVerticalGap) ~= "number" then SFramesDB.raidVerticalGap = 2 end
    if type(SFramesDB.raidGroupGap) ~= "number" then SFramesDB.raidGroupGap = 8 end
    if type(SFramesDB.raidNameFontSize) ~= "number" then SFramesDB.raidNameFontSize = 10 end
    if type(SFramesDB.raidValueFontSize) ~= "number" then SFramesDB.raidValueFontSize = 9 end
    if SFramesDB.raidShowPower == nil then SFramesDB.raidShowPower = true end
    if SFramesDB.enableRaidFrames == nil then SFramesDB.enableRaidFrames = true end
    if SFramesDB.raidHealthFormat == nil then SFramesDB.raidHealthFormat = "compact" end
    if SFramesDB.raidShowGroupLabel == nil then SFramesDB.raidShowGroupLabel = true end

    if SFramesDB.charPanelEnable == nil then SFramesDB.charPanelEnable = true end

    if SFramesDB.afkEnabled == nil then SFramesDB.afkEnabled = true end
    if type(SFramesDB.afkDelay) ~= "number" then SFramesDB.afkDelay = 5 end
    if SFramesDB.afkOutsideRest == nil then SFramesDB.afkOutsideRest = false end

    if SFramesDB.trainerReminder == nil then SFramesDB.trainerReminder = true end
    if SFramesDB.trainerCache == nil then SFramesDB.trainerCache = {} end

    if SFramesDB.smoothBars == nil then SFramesDB.smoothBars = true end
    if SFramesDB.mobRealHealth == nil then SFramesDB.mobRealHealth = true end
    if SFramesDB.showItemLevel == nil then SFramesDB.showItemLevel = true end
    if SFramesDB.showTooltipIDs == nil then SFramesDB.showTooltipIDs = true end
    if SFramesDB.itemCompare == nil then SFramesDB.itemCompare = true end
    if SFramesDB.gearScore == nil then SFramesDB.gearScore = true end

    if type(SFramesDB.Bags) ~= "table" then SFramesDB.Bags = {} end
    local defaults = BAG_DEFAULTS
    if SFrames and SFrames.Config and type(SFrames.Config.Bags) == "table" then defaults = SFrames.Config.Bags end
    for key, value in pairs(defaults) do
        if SFramesDB.Bags[key] == nil then SFramesDB.Bags[key] = value end
    end
    if type(SFramesDB.Bags.alpha) ~= "number" then SFramesDB.Bags.alpha = 1 end
    if type(SFramesDB.Bags.bankAlpha) ~= "number" then SFramesDB.Bags.bankAlpha = 1 end

    if type(SFramesDB.Minimap) ~= "table" then SFramesDB.Minimap = {} end
    if SFramesDB.Minimap.enabled == nil then SFramesDB.Minimap.enabled = true end
    if type(SFramesDB.Minimap.scale) ~= "number" then SFramesDB.Minimap.scale = 1.0 end
    if SFramesDB.Minimap.showClock == nil then SFramesDB.Minimap.showClock = true end
    if SFramesDB.Minimap.showCoords == nil then SFramesDB.Minimap.showCoords = true end
    if SFramesDB.Minimap.mapStyle == nil then SFramesDB.Minimap.mapStyle = "auto" end
    if SFramesDB.Minimap.mapShape == nil then SFramesDB.Minimap.mapShape = "square1" end
    if type(SFramesDB.Minimap.posX) ~= "number" then SFramesDB.Minimap.posX = -5 end
    if type(SFramesDB.Minimap.posY) ~= "number" then SFramesDB.Minimap.posY = -5 end

    if type(SFramesDB.MapReveal) ~= "table" then SFramesDB.MapReveal = {} end
    if SFramesDB.MapReveal.enabled == nil then SFramesDB.MapReveal.enabled = true end
    if type(SFramesDB.MapReveal.unexploredAlpha) ~= "number" then SFramesDB.MapReveal.unexploredAlpha = 0.7 end

    if type(SFramesDB.Tweaks) ~= "table" then SFramesDB.Tweaks = {} end
    if SFramesDB.Tweaks.autoStance == nil then SFramesDB.Tweaks.autoStance = true end
    if SFramesDB.Tweaks.autoDismount == nil then SFramesDB.Tweaks.autoDismount = true end
    if SFramesDB.Tweaks.superWoW == nil then SFramesDB.Tweaks.superWoW = true end
    if SFramesDB.Tweaks.turtleCompat == nil then SFramesDB.Tweaks.turtleCompat = true end
    if SFramesDB.Tweaks.cooldownNumbers == nil then SFramesDB.Tweaks.cooldownNumbers = true end
    if SFramesDB.Tweaks.darkUI == nil then SFramesDB.Tweaks.darkUI = false end
    if SFramesDB.Tweaks.worldMapWindow == nil then SFramesDB.Tweaks.worldMapWindow = false end

    if type(SFramesDB.WorldMap) ~= "table" then SFramesDB.WorldMap = {} end
    if SFramesDB.WorldMap.enabled == nil then SFramesDB.WorldMap.enabled = true end
    if type(SFramesDB.WorldMap.scale) ~= "number" then SFramesDB.WorldMap.scale = 0.85 end
    if type(SFramesDB.WorldMap.nav) ~= "table" then
        SFramesDB.WorldMap.nav = { enabled = false, width = 350, alpha = 0.70, locked = false }
    end

    if type(SFramesDB.MinimapBuffs) ~= "table" then SFramesDB.MinimapBuffs = {} end
    if SFramesDB.MinimapBuffs.enabled == nil then SFramesDB.MinimapBuffs.enabled = true end
    if type(SFramesDB.MinimapBuffs.iconSize) ~= "number" then SFramesDB.MinimapBuffs.iconSize = 30 end
    if type(SFramesDB.MinimapBuffs.iconsPerRow) ~= "number" then SFramesDB.MinimapBuffs.iconsPerRow = 8 end
    if type(SFramesDB.MinimapBuffs.spacing) ~= "number" then SFramesDB.MinimapBuffs.spacing = 2 end
    if SFramesDB.MinimapBuffs.growDirection ~= "LEFT" and SFramesDB.MinimapBuffs.growDirection ~= "RIGHT" then
        SFramesDB.MinimapBuffs.growDirection = "LEFT"
    end
    if SFramesDB.MinimapBuffs.position ~= "TOPRIGHT" and SFramesDB.MinimapBuffs.position ~= "TOPLEFT"
        and SFramesDB.MinimapBuffs.position ~= "BOTTOMRIGHT" and SFramesDB.MinimapBuffs.position ~= "BOTTOMLEFT" then
        SFramesDB.MinimapBuffs.position = "TOPRIGHT"
    end
    if type(SFramesDB.MinimapBuffs.offsetX) ~= "number" then SFramesDB.MinimapBuffs.offsetX = 0 end
    if type(SFramesDB.MinimapBuffs.offsetY) ~= "number" then SFramesDB.MinimapBuffs.offsetY = 0 end
    if SFramesDB.MinimapBuffs.showTimer == nil then SFramesDB.MinimapBuffs.showTimer = true end
    if SFramesDB.MinimapBuffs.showDebuffs == nil then SFramesDB.MinimapBuffs.showDebuffs = true end
    if type(SFramesDB.MinimapBuffs.debuffIconSize) ~= "number" then SFramesDB.MinimapBuffs.debuffIconSize = 30 end

    if type(SFramesDB.ActionBars) ~= "table" then SFramesDB.ActionBars = {} end
    local abDef = {
        enable = true, buttonSize = 36, buttonGap = 2, smallBarSize = 27,
        scale = 1.0, barCount = 3, showHotkey = true, showMacroName = false,
        rangeColoring = true, showPetBar = true, showStanceBar = true,
        showRightBars = true, buttonRounded = false, buttonInnerShadow = false,
        alpha = 1.0,
    }
    for k, v in pairs(abDef) do
        if SFramesDB.ActionBars[k] == nil then SFramesDB.ActionBars[k] = v end
    end

    if SFramesDB.enableUnitFrames == nil then SFramesDB.enableUnitFrames = true end
    if SFramesDB.enablePlayerFrame == nil then SFramesDB.enablePlayerFrame = true end
    if SFramesDB.enableTargetFrame == nil then SFramesDB.enableTargetFrame = true end
    if SFramesDB.enablePartyFrame == nil then SFramesDB.enablePartyFrame = true end
    if SFramesDB.enableChat == nil then SFramesDB.enableChat = true end

    if type(SFramesDB.Theme) ~= "table" then SFramesDB.Theme = {} end
    if SFramesDB.Theme.preset == nil then SFramesDB.Theme.preset = "pink" end
    if SFramesDB.Theme.useClassTheme == nil then SFramesDB.Theme.useClassTheme = false end
    if SFramesDB.Theme.iconSet == nil or SFramesDB.Theme.iconSet == "default" then SFramesDB.Theme.iconSet = "icon" end
end

local function CreateSection(parent, title, x, y, width, height, font)
    local section = CreateFrame("Frame", NextWidgetName("Section"), parent)
    section:SetWidth(width)
    section:SetHeight(height)
    section:SetPoint("TOPLEFT", parent, "TOPLEFT", x, y)

    EnsureSoftBackdrop(section)
    if section.SetBackdropColor then
        section:SetBackdropColor(SOFT_THEME.sectionBg[1], SOFT_THEME.sectionBg[2], SOFT_THEME.sectionBg[3], SOFT_THEME.sectionBg[4])
    end
    if section.SetBackdropBorderColor then
        section:SetBackdropBorderColor(SOFT_THEME.sectionBorder[1], SOFT_THEME.sectionBorder[2], SOFT_THEME.sectionBorder[3], SOFT_THEME.sectionBorder[4])
    end

    local titleFS = section:CreateFontString(nil, "OVERLAY")
    titleFS:SetFont(font, 11, "OUTLINE")
    titleFS:SetPoint("TOPLEFT", section, "TOPLEFT", 10, -9)
    titleFS:SetText(title)
    titleFS:SetTextColor(SOFT_THEME.title[1], SOFT_THEME.title[2], SOFT_THEME.title[3])

    -- Divider line under title
    local div = section:CreateTexture(nil, "ARTWORK")
    div:SetTexture("Interface\\Buttons\\WHITE8X8")
    div:SetHeight(1)
    div:SetPoint("TOPLEFT",  section, "TOPLEFT",  8, -24)
    div:SetPoint("TOPRIGHT", section, "TOPRIGHT", -8, -24)
    div:SetVertexColor(SOFT_THEME.sectionBorder[1], SOFT_THEME.sectionBorder[2], SOFT_THEME.sectionBorder[3], 0.6)

    return section
end

local function CreateLabel(parent, text, x, y, font, size, r, g, b)
    local fs = parent:CreateFontString(nil, "OVERLAY")
    fs:SetFont(font, size or 11, "OUTLINE")
    fs:SetPoint("TOPLEFT", parent, "TOPLEFT", x, y)
    fs:SetText(text)
    fs:SetTextColor(r or 1, g or 1, b or 1)
    return fs
end

local function CreateDesc(parent, text, x, y, font, maxWidth)
    local fs = parent:CreateFontString(nil, "OVERLAY")
    fs:SetFont(font, 9, "OUTLINE")
    fs:SetPoint("TOPLEFT", parent, "TOPLEFT", x, y)
    if maxWidth then
        fs:SetWidth(maxWidth)
        fs:SetJustifyH("LEFT")
    end
    fs:SetText(text)
    fs:SetTextColor(0.65, 0.58, 0.62)
    return fs
end

local function CreateCheckBox(parent, label, x, y, getter, setter, onValueChanged)
    local cb = CreateFrame("CheckButton", NextWidgetName("Check"), parent, "UICheckButtonTemplate")
    cb:SetWidth(18)
    cb:SetHeight(18)
    cb:SetPoint("TOPLEFT", parent, "TOPLEFT", x, y)
    StyleCheckBox(cb)

    local text = _G[cb:GetName() .. "Text"]
    if text then
        text:SetText(label)
        local parentWidth = parent:GetWidth() or 520
        local textWidth = parentWidth - x - 24
        if textWidth > 240 then textWidth = 240 end
        text:SetWidth(textWidth)
    end

    local internalRefresh = false

    cb:SetScript("OnClick", function()
        if internalRefresh then return end
        local v = this:GetChecked()
        local checkedBool = (v == 1 or v == true)
        this.sfChecked = checkedBool
        if this.sfApplyState then this.sfApplyState(checkedBool) end
        setter(checkedBool)
        if onValueChanged then onValueChanged(checkedBool) end
        PlaySound(checkedBool and "igMainMenuOptionCheckBoxOn" or "igMainMenuOptionCheckBoxOff")
    end)

    cb.Refresh = function()
        internalRefresh = true
        local isChecked = getter() and true or false
        cb:SetChecked(isChecked and 1 or 0)
        cb.sfChecked = isChecked
        if cb.sfApplyState then cb.sfApplyState(isChecked) end
        internalRefresh = false
    end

    cb:Refresh()
    return cb
end

local function CreateSlider(parent, label, x, y, width, minValue, maxValue, step, getter, setter, formatter, onValueChanged)
    local sliderName = NextWidgetName("Slider")
    local slider = CreateFrame("Slider", sliderName, parent, "OptionsSliderTemplate")
    slider:SetWidth(width)
    slider:SetHeight(26)
    slider:SetPoint("TOPLEFT", parent, "TOPLEFT", x, y)
    slider:SetMinMaxValues(minValue, maxValue)
    slider:SetValueStep(step)
    if slider.SetObeyStepOnDrag then slider:SetObeyStepOnDrag(true) end

    local low = _G[sliderName .. "Low"]
    local high = _G[sliderName .. "High"]
    local text = _G[sliderName .. "Text"]
    if low then low:SetText(tostring(minValue)) end
    if high then high:SetText(tostring(maxValue)) end

    local internalRefresh = false
    local function UpdateLabel(value)
        local display = formatter and formatter(value) or value
        if text then text:SetText(label .. ": " .. tostring(display)) end
    end

    slider:SetScript("OnValueChanged", function()
        if internalRefresh then return end
        local raw = this:GetValue() or minValue or 0
        local safeStep = step or 1
        local value
        if safeStep >= 1 then
            value = math.floor(raw + 0.5)
        else
            if safeStep == 0 then safeStep = 1 end
            local scaled = raw / safeStep
            value = math.floor(scaled + 0.5) * safeStep
        end
        value = Clamp(value, minValue, maxValue)
        UpdateLabel(value)
        setter(value)
        if onValueChanged then onValueChanged(value) end
    end)

    slider.Refresh = function()
        local value = tonumber(getter()) or minValue
        value = Clamp(value, minValue, maxValue)
        internalRefresh = true
        slider:SetValue(value)
        internalRefresh = false
        UpdateLabel(value)
    end

    StyleSlider(slider, low, high, text)
    slider:Refresh()
    return slider
end

local function CreateButton(parent, text, x, y, width, height, onClick)
    local btn = CreateFrame("Button", NextWidgetName("Button"), parent, "UIPanelButtonTemplate")
    btn:SetWidth(width)
    btn:SetHeight(height)
    btn:SetPoint("TOPLEFT", parent, "TOPLEFT", x, y)
    btn:SetText(text)
    btn:SetScript("OnClick", onClick)
    StyleButton(btn)
    return btn
end
local function CreateScrollArea(parent, x, y, width, height, childHeight)
    local holder = CreateFrame("Frame", NextWidgetName("ScrollHolder"), parent)
    holder:SetWidth(width)
    holder:SetHeight(height)
    holder:SetPoint("TOPLEFT", parent, "TOPLEFT", x, y)

    local scroll = CreateFrame("ScrollFrame", NextWidgetName("ScrollFrame"), holder)
    scroll:SetPoint("TOPLEFT", holder, "TOPLEFT", 0, 0)
    scroll:SetPoint("BOTTOMRIGHT", holder, "BOTTOMRIGHT", -18, 0)

    local child = CreateFrame("Frame", NextWidgetName("ScrollChild"), scroll)
    child:SetWidth(width - 22)
    child:SetHeight(childHeight)
    scroll:SetScrollChild(child)

    local sliderName = NextWidgetName("ScrollBar")
    local slider = CreateFrame("Slider", sliderName, holder, "UIPanelScrollBarTemplate")
    slider:SetPoint("TOPRIGHT", holder, "TOPRIGHT", 0, -16)
    slider:SetPoint("BOTTOMRIGHT", holder, "BOTTOMRIGHT", 0, 16)
    slider:SetScript("OnValueChanged", function()
        if scroll and scroll.SetVerticalScroll then
            scroll:SetVerticalScroll(this:GetValue())
        end
    end)
    slider:SetMinMaxValues(0, 0)
    slider:SetValueStep(20)
    slider:SetValue(0)

    local upBtn = _G[sliderName .. "ScrollUpButton"]
    if upBtn then
        upBtn:SetScript("OnClick", function()
            local value = slider:GetValue() - 24
            if value < 0 then value = 0 end
            slider:SetValue(value)
        end)
    end

    local downBtn = _G[sliderName .. "ScrollDownButton"]
    if downBtn then
        downBtn:SetScript("OnClick", function()
            local _, maxValue = slider:GetMinMaxValues()
            local value = slider:GetValue() + 24
            if value > maxValue then value = maxValue end
            slider:SetValue(value)
        end)
    end
    StyleScrollBar(slider, sliderName)

    local function UpdateRange()
        local maxScroll = child:GetHeight() - scroll:GetHeight()
        if maxScroll < 0 then maxScroll = 0 end

        slider:SetMinMaxValues(0, maxScroll)
        slider:SetValueStep(20)

        local current = slider:GetValue()
        if current > maxScroll then
            current = maxScroll
            slider:SetValue(current)
        end
        if scroll and scroll.SetVerticalScroll then
            scroll:SetVerticalScroll(current)
        end

        if maxScroll <= 0 then slider:Hide() else slider:Show() end
    end

    local function ScrollBy(delta)
        local _, maxValue = slider:GetMinMaxValues()
        local value = slider:GetValue() - delta * 28
        if value < 0 then value = 0 end
        if value > maxValue then value = maxValue end
        slider:SetValue(value)
    end

    if scroll.EnableMouseWheel then
        scroll:EnableMouseWheel(true)
        scroll:SetScript("OnMouseWheel", function() ScrollBy(arg1) end)
    end

    if child.EnableMouseWheel then
        child:EnableMouseWheel(true)
        child:SetScript("OnMouseWheel", function() ScrollBy(arg1) end)
    end

    return {
        holder = holder,
        scroll = scroll,
        child = child,
        slider = slider,
        UpdateRange = UpdateRange,
        Reset = function()
            slider:SetValue(0)
            if scroll and scroll.SetVerticalScroll then
                scroll:SetVerticalScroll(0)
            end
            UpdateRange()
        end,
    }
end

function SFrames.ConfigUI:RefreshControls(controlList)
    if not controlList then return end
    for _, control in ipairs(controlList) do
        if control and control.Refresh then control:Refresh() end
    end
end

function SFrames.ConfigUI:BuildUIPage()
    local font = SFrames:GetFont()
    local page = self.uiPage
    local controls = {}

    local function RefreshPlayer()
        if SFrames.Player and SFrames.Player.ApplyConfig then SFrames.Player:ApplyConfig() return end
        if SFrames.Player and SFrames.Player.UpdateAll then SFrames.Player:UpdateAll() end
    end
    local function RefreshTarget()
        if SFrames.Target and SFrames.Target.ApplyConfig then SFrames.Target:ApplyConfig() return end
        if SFrames.Target and SFrames.Target.UpdateAll then SFrames.Target:UpdateAll() end
    end
    local function RefreshParty()
        if SFrames.Party and SFrames.Party.ApplyConfig then SFrames.Party:ApplyConfig() return end
        if SFrames.Party and SFrames.Party.UpdateAll then SFrames.Party:UpdateAll() end
    end

    -- Section 内容从 y=-32 开始（标题24 + 分隔线 + 8px 间距）
    -- 每个选项行占 26px，描述文字占 16px
    local uiScroll = CreateScrollArea(page, 4, -4, 548, 458, 990)
    local root = uiScroll.child

    -- ── 初始化向导 ──────────────────────────────────────────────
    local wizSection = CreateSection(root, "初始化向导", 8, -8, 520, 62, font)
    CreateDesc(wizSection, "重新打开首次配置向导，可重新选择各项功能开关", 14, -30, font, 380)
    CreateButton(wizSection, "重新运行初始化向导", 370, -30, 140, 24, function()
        if SFrames.ConfigUI.frame then SFrames.ConfigUI.frame:Hide() end
        if SFrames.SetupWizard and SFrames.SetupWizard.Show then
            SFrames.SetupWizard:Show(nil, "rerun")
        end
    end)

    -- ── 布局模式 ──────────────────────────────────────────────────
    local layoutSection = CreateSection(root, "布局模式", 8, -78, 520, 62, font)
    CreateDesc(layoutSection, "进入布局模式，拖拽调整所有 UI 元素的位置", 14, -30, font, 380)
    CreateButton(layoutSection, "进入布局模式", 370, -30, 140, 24, function()
        if SFrames.ConfigUI.frame then SFrames.ConfigUI.frame:Hide() end
        if SFrames.Movers and SFrames.Movers.ToggleLayoutMode then
            SFrames.Movers:ToggleLayoutMode()
        end
    end)

    -- ── UI 缩放 ──────────────────────────────────────────────────
    local scaleSection = CreateSection(root, "UI 缩放", 8, -148, 520, 82, font)

    local function ApplyUIScaleKeepPos(newScale)
        UIParent:SetScale(newScale)
        local panel = SFrames.ConfigUI.frame
        if panel and panel:IsShown() then
            panel:ClearAllPoints()
            panel:SetPoint("CENTER", UIParent, "CENTER", 0, 0)
        end
    end

    table.insert(controls, CreateCheckBox(scaleSection,
        "启用自定义 UI 缩放", 14, -34,
        function() return GetCVar("useUiScale") == "1" end,
        function(checked)
            SetCVar("useUiScale", checked and "1" or "0")
            if checked then
                local s = tonumber(GetCVar("uiScale")) or 1.0
                ApplyUIScaleKeepPos(s)
            else
                ApplyUIScaleKeepPos(1.0)
            end
        end
    ))
    CreateDesc(scaleSection, "调用系统 UI 缩放，对所有界面元素生效", 36, -50, font)

    local uiScaleSlider = CreateSlider(scaleSection, "缩放比例", 270, -34, 200, 0.64, 1.00, 0.01,
        function() return tonumber(GetCVar("uiScale")) or 1.0 end,
        function(value)
            SetCVar("uiScale", tostring(value))
        end,
        function(v) return string.format("%.0f%%", v * 100) end
    )
    table.insert(controls, uiScaleSlider)

    uiScaleSlider:SetScript("OnMouseUp", function()
        if GetCVar("useUiScale") == "1" then
            local s = tonumber(GetCVar("uiScale")) or 1.0
            ApplyUIScaleKeepPos(s)
        end
    end)

    -- ── 全局 ──────────────────────────────────────────────────────
    local globalSection = CreateSection(root, "全局", 8, -238, 520, 288, font)

    table.insert(controls, CreateCheckBox(globalSection,
        "显示玩家/目标等级文本", 14, -34,
        function() return SFramesDB.showLevel ~= false end,
        function(checked) SFramesDB.showLevel = checked end,
        function() RefreshPlayer() RefreshTarget() RefreshParty() end
    ))
    CreateDesc(globalSection, "在玩家和目标框体中显示角色等级数字", 36, -50, font)

    table.insert(controls, CreateCheckBox(globalSection,
        "玩家/目标生命条使用职业颜色", 14, -70,
        function() return SFramesDB.classColorHealth ~= false end,
        function(checked) SFramesDB.classColorHealth = checked end,
        function() RefreshPlayer() RefreshTarget() RefreshParty() end
    ))
    CreateDesc(globalSection, "血条颜色跟随职业（关闭则统一绿色）", 36, -86, font)

    table.insert(controls, CreateCheckBox(globalSection,
        "启用全新商人购买界面", 14, -106,
        function() return SFramesDB.enableMerchant ~= false end,
        function(checked) SFramesDB.enableMerchant = checked end
    ))
    CreateDesc(globalSection, "替换默认商人窗口为自定义界面", 36, -122, font)

    table.insert(controls, CreateCheckBox(globalSection,
        "启用全新任务/NPC对话界面", 14, -142,
        function() return SFramesDB.enableQuestUI ~= false end,
        function(checked) SFramesDB.enableQuestUI = checked end
    ))
    CreateDesc(globalSection, "替换默认任务和NPC对话窗口（需重载UI）", 36, -158, font)

    table.insert(controls, CreateCheckBox(globalSection,
        "启用任务日志美化皮肤", 14, -178,
        function() return SFramesDB.enableQuestLogSkin ~= false end,
        function(checked) SFramesDB.enableQuestLogSkin = checked end
    ))
    CreateDesc(globalSection, "美化任务日志界面，兼容 pfQuest（需重载UI）", 36, -194, font)

    table.insert(controls, CreateCheckBox(globalSection,
        "启用全新训练师界面", 270, -34,
        function() return SFramesDB.enableTrainer ~= false end,
        function(checked) SFramesDB.enableTrainer = checked end
    ))
    CreateDesc(globalSection, "替换默认职业/专业训练师窗口（需重载UI）", 292, -50, font)

    table.insert(controls, CreateCheckBox(globalSection,
        "启用全新法术书界面", 270, -70,
        function() return SFramesDB.enableSpellBook ~= false end,
        function(checked) SFramesDB.enableSpellBook = checked end
    ))
    CreateDesc(globalSection, "替换默认法术书窗口（需重载UI）", 292, -86, font)

    table.insert(controls, CreateCheckBox(globalSection,
        "启用全新社交界面", 270, -106,
        function() return SFramesDB.enableSocial ~= false end,
        function(checked) SFramesDB.enableSocial = checked end
    ))
    CreateDesc(globalSection, "替换好友/查询/工会/团队窗口（需重载UI）", 292, -122, font)

    table.insert(controls, CreateCheckBox(globalSection,
        "NPC单选项自动跳过", 270, -142,
        function() return SFramesDB.autoGossip ~= false end,
        function(checked) SFramesDB.autoGossip = checked end
    ))
    CreateDesc(globalSection, "只有一个对话选项时自动确认（按 Shift 临时禁用）", 292, -158, font)

    table.insert(controls, CreateCheckBox(globalSection,
        "启用兽栏皮肤", 270, -178,
        function() return SFramesDB.enablePetStable ~= false end,
        function(checked) SFramesDB.enablePetStable = checked end
    ))
    CreateDesc(globalSection, "美化宠物兽栏界面（需重载UI）", 292, -194, font)

    table.insert(controls, CreateCheckBox(globalSection,
        "启用全新专业技能界面", 14, -214,
        function() return SFramesDB.enableTradeSkill ~= false end,
        function(checked) SFramesDB.enableTradeSkill = checked end
    ))
    CreateDesc(globalSection, "替换默认专业技能窗口（需重载UI）", 36, -230, font)

    table.insert(controls, CreateCheckBox(globalSection,
        "启用全新飞行地图界面", 270, -214,
        function() return SFramesDB.enableFlightMap ~= false end,
        function(checked) SFramesDB.enableFlightMap = checked end
    ))
    CreateDesc(globalSection, "美化飞行管理员地图，增加目的地列表（需重载UI）", 292, -230, font)

    table.insert(controls, CreateCheckBox(globalSection,
        "启用全新邮箱界面", 14, -250,
        function() return SFramesDB.enableMail ~= false end,
        function(checked) SFramesDB.enableMail = checked end
    ))
    CreateDesc(globalSection, "替换默认邮箱窗口，支持批量收取和多物品发送（需重载UI）", 36, -266, font)

    table.insert(controls, CreateCheckBox(globalSection,
        "启用自定义拾取界面", 270, -250,
        function()
            if type(SFramesDB.lootDisplay) ~= "table" then return true end
            return SFramesDB.lootDisplay.enable ~= false
        end,
        function(checked)
            if type(SFramesDB.lootDisplay) ~= "table" then SFramesDB.lootDisplay = {} end
            SFramesDB.lootDisplay.enable = checked
        end
    ))
    CreateDesc(globalSection, "替换原生拾取窗口，品质染色+拾取提示（需重载UI）", 292, -266, font)

    -- ── 增强功能（Libs 库集成）──────────────────────────────────
    local enhSection = CreateSection(root, "增强功能（需安装 !Libs 插件）", 8, -536, 520, 204, font)

    table.insert(controls, CreateCheckBox(enhSection,
        "血条平滑动画（需 /reload）", 14, -34,
        function() return SFramesDB.smoothBars ~= false end,
        function(checked) SFramesDB.smoothBars = checked end
    ))
    CreateDesc(enhSection, "血量/法力变化时丝滑过渡，需要 LibSmoothStatusBar", 36, -50, font, 218)

    table.insert(controls, CreateCheckBox(enhSection,
        "目标真实血量", 270, -34,
        function() return SFramesDB.mobRealHealth ~= false end,
        function(checked) SFramesDB.mobRealHealth = checked end
    ))
    CreateDesc(enhSection, "怪物血条显示实际HP数值，需要 LibMobHealthCache", 292, -50, font, 218)

    table.insert(controls, CreateCheckBox(enhSection,
        "物品等级显示", 14, -80,
        function() return SFramesDB.showItemLevel ~= false end,
        function(checked) SFramesDB.showItemLevel = checked end,
        function()
            if SFrames.CharacterPanel and SFrames.CharacterPanel.UpdateCurrentTab then
                SFrames.CharacterPanel:UpdateCurrentTab()
            end
        end
    ))
    CreateDesc(enhSection, "装备栏/背包角标显示 iLvl + 面板平均装等，需要 LibItem-Level", 36, -96, font, 218)

    table.insert(controls, CreateCheckBox(enhSection,
        "装备属性对比", 270, -80,
        function() return SFramesDB.itemCompare ~= false end,
        function(checked) SFramesDB.itemCompare = checked end
    ))
    CreateDesc(enhSection, "背包物品 tooltip 显示与已装备的属性差异，需要 ItemBonusLib", 292, -96, font, 218)

    table.insert(controls, CreateCheckBox(enhSection,
        "装备评分 (EP)", 14, -126,
        function() return SFramesDB.gearScore ~= false end,
        function(checked) SFramesDB.gearScore = checked end
    ))
    CreateDesc(enhSection, "Tooltip 显示当前职业各天赋EP评分及硬核评分，需要 ItemBonusLib", 36, -142, font, 480)

    table.insert(controls, CreateCheckBox(enhSection,
        "显示物品/法术ID", 270, -126,
        function() return SFramesDB.showTooltipIDs ~= false end,
        function(checked) SFramesDB.showTooltipIDs = checked end
    ))
    CreateDesc(enhSection, "在 Tooltip 中显示物品ID和法术ID", 292, -142, font, 218)

    CreateLabel(enhSection, "提示：以上功能需要安装对应的 !Libs 库才能生效。", 14, -172, font, 10, 0.6, 0.6, 0.65)

    -- ── ShaguTweaks 功能移植 ──────────────────────────────────────
    local tweaksSection = CreateSection(root, "ShaguTweaks 功能移植（需 /reload 生效）", 8, -750, 520, 214, font)

    table.insert(controls, CreateCheckBox(tweaksSection,
        "自动切换姿态/形态", 14, -34,
        function() return SFramesDB.Tweaks.autoStance ~= false end,
        function(checked) SFramesDB.Tweaks.autoStance = checked end
    ))
    CreateDesc(tweaksSection, "施法需要特定姿态时自动切换（如人形按熊掌→自动变熊）", 36, -50, font, 218)

    table.insert(controls, CreateCheckBox(tweaksSection,
        "自动取消变形/下马", 270, -34,
        function() return SFramesDB.Tweaks.autoDismount ~= false end,
        function(checked) SFramesDB.Tweaks.autoDismount = checked end
    ))
    CreateDesc(tweaksSection, "变形/骑马时施法自动取消（如熊形按治疗→自动回人形）", 292, -50, font, 218)

    table.insert(controls, CreateCheckBox(tweaksSection,
        "乌龟服兼容修改", 14, -80,
        function() return SFramesDB.Tweaks.turtleCompat ~= false end,
        function(checked) SFramesDB.Tweaks.turtleCompat = checked end
    ))
    CreateDesc(tweaksSection, "隐藏乌龟服自带目标血量文字，修复地图窗口标题偏移", 36, -96, font, 218)

    table.insert(controls, CreateCheckBox(tweaksSection,
        "SuperWoW 客户端兼容", 270, -80,
        function() return SFramesDB.Tweaks.superWoW ~= false end,
        function(checked) SFramesDB.Tweaks.superWoW = checked end
    ))
    CreateDesc(tweaksSection, "为 SuperWoW 提供 GUID 施法数据支持（需安装 SuperWoW）", 292, -96, font, 218)

    table.insert(controls, CreateCheckBox(tweaksSection,
        "冷却倒计时", 14, -126,
        function() return SFramesDB.Tweaks.cooldownNumbers ~= false end,
        function(checked) SFramesDB.Tweaks.cooldownNumbers = checked end
    ))
    CreateDesc(tweaksSection, "在技能/物品冷却图标上显示剩余时间文字（>2秒）", 36, -142, font, 218)

    table.insert(controls, CreateCheckBox(tweaksSection,
        "暗色界面风格", 270, -126,
        function() return SFramesDB.Tweaks.darkUI == true end,
        function(checked) SFramesDB.Tweaks.darkUI = checked end
    ))
    CreateDesc(tweaksSection, "将整个游戏界面调暗为深色主题（默认关闭）", 292, -142, font, 218)

    CreateLabel(tweaksSection, "提示：以上所有选项修改后需要 /reload 才能生效。", 14, -172, font, 10, 0.6, 0.6, 0.65)

    uiScroll:UpdateRange()
    self.uiControls = controls
    self.uiScroll = uiScroll
end


function SFrames.ConfigUI:BuildPlayerPage()
    local font = SFrames:GetFont()
    local page = self.playerPage
    local controls = {}

    local function RefreshPlayer()
        if SFrames.Player and SFrames.Player.ApplyConfig then
            SFrames.Player:ApplyConfig()
            return
        end
        if SFrames.Player and SFrames.Player.UpdateAll then SFrames.Player:UpdateAll() end
    end

    local playerSection = CreateSection(page, "玩家框体", 8, -8, 520, 342, font)

    table.insert(controls, CreateCheckBox(playerSection,
        "启用 Nanami 玩家框体（需 /reload）", 12, -28,
        function() return SFramesDB.enablePlayerFrame ~= false end,
        function(checked) SFramesDB.enablePlayerFrame = checked end
    ))

    table.insert(controls, CreateSlider(playerSection, "缩放", 14, -72, 150, 0.7, 1.8, 0.05,
        function() return SFramesDB.playerFrameScale end,
        function(value) SFramesDB.playerFrameScale = value end,
        function(v) return string.format("%.2f", v) end,
        function() RefreshPlayer() end
    ))

    table.insert(controls, CreateSlider(playerSection, "框体宽度", 170, -72, 150, 170, 420, 1,
        function() return SFramesDB.playerFrameWidth end,
        function(value) SFramesDB.playerFrameWidth = value end,
        function(v) return tostring(math.floor(v + 0.5)) end,
        function() RefreshPlayer() end
    ))

    table.insert(controls, CreateSlider(playerSection, "头像宽度", 326, -72, 130, 32, 95, 1,
        function() return SFramesDB.playerPortraitWidth end,
        function(value) SFramesDB.playerPortraitWidth = value end,
        function(v) return tostring(math.floor(v + 0.5)) end,
        function() RefreshPlayer() end
    ))

    table.insert(controls, CreateSlider(playerSection, "生命条高度", 14, -134, 150, 14, 80, 1,
        function() return SFramesDB.playerHealthHeight end,
        function(value) SFramesDB.playerHealthHeight = value end,
        function(v) return tostring(math.floor(v + 0.5)) end,
        function() RefreshPlayer() end
    ))

    table.insert(controls, CreateSlider(playerSection, "能量条高度", 170, -134, 150, 6, 40, 1,
        function() return SFramesDB.playerPowerHeight end,
        function(value) SFramesDB.playerPowerHeight = value end,
        function(v) return tostring(math.floor(v + 0.5)) end,
        function() RefreshPlayer() end
    ))

    table.insert(controls, CreateCheckBox(playerSection,
        "玩家姓名显示职业", 326, -132,
        function() return SFramesDB.playerShowClass ~= false end,
        function(checked) SFramesDB.playerShowClass = checked end,
        function() RefreshPlayer() end
    ))

    table.insert(controls, CreateCheckBox(playerSection,
        "玩家显示职业图标", 326, -158,
        function() return SFramesDB.playerShowClassIcon ~= false end,
        function(checked) SFramesDB.playerShowClassIcon = checked end,
        function() RefreshPlayer() end
    ))

    table.insert(controls, CreateCheckBox(playerSection,
        "启用3D头像", 12, -188,
        function() return SFramesDB.playerShowPortrait ~= false end,
        function(checked) SFramesDB.playerShowPortrait = checked end,
        function() RefreshPlayer() end
    ))

    table.insert(controls, CreateSlider(playerSection, "姓名字号", 14, -226, 150, 8, 18, 1,
        function() return SFramesDB.playerNameFontSize end,
        function(value) SFramesDB.playerNameFontSize = value end,
        function(v) return tostring(math.floor(v + 0.5)) end,
        function() RefreshPlayer() end
    ))

    table.insert(controls, CreateSlider(playerSection, "数值字号", 170, -226, 150, 8, 18, 1,
        function() return SFramesDB.playerValueFontSize end,
        function(value) SFramesDB.playerValueFontSize = value end,
        function(v) return tostring(math.floor(v + 0.5)) end,
        function() RefreshPlayer() end
    ))

    table.insert(controls, CreateSlider(playerSection, "透明度", 326, -226, 130, 0.1, 1.0, 0.05,
        function() return SFramesDB.playerFrameAlpha end,
        function(value) SFramesDB.playerFrameAlpha = value end,
        function(v) return string.format("%.0f%%", v * 100) end,
        function(value)
            if SFrames.Player and SFrames.Player.frame then
                SFrames.Player.frame:SetAlpha(value)
            end
        end
    ))

    CreateLabel(playerSection,
        "提示：关闭头像后职业图标移到框体最左侧，框体仅显示血条。",
        14, -299, font, 10, 0.9, 0.9, 0.9)

    -- ── 宠物框体 ──────────────────────────────────────────────────
    local petSection = CreateSection(page, "宠物框体", 8, -358, 520, 98, font)

    table.insert(controls, CreateCheckBox(petSection,
        "显示宠物框体", 14, -34,
        function() return SFramesDB.showPetFrame ~= false end,
        function(checked) SFramesDB.showPetFrame = checked end,
        function() if SFrames.Pet and SFrames.Pet.UpdateAll then SFrames.Pet:UpdateAll() end end
    ))
    CreateDesc(petSection, "在玩家框体附近显示当前宠物的血量条", 36, -50, font)

    table.insert(controls, CreateSlider(petSection, "缩放", 270, -52, 200, 0.7, 1.8, 0.05,
        function() return SFramesDB.petFrameScale or 1 end,
        function(value) SFramesDB.petFrameScale = value end,
        function(v) return string.format("%.2f", v) end,
        function(value) if SFrames.Pet and SFrames.Pet.frame then SFrames.Pet.frame:SetScale(value) end end
    ))

    self.playerControls = controls
end

function SFrames.ConfigUI:BuildTargetPage()
    local font = SFrames:GetFont()
    local page = self.targetPage
    local controls = {}

    local function RefreshTarget()
        if SFrames.Target and SFrames.Target.ApplyConfig then
            SFrames.Target:ApplyConfig()
            return
        end
        if SFrames.Target and SFrames.Target.UpdateAll then SFrames.Target:UpdateAll() end
    end

    local targetSection = CreateSection(page, "目标框体", 8, -8, 520, 382, font)

    table.insert(controls, CreateCheckBox(targetSection,
        "启用 Nanami 目标框体（需 /reload）", 12, -28,
        function() return SFramesDB.enableTargetFrame ~= false end,
        function(checked) SFramesDB.enableTargetFrame = checked end
    ))

    table.insert(controls, CreateSlider(targetSection, "缩放", 14, -72, 150, 0.7, 1.8, 0.05,
        function() return SFramesDB.targetFrameScale end,
        function(value) SFramesDB.targetFrameScale = value end,
        function(v) return string.format("%.2f", v) end,
        function() RefreshTarget() end
    ))

    table.insert(controls, CreateSlider(targetSection, "框体宽度", 170, -72, 150, 170, 420, 1,
        function() return SFramesDB.targetFrameWidth end,
        function(value) SFramesDB.targetFrameWidth = value end,
        function(v) return tostring(math.floor(v + 0.5)) end,
        function() RefreshTarget() end
    ))

    table.insert(controls, CreateSlider(targetSection, "头像宽度", 326, -72, 130, 32, 95, 1,
        function() return SFramesDB.targetPortraitWidth end,
        function(value) SFramesDB.targetPortraitWidth = value end,
        function(v) return tostring(math.floor(v + 0.5)) end,
        function() RefreshTarget() end
    ))

    table.insert(controls, CreateSlider(targetSection, "生命条高度", 14, -134, 150, 14, 80, 1,
        function() return SFramesDB.targetHealthHeight end,
        function(value) SFramesDB.targetHealthHeight = value end,
        function(v) return tostring(math.floor(v + 0.5)) end,
        function() RefreshTarget() end
    ))

    table.insert(controls, CreateSlider(targetSection, "能量条高度", 170, -134, 150, 6, 40, 1,
        function() return SFramesDB.targetPowerHeight end,
        function(value) SFramesDB.targetPowerHeight = value end,
        function(v) return tostring(math.floor(v + 0.5)) end,
        function() RefreshTarget() end
    ))

    table.insert(controls, CreateCheckBox(targetSection,
        "目标姓名显示职业", 326, -132,
        function() return SFramesDB.targetShowClass ~= false end,
        function(checked) SFramesDB.targetShowClass = checked end,
        function() RefreshTarget() end
    ))

    table.insert(controls, CreateCheckBox(targetSection,
        "目标显示职业图标", 326, -158,
        function() return SFramesDB.targetShowClassIcon ~= false end,
        function(checked) SFramesDB.targetShowClassIcon = checked end,
        function() RefreshTarget() end
    ))

    table.insert(controls, CreateCheckBox(targetSection,
        "启用3D头像", 12, -188,
        function() return SFramesDB.targetShowPortrait ~= false end,
        function(checked) SFramesDB.targetShowPortrait = checked end,
        function() RefreshTarget() end
    ))

    table.insert(controls, CreateSlider(targetSection, "姓名字号", 14, -226, 150, 8, 18, 1,
        function() return SFramesDB.targetNameFontSize end,
        function(value) SFramesDB.targetNameFontSize = value end,
        function(v) return tostring(math.floor(v + 0.5)) end,
        function() RefreshTarget() end
    ))

    table.insert(controls, CreateSlider(targetSection, "数值字号", 170, -226, 150, 8, 18, 1,
        function() return SFramesDB.targetValueFontSize end,
        function(value) SFramesDB.targetValueFontSize = value end,
        function(v) return tostring(math.floor(v + 0.5)) end,
        function() RefreshTarget() end
    ))

    table.insert(controls, CreateSlider(targetSection, "透明度", 326, -226, 130, 0.1, 1.0, 0.05,
        function() return SFramesDB.targetFrameAlpha end,
        function(value) SFramesDB.targetFrameAlpha = value end,
        function(v) return string.format("%.0f%%", v * 100) end,
        function(value)
            if SFrames.Target and SFrames.Target.frame then
                SFrames.Target.frame:SetAlpha(value)
            end
        end
    ))

    table.insert(controls, CreateCheckBox(targetSection,
        "启用目标距离文本", 326, -266,
        function() return SFramesDB.targetDistanceEnabled ~= false end,
        function(checked) SFramesDB.targetDistanceEnabled = checked end,
        function(checked)
            if SFrames.Target and SFrames.Target.distanceFrame then
                if checked and UnitExists("target") then
                    SFrames.Target.distanceFrame:Show()
                elseif not checked then
                    SFrames.Target.distanceFrame:Hide()
                end
            end
        end
    ))

    table.insert(controls, CreateSlider(targetSection, "距离文本缩放", 14, -318, 150, 0.7, 1.8, 0.05,
        function() return SFramesDB.targetDistanceScale end,
        function(value) SFramesDB.targetDistanceScale = value end,
        function(v) return string.format("%.2f", v) end,
        function(value)
            if SFrames.Target and SFrames.Target.ApplyDistanceScale then
                SFrames.Target:ApplyDistanceScale(value)
            end
        end
    ))

    self.targetControls = controls
end

function SFrames.ConfigUI:BuildPartyPage()
    local font = SFrames:GetFont()
    local page = self.partyPage
    local controls = {}

    local function RefreshParty()
        if SFrames.Party and SFrames.Party.ApplyConfig then
            SFrames.Party:ApplyConfig()
        end
        if SFrames.Party and SFrames.Party.UpdateAll then SFrames.Party:UpdateAll() end
    end

    local uiScroll = CreateScrollArea(page, 4, -4, 548, 458, 320)
    local root = uiScroll.child

    local partySection = CreateSection(root, "小队", 8, -8, 520, 382, font)

    table.insert(controls, CreateCheckBox(partySection,
        "启用 Nanami 小队框体（需 /reload）", 12, -28,
        function() return SFramesDB.enablePartyFrame ~= false end,
        function(checked) SFramesDB.enablePartyFrame = checked end
    ))

    CreateButton(partySection, "解锁小队框架", 14, -52, 130, 22, function()
        if SFrames and SFrames.UnlockFrames then SFrames:UnlockFrames() end
    end)

    CreateButton(partySection, "锁定小队框架", 154, -52, 130, 22, function()
        if SFrames and SFrames.LockFrames then SFrames:LockFrames() end
    end)

    table.insert(controls, CreateCheckBox(partySection,
        "横向布局（关闭为竖向）", 12, -86,
        function() return SFramesDB.partyLayout == "horizontal" end,
        function(checked)
            if checked then SFramesDB.partyLayout = "horizontal" else SFramesDB.partyLayout = "vertical" end
        end,
        function(checked)
            if SFrames.Party and SFrames.Party.SetLayout then
                if checked then SFrames.Party:SetLayout("horizontal") else SFrames.Party:SetLayout("vertical") end
            end
            RefreshParty()
        end
    ))

    table.insert(controls, CreateSlider(partySection, "缩放", 14, -134, 150, 0.7, 1.8, 0.05,
        function() return SFramesDB.partyFrameScale end,
        function(value) SFramesDB.partyFrameScale = value end,
        function(v) return string.format("%.2f", v) end,
        function() RefreshParty() end
    ))

    table.insert(controls, CreateSlider(partySection, "框体宽度", 170, -134, 150, 120, 320, 1,
        function() return SFramesDB.partyFrameWidth end,
        function(value) SFramesDB.partyFrameWidth = value end,
        function(v) return tostring(math.floor(v + 0.5)) end,
        function() RefreshParty() end
    ))

    table.insert(controls, CreateSlider(partySection, "框体高度", 326, -134, 130, 28, 80, 1,
        function() return SFramesDB.partyFrameHeight end,
        function(value) SFramesDB.partyFrameHeight = value end,
        function(v) return tostring(math.floor(v + 0.5)) end,
        function() RefreshParty() end
    ))

    table.insert(controls, CreateSlider(partySection, "头像宽度", 14, -196, 150, 24, 64, 1,
        function() return SFramesDB.partyPortraitWidth end,
        function(value) SFramesDB.partyPortraitWidth = value end,
        function(v) return tostring(math.floor(v + 0.5)) end,
        function() RefreshParty() end
    ))

    table.insert(controls, CreateSlider(partySection, "生命条高度", 170, -196, 150, 10, 60, 1,
        function() return SFramesDB.partyHealthHeight end,
        function(value) SFramesDB.partyHealthHeight = value end,
        function(v) return tostring(math.floor(v + 0.5)) end,
        function() RefreshParty() end
    ))

    table.insert(controls, CreateSlider(partySection, "能量条高度", 326, -196, 130, 6, 30, 1,
        function() return SFramesDB.partyPowerHeight end,
        function(value) SFramesDB.partyPowerHeight = value end,
        function(v) return tostring(math.floor(v + 0.5)) end,
        function() RefreshParty() end
    ))

    table.insert(controls, CreateSlider(partySection, "横向间距", 14, -258, 150, 0, 40, 1,
        function() return SFramesDB.partyHorizontalGap end,
        function(value) SFramesDB.partyHorizontalGap = value end,
        function(v) return tostring(math.floor(v + 0.5)) end,
        function() RefreshParty() end
    ))

    table.insert(controls, CreateSlider(partySection, "纵向间距", 170, -258, 150, 0, 80, 1,
        function() return SFramesDB.partyVerticalGap end,
        function(value) SFramesDB.partyVerticalGap = value end,
        function(v) return tostring(math.floor(v + 0.5)) end,
        function() RefreshParty() end
    ))

    table.insert(controls, CreateSlider(partySection, "姓名字号", 326, -258, 130, 8, 18, 1,
        function() return SFramesDB.partyNameFontSize end,
        function(value) SFramesDB.partyNameFontSize = value end,
        function(v) return tostring(math.floor(v + 0.5)) end,
        function() RefreshParty() end
    ))

    table.insert(controls, CreateSlider(partySection, "数值字号", 14, -320, 150, 8, 18, 1,
        function() return SFramesDB.partyValueFontSize end,
        function(value) SFramesDB.partyValueFontSize = value end,
        function(v) return tostring(math.floor(v + 0.5)) end,
        function() RefreshParty() end
    ))

    table.insert(controls, CreateCheckBox(partySection,
        "显示小队增益", 170, -318,
        function() return SFramesDB.partyShowBuffs ~= false end,
        function(checked) SFramesDB.partyShowBuffs = checked end,
        function() RefreshParty() end
    ))

    table.insert(controls, CreateCheckBox(partySection,
        "显示小队减益", 326, -318,
        function() return SFramesDB.partyShowDebuffs ~= false end,
        function(checked) SFramesDB.partyShowDebuffs = checked end,
        function() RefreshParty() end
    ))

    CreateButton(partySection, "小队测试模式", 326, -350, 130, 22, function()
        if SFrames.Party and SFrames.Party.TestMode then SFrames.Party:TestMode() end
    end)
    uiScroll:UpdateRange()
    self.partyControls = controls
    self.partyScroll = uiScroll
end

function SFrames.ConfigUI:BuildBagPage()
    local font = SFrames:GetFont()
    local page = self.bagPage
    local controls = {}

    local bagScroll = CreateScrollArea(page, 4, -4, 548, 458, 630)
    local root = bagScroll.child

    local bagSection = CreateSection(root, "背包", 8, -8, 502, 246, font)

    table.insert(controls, CreateCheckBox(bagSection,
        "启用 Nanami 背包（需 /reload）", 12, -28,
        function() return SFramesDB.Bags.enable ~= false end,
        function(checked) SFramesDB.Bags.enable = checked end
    ))

    table.insert(controls, CreateCheckBox(bagSection,
        "自动出售灰色物品", 12, -52,
        function() return SFramesDB.Bags.sellGrey ~= false end,
        function(checked) SFramesDB.Bags.sellGrey = checked end
    ))

    table.insert(controls, CreateSlider(bagSection, "背包列数", 16, -90, 220, 4, 20, 1,
        function() return SFramesDB.Bags.columns end,
        function(value) SFramesDB.Bags.columns = value end,
        function(value) return tostring(math.floor(value + 0.5)) end,
        function()
            if SFrames and SFrames.Bags and SFrames.Bags.Container and SFrames.Bags.Container.UpdateLayout then
                SFrames.Bags.Container:UpdateLayout()
            end
        end
    ))

    table.insert(controls, CreateSlider(bagSection, "背包间距", 258, -90, 220, 0, 12, 1,
        function() return SFramesDB.Bags.bagSpacing end,
        function(value) SFramesDB.Bags.bagSpacing = value end,
        function(value) return tostring(math.floor(value + 0.5)) end,
        function()
            if SFrames and SFrames.Bags and SFrames.Bags.Container and SFrames.Bags.Container.UpdateLayout then
                SFrames.Bags.Container:UpdateLayout()
            end
        end
    ))

    table.insert(controls, CreateSlider(bagSection, "背包缩放", 16, -150, 220, 0.50, 2.00, 0.05,
        function() return SFramesDB.Bags.scale end,
        function(value) SFramesDB.Bags.scale = value end,
        function(value) return string.format("%.2f", value) end,
        function(value)
            if SFramesBagFrame then SFramesBagFrame:SetScale(value) end
        end
    ))

    table.insert(controls, CreateSlider(bagSection, "背包透明度", 258, -150, 220, 0.1, 1.0, 0.05,
        function() return SFramesDB.Bags.alpha or 1 end,
        function(value) SFramesDB.Bags.alpha = value end,
        function(value) return string.format("%.0f%%", value * 100) end,
        function(value)
            if SFramesBagFrame then SFramesBagFrame:SetAlpha(value) end
        end
    ))

    CreateButton(bagSection, "打开背包", 16, -196, 220, 24, function()
        if SFrames and SFrames.Bags and SFrames.Bags.Container and SFrames.Bags.Container.Open then
            SFrames.Bags.Container:Open()
        end
    end)

    local bankSection = CreateSection(root, "银行", 8, -260, 502, 215, font)

    table.insert(controls, CreateSlider(bankSection, "银行列数", 16, -50, 220, 4, 24, 1,
        function() return SFramesDB.Bags.bankColumns end,
        function(value) SFramesDB.Bags.bankColumns = value end,
        function(value) return tostring(math.floor(value + 0.5)) end,
        function()
            if SFrames and SFrames.Bags and SFrames.Bags.Bank and SFrames.Bags.Bank.UpdateLayout then
                SFrames.Bags.Bank:UpdateLayout()
            end
        end
    ))

    table.insert(controls, CreateSlider(bankSection, "银行间距", 258, -50, 220, 0, 12, 1,
        function() return SFramesDB.Bags.bankSpacing end,
        function(value) SFramesDB.Bags.bankSpacing = value end,
        function(value) return tostring(math.floor(value + 0.5)) end,
        function()
            if SFrames and SFrames.Bags and SFrames.Bags.Bank and SFrames.Bags.Bank.UpdateLayout then
                SFrames.Bags.Bank:UpdateLayout()
            end
        end
    ))

    table.insert(controls, CreateSlider(bankSection, "银行缩放", 16, -110, 220, 0.50, 2.00, 0.05,
        function() return SFramesDB.Bags.bankScale end,
        function(value) SFramesDB.Bags.bankScale = value end,
        function(value) return string.format("%.2f", value) end,
        function(value)
            if SFramesBankFrame then SFramesBankFrame:SetScale(value) end
        end
    ))

    table.insert(controls, CreateSlider(bankSection, "银行透明度", 258, -110, 220, 0.1, 1.0, 0.05,
        function() return SFramesDB.Bags.bankAlpha or 1 end,
        function(value) SFramesDB.Bags.bankAlpha = value end,
        function(value) return string.format("%.0f%%", value * 100) end,
        function(value)
            if SFramesBankFrame then SFramesBankFrame:SetAlpha(value) end
        end
    ))

    CreateButton(bankSection, "打开离线银行", 16, -170, 220, 24, function()
        if SFrames and SFrames.Bags and SFrames.Bags.Bank and SFrames.Bags.Bank.OpenOffline then
            SFrames.Bags.Bank:OpenOffline()
        end
    end)

    ---------------------------------------------------------------------------
    -- Sell Price Database Section
    ---------------------------------------------------------------------------
    local priceSection = CreateSection(root, "售价数据库", 8, -481, 502, 126, font)

    local function CountCacheEntries()
        local n = 0
        if SFramesGlobalDB and SFramesGlobalDB.sellPriceCache then
            for _ in pairs(SFramesGlobalDB.sellPriceCache) do n = n + 1 end
        end
        return n
    end

    local function CountDBEntries()
        local n = 0
        if NanamiSellPriceDB then
            for _ in pairs(NanamiSellPriceDB) do n = n + 1 end
        end
        return n
    end

    local dbCount = CountDBEntries()
    local statusLabel = CreateLabel(priceSection,
        string.format("内置: %d 条  |  已学习: %d 条", dbCount, CountCacheEntries()),
        14, -30, font, 10, 0.8, 0.8, 0.7)

    local scanResultLabel = CreateLabel(priceSection, "", 14, -104, font, 10, 0.45, 0.9, 0.45)

    CreateButton(priceSection, "扫描全部物品售价", 16, -48, 228, 24, function()
        if not SFramesGlobalDB then SFramesGlobalDB = {} end
        if not SFramesGlobalDB.sellPriceCache then SFramesGlobalDB.sellPriceCache = {} end
        local cache = SFramesGlobalDB.sellPriceCache
        local scanned, learned = 0, 0

        local function TryScan(link)
            if not link then return end
            local _, _, sid = string.find(link, "item:(%d+)")
            if not sid then return end
            local id = tonumber(sid)
            if not id then return end
            scanned = scanned + 1
            if cache[id] and cache[id] > 0 then return end

            local price
            if NanamiSellPriceDB and NanamiSellPriceDB[id] and NanamiSellPriceDB[id] > 0 then
                price = NanamiSellPriceDB[id]
            elseif ShaguTweaks and ShaguTweaks.SellValueDB and ShaguTweaks.SellValueDB[id] and ShaguTweaks.SellValueDB[id] > 0 then
                price = ShaguTweaks.SellValueDB[id]
            else
                local _,_,_,_,_,_,_,_,_,_,sp = GetItemInfo(link)
                if sp and type(sp) == "number" and sp > 0 then price = sp end
            end
            if price then
                cache[id] = price
                learned = learned + 1
            end
        end

        for bag = 0, 4 do
            for slot = 1, (GetContainerNumSlots(bag) or 0) do
                TryScan(GetContainerItemLink(bag, slot))
            end
        end
        for bag = 5, 10 do
            pcall(function()
                for slot = 1, (GetContainerNumSlots(bag) or 0) do
                    TryScan(GetContainerItemLink(bag, slot))
                end
            end)
        end
        pcall(function()
            for slot = 1, (GetContainerNumSlots(-1) or 0) do
                TryScan(GetContainerItemLink(-1, slot))
            end
        end)
        for slot = 1, 19 do
            TryScan(GetInventoryItemLink("player", slot))
        end

        statusLabel:SetText(string.format("内置: %d 条  |  已学习: %d 条", dbCount, CountCacheEntries()))
        scanResultLabel:SetText(string.format("完成! 检查 %d 件, 新增 %d 条售价", scanned, learned))
    end)

    CreateButton(priceSection, "清空学习缓存", 258, -48, 228, 24, function()
        if SFramesGlobalDB then SFramesGlobalDB.sellPriceCache = {} end
        statusLabel:SetText(string.format("内置: %d 条  |  已学习: 0 条", dbCount))
        scanResultLabel:SetText("学习缓存已清空")
    end)

    CreateDesc(priceSection, "扫描背包/银行/装备中所有物品, 缓存售价到离线数据库 (随存档保存)", 14, -76, font, 480)
    CreateDesc(priceSection, "多次游玩后数据自动丰富, 其他角色也能共享离线数据", 14, -90, font, 480)

    bagScroll:UpdateRange()
    self.bagScroll = bagScroll
    self.bagControls = controls
end

function SFrames.ConfigUI:BuildRaidPage()
    local font = SFrames:GetFont()
    local page = self.raidPage
    local controls = {}

    local function RefreshRaid()
        if SFrames.Raid and SFrames.Raid.ApplyLayout then
            if not SFrames.Raid._framesBuilt then return end
            SFrames.Raid:ApplyLayout()
            SFrames.Raid:UpdateAll()
            for i = 1, 40 do
                local f = SFrames.Raid.frames[i] and SFrames.Raid.frames[i].frame
                if f then SFrames.Raid:ApplyFrameStyle(f, SFrames.Raid:GetMetrics()) end
            end
        end
    end

    local uiScroll = CreateScrollArea(page, 4, -4, 548, 458, 600)
    local root = uiScroll.child

    local raidSection = CreateSection(root, "团队框架设置", 8, -8, 520, 570, font)

    table.insert(controls, CreateCheckBox(raidSection,
        "启用 Nanami 团队框架（默认启用，需 /reload）", 12, -28,
        function() return SFramesDB.enableRaidFrames ~= false end,
        function(checked) SFramesDB.enableRaidFrames = checked end
    ))

    CreateButton(raidSection, "解锁团队框架", 14, -56, 130, 22, function()
        if SFrames and SFrames.UnlockFrames then SFrames:UnlockFrames() end
    end)

    CreateButton(raidSection, "锁定团队框架", 154, -56, 130, 22, function()
        if SFrames and SFrames.LockFrames then SFrames:LockFrames() end
    end)

    table.insert(controls, CreateCheckBox(raidSection,
        "横向小队排列（关闭为竖向叠放）", 12, -88,
        function() return SFramesDB.raidLayout == "horizontal" end,
        function(checked)
            SFramesDB.raidLayout = checked and "horizontal" or "vertical"
        end,
        function() RefreshRaid() end
    ))

    table.insert(controls, CreateSlider(raidSection, "缩放", 14, -150, 150, 0.5, 2.0, 0.05,
        function() return SFramesDB.raidFrameScale end,
        function(value) SFramesDB.raidFrameScale = value end,
        function(v) return string.format("%.2f", v) end,
        function(val) 
            if SFrames.Raid and SFrames.Raid.parent then
                SFrames.Raid.parent:SetScale(val)
            end 
        end
    ))

    table.insert(controls, CreateSlider(raidSection, "框体宽度", 170, -150, 150, 30, 150, 1,
        function() return SFramesDB.raidFrameWidth end,
        function(value) SFramesDB.raidFrameWidth = value end,
        function(v) return tostring(math.floor(v + 0.5)) end,
        function() RefreshRaid() end
    ))

    table.insert(controls, CreateSlider(raidSection, "框体高度", 326, -150, 130, 20, 80, 1,
        function() return SFramesDB.raidFrameHeight end,
        function(value) SFramesDB.raidFrameHeight = value end,
        function(v) return tostring(math.floor(v + 0.5)) end,
        function() RefreshRaid() end
    ))

    table.insert(controls, CreateSlider(raidSection, "生命条高度", 14, -212, 150, 10, 78, 1,
        function() return SFramesDB.raidHealthHeight end,
        function(value) SFramesDB.raidHealthHeight = value end,
        function(v) return tostring(math.floor(v + 0.5)) end,
        function() RefreshRaid() end
    ))

    table.insert(controls, CreateCheckBox(raidSection,
        "显示能量条", 170, -210,
        function() return SFramesDB.raidShowPower ~= false end,
        function(checked) SFramesDB.raidShowPower = checked end,
        function() RefreshRaid() end
    ))

    table.insert(controls, CreateCheckBox(raidSection,
        "显示小队标题（横向=顶部，竖向=左侧）", 14, -244,
        function() return SFramesDB.raidShowGroupLabel ~= false end,
        function(checked) SFramesDB.raidShowGroupLabel = checked end,
        function() RefreshRaid() end
    ))

    table.insert(controls, CreateSlider(raidSection, "横向间距", 14, -274, 150, 0, 40, 1,
        function() return SFramesDB.raidHorizontalGap end,
        function(value) SFramesDB.raidHorizontalGap = value end,
        function(v) return tostring(math.floor(v + 0.5)) end,
        function() RefreshRaid() end
    ))

    table.insert(controls, CreateSlider(raidSection, "纵向间距", 170, -274, 150, 0, 40, 1,
        function() return SFramesDB.raidVerticalGap end,
        function(value) SFramesDB.raidVerticalGap = value end,
        function(v) return tostring(math.floor(v + 0.5)) end,
        function() RefreshRaid() end
    ))

    table.insert(controls, CreateSlider(raidSection, "小队间隔", 326, -274, 130, 0, 60, 1,
        function() return SFramesDB.raidGroupGap end,
        function(value) SFramesDB.raidGroupGap = value end,
        function(v) return tostring(math.floor(v + 0.5)) end,
        function() RefreshRaid() end
    ))

    table.insert(controls, CreateSlider(raidSection, "姓名字号", 14, -336, 150, 8, 18, 1,
        function() return SFramesDB.raidNameFontSize end,
        function(value) SFramesDB.raidNameFontSize = value end,
        function(v) return tostring(math.floor(v + 0.5)) end,
        function() RefreshRaid() end
    ))

    table.insert(controls, CreateSlider(raidSection, "数值字号", 170, -336, 150, 8, 18, 1,
        function() return SFramesDB.raidValueFontSize end,
        function(value) SFramesDB.raidValueFontSize = value end,
        function(v) return tostring(math.floor(v + 0.5)) end,
        function() RefreshRaid() end
    ))
    
    table.insert(controls, CreateCheckBox(raidSection,
        "血量百分比模式 (关=紧凑数值/-缺口)", 14, -390,
        function() return SFramesDB.raidHealthFormat == "percent" end,
        function(checked) SFramesDB.raidHealthFormat = checked and "percent" or "compact" end,
        function() RefreshRaid() end
    ))

    CreateButton(raidSection, "测试模式(假团队)", 14, -450, 150, 24, function()
        if SFrames.Raid then
            if SFrames.Raid.testing then
                SFrames.Raid.testing = false
                SFrames.Raid:UpdateAll()
            else
                SFrames.Raid.testing = true
                SFrames.Raid:EnsureFrames()
                SFrames.Raid.parent:Show()
                for i = 1, 40 do
                    local f = SFrames.Raid.frames[i].frame
                    f:Show()
                    f.nameText:SetText("玩家"..i)
                    f.healthText:SetText("100%")
                    f.health:SetMinMaxValues(0, 100)
                    f.health:SetValue(100)
                    f.health:SetStatusBarColor(0, 1, 0)
                end
            end
        end
    end)
    
    uiScroll:UpdateRange()
    self.raidControls = controls
    self.raidScroll = uiScroll
end

function SFrames.ConfigUI:BuildCharPage()
    local font = SFrames:GetFont()
    local page = self.charPage
    local controls = {}

    local uiScroll = CreateScrollArea(page, 4, -4, 552, PANEL_HEIGHT - 110, 720)
    local root = uiScroll.child

    local charSection = CreateSection(root, "人物面板", 8, -8, 524, 430, font)

    table.insert(controls, CreateCheckBox(charSection,
        "启用 Nanami 人物面板（需 /reload）", 12, -34,
        function() return SFramesDB.charPanelEnable ~= false end,
        function(checked) SFramesDB.charPanelEnable = checked end
    ))
    CreateDesc(charSection, "关闭后按 C 键将打开原版人物面板", 36, -50, font)

    table.insert(controls, CreateSlider(charSection,
        "面板缩放", 14, -72, 240, 0.6, 1.5, 0.05,
        function() return SFramesDB.charPanelScale or 1.0 end,
        function(v) SFramesDB.charPanelScale = v end,
        function(v) return string.format("%.0f%%", v * 100) end,
        function()
            if SFramesCharacterPanel then
                SFramesCharacterPanel:SetScale(SFramesDB.charPanelScale or 1.0)
            end
        end
    ))

    table.insert(controls, CreateCheckBox(charSection,
        "显示PVP称号", 12, -118,
        function() return SFramesDB.charShowTitle ~= false end,
        function(checked) SFramesDB.charShowTitle = checked end,
        function()
            if SFrames.CharacterPanel and SFrames.CharacterPanel.UpdateCurrentTab then
                SFrames.CharacterPanel:UpdateCurrentTab()
            end
        end
    ))

    table.insert(controls, CreateCheckBox(charSection,
        "显示公会名", 12, -146,
        function() return SFramesDB.charShowGuild ~= false end,
        function(checked) SFramesDB.charShowGuild = checked end,
        function()
            if SFrames.CharacterPanel and SFrames.CharacterPanel.UpdateCurrentTab then
                SFrames.CharacterPanel:UpdateCurrentTab()
            end
        end
    ))

    table.insert(controls, CreateCheckBox(charSection,
        "显示装备品质光晕", 12, -174,
        function() return SFramesDB.charShowQualityGlow ~= false end,
        function(checked) SFramesDB.charShowQualityGlow = checked end,
        function()
            if SFrames.CharacterPanel and SFrames.CharacterPanel.UpdateCurrentTab then
                SFrames.CharacterPanel:UpdateCurrentTab()
            end
        end
    ))

    table.insert(controls, CreateCheckBox(charSection,
        "显示3D角色模型", 12, -202,
        function() return SFramesDB.charShowModel ~= false end,
        function(checked) SFramesDB.charShowModel = checked end,
        function()
            if SFrames.CharacterPanel and SFrames.CharacterPanel.UpdateCurrentTab then
                SFrames.CharacterPanel:UpdateCurrentTab()
            end
        end
    ))

    table.insert(controls, CreateCheckBox(charSection,
        "启用 Nanami 观察面板（需 /reload）", 12, -230,
        function() return SFramesDB.enableInspect ~= false end,
        function(checked) SFramesDB.enableInspect = checked end
    ))
    CreateDesc(charSection, "关闭后观察他人将使用原版界面", 36, -246, font)

    CreateLabel(charSection, "提示: 面板使用 C 键打开/关闭，按 ESC 关闭。", 14, -274, font, 10, 0.6, 0.6, 0.65)
    CreateLabel(charSection, "装备页下方可滚动查看全部角色属性。", 14, -292, font, 10, 0.6, 0.6, 0.65)

    -- Spell Book section
    local sbSection = CreateSection(root, "法术书", 8, -446, 524, 200, font)

    table.insert(controls, CreateSlider(sbSection,
        "法术书缩放", 14, -34, 240, 0.6, 1.5, 0.05,
        function() return SFramesDB.spellBookScale or 1.0 end,
        function(v) SFramesDB.spellBookScale = v end,
        function(v) return string.format("%.0f%%", v * 100) end,
        function()
            if SFramesSpellBookUI then
                SFramesSpellBookUI:SetScale(SFramesDB.spellBookScale or 1.0)
            end
        end
    ))

    table.insert(controls, CreateCheckBox(sbSection,
        "只显示最高等级法术", 14, -80,
        function() return SFramesDB.spellBookHighestOnly == true end,
        function(checked) SFramesDB.spellBookHighestOnly = checked end
    ))
    CreateDesc(sbSection, "隐藏同名法术的低等级版本，只保留最高等级", 36, -96, font)

    table.insert(controls, CreateCheckBox(sbSection,
        "学习新等级后自动替换动作条", 14, -116,
        function() return SFramesDB.spellBookAutoReplace == true end,
        function(checked) SFramesDB.spellBookAutoReplace = checked end
    ))
    CreateDesc(sbSection, "学习高等级法术时，自动替换动作条上的低等级版本", 36, -132, font)

    CreateLabel(sbSection, "提示: 法术书使用 P 键打开/关闭，支持鼠标滚轮翻页。", 14, -160, font, 10, 0.6, 0.6, 0.65)

    -- Social section
    local socialSection = CreateSection(root, "社交面板", 8, -660, 524, 120, font)

    table.insert(controls, CreateSlider(socialSection,
        "社交面板缩放", 14, -34, 240, 0.6, 1.5, 0.05,
        function() return SFramesDB.socialScale or 1.0 end,
        function(v) SFramesDB.socialScale = v end,
        function(v) return string.format("%.0f%%", v * 100) end,
        function()
            if SFramesSocialUI then
                SFramesSocialUI:SetScale(SFramesDB.socialScale or 1.0)
            end
        end
    ))

    CreateLabel(socialSection, "提示: 社交面板使用 O 键打开/关闭。", 14, -80, font, 10, 0.6, 0.6, 0.65)

    uiScroll:UpdateRange()
    self.charControls = controls
    self.charScroll = uiScroll
end

function SFrames.ConfigUI:BuildActionBarPage()
    local font = SFrames:GetFont()
    local page = self.actionBarPage
    local controls = {}

    local function RefreshAB()
        if SFrames.ActionBars and SFrames.ActionBars.ApplyConfig then
            SFrames.ActionBars:ApplyConfig()
        end
    end

    local uiScroll = CreateScrollArea(page, 4, -4, 548, 458, 1040)
    local root = uiScroll.child

    local abSection = CreateSection(root, "动作条", 8, -8, 520, 840, font)

    table.insert(controls, CreateCheckBox(abSection,
        "启用动作条接管（需 /reload 生效）", 12, -28,
        function() return SFramesDB.ActionBars.enable ~= false end,
        function(checked) SFramesDB.ActionBars.enable = checked end
    ))

    CreateLabel(abSection, "按键绑定：", 14, -56, font, 11, 0.85, 0.75, 0.80)

    CreateButton(abSection, "进入按键绑定模式", 14, -76, 160, 24, function()
        if SFrames.ActionBars and SFrames.ActionBars.EnterKeyBindMode then
            if self.frame then self.frame:Hide() end
            SFrames.ActionBars:EnterKeyBindMode()
        end
    end)

    CreateDesc(abSection,
        "悬停动作条/姿态栏/宠物栏按钮，按下键盘键/鼠标键(3-5)/滚轮绑定。右键清除。ESC 退出。也可用 /nui bind",
        14, -100, font, 480)

    table.insert(controls, CreateSlider(abSection, "按钮大小", 14, -130, 150, 24, 48, 1,
        function() return SFramesDB.ActionBars.buttonSize end,
        function(value) SFramesDB.ActionBars.buttonSize = value end,
        function(v) return tostring(math.floor(v + 0.5)) end,
        function() RefreshAB() end
    ))

    table.insert(controls, CreateSlider(abSection, "按钮间距", 180, -130, 150, 0, 8, 1,
        function() return SFramesDB.ActionBars.buttonGap end,
        function(value) SFramesDB.ActionBars.buttonGap = value end,
        function(v) return tostring(math.floor(v + 0.5)) end,
        function() RefreshAB() end
    ))

    table.insert(controls, CreateSlider(abSection, "整体缩放", 346, -130, 150, 0.5, 2.0, 0.05,
        function() return SFramesDB.ActionBars.scale end,
        function(value) SFramesDB.ActionBars.scale = value end,
        function(v) return string.format("%.2f", v) end,
        function() RefreshAB() end
    ))

    table.insert(controls, CreateSlider(abSection, "显示行数", 14, -192, 150, 1, 3, 1,
        function() return SFramesDB.ActionBars.barCount end,
        function(value) SFramesDB.ActionBars.barCount = value end,
        function(v) return tostring(math.floor(v + 0.5)) end,
        function() RefreshAB() end
    ))

    table.insert(controls, CreateSlider(abSection, "姿态/宠物栏按钮大小", 180, -192, 150, 16, 40, 1,
        function() return SFramesDB.ActionBars.smallBarSize end,
        function(value) SFramesDB.ActionBars.smallBarSize = value end,
        function(v) return tostring(math.floor(v + 0.5)) end,
        function() RefreshAB() end
    ))

    table.insert(controls, CreateSlider(abSection, "透明度", 14, -242, 150, 0.1, 1.0, 0.05,
        function() return SFramesDB.ActionBars.alpha or 1 end,
        function(value) SFramesDB.ActionBars.alpha = value end,
        function(v) return string.format("%.0f%%", v * 100) end,
        function(value)
            if SFrames.ActionBars then
                if SFrames.ActionBars.anchor then SFrames.ActionBars.anchor:SetAlpha(value) end
                if SFrames.ActionBars.rightHolder then SFrames.ActionBars.rightHolder:SetAlpha(value) end
                if SFrames.ActionBars.stanceHolder then SFrames.ActionBars.stanceHolder:SetAlpha(value) end
                if SFrames.ActionBars.petHolder then SFrames.ActionBars.petHolder:SetAlpha(value) end
            end
        end
    ))

    table.insert(controls, CreateCheckBox(abSection,
        "显示快捷键文字", 12, -300,
        function() return SFramesDB.ActionBars.showHotkey ~= false end,
        function(checked) SFramesDB.ActionBars.showHotkey = checked end,
        function() RefreshAB() end
    ))

    table.insert(controls, CreateCheckBox(abSection,
        "显示宏名称", 200, -300,
        function() return SFramesDB.ActionBars.showMacroName == true end,
        function(checked) SFramesDB.ActionBars.showMacroName = checked end,
        function() RefreshAB() end
    ))

    table.insert(controls, CreateCheckBox(abSection,
        "超距红色着色（技能超出射程时按钮变红）", 12, -328,
        function() return SFramesDB.ActionBars.rangeColoring ~= false end,
        function(checked) SFramesDB.ActionBars.rangeColoring = checked end
    ))

    table.insert(controls, CreateCheckBox(abSection,
        "显示宠物动作条", 12, -356,
        function() return SFramesDB.ActionBars.showPetBar ~= false end,
        function(checked) SFramesDB.ActionBars.showPetBar = checked end,
        function() RefreshAB() end
    ))

    table.insert(controls, CreateCheckBox(abSection,
        "显示姿态栏", 200, -356,
        function() return SFramesDB.ActionBars.showStanceBar ~= false end,
        function(checked) SFramesDB.ActionBars.showStanceBar = checked end,
        function() RefreshAB() end
    ))

    table.insert(controls, CreateCheckBox(abSection,
        "显示右侧动作条（两列竖向栏）", 12, -384,
        function() return SFramesDB.ActionBars.showRightBars ~= false end,
        function(checked) SFramesDB.ActionBars.showRightBars = checked end,
        function() RefreshAB() end
    ))

    table.insert(controls, CreateCheckBox(abSection,
        "始终显示动作条（空格子也显示背景框）", 12, -412,
        function() return SFramesDB.ActionBars.alwaysShowGrid == true end,
        function(checked) SFramesDB.ActionBars.alwaysShowGrid = checked end,
        function() RefreshAB() end
    ))

    table.insert(controls, CreateCheckBox(abSection,
        "按钮圆角", 12, -440,
        function() return SFramesDB.ActionBars.buttonRounded == true end,
        function(checked) SFramesDB.ActionBars.buttonRounded = checked end,
        function() RefreshAB() end
    ))

    table.insert(controls, CreateCheckBox(abSection,
        "按钮内阴影", 200, -440,
        function() return SFramesDB.ActionBars.buttonInnerShadow == true end,
        function(checked) SFramesDB.ActionBars.buttonInnerShadow = checked end,
        function() RefreshAB() end
    ))

    table.insert(controls, CreateCheckBox(abSection,
        "显示动作条狮鹫（在底部动作条两侧显示装饰狮鹫）", 12, -468,
        function() return SFramesDB.ActionBars.hideGryphon == false end,
        function(checked) SFramesDB.ActionBars.hideGryphon = not checked end,
        function() RefreshAB() end
    ))

    table.insert(controls, CreateCheckBox(abSection,
        "狮鹫置于动作条之上（否则在动作条之下）", 12, -496,
        function() return SFramesDB.ActionBars.gryphonOnTop == true end,
        function(checked) SFramesDB.ActionBars.gryphonOnTop = checked end,
        function() RefreshAB() end
    ))

    table.insert(controls, CreateSlider(abSection, "狮鹫宽度", 14, -550, 150, 24, 200, 2,
        function() return SFramesDB.ActionBars.gryphonWidth or 64 end,
        function(value) SFramesDB.ActionBars.gryphonWidth = value end,
        function(v) return tostring(math.floor(v + 0.5)) end,
        function() RefreshAB() end
    ))

    table.insert(controls, CreateSlider(abSection, "狮鹫高度", 180, -550, 150, 24, 200, 2,
        function() return SFramesDB.ActionBars.gryphonHeight or 64 end,
        function(value) SFramesDB.ActionBars.gryphonHeight = value end,
        function(v) return tostring(math.floor(v + 0.5)) end,
        function() RefreshAB() end
    ))

    CreateDesc(abSection, "使用布局模式 (/nui layout) 调整狮鹫位置", 14, -612, font, 480)

    -- 狮鹫样式选择器（带图例预览）
    CreateLabel(abSection, "狮鹫样式：", 14, -664, font, 11, 0.85, 0.75, 0.80)

    local GRYPHON_STYLES_UI = {
        { key = "dragonflight", label = "巨龙时代",
          tex = "Interface\\AddOns\\Nanami-UI\\img\\df-gryphon" },
        { key = "dragonflight_beta", label = "巨龙时代(Beta)",
          tex = "Interface\\AddOns\\Nanami-UI\\img\\df-gryphon-beta" },
        { key = "classic", label = "经典",
          tex = "Interface\\MainMenuBar\\UI-MainMenuBar-EndCap-Human" },
        { key = "cat", label = "猫",
          tex = "Interface\\AddOns\\Nanami-UI\\img\\cat" },
    }

    local styleBorders = {}
    local styleStartX = 14
    local styleY = -684

    for idx, style in ipairs(GRYPHON_STYLES_UI) do
        local xOff = styleStartX + (idx - 1) * 125

        -- 预览框
        local preview = CreateFrame("Frame", nil, abSection)
        preview:SetWidth(64)
        preview:SetHeight(64)
        preview:SetPoint("TOPLEFT", abSection, "TOPLEFT", xOff, styleY)

        -- 预览背景
        local bg = preview:CreateTexture(nil, "BACKGROUND")
        bg:SetAllPoints()
        bg:SetTexture(0, 0, 0, 0.4)

        -- 纹理预览
        local tex = preview:CreateTexture(nil, "ARTWORK")
        tex:SetAllPoints()
        tex:SetTexture(style.tex)

        -- 选中边框
        local border = CreateFrame("Frame", nil, preview)
        border:SetPoint("TOPLEFT", preview, "TOPLEFT", -2, 2)
        border:SetPoint("BOTTOMRIGHT", preview, "BOTTOMRIGHT", 2, -2)
        border:SetBackdrop({
            edgeFile = "Interface\\Buttons\\WHITE8X8",
            edgeSize = 2,
        })
        border:Hide()
        styleBorders[idx] = border

        -- 样式名标签
        local label = preview:CreateFontString(nil, "OVERLAY")
        label:SetFont(font, 10, "OUTLINE")
        label:SetPoint("TOP", preview, "BOTTOM", 0, -4)
        label:SetText(style.label)
        label:SetTextColor(0.7, 0.65, 0.7)

        -- 点击选择
        preview:EnableMouse(true)
        preview._sfStyleKey = style.key
        preview._sfIdx = idx
        preview:SetScript("OnMouseUp", function()
            SFramesDB.ActionBars.gryphonStyle = this._sfStyleKey
            for i, bd in ipairs(styleBorders) do
                if i == this._sfIdx then
                    bd:Show()
                    bd:SetBackdropBorderColor(1, 0.78, 0.2, 1)
                else
                    bd:Hide()
                end
            end
            RefreshAB()
        end)

        -- 悬停高亮
        preview:SetScript("OnEnter", function()
            local bd = styleBorders[this._sfIdx]
            if bd and not bd:IsShown() then
                bd:Show()
                bd:SetBackdropBorderColor(SOFT_THEME.panelBorder[1], SOFT_THEME.panelBorder[2], SOFT_THEME.panelBorder[3], 0.8)
            end
        end)
        preview:SetScript("OnLeave", function()
            local bd = styleBorders[this._sfIdx]
            local current = SFramesDB.ActionBars.gryphonStyle or "dragonflight"
            if bd and this._sfStyleKey ~= current then
                bd:Hide()
            end
        end)
    end

    -- 初始化选中边框
    local currentStyle = SFramesDB.ActionBars.gryphonStyle or "dragonflight"
    for idx, style in ipairs(GRYPHON_STYLES_UI) do
        if style.key == currentStyle then
            styleBorders[idx]:Show()
            styleBorders[idx]:SetBackdropBorderColor(1, 0.78, 0.2, 1)
        end
    end

    CreateLabel(abSection, "动作条位置：", 14, -778, font, 11, 0.85, 0.75, 0.80)
    CreateDesc(abSection, "使用 /nui layout 或右键聊天框 Nanami 标题进入布局模式调整动作条位置", 14, -798, font, 480)

    CreateLabel(abSection,
        "提示：启用/禁用动作条接管需要 /reload 才能生效。",
        14, -818, font, 10, 1, 0.92, 0.38)

    uiScroll:UpdateRange()
    self.actionBarControls = controls
    self.actionBarScroll = uiScroll
end

function SFrames.ConfigUI:BuildKeybindsPage()
    local font = SFrames:GetFont()
    local page = self.keybindsPage
    local controls = {}

    local uiScroll = CreateScrollArea(page, 4, -4, 548, 458, 660)
    local root = uiScroll.child

    -- Quick keybind mode entry
    local bindSection = CreateSection(root, "动作条按键绑定", 8, -8, 520, 80, font)

    CreateButton(bindSection, "进入按键绑定模式", 14, -30, 160, 24, function()
        if SFrames.ActionBars and SFrames.ActionBars.EnterKeyBindMode then
            if self.frame then self.frame:Hide() end
            SFrames.ActionBars:EnterKeyBindMode()
        end
    end)

    CreateDesc(bindSection,
        "悬停动作条/姿态栏/宠物栏按钮，按下键盘键/鼠标键(3-5)/滚轮绑定。右键清除。ESC 退出。",
        184, -32, font, 320)

    -- Profile management
    local kbSection = CreateSection(root, "按键绑定方案管理", 8, -98, 520, 520, font)

    CreateDesc(kbSection,
        "保存/加载所有按键绑定（包含动作条、移动、聊天、目标选择、菜单等全部设定），可在角色间共享方案。",
        14, -28, font, 490)

    -- Profile list container
    local listHolder = CreateFrame("Frame", "SFramesKBMListHolder", kbSection)
    listHolder:SetWidth(496)
    listHolder:SetHeight(192)
    listHolder:SetPoint("TOPLEFT", kbSection, "TOPLEFT", 12, -56)
    listHolder:SetBackdrop({
        bgFile   = "Interface\\Tooltips\\UI-Tooltip-Background",
        edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
        tile = true, tileSize = 16, edgeSize = 10,
        insets = { left = 3, right = 3, top = 3, bottom = 3 },
    })
    listHolder:SetBackdropColor(0.08, 0.06, 0.1, 0.85)
    listHolder:SetBackdropBorderColor(0.4, 0.35, 0.45, 0.8)

    local ROW_HEIGHT = 24
    local MAX_VISIBLE = 8
    local selectedProfile = nil
    local profileRows = {}

    local listScroll = CreateFrame("ScrollFrame", "SFramesKBMListScroll", listHolder)
    listScroll:SetPoint("TOPLEFT", listHolder, "TOPLEFT", 4, -4)
    listScroll:SetPoint("BOTTOMRIGHT", listHolder, "BOTTOMRIGHT", -4, 4)

    local listChild = CreateFrame("Frame", "SFramesKBMListChild", listScroll)
    listChild:SetWidth(488)
    listChild:SetHeight(ROW_HEIGHT * MAX_VISIBLE)
    listScroll:SetScrollChild(listChild)

    listScroll:EnableMouseWheel(true)
    listScroll:SetScript("OnMouseWheel", function()
        local cur = listScroll:GetVerticalScroll() or 0
        local maxS = listChild:GetHeight() - listScroll:GetHeight()
        if maxS < 0 then maxS = 0 end
        local newVal = cur - arg1 * ROW_HEIGHT
        if newVal < 0 then newVal = 0 end
        if newVal > maxS then newVal = maxS end
        listScroll:SetVerticalScroll(newVal)
    end)

    local emptyLabel = listChild:CreateFontString(nil, "OVERLAY")
    emptyLabel:SetFont(font, 11, "OUTLINE")
    emptyLabel:SetPoint("CENTER", listChild, "CENTER", 0, 0)
    emptyLabel:SetText("暂无保存的方案")
    emptyLabel:SetTextColor(0.5, 0.5, 0.5)

    local function RefreshProfileList()
        local KBM = SFrames.KeyBindManager
        if not KBM then return end
        local list = KBM:GetProfileList()

        for _, row in ipairs(profileRows) do
            row:Hide()
        end

        if table.getn(list) == 0 then
            emptyLabel:Show()
            listChild:SetHeight(ROW_HEIGHT * MAX_VISIBLE)
            return
        end
        emptyLabel:Hide()

        local totalH = table.getn(list) * ROW_HEIGHT
        if totalH < ROW_HEIGHT * MAX_VISIBLE then totalH = ROW_HEIGHT * MAX_VISIBLE end
        listChild:SetHeight(totalH)

        for idx, name in ipairs(list) do
            local row = profileRows[idx]
            if not row then
                row = CreateFrame("Button", nil, listChild)
                row:SetWidth(480)
                row:SetHeight(ROW_HEIGHT)
                row:EnableMouse(true)

                row.bg = row:CreateTexture(nil, "BACKGROUND")
                row.bg:SetAllPoints()
                row.bg:SetTexture("Interface\\Buttons\\WHITE8X8")
                row.bg:SetVertexColor(0.15, 0.12, 0.18, 0)

                row.nameText = row:CreateFontString(nil, "OVERLAY")
                row.nameText:SetFont(font, 11, "OUTLINE")
                row.nameText:SetPoint("LEFT", row, "LEFT", 8, 0)
                row.nameText:SetWidth(200)
                row.nameText:SetJustifyH("LEFT")

                row.infoText = row:CreateFontString(nil, "OVERLAY")
                row.infoText:SetFont(font, 9, "OUTLINE")
                row.infoText:SetPoint("RIGHT", row, "RIGHT", -8, 0)
                row.infoText:SetWidth(240)
                row.infoText:SetJustifyH("RIGHT")
                row.infoText:SetTextColor(0.6, 0.6, 0.65)

                row.highlight = row:CreateTexture(nil, "HIGHLIGHT")
                row.highlight:SetAllPoints()
                row.highlight:SetTexture("Interface\\Buttons\\WHITE8X8")
                row.highlight:SetVertexColor(0.4, 0.35, 0.5, 0.2)

                row:SetScript("OnClick", function()
                    selectedProfile = this.profileName
                    RefreshProfileList()
                end)

                profileRows[idx] = row
            end

            row:SetPoint("TOPLEFT", listChild, "TOPLEFT", 0, -(idx - 1) * ROW_HEIGHT)
            row.profileName = name

            local info = KBM:GetProfileInfo(name)
            row.nameText:SetText(name)

            local infoStr = ""
            if info then
                infoStr = info.charName .. "  |  " .. info.count .. " 条绑定"
                if info.timestamp and info.timestamp > 0 then
                    local d = date and date("%m/%d %H:%M", info.timestamp) or ""
                    if d ~= "" then infoStr = infoStr .. "  |  " .. d end
                end
            end
            row.infoText:SetText(infoStr)

            if selectedProfile == name then
                row.bg:SetVertexColor(0.3, 0.25, 0.45, 0.6)
                row.nameText:SetTextColor(1, 0.85, 0.4)
            else
                row.bg:SetVertexColor(0.15, 0.12, 0.18, (math.mod(idx, 2) == 0) and 0.25 or 0)
                row.nameText:SetTextColor(0.9, 0.88, 0.92)
            end

            row:Show()
        end
    end

    -- Buttons row 1: Save / Load / Delete / Rename
    local btnY1 = -256
    CreateButton(kbSection, "保存当前绑定", 12, btnY1, 120, 24, function()
        local KBM = SFrames.KeyBindManager
        if not KBM then return end
        KBM.ShowInputDialog("|cffffcc00保存按键绑定方案|r", "", function(name)
            KBM:SaveProfile(name)
            RefreshProfileList()
        end)
    end)

    CreateButton(kbSection, "加载方案", 142, btnY1, 100, 24, function()
        local KBM = SFrames.KeyBindManager
        if not KBM or not selectedProfile then
            SFrames:Print("请先在列表中选择一个方案")
            return
        end
        KBM.ShowConfirmDialog(
            "确定要加载方案 |cffffd100" .. selectedProfile .. "|r 吗？\n当前所有按键绑定将被替换。",
            function()
                KBM:LoadProfile(selectedProfile)
                RefreshProfileList()
            end)
    end)

    CreateButton(kbSection, "删除", 252, btnY1, 80, 24, function()
        local KBM = SFrames.KeyBindManager
        if not KBM or not selectedProfile then
            SFrames:Print("请先在列表中选择一个方案")
            return
        end
        KBM.ShowConfirmDialog(
            "确定要删除方案 |cffffd100" .. selectedProfile .. "|r 吗？",
            function()
                KBM:DeleteProfile(selectedProfile)
                selectedProfile = nil
                RefreshProfileList()
            end)
    end)

    CreateButton(kbSection, "重命名", 342, btnY1, 90, 24, function()
        local KBM = SFrames.KeyBindManager
        if not KBM or not selectedProfile then
            SFrames:Print("请先在列表中选择一个方案")
            return
        end
        local old = selectedProfile
        KBM.ShowInputDialog("|cffffcc00重命名方案|r", old, function(newName)
            local ok, err = KBM:RenameProfile(old, newName)
            if ok then
                selectedProfile = newName
            else
                SFrames:Print("|cffff4444重命名失败: " .. (err or "") .. "|r")
            end
            RefreshProfileList()
        end)
    end)

    -- Buttons row 2: Export / Import
    local btnY2 = -288
    CreateButton(kbSection, "导出当前绑定", 12, btnY2, 130, 24, function()
        local KBM = SFrames.KeyBindManager
        if KBM then KBM:ShowExportDialog() end
    end)

    CreateButton(kbSection, "导入绑定", 152, btnY2, 130, 24, function()
        local KBM = SFrames.KeyBindManager
        if KBM then KBM:ShowImportDialog() end
    end)

    CreateButton(kbSection, "导出选中方案", 292, btnY2, 130, 24, function()
        local KBM = SFrames.KeyBindManager
        if not KBM or not selectedProfile then
            SFrames:Print("请先在列表中选择一个方案")
            return
        end
        if not SFramesGlobalDB then SFramesGlobalDB = {} end
        if not SFramesGlobalDB.KeyBindProfiles then return end
        local profile = SFramesGlobalDB.KeyBindProfiles[selectedProfile]
        if profile and profile.bindings then
            local text = KBM:SerializeBindings(profile.bindings)
            KBM:ShowExportDialog()
            local ef = _G["SFramesKBMExport"]
            if ef and ef.edit then
                ef.edit:SetText(text)
                ef.edit:HighlightText()
            end
        end
    end)

    -- Info section
    CreateLabel(kbSection, "涵盖范围：", 14, -326, font, 10, 0.85, 0.75, 0.80)
    CreateDesc(kbSection,
        "移动按键 | 聊天按键 | 动作条快捷键 | 目标选择 | 界面面板 |\n宠物/姿态栏 | 镜头控制 | 多动作条 | 所有系统按键绑定",
        14, -340, font, 490)

    CreateLabel(kbSection, "使用说明：", 14, -380, font, 10, 0.85, 0.75, 0.80)
    CreateDesc(kbSection,
        "1. 点击「保存当前绑定」将当前所有按键设定存为方案\n2. 在列表中选中方案后点击「加载方案」恢复\n3. 导出会将绑定编码为字符串，可安全复制分享给其他玩家\n4. 方案存储在全局变量中，所有角色共享",
        14, -394, font, 490)

    CreateLabel(kbSection, "命令行：", 14, -452, font, 10, 0.85, 0.75, 0.80)
    CreateDesc(kbSection,
        "/nui keybinds save <名称> | /nui keybinds load <名称> | /nui keybinds list",
        14, -466, font, 490)

    RefreshProfileList()
    self.RefreshKBMList = RefreshProfileList

    uiScroll:UpdateRange()
    self.keybindsControls = controls
    self.keybindsScroll = uiScroll
end

function SFrames.ConfigUI:BuildMinimapPage()
    local font = SFrames:GetFont()
    local page = self.minimapPage
    local controls = {}

    local function RefreshMinimap()
        if SFrames.Minimap and SFrames.Minimap.Refresh then
            SFrames.Minimap:Refresh()
        end
    end

    local uiScroll = CreateScrollArea(page, 4, -4, 548, 458, 800)
    local root = uiScroll.child

    -- ══════════════════════════════════════════════════════════════
    -- 世界地图 & 迷雾揭示 (合并 · 置顶)
    -- ══════════════════════════════════════════════════════════════
    local wmSection = CreateSection(root, "世界地图", 8, -8, 520, 260, font)

    table.insert(controls, CreateCheckBox(wmSection,
        "启用全新世界地图界面", 14, -34,
        function() return SFramesDB.WorldMap.enabled == true end,
        function(checked)
            SFramesDB.WorldMap.enabled = checked
            if checked then SFramesDB.Tweaks.worldMapWindow = false end
        end
    ))
    CreateDesc(wmSection, "Nanami主题窗口化地图，隐藏原生装饰（需重载UI）", 36, -50, font)

    table.insert(controls, CreateCheckBox(wmSection,
        "启用导航地图", 270, -34,
        function()
            return SFramesDB.WorldMap.nav and SFramesDB.WorldMap.nav.enabled == true
        end,
        function(checked)
            if type(SFramesDB.WorldMap.nav) ~= "table" then
                SFramesDB.WorldMap.nav = { enabled = false, width = 350, alpha = 0.70, locked = false }
            end
            SFramesDB.WorldMap.nav.enabled = checked
            if SFrames.WorldMap and SFrames.WorldMap.ToggleNav then
                if (checked and not (NanamiNavMap and NanamiNavMap:IsVisible()))
                  or (not checked and NanamiNavMap and NanamiNavMap:IsVisible()) then
                    SFrames.WorldMap:ToggleNav()
                end
            end
        end
    ))
    CreateDesc(wmSection, "实时导航地图，/nui nav | 滚轮缩放 | Ctrl+滚轮透明度", 292, -50, font)

    -- 迷雾揭示 (合并在同一 section)
    CreateLabel(wmSection, "── 迷雾揭示 ──", 14, -76, font, 11, 0.70, 0.60, 0.65)

    table.insert(controls, CreateCheckBox(wmSection,
        "启用地图迷雾揭示", 14, -96,
        function() return SFramesDB.MapReveal.enabled ~= false end,
        function(checked)
            SFramesDB.MapReveal.enabled = checked
            if SFrames.MapReveal and SFrames.MapReveal.Refresh then
                SFrames.MapReveal:Refresh()
            end
        end
    ))
    CreateDesc(wmSection, "在世界地图上显示未探索区域（变暗显示），需要 LibMapOverlayData", 36, -112, font)

    table.insert(controls, CreateSlider(wmSection, "未探索区域亮度", 14, -140, 220, 0.2, 1.0, 0.05,
        function() return SFramesDB.MapReveal.unexploredAlpha or 0.7 end,
        function(value) SFramesDB.MapReveal.unexploredAlpha = value end,
        function(v) return string.format("%.0f%%", v * 100) end,
        function(value)
            if SFrames.MapReveal and SFrames.MapReveal.SetAlpha then
                SFrames.MapReveal:SetAlpha(value)
            end
        end
    ))

    CreateButton(wmSection, "扫描所有地图", 270, -148, 120, 24, function()
        if SFrames.MapReveal and SFrames.MapReveal.ScanAllMaps then
            SFrames.MapReveal:ScanAllMaps()
        else
            SFrames:Print("MapReveal 模块不可用")
        end
    end)
    CreateDesc(wmSection, "遍历所有大陆区域，发现新地图并自动补充迷雾数据", 292, -174, font)

    CreateButton(wmSection, "查看统计", 400, -148, 80, 24, function()
        if SFrames.MapReveal and SFrames.MapReveal.ShowStats then
            SFrames.MapReveal:ShowStats()
        else
            SFrames:Print("MapReveal 模块不可用")
        end
    end)

    CreateButton(wmSection, "导出数据", 488, -148, 80, 24, function()
        if SFrames.MapReveal and SFrames.MapReveal.ExportScannedData then
            SFrames.MapReveal:ExportScannedData()
        else
            SFrames:Print("MapReveal 模块不可用")
        end
    end)

    CreateLabel(wmSection, "浏览世界地图时自动发现并持久化覆盖层数据，扫描可批量发现已探索区域。", 14, -200, font, 10, 0.6, 0.6, 0.65)
    CreateDesc(wmSection, "命令: /nui mapscan  |  /nui mapstats  |  /nui mapreveal 切换", 14, -216, font)

    -- ══════════════════════════════════════════════════════════════
    -- 小地图 (移至下方)
    -- ══════════════════════════════════════════════════════════════
    local mmSection = CreateSection(root, "小地图", 8, -278, 520, 480, font)

    table.insert(controls, CreateCheckBox(mmSection,
        "启用 Nanami 小地图皮肤", 14, -36,
        function() return SFramesDB.Minimap.enabled ~= false end,
        function(checked) SFramesDB.Minimap.enabled = checked end
    ))
    CreateDesc(mmSection, "关闭后恢复默认小地图，需要 /reload 生效", 36, -52, font)

    table.insert(controls, CreateSlider(mmSection, "缩放", 14, -80, 220, 0.6, 2.0, 0.05,
        function() return SFramesDB.Minimap.scale or 1.0 end,
        function(value) SFramesDB.Minimap.scale = value end,
        function(v) return string.format("%.0f%%", v * 100) end,
        function() RefreshMinimap() end
    ))

    table.insert(controls, CreateCheckBox(mmSection,
        "显示游戏时钟", 14, -134,
        function() return SFramesDB.Minimap.showClock ~= false end,
        function(checked) SFramesDB.Minimap.showClock = checked end
    ))
    CreateDesc(mmSection, "在小地图下方显示服务器时间", 36, -150, font)

    table.insert(controls, CreateCheckBox(mmSection,
        "显示玩家坐标", 270, -134,
        function() return SFramesDB.Minimap.showCoords ~= false end,
        function(checked) SFramesDB.Minimap.showCoords = checked end
    ))
    CreateDesc(mmSection, "在小地图圆内底部显示当前坐标", 292, -150, font)

    CreateDesc(mmSection, "使用 /nui layout 或右键聊天框 Nanami 标题进入布局模式调整位置", 14, -182, font, 480)

    -- ── Shape selector (方形 / 圆形) ──────────────────────────────
    CreateLabel(mmSection, "地图形状：", 14, -210, font, 11, 0.85, 0.75, 0.80)

    local shapeKeys  = {"square1", "square2", "circle"}
    local shapeNames = {"方形·金", "方形·暗", "圆形"}
    local shapeBtns  = {}
    local shapeLbls  = {}

    for i = 1, 3 do
        local btn = CreateFrame("Button", nil, mmSection)
        btn:SetWidth(72)
        btn:SetHeight(22)
        btn:SetPoint("TOPLEFT", mmSection, "TOPLEFT", 100 + (i - 1) * 80, -208)
        btn:SetBackdrop({
            bgFile   = "Interface\\Tooltips\\UI-Tooltip-Background",
            edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
            tile = true, tileSize = 16, edgeSize = 8,
            insets = { left = 2, right = 2, top = 2, bottom = 2 },
        })
        local lbl = btn:CreateFontString(nil, "OVERLAY")
        lbl:SetFont(font, 10, "OUTLINE")
        lbl:SetPoint("CENTER", btn, "CENTER", 0, 0)
        lbl:SetText(shapeNames[i])
        btn._sfShapeKey = shapeKeys[i]
        shapeBtns[i] = btn
        shapeLbls[i] = lbl
    end

    CreateLabel(mmSection, "提示: 缩放、位置和样式修改后实时生效。", 14, -238, font, 10, 0.6, 0.6, 0.65)

    -- ── Circular style container (only visible when shape == "circle") ──
    local circleStyleFrame = CreateFrame("Frame", nil, mmSection)
    circleStyleFrame:SetPoint("TOPLEFT", mmSection, "TOPLEFT", 0, -256)
    circleStyleFrame:SetWidth(520)
    circleStyleFrame:SetHeight(220)

    CreateLabel(circleStyleFrame, "圆形边框样式：", 14, -4, font, 11, 0.85, 0.75, 0.80)

    local styles = SFrames.Minimap and SFrames.Minimap.MAP_STYLES or {}

    local autoBtn = CreateFrame("Button", nil, circleStyleFrame)
    autoBtn:SetWidth(100)
    autoBtn:SetHeight(18)
    autoBtn:SetPoint("TOPLEFT", circleStyleFrame, "TOPLEFT", 130, -2)
    autoBtn:SetBackdrop({
        bgFile   = "Interface\\Tooltips\\UI-Tooltip-Background",
        edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
        tile = true, tileSize = 16, edgeSize = 8,
        insets = { left = 2, right = 2, top = 2, bottom = 2 },
    })

    local autoLabel = autoBtn:CreateFontString(nil, "OVERLAY")
    autoLabel:SetFont(font, 10, "OUTLINE")
    autoLabel:SetPoint("CENTER", autoBtn, "CENTER", 0, 0)
    autoLabel:SetText("自动（匹配职业）")

    local function UpdateAutoBtn(isAuto)
        if isAuto then
            autoBtn:SetBackdropColor(0.2, 0.4, 0.2, 0.9)
            autoBtn:SetBackdropBorderColor(0.4, 0.8, 0.3, 1)
            autoLabel:SetTextColor(0.5, 1, 0.5)
        else
            autoBtn:SetBackdropColor(0.15, 0.15, 0.15, 0.7)
            autoBtn:SetBackdropBorderColor(0.4, 0.4, 0.4, 0.6)
            autoLabel:SetTextColor(0.6, 0.6, 0.6)
        end
    end

    local styleBorders = {}
    local cols = 5
    local cellW = 56
    local cellH = 56
    local gapX = 10
    local gapY = 18
    local styleStartX = 14
    local styleY = -26

    local function HighlightSelection()
        local current = SFramesDB.Minimap.mapStyle or "auto"
        local isAuto = (current == "auto")
        UpdateAutoBtn(isAuto)
        for i, bd in ipairs(styleBorders) do
            if (not isAuto) and styles[i] and styles[i].key == current then
                bd:Show()
                bd:SetBackdropBorderColor(1, 0.78, 0.2, 1)
            else
                bd:Hide()
            end
        end
    end

    for idx, style in ipairs(styles) do
        local col = math.mod(idx - 1, cols)
        local row = math.floor((idx - 1) / cols)
        local xOff = styleStartX + col * (cellW + gapX)
        local yOff = styleY - row * (cellH + gapY)

        local preview = CreateFrame("Frame", nil, circleStyleFrame)
        preview:SetWidth(cellW)
        preview:SetHeight(cellH)
        preview:SetPoint("TOPLEFT", circleStyleFrame, "TOPLEFT", xOff, yOff)

        local bg = preview:CreateTexture(nil, "BACKGROUND")
        bg:SetAllPoints()
        bg:SetTexture(0, 0, 0, 0.4)

        local ptex = preview:CreateTexture(nil, "ARTWORK")
        ptex:SetAllPoints()
        ptex:SetTexture(style.tex)

        local border = CreateFrame("Frame", nil, preview)
        border:SetPoint("TOPLEFT", preview, "TOPLEFT", -2, 2)
        border:SetPoint("BOTTOMRIGHT", preview, "BOTTOMRIGHT", 2, -2)
        border:SetBackdrop({ edgeFile = "Interface\\Buttons\\WHITE8X8", edgeSize = 2 })
        border:Hide()
        styleBorders[idx] = border

        local label = preview:CreateFontString(nil, "OVERLAY")
        label:SetFont(font, 9, "OUTLINE")
        label:SetPoint("TOP", preview, "BOTTOM", 0, -2)
        label:SetText(style.label)
        label:SetTextColor(0.7, 0.65, 0.7)

        preview:EnableMouse(true)
        preview._sfStyleKey = style.key
        preview._sfIdx = idx
        preview:SetScript("OnMouseUp", function()
            SFramesDB.Minimap.mapStyle = this._sfStyleKey
            HighlightSelection()
            RefreshMinimap()
        end)
        preview:SetScript("OnEnter", function()
            local bd = styleBorders[this._sfIdx]
            if bd and not bd:IsShown() then
                bd:Show()
                bd:SetBackdropBorderColor(SOFT_THEME.panelBorder[1], SOFT_THEME.panelBorder[2], SOFT_THEME.panelBorder[3], 0.8)
            end
        end)
        preview:SetScript("OnLeave", function()
            local bd = styleBorders[this._sfIdx]
            local current = SFramesDB.Minimap.mapStyle or "auto"
            if bd and this._sfStyleKey ~= current then
                bd:Hide()
            end
        end)
    end

    autoBtn:SetScript("OnClick", function()
        SFramesDB.Minimap.mapStyle = "auto"
        HighlightSelection()
        RefreshMinimap()
    end)

    HighlightSelection()

    -- Shape UI update: highlight active shape button, toggle circular style visibility
    local function UpdateShapeUI()
        local current = SFramesDB.Minimap.mapShape or "square1"
        for i = 1, 3 do
            if shapeKeys[i] == current then
                shapeBtns[i]:SetBackdropColor(0.2, 0.4, 0.2, 0.9)
                shapeBtns[i]:SetBackdropBorderColor(0.4, 0.8, 0.3, 1)
                shapeLbls[i]:SetTextColor(0.5, 1, 0.5)
            else
                shapeBtns[i]:SetBackdropColor(0.15, 0.15, 0.15, 0.7)
                shapeBtns[i]:SetBackdropBorderColor(0.4, 0.4, 0.4, 0.6)
                shapeLbls[i]:SetTextColor(0.6, 0.6, 0.6)
            end
        end
        if current == "circle" then
            circleStyleFrame:Show()
        else
            circleStyleFrame:Hide()
        end
    end

    for i = 1, 3 do
        shapeBtns[i]:SetScript("OnClick", function()
            SFramesDB.Minimap.mapShape = this._sfShapeKey
            UpdateShapeUI()
            RefreshMinimap()
        end)
    end

    UpdateShapeUI()

    uiScroll:UpdateRange()
    self.minimapControls = controls
    self.minimapScroll = uiScroll
end

--------------------------------------------------------------------------------
-- Buff / Debuff Page (standalone tab)
--------------------------------------------------------------------------------
function SFrames.ConfigUI:BuildBuffPage()
    local font = SFrames:GetFont()
    local page = self.buffPage
    local controls = {}

    local function RefreshBuffs()
        if SFrames.MinimapBuffs and SFrames.MinimapBuffs.Refresh then
            SFrames.MinimapBuffs:Refresh()
        end
    end

    local uiScroll = CreateScrollArea(page, 4, -4, 548, 458, 520)
    local root = uiScroll.child

    local buffSection = CreateSection(root, "Buff / Debuff 栏", 8, -8, 520, 420, font)

    -- Row 1: enable + show timer
    table.insert(controls, CreateCheckBox(buffSection,
        "启用 Buff / Debuff 栏", 14, -36,
        function() return SFramesDB.MinimapBuffs.enabled ~= false end,
        function(checked) SFramesDB.MinimapBuffs.enabled = checked end
    ))
    CreateDesc(buffSection, "替代暴雪默认 Buff 栏显示（需 /reload 生效）", 36, -52, font)

    table.insert(controls, CreateCheckBox(buffSection,
        "显示时间文本", 270, -36,
        function() return SFramesDB.MinimapBuffs.showTimer ~= false end,
        function(checked)
            SFramesDB.MinimapBuffs.showTimer = checked
            RefreshBuffs()
        end
    ))
    CreateDesc(buffSection, "图标下方显示剩余时间，永久 Buff 显示 N/A", 292, -52, font)

    -- Row 2: show debuffs
    table.insert(controls, CreateCheckBox(buffSection,
        "显示 Debuff", 14, -72,
        function() return SFramesDB.MinimapBuffs.showDebuffs ~= false end,
        function(checked)
            SFramesDB.MinimapBuffs.showDebuffs = checked
            RefreshBuffs()
        end
    ))
    CreateDesc(buffSection, "在 Buff 栏下方显示 Debuff（边框按类型着色）", 36, -88, font)

    -- Row 3: sliders - icon sizes
    table.insert(controls, CreateSlider(buffSection, "Buff 图标大小", 14, -114, 220, 20, 50, 1,
        function() return SFramesDB.MinimapBuffs.iconSize or 30 end,
        function(value) SFramesDB.MinimapBuffs.iconSize = value end,
        function(v) return tostring(math.floor(v + 0.5)) end,
        function() RefreshBuffs() end
    ))

    table.insert(controls, CreateSlider(buffSection, "Debuff 图标大小", 270, -114, 220, 20, 50, 1,
        function() return SFramesDB.MinimapBuffs.debuffIconSize or 30 end,
        function(value) SFramesDB.MinimapBuffs.debuffIconSize = value end,
        function(v) return tostring(math.floor(v + 0.5)) end,
        function() RefreshBuffs() end
    ))

    -- Row 4: per-row + spacing
    table.insert(controls, CreateSlider(buffSection, "每行图标数", 14, -176, 220, 4, 16, 1,
        function() return SFramesDB.MinimapBuffs.iconsPerRow or 8 end,
        function(value) SFramesDB.MinimapBuffs.iconsPerRow = value end,
        function(v) return tostring(math.floor(v + 0.5)) end,
        function() RefreshBuffs() end
    ))

    table.insert(controls, CreateSlider(buffSection, "间距", 270, -176, 220, 0, 8, 1,
        function() return SFramesDB.MinimapBuffs.spacing or 2 end,
        function(value) SFramesDB.MinimapBuffs.spacing = value end,
        function(v) return tostring(math.floor(v + 0.5)) end,
        function() RefreshBuffs() end
    ))

    -- Row 5: grow direction + anchor
    CreateLabel(buffSection, "增长方向：", 14, -240, font, 11, 0.85, 0.75, 0.80)
    CreateButton(buffSection, "向左", 100, -240, 70, 22, function()
        SFramesDB.MinimapBuffs.growDirection = "LEFT"
        RefreshBuffs()
    end)
    CreateButton(buffSection, "向右", 176, -240, 70, 22, function()
        SFramesDB.MinimapBuffs.growDirection = "RIGHT"
        RefreshBuffs()
    end)

    CreateLabel(buffSection, "锚点位置：", 270, -240, font, 11, 0.85, 0.75, 0.80)
    local posLabels = { TOPRIGHT = "右上", TOPLEFT = "左上", BOTTOMRIGHT = "右下", BOTTOMLEFT = "左下" }
    local posKeys = { "TOPRIGHT", "TOPLEFT", "BOTTOMRIGHT", "BOTTOMLEFT" }
    local posStartX = 356
    for _, key in ipairs(posKeys) do
        CreateButton(buffSection, posLabels[key], posStartX, -240, 40, 22, function()
            SFramesDB.MinimapBuffs.position = key
            RefreshBuffs()
        end)
        posStartX = posStartX + 42
    end

    -- Row 6: position hint
    CreateDesc(buffSection, "使用 /nui layout 进入布局模式调整 Buff 栏位置", 14, -286, font, 480)

    -- Row 7: action buttons
    CreateButton(buffSection, "重置默认位置", 14, -310, 120, 24, function()
        SFramesDB.MinimapBuffs.offsetX = 0
        SFramesDB.MinimapBuffs.offsetY = 0
        SFramesDB.MinimapBuffs.position = "TOPRIGHT"
        SFramesDB.MinimapBuffs.growDirection = "LEFT"
        if SFramesDB.Positions then SFramesDB.Positions["MinimapBuffs"] = nil end
        if SFramesDB.Positions then SFramesDB.Positions["MinimapDebuffs"] = nil end
        RefreshBuffs()
    end)

    local simBtn
    simBtn = CreateButton(buffSection, "模拟预览", 142, -310, 100, 24, function()
        if not SFrames.MinimapBuffs then return end
        if SFrames.MinimapBuffs._simulating then
            SFrames.MinimapBuffs:StopSimulation()
            simBtn:SetText("模拟预览")
        else
            SFrames.MinimapBuffs:SimulateBuffs()
            simBtn:SetText("停止模拟")
        end
    end)

    -- Row 8: tips
    CreateLabel(buffSection, "模拟预览：显示假 Buff / Debuff 以预览布局效果，不影响实际状态。", 14, -344, font, 9, 0.65, 0.58, 0.62)
    CreateLabel(buffSection, "Debuff 边框颜色: |cff3399ff魔法|r  |cff9900ff诅咒|r  |cff996600疾病|r  |cff009900毒药|r  |cffcc0000物理|r", 14, -360, font, 9, 0.65, 0.58, 0.62)

    CreateLabel(buffSection, "提示：启用/禁用 Buff 栏需要 /reload 才能生效。其他调整实时生效。", 14, -384, font, 10, 0.6, 0.6, 0.65)

    uiScroll:UpdateRange()
    self.buffControls = controls
    self.buffScroll = uiScroll
end

function SFrames.ConfigUI:ShowPage(mode)
    self.activePage = mode

    self.uiPage:Hide()
    self.playerPage:Hide()
    self.targetPage:Hide()
    self.bagPage:Hide()
    self.raidPage:Hide()
    self.partyPage:Hide()
    self.charPage:Hide()
    self.actionBarPage:Hide()
    self.keybindsPage:Hide()
    self.minimapPage:Hide()
    self.buffPage:Hide()
    self.personalizePage:Hide()
    self.themePage:Hide()

    local allTabs = { self.uiTab, self.playerTab, self.targetTab, self.partyTab, self.raidTab, self.bagTab, self.charTab, self.actionBarTab, self.keybindsTab, self.minimapTab, self.buffTab, self.personalizeTab, self.themeTab }
    for _, tab in ipairs(allTabs) do
        tab.sfSoftActive = false
        tab:Enable()
        tab:RefreshVisual()
    end

    self:EnsurePage(mode or "ui")

    if mode == "player" then
        self.playerPage:Show()
        self.playerTab.sfSoftActive = true
        self.playerTab:Disable()
        self.playerTab:RefreshVisual()
        self.title:SetText("Nanami-UI 设置 - 玩家框体")
        self:RefreshControls(self.playerControls)
    elseif mode == "target" then
        self.targetPage:Show()
        self.targetTab.sfSoftActive = true
        self.targetTab:Disable()
        self.targetTab:RefreshVisual()
        self.title:SetText("Nanami-UI 设置 - 目标框体")
        self:RefreshControls(self.targetControls)
    elseif mode == "bags" then
        self.bagPage:Show()
        self.bagTab.sfSoftActive = true
        self.bagTab:Disable()
        self.bagTab:RefreshVisual()
        self.title:SetText("Nanami-UI 设置 - 背包")
        self:RefreshControls(self.bagControls)
        if self.bagScroll and self.bagScroll.Reset then self.bagScroll:Reset() end
    elseif mode == "raid" then
        self.raidPage:Show()
        self.raidTab.sfSoftActive = true
        self.raidTab:Disable()
        self.raidTab:RefreshVisual()
        self.title:SetText("Nanami-UI 设置 - 团队")
        self:RefreshControls(self.raidControls)
        if self.raidScroll and self.raidScroll.Reset then self.raidScroll:Reset() end
    elseif mode == "party" then
        self.partyPage:Show()
        self.partyTab.sfSoftActive = true
        self.partyTab:Disable()
        self.partyTab:RefreshVisual()
        self.title:SetText("Nanami-UI 设置 - 小队")
        self:RefreshControls(self.partyControls)
        if self.partyScroll and self.partyScroll.Reset then self.partyScroll:Reset() end
    elseif mode == "char" then
        self.charPage:Show()
        self.charTab.sfSoftActive = true
        self.charTab:Disable()
        self.charTab:RefreshVisual()
        self.title:SetText("Nanami-UI 设置 - 人物面板")
        self:RefreshControls(self.charControls)
        if self.charScroll and self.charScroll.Reset then self.charScroll:Reset() end
    elseif mode == "actionbar" then
        self.actionBarPage:Show()
        self.actionBarTab.sfSoftActive = true
        self.actionBarTab:Disable()
        self.actionBarTab:RefreshVisual()
        self.title:SetText("Nanami-UI 设置 - 动作条")
        self:RefreshControls(self.actionBarControls)
        if self.actionBarScroll and self.actionBarScroll.Reset then self.actionBarScroll:Reset() end
    elseif mode == "keybinds" then
        self.keybindsPage:Show()
        self.keybindsTab.sfSoftActive = true
        self.keybindsTab:Disable()
        self.keybindsTab:RefreshVisual()
        self.title:SetText("Nanami-UI 设置 - 按键绑定方案")
        self:RefreshControls(self.keybindsControls)
        if self.keybindsScroll and self.keybindsScroll.Reset then self.keybindsScroll:Reset() end
        if self.RefreshKBMList then self.RefreshKBMList() end
    elseif mode == "minimap" then
        self.minimapPage:Show()
        self.minimapTab.sfSoftActive = true
        self.minimapTab:Disable()
        self.minimapTab:RefreshVisual()
        self.title:SetText("Nanami-UI 设置 - 地图设置")
        self:RefreshControls(self.minimapControls)
        if self.minimapScroll and self.minimapScroll.Reset then self.minimapScroll:Reset() end
    elseif mode == "buff" then
        self.buffPage:Show()
        self.buffTab.sfSoftActive = true
        self.buffTab:Disable()
        self.buffTab:RefreshVisual()
        self.title:SetText("Nanami-UI 设置 - Buff / Debuff")
        self:RefreshControls(self.buffControls)
        if self.buffScroll and self.buffScroll.Reset then self.buffScroll:Reset() end
    elseif mode == "personalize" then
        self.personalizePage:Show()
        self.personalizeTab.sfSoftActive = true
        self.personalizeTab:Disable()
        self.personalizeTab:RefreshVisual()
        self.title:SetText("Nanami-UI 设置 - 个性化")
        self:RefreshControls(self.personalizeControls)
        if self.personalizeScroll and self.personalizeScroll.Reset then self.personalizeScroll:Reset() end
    elseif mode == "theme" then
        self.themePage:Show()
        self.themeTab.sfSoftActive = true
        self.themeTab:Disable()
        self.themeTab:RefreshVisual()
        self.title:SetText("Nanami-UI 设置 - 主题")
        self:RefreshControls(self.themeControls)
        if self.themeScroll and self.themeScroll.Reset then self.themeScroll:Reset() end
    else
        self.uiPage:Show()
        self.uiTab.sfSoftActive = true
        self.uiTab:Disable()
        self.uiTab:RefreshVisual()
        self.title:SetText("Nanami-UI 设置 - 界面")
        self:RefreshControls(self.uiControls)
        if self.uiScroll and self.uiScroll.Reset then self.uiScroll:Reset() end
    end
end

function SFrames.ConfigUI:EnsureFrame()
    if self.frame then return end

    local font = SFrames:GetFont()

    local panel = CreateFrame("Frame", "SFramesConfigPanel", UIParent)
    panel:SetWidth(PANEL_WIDTH)
    panel:SetHeight(PANEL_HEIGHT)
    panel:SetPoint("CENTER", UIParent, "CENTER", 0, 0)
    panel:SetMovable(true)
    panel:EnableMouse(true)
    panel:SetClampedToScreen(true)
    panel:SetToplevel(true)
    panel:SetFrameStrata("TOOLTIP")
    panel:SetFrameLevel(100)
    panel:RegisterForDrag("LeftButton")
    panel:SetScript("OnDragStart", function() this:StartMoving() end)
    panel:SetScript("OnDragStop", function() this:StopMovingOrSizing() end)
    tinsert(UISpecialFrames, "SFramesConfigPanel")

    -- Apply the rounded "Roll UI" textured backdrop to the main settings panel
    if SFrames and SFrames.CreateRoundBackdrop then
        SFrames:CreateRoundBackdrop(panel)
        panel.sfSoftBackdrop = true
    else
        EnsureSoftBackdrop(panel)
    end
    
    if panel.SetBackdropColor then
        panel:SetBackdropColor(SOFT_THEME.panelBg[1], SOFT_THEME.panelBg[2], SOFT_THEME.panelBg[3], SOFT_THEME.panelBg[4])
    end
    if panel.SetBackdropBorderColor then
        panel:SetBackdropBorderColor(SOFT_THEME.panelBorder[1], SOFT_THEME.panelBorder[2], SOFT_THEME.panelBorder[3], SOFT_THEME.panelBorder[4])
    end

    local titleIco = SFrames:CreateIcon(panel, "logo", 18)
    titleIco:SetDrawLayer("OVERLAY")
    titleIco:SetPoint("TOP", panel, "TOP", -52, -14)
    titleIco:SetVertexColor(SOFT_THEME.title[1], SOFT_THEME.title[2], SOFT_THEME.title[3])

    local title = panel:CreateFontString(nil, "OVERLAY")
    title:SetFont(font, 14, "OUTLINE")
    title:SetPoint("LEFT", titleIco, "RIGHT", 5, 0)
    title:SetTextColor(SOFT_THEME.title[1], SOFT_THEME.title[2], SOFT_THEME.title[3])
    title:SetText("Nanami-UI 设置")

    local divider = panel:CreateTexture(nil, "ARTWORK")
    divider:SetWidth(1)
    divider:SetPoint("TOPLEFT", panel, "TOPLEFT", 120, -40)
    divider:SetPoint("BOTTOMLEFT", panel, "BOTTOMLEFT", 120, 10)
    divider:SetTexture(0.44, 0.42, 0.5, 0.9)

    local closeBtn = CreateFrame("Button", "SFramesConfigCloseBtn", panel)
    closeBtn:SetWidth(20)
    closeBtn:SetHeight(20)
    closeBtn:SetPoint("TOPRIGHT", panel, "TOPRIGHT", -8, -8)
    closeBtn:SetBackdrop({
        bgFile   = "Interface\\Tooltips\\UI-Tooltip-Background",
        edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
        tile = true, tileSize = 16, edgeSize = 14,
        insets = { left = 3, right = 3, top = 3, bottom = 3 },
    })
    local _cA = SOFT_THEME
    closeBtn:SetBackdropColor(_cA.buttonDownBg[1], _cA.buttonDownBg[2], _cA.buttonDownBg[3], _cA.buttonDownBg[4] or 0.85)
    closeBtn:SetBackdropBorderColor(_cA.btnBorder[1], _cA.btnBorder[2], _cA.btnBorder[3], _cA.btnBorder[4] or 0.7)
    local closeIco = SFrames:CreateIcon(closeBtn, "close", 14)
    closeIco:SetDrawLayer("OVERLAY")
    closeIco:SetPoint("CENTER", closeBtn, "CENTER", 0, 0)
    closeIco:SetVertexColor(1, 0.7, 0.7)
    closeBtn:SetScript("OnClick", function() SFrames.ConfigUI.frame:Hide() end)
    closeBtn:SetScript("OnEnter", function()
        this:SetBackdropColor(_cA.btnHoverBg[1], _cA.btnHoverBg[2], _cA.btnHoverBg[3], _cA.btnHoverBg[4] or 0.95)
        this:SetBackdropBorderColor(_cA.btnHoverBd[1], _cA.btnHoverBd[2], _cA.btnHoverBd[3], _cA.btnHoverBd[4] or 0.9)
    end)
    closeBtn:SetScript("OnLeave", function()
        this:SetBackdropColor(_cA.buttonDownBg[1], _cA.buttonDownBg[2], _cA.buttonDownBg[3], _cA.buttonDownBg[4] or 0.85)
        this:SetBackdropBorderColor(_cA.btnBorder[1], _cA.btnBorder[2], _cA.btnBorder[3], _cA.btnBorder[4] or 0.7)
    end)

    local tabUI = CreateFrame("Button", "SFramesConfigTabUI", panel, "UIPanelButtonTemplate")
    tabUI:SetWidth(100)
    tabUI:SetHeight(28)
    tabUI:SetPoint("TOPLEFT", panel, "TOPLEFT", 10, -50)
    tabUI:SetText("界面设置")
    tabUI:SetScript("OnClick", function() SFrames.ConfigUI:ShowPage("ui") end)
    StyleButton(tabUI)
    AddBtnIcon(tabUI, "settings", nil, "left")

    local tabPlayer = CreateFrame("Button", "SFramesConfigTabPlayer", panel, "UIPanelButtonTemplate")
    tabPlayer:SetWidth(100)
    tabPlayer:SetHeight(28)
    tabPlayer:SetPoint("TOP", tabUI, "BOTTOM", 0, -4)
    tabPlayer:SetText("玩家框体")
    tabPlayer:SetScript("OnClick", function() SFrames.ConfigUI:ShowPage("player") end)
    StyleButton(tabPlayer)
    AddBtnIcon(tabPlayer, "character", nil, "left")

    local tabTarget = CreateFrame("Button", "SFramesConfigTabTarget", panel, "UIPanelButtonTemplate")
    tabTarget:SetWidth(100)
    tabTarget:SetHeight(28)
    tabTarget:SetPoint("TOP", tabPlayer, "BOTTOM", 0, -4)
    tabTarget:SetText("目标框体")
    tabTarget:SetScript("OnClick", function() SFrames.ConfigUI:ShowPage("target") end)
    StyleButton(tabTarget)
    AddBtnIcon(tabTarget, "search", nil, "left")

    local tabParty = CreateFrame("Button", "SFramesConfigTabParty", panel, "UIPanelButtonTemplate")
    tabParty:SetWidth(100)
    tabParty:SetHeight(28)
    tabParty:SetPoint("TOP", tabTarget, "BOTTOM", 0, -4)
    tabParty:SetText("小队框架")
    tabParty:SetScript("OnClick", function() SFrames.ConfigUI:ShowPage("party") end)
    StyleButton(tabParty)
    AddBtnIcon(tabParty, "friends", nil, "left")

    local tabRaid = CreateFrame("Button", "SFramesConfigTabRaid", panel, "UIPanelButtonTemplate")
    tabRaid:SetWidth(100)
    tabRaid:SetHeight(28)
    tabRaid:SetPoint("TOP", tabParty, "BOTTOM", 0, -4)
    tabRaid:SetText("团队框架")
    tabRaid:SetScript("OnClick", function() SFrames.ConfigUI:ShowPage("raid") end)
    StyleButton(tabRaid)
    AddBtnIcon(tabRaid, "party", nil, "left")

    local tabBags = CreateFrame("Button", "SFramesConfigTabBags", panel, "UIPanelButtonTemplate")
    tabBags:SetWidth(100)
    tabBags:SetHeight(28)
    tabBags:SetPoint("TOP", tabRaid, "BOTTOM", 0, -4)
    tabBags:SetText("背包设置")
    tabBags:SetScript("OnClick", function() SFrames.ConfigUI:ShowPage("bags") end)
    StyleButton(tabBags)
    AddBtnIcon(tabBags, "backpack", nil, "left")

    local tabChar = CreateFrame("Button", "SFramesConfigTabChar", panel, "UIPanelButtonTemplate")
    tabChar:SetWidth(100)
    tabChar:SetHeight(28)
    tabChar:SetPoint("TOP", tabBags, "BOTTOM", 0, -4)
    tabChar:SetText("人物面板")
    tabChar:SetScript("OnClick", function() SFrames.ConfigUI:ShowPage("char") end)
    StyleButton(tabChar)
    AddBtnIcon(tabChar, "charsheet", nil, "left")

    local tabActionBar = CreateFrame("Button", "SFramesConfigTabActionBar", panel, "UIPanelButtonTemplate")
    tabActionBar:SetWidth(100)
    tabActionBar:SetHeight(28)
    tabActionBar:SetPoint("TOP", tabChar, "BOTTOM", 0, -4)
    tabActionBar:SetText("动作条")
    tabActionBar:SetScript("OnClick", function() SFrames.ConfigUI:ShowPage("actionbar") end)
    StyleButton(tabActionBar)
    AddBtnIcon(tabActionBar, "attack", nil, "left")

    local tabKeybinds = CreateFrame("Button", "SFramesConfigTabKeybinds", panel, "UIPanelButtonTemplate")
    tabKeybinds:SetWidth(100)
    tabKeybinds:SetHeight(28)
    tabKeybinds:SetPoint("TOP", tabActionBar, "BOTTOM", 0, -4)
    tabKeybinds:SetText("按键方案")
    tabKeybinds:SetScript("OnClick", function() SFrames.ConfigUI:ShowPage("keybinds") end)
    StyleButton(tabKeybinds)
    AddBtnIcon(tabKeybinds, "key", nil, "left")

    local tabMinimap = CreateFrame("Button", "SFramesConfigTabMinimap", panel, "UIPanelButtonTemplate")
    tabMinimap:SetWidth(100)
    tabMinimap:SetHeight(28)
    tabMinimap:SetPoint("TOP", tabKeybinds, "BOTTOM", 0, -4)
    tabMinimap:SetText("地图设置")
    tabMinimap:SetScript("OnClick", function() SFrames.ConfigUI:ShowPage("minimap") end)
    StyleButton(tabMinimap)
    AddBtnIcon(tabMinimap, "worldmap", nil, "left")

    local tabBuff = CreateFrame("Button", "SFramesConfigTabBuff", panel, "UIPanelButtonTemplate")
    tabBuff:SetWidth(100)
    tabBuff:SetHeight(28)
    tabBuff:SetPoint("TOP", tabMinimap, "BOTTOM", 0, -4)
    tabBuff:SetText("Buff栏")
    tabBuff:SetScript("OnClick", function() SFrames.ConfigUI:ShowPage("buff") end)
    StyleButton(tabBuff)
    AddBtnIcon(tabBuff, "buff", nil, "left")

    local tabPersonalize = CreateFrame("Button", "SFramesConfigTabPersonalize", panel, "UIPanelButtonTemplate")
    tabPersonalize:SetWidth(100)
    tabPersonalize:SetHeight(28)
    tabPersonalize:SetPoint("TOP", tabBuff, "BOTTOM", 0, -4)
    tabPersonalize:SetText("个性化")
    tabPersonalize:SetScript("OnClick", function() SFrames.ConfigUI:ShowPage("personalize") end)
    StyleButton(tabPersonalize)
    AddBtnIcon(tabPersonalize, "potion", nil, "left")

    local tabTheme = CreateFrame("Button", "SFramesConfigTabTheme", panel, "UIPanelButtonTemplate")
    tabTheme:SetWidth(100)
    tabTheme:SetHeight(28)
    tabTheme:SetPoint("TOP", tabPersonalize, "BOTTOM", 0, -4)
    tabTheme:SetText("主题设置")
    tabTheme:SetScript("OnClick", function() SFrames.ConfigUI:ShowPage("theme") end)
    StyleButton(tabTheme)
    AddBtnIcon(tabTheme, "star", nil, "left")

    local content = CreateFrame("Frame", "SFramesConfigContentMain", panel)
    content:SetWidth(PANEL_WIDTH - 140)
    content:SetHeight(PANEL_HEIGHT - 60)
    content:SetPoint("TOPLEFT", panel, "TOPLEFT", 130, -50)

    local uiPage = CreateFrame("Frame", "SFramesConfigUIPage", content)
    uiPage:SetAllPoints(content)

    local playerPage = CreateFrame("Frame", "SFramesConfigPlayerPage", content)
    playerPage:SetAllPoints(content)

    local targetPage = CreateFrame("Frame", "SFramesConfigTargetPage", content)
    targetPage:SetAllPoints(content)

    local bagPage = CreateFrame("Frame", "SFramesConfigBagPage", content)
    bagPage:SetAllPoints(content)

    local raidPage = CreateFrame("Frame", "SFramesConfigRaidPage", content)
    raidPage:SetAllPoints(content)

    local partyPage = CreateFrame("Frame", "SFramesConfigPartyPage", content)
    partyPage:SetAllPoints(content)

    local charPage = CreateFrame("Frame", "SFramesConfigCharPage", content)
    charPage:SetAllPoints(content)

    local actionBarPage = CreateFrame("Frame", "SFramesConfigActionBarPage", content)
    actionBarPage:SetAllPoints(content)

    local keybindsPage = CreateFrame("Frame", "SFramesConfigKeybindsPage", content)
    keybindsPage:SetAllPoints(content)

    local minimapPage = CreateFrame("Frame", "SFramesConfigMinimapPage", content)
    minimapPage:SetAllPoints(content)

    local buffPage = CreateFrame("Frame", "SFramesConfigBuffPage", content)
    buffPage:SetAllPoints(content)

    local personalizePage = CreateFrame("Frame", "SFramesConfigPersonalizePage", content)
    personalizePage:SetAllPoints(content)

    local themePage = CreateFrame("Frame", "SFramesConfigThemePage", content)
    themePage:SetAllPoints(content)

    self.frame = panel
    self.title = title
    self.uiTab = tabUI
    self.playerTab = tabPlayer
    self.targetTab = tabTarget
    self.bagTab = tabBags
    self.raidTab = tabRaid
    self.partyTab = tabParty
    self.charTab = tabChar
    self.actionBarTab = tabActionBar
    self.keybindsTab = tabKeybinds
    self.minimapTab = tabMinimap
    self.buffTab = tabBuff
    self.personalizeTab = tabPersonalize
    self.themeTab = tabTheme
    self.content = content
    self.uiPage = uiPage
    self.playerPage = playerPage
    self.targetPage = targetPage
    self.bagPage = bagPage
    self.raidPage = raidPage
    self.partyPage = partyPage
    self.charPage = charPage
    self.actionBarPage = actionBarPage
    self.keybindsPage = keybindsPage
    self.minimapPage = minimapPage
    self.buffPage = buffPage
    self.personalizePage = personalizePage
    self.themePage = themePage

    local btnSaveReload = CreateFrame("Button", "SFramesConfigSaveReloadBtn", panel, "UIPanelButtonTemplate")
    btnSaveReload:SetWidth(100)
    btnSaveReload:SetHeight(26)
    btnSaveReload:SetPoint("BOTTOMRIGHT", panel, "BOTTOMRIGHT", -15, 12)
    btnSaveReload:SetText("保存并重载")
    btnSaveReload:SetScript("OnClick", function() ReloadUI() end)
    StyleButton(btnSaveReload)
    AddBtnIcon(btnSaveReload, "save")

    local btnConfirm = CreateFrame("Button", "SFramesConfigConfirmBtn", panel, "UIPanelButtonTemplate")
    btnConfirm:SetWidth(80)
    btnConfirm:SetHeight(26)
    btnConfirm:SetPoint("RIGHT", btnSaveReload, "LEFT", -6, 0)
    btnConfirm:SetText("确认")
    btnConfirm:SetScript("OnClick", function() SFrames.ConfigUI.frame:Hide() end)
    StyleButton(btnConfirm)
    AddBtnIcon(btnConfirm, "close")

    self._pageBuilt = {}
end

function SFrames.ConfigUI:BuildPersonalizePage()
    local font = SFrames:GetFont()
    local page = self.personalizePage
    local controls = {}

    local personalizeScroll = CreateScrollArea(page, 4, -4, 548, 458, 620)
    local root = personalizeScroll.child

    -- ── 升级训练师提醒 ──────────────────────────────────────────
    local trainerSection = CreateSection(root, "升级技能提醒", 8, -8, 520, 100, font)

    table.insert(controls, CreateCheckBox(trainerSection,
        "升级时提醒可学习的新技能", 14, -34,
        function() return SFramesDB.trainerReminder ~= false end,
        function(checked) SFramesDB.trainerReminder = checked end
    ))
    CreateDesc(trainerSection, "升级后如果有新的职业技能可学习，将在屏幕和聊天窗口显示技能列表提醒", 36, -50, font, 340)

    CreateButton(trainerSection, "模拟提醒", 370, -34, 120, 22, function()
        if SFrames.Player and SFrames.Player.ShowTrainerReminder then
            local testLevel = UnitLevel("player")
            if mod(testLevel, 2) ~= 0 then testLevel = testLevel + 1 end
            local _, classEn = UnitClass("player")
            local cachedSkills = SFramesDB and SFramesDB.trainerCache and SFramesDB.trainerCache[classEn]
            local staticSkills = SFrames.ClassSkillData and SFrames.ClassSkillData[classEn]
            local function hasData(lv)
                if cachedSkills then
                    if cachedSkills[lv] then return true end
                    if lv > 1 and cachedSkills[lv - 1] then return true end
                end
                if staticSkills and staticSkills[lv] then return true end
                return false
            end
            if not hasData(testLevel) then
                while testLevel <= 60 and not hasData(testLevel) do
                    testLevel = testLevel + 2
                end
                if testLevel > 60 then testLevel = 60 end
            end
            SFrames.Player:ShowTrainerReminder(testLevel)
        end
    end)
    CreateDesc(trainerSection, "点击按钮预览升级提醒效果", 392, -55, font, 100)

    -- ── 鼠标提示框 ────────────────────────────────────────────────
    local tooltipSection = CreateSection(root, "鼠标提示框", 8, -118, 520, 160, font)
    CreateDesc(tooltipSection, "选择游戏提示框的显示位置方式（三选一）", 14, -28, font)

    local function RefreshTooltipMode()
        if SFrames.FloatingTooltip and SFrames.FloatingTooltip.ToggleAnchor then
            SFrames.FloatingTooltip:ToggleAnchor(SFramesDB.tooltipMode == "CUSTOM")
        end
        if SFrames.ConfigUI.tooltipCBs then
            for _, cb in ipairs(SFrames.ConfigUI.tooltipCBs) do cb:Refresh() end
        end
    end
    SFrames.ConfigUI.tooltipCBs = {}

    local cbDefault = CreateCheckBox(tooltipSection,
        "系统默认位置", 14, -46,
        function() return (SFramesDB.tooltipMode == "DEFAULT" or SFramesDB.tooltipMode == nil) end,
        function(checked) if checked then SFramesDB.tooltipMode = "DEFAULT" RefreshTooltipMode() end end
    )
    table.insert(SFrames.ConfigUI.tooltipCBs, cbDefault)
    table.insert(controls, cbDefault)

    local cbCursor = CreateCheckBox(tooltipSection,
        "跟随鼠标（鼠标右下方）", 14, -72,
        function() return SFramesDB.tooltipMode == "CURSOR" end,
        function(checked) if checked then SFramesDB.tooltipMode = "CURSOR" RefreshTooltipMode() end end
    )
    table.insert(SFrames.ConfigUI.tooltipCBs, cbCursor)
    table.insert(controls, cbCursor)

    local cbCustom = CreateCheckBox(tooltipSection,
        "自定义固定位置", 270, -46,
        function() return SFramesDB.tooltipMode == "CUSTOM" end,
        function(checked) if checked then SFramesDB.tooltipMode = "CUSTOM" RefreshTooltipMode() end end
    )
    table.insert(SFrames.ConfigUI.tooltipCBs, cbCustom)
    table.insert(controls, cbCustom)
    CreateDesc(tooltipSection, "启用后屏幕上会出现绿色锚点，拖动到目标位置后锁定", 292, -62, font)

    table.insert(controls, CreateSlider(tooltipSection,
        "提示框缩放", 14, -100, 200, 0.5, 2.0, 0.05,
        function() return SFramesDB.tooltipScale or 1.0 end,
        function(value)
            SFramesDB.tooltipScale = value
            if SFrames.FloatingTooltip and SFrames.FloatingTooltip.ApplyScale then
                SFrames.FloatingTooltip:ApplyScale()
            end
        end,
        function(v) return string.format("%.0f%%", v * 100) end
    ))

    -- ── AFK 待机动画 ──────────────────────────────────────────────
    local afkSection = CreateSection(root, "AFK 待机动画", 8, -288, 520, 146, font)

    table.insert(controls, CreateCheckBox(afkSection,
        "启用 AFK 待机画面", 14, -34,
        function() return SFramesDB.afkEnabled ~= false end,
        function(checked) SFramesDB.afkEnabled = checked end
    ))
    CreateDesc(afkSection, "进入暂离状态后显示全屏待机画面（角色跳舞 + 信息面板）", 36, -50, font)

    table.insert(controls, CreateCheckBox(afkSection,
        "在非休息区也启用", 14, -70,
        function() return SFramesDB.afkOutsideRest == true end,
        function(checked) SFramesDB.afkOutsideRest = checked end
    ))
    CreateDesc(afkSection, "默认仅在旅店/主城等休息区触发，勾选后在任何区域都会触发", 36, -86, font)

    table.insert(controls, CreateSlider(afkSection,
        "AFK 延迟（分钟）", 14, -116, 200, 0, 15, 1,
        function() return SFramesDB.afkDelay or 5 end,
        function(v) SFramesDB.afkDelay = v end,
        function(v) if v == 0 then return "立即" end return v .. " 分钟" end
    ))
    CreateDesc(afkSection, "进入暂离后等待多久才显示待机画面", 240, -116, font)

    CreateButton(afkSection, "预览待机画面", 370, -34, 120, 22, function()
        if SFrames.AFKScreen and SFrames.AFKScreen.Toggle then
            SFrames.AFKScreen._manualTrigger = true
            SFrames.AFKScreen:Show()
        end
    end)

    personalizeScroll:UpdateRange()
    self.personalizeControls = controls
    self.personalizeScroll = personalizeScroll
end

function SFrames.ConfigUI:BuildThemePage()
    local font = SFrames:GetFont()
    local page = self.themePage
    local controls = {}

    local themeScroll = CreateScrollArea(page, 4, -4, 548, 458, 680)
    local root = themeScroll.child

    ---------------------------------------------------------------------------
    -- Section 1: Theme color selection (regular + class presets)
    ---------------------------------------------------------------------------
    local sec1 = CreateSection(root, "主题色", 8, -8, 520, 340, font)

    CreateDesc(sec1, "选择主题色后界面全局配色将跟随变化，重载 UI 后完全生效。",
        14, -34, font, 480)

    local SWATCH_SIZE = 32
    local SWATCH_GAP  = 8
    local SWATCH_ROW  = SWATCH_SIZE + SWATCH_GAP + 14
    local presets = SFrames.Theme.Presets
    local order   = SFrames.Theme.PresetOrder
    local swatches = {}

    local function CreateSwatchRow(parent, presetList, startY)
        for idx = 1, table.getn(presetList) do
            local key = presetList[idx]
            local preset = presets[key]
            if preset then
                local col = (idx - 1) - math.floor((idx - 1) / 5) * 5
                local row = math.floor((idx - 1) / 5)
                local sx = 14 + col * (SWATCH_SIZE + SWATCH_GAP)
                local sy = startY + row * -SWATCH_ROW

                local swatch = CreateFrame("Button", NextWidgetName("Swatch"), parent)
                swatch:SetWidth(SWATCH_SIZE)
                swatch:SetHeight(SWATCH_SIZE)
                swatch:SetPoint("TOPLEFT", parent, "TOPLEFT", sx, sy)

                swatch:SetBackdrop({
                    bgFile = "Interface\\Buttons\\WHITE8X8",
                    edgeFile = "Interface\\Buttons\\WHITE8X8",
                    tile = false, edgeSize = 2,
                    insets = { left = 2, right = 2, top = 2, bottom = 2 },
                })

                local r, g, b
                if preset.swatchRGB then
                    r, g, b = preset.swatchRGB[1], preset.swatchRGB[2], preset.swatchRGB[3]
                else
                    r, g, b = SFrames.Theme.HSVtoRGB(preset.hue, 0.50 * (preset.satMul or 1), 0.75)
                end
                swatch:SetBackdropColor(r, g, b, 1)
                swatch:SetBackdropBorderColor(0.25, 0.25, 0.25, 1)

                local nameFs = swatch:CreateFontString(nil, "OVERLAY")
                nameFs:SetFont(font, 8, "OUTLINE")
                nameFs:SetPoint("TOP", swatch, "BOTTOM", 0, -1)
                nameFs:SetText(preset.name)
                nameFs:SetTextColor(0.75, 0.75, 0.75)

                swatch.presetKey = key
                swatch.selectFrame = CreateFrame("Frame", nil, swatch)
                swatch.selectFrame:SetPoint("TOPLEFT", swatch, "TOPLEFT", -4, 4)
                swatch.selectFrame:SetPoint("BOTTOMRIGHT", swatch, "BOTTOMRIGHT", 4, -4)
                swatch.selectFrame:SetBackdrop({
                    edgeFile = "Interface\\Buttons\\WHITE8X8",
                    edgeSize = 2,
                })
                swatch.selectFrame:SetBackdropBorderColor(1, 0.85, 0, 1)
                swatch.selectFrame:Hide()

                swatch.selectGlow = swatch.selectFrame:CreateTexture(nil, "BACKGROUND")
                swatch.selectGlow:SetTexture("Interface\\Buttons\\WHITE8X8")
                swatch.selectGlow:SetAllPoints(swatch.selectFrame)
                swatch.selectGlow:SetVertexColor(1, 0.85, 0, 0.15)

                swatch:SetScript("OnClick", function()
                    EnsureDB()
                    SFramesDB.Theme.preset = this.presetKey
                    SFramesDB.Theme.useClassTheme = false
                    SFrames.Theme:Apply(this.presetKey)
                    SFrames.ConfigUI:RefreshThemeSwatches()
                    SFrames.ConfigUI:RefreshThemePreview()
                    PlaySound("igMainMenuOptionCheckBoxOn")
                end)
                swatch:SetScript("OnEnter", function()
                    this:SetBackdropBorderColor(0.8, 0.8, 0.8, 1)
                end)
                swatch:SetScript("OnLeave", function()
                    local active = SFramesDB and SFramesDB.Theme and SFramesDB.Theme.preset
                    local useClass = SFramesDB and SFramesDB.Theme and SFramesDB.Theme.useClassTheme
                    if useClass then active = SFrames.Theme:GetCurrentPreset() end
                    if this.presetKey == active then
                        this:SetBackdropBorderColor(1, 0.85, 0, 1)
                    else
                        this:SetBackdropBorderColor(0.25, 0.25, 0.25, 1)
                    end
                end)

                table.insert(swatches, swatch)
            end
        end
    end

    CreateSwatchRow(sec1, order, -52)

    local classLabel = sec1:CreateFontString(nil, "OVERLAY")
    classLabel:SetFont(font, 10, "OUTLINE")
    classLabel:SetPoint("TOPLEFT", sec1, "TOPLEFT", 14, -164)
    classLabel:SetText("职业色")
    classLabel:SetTextColor(0.85, 0.85, 0.85)

    local divLine = sec1:CreateTexture(nil, "ARTWORK")
    divLine:SetTexture("Interface\\Buttons\\WHITE8X8")
    divLine:SetHeight(1)
    divLine:SetPoint("TOPLEFT", classLabel, "BOTTOMLEFT", 0, -3)
    divLine:SetPoint("RIGHT", sec1, "RIGHT", -14, 0)
    divLine:SetVertexColor(0.35, 0.35, 0.40, 0.6)

    CreateSwatchRow(sec1, SFrames.Theme.ClassPresetOrder, -182)

    local classCheck = CreateCheckBox(sec1,
        "跟随当前职业自动切换", 14, -296,
        function() return SFramesDB and SFramesDB.Theme and SFramesDB.Theme.useClassTheme end,
        function(checked)
            EnsureDB()
            SFramesDB.Theme.useClassTheme = checked
            if checked then
                SFrames.Theme:Apply(SFrames.Theme:GetCurrentPreset())
            else
                SFrames.Theme:Apply(SFramesDB.Theme.preset or "pink")
            end
            SFrames.ConfigUI:RefreshThemeSwatches()
            SFrames.ConfigUI:RefreshThemePreview()
        end
    )
    table.insert(controls, classCheck)

    self._themeSwatches = swatches

    ---------------------------------------------------------------------------
    -- Section 3: Icon set picker
    ---------------------------------------------------------------------------
    local sec3 = CreateSection(root, "图标风格", 8, -356, 520, 110, font)

    CreateDesc(sec3, "选择图标风格后需重载 UI 以完全生效。",
        14, -34, font, 480)

    local ICON_SETS = { "icon", "icon2", "icon3", "icon4", "icon5", "icon6", "icon7", "icon8" }
    local ICON_SET_SIZE = 48
    local ICON_SET_GAP  = 10
    local faction = UnitFactionGroup and UnitFactionGroup("player") or "Alliance"
    local factionKey = (faction == "Horde") and "horde" or "alliance"
    local factionCoords = SFrames.ICON_TCOORDS and SFrames.ICON_TCOORDS[factionKey]
    if not factionCoords then
        factionCoords = (factionKey == "horde")
            and { 0.75, 0.875, 0, 0.125 }
            or  { 0.625, 0.75, 0, 0.125 }
    end

    local iconSetBtns = {}
    for idx = 1, table.getn(ICON_SETS) do
        local setKey = ICON_SETS[idx]
        local texPath = "Interface\\AddOns\\Nanami-UI\\img\\" .. setKey
        local sx = 14 + (idx - 1) * (ICON_SET_SIZE + ICON_SET_GAP)

        local btn = CreateFrame("Button", NextWidgetName("IcoSet"), sec3)
        btn:SetWidth(ICON_SET_SIZE)
        btn:SetHeight(ICON_SET_SIZE)
        btn:SetPoint("TOPLEFT", sec3, "TOPLEFT", sx, -50)

        btn:SetBackdrop({
            bgFile   = "Interface\\Tooltips\\UI-Tooltip-Background",
            edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
            tile = true, tileSize = 16, edgeSize = 10,
            insets = { left = 2, right = 2, top = 2, bottom = 2 },
        })
        btn:SetBackdropColor(0.08, 0.08, 0.1, 0.85)
        btn:SetBackdropBorderColor(0.3, 0.3, 0.35, 1)

        local preview = btn:CreateTexture(nil, "ARTWORK")
        preview:SetTexture(texPath)
        preview:SetTexCoord(factionCoords[1], factionCoords[2], factionCoords[3], factionCoords[4])
        preview:SetPoint("CENTER", btn, "CENTER", 0, 0)
        preview:SetWidth(ICON_SET_SIZE - 8)
        preview:SetHeight(ICON_SET_SIZE - 8)

        btn.setKey = setKey
        btn.selectBorder = CreateFrame("Frame", nil, btn)
        btn.selectBorder:SetPoint("TOPLEFT", btn, "TOPLEFT", -2, 2)
        btn.selectBorder:SetPoint("BOTTOMRIGHT", btn, "BOTTOMRIGHT", 2, -2)
        btn.selectBorder:SetBackdrop({ edgeFile = "Interface\\Buttons\\WHITE8X8", edgeSize = 2 })
        btn.selectBorder:SetBackdropBorderColor(1, 1, 1, 1)
        btn.selectBorder:Hide()

        btn:SetScript("OnClick", function()
            EnsureDB()
            SFramesDB.Theme.iconSet = this.setKey
            SFrames.ConfigUI:RefreshIconSetButtons()
            if SFrames.MinimapButton and SFrames.MinimapButton.Refresh then
                SFrames.MinimapButton:Refresh()
            end
            PlaySound("igMainMenuOptionCheckBoxOn")
        end)
        btn:SetScript("OnEnter", function()
            this:SetBackdropBorderColor(0.7, 0.7, 0.7, 1)
            GameTooltip:SetOwner(this, "ANCHOR_NONE")
            GameTooltip:ClearAllPoints()
            GameTooltip:SetPoint("BOTTOM", this, "TOP", 0, 6)
            local num = this.setKey == "icon" and "1" or string.sub(this.setKey, 5)
            GameTooltip:SetText("图标风格 " .. num .. (this.setKey == "icon" and " (默认)" or ""))
            GameTooltip:AddLine("点击选择，重载 UI 后生效", 0.7, 0.7, 0.7)
            GameTooltip:Show()
            GameTooltip:SetFrameStrata("TOOLTIP")
            GameTooltip:Raise()
        end)
        btn:SetScript("OnLeave", function()
            local cur = SFramesDB and SFramesDB.Theme and SFramesDB.Theme.iconSet or "icon"
            if this.setKey == cur then
                this:SetBackdropBorderColor(1, 0.85, 0.6, 1)
            else
                this:SetBackdropBorderColor(0.3, 0.3, 0.35, 1)
            end
            GameTooltip:Hide()
        end)

        table.insert(iconSetBtns, btn)
    end
    self._iconSetBtns = iconSetBtns
    self:RefreshIconSetButtons()

    ---------------------------------------------------------------------------
    -- Section 4: Preview
    ---------------------------------------------------------------------------
    local sec4 = CreateSection(root, "主题预览", 8, -478, 520, 180, font)

    local previewPanel = CreateFrame("Frame", NextWidgetName("Preview"), sec4)
    previewPanel:SetWidth(490)
    previewPanel:SetHeight(50)
    previewPanel:SetPoint("TOPLEFT", sec4, "TOPLEFT", 14, -34)
    EnsureSoftBackdrop(previewPanel)

    local previewTitle = previewPanel:CreateFontString(nil, "OVERLAY")
    previewTitle:SetFont(font, 12, "OUTLINE")
    previewTitle:SetPoint("TOPLEFT", previewPanel, "TOPLEFT", 10, -8)
    previewTitle:SetText("面板标题示例")

    local previewText = previewPanel:CreateFontString(nil, "OVERLAY")
    previewText:SetFont(font, 10, "OUTLINE")
    previewText:SetPoint("TOPLEFT", previewPanel, "TOPLEFT", 10, -26)
    previewText:SetText("正文文本 / Body text")

    local previewDim = previewPanel:CreateFontString(nil, "OVERLAY")
    previewDim:SetFont(font, 9, "OUTLINE")
    previewDim:SetPoint("TOPLEFT", previewPanel, "TOPLEFT", 10, -40)
    previewDim:SetText("辅助文字 / Dim text")

    local previewBtn = CreateFrame("Button", NextWidgetName("PreviewBtn"), sec4, "UIPanelButtonTemplate")
    previewBtn:SetWidth(90)
    previewBtn:SetHeight(24)
    previewBtn:SetPoint("TOPLEFT", sec4, "TOPLEFT", 14, -92)
    previewBtn:SetText("按钮示例")
    StyleButton(previewBtn)

    local previewAccent = sec4:CreateTexture(nil, "ARTWORK")
    previewAccent:SetTexture("Interface\\Buttons\\WHITE8X8")
    previewAccent:SetWidth(490)
    previewAccent:SetHeight(4)
    previewAccent:SetPoint("TOPLEFT", sec4, "TOPLEFT", 14, -122)

    local previewSlot = CreateFrame("Frame", NextWidgetName("PreviewSlot"), sec4)
    previewSlot:SetWidth(80)
    previewSlot:SetHeight(24)
    previewSlot:SetPoint("TOPLEFT", sec4, "TOPLEFT", 14, -134)
    previewSlot:SetBackdrop({
        bgFile = "Interface\\Buttons\\WHITE8X8",
        edgeFile = "Interface\\Buttons\\WHITE8X8",
        tile = false, edgeSize = 1,
        insets = { left = 1, right = 1, top = 1, bottom = 1 },
    })

    local previewSlotLabel = previewSlot:CreateFontString(nil, "OVERLAY")
    previewSlotLabel:SetFont(font, 9, "OUTLINE")
    previewSlotLabel:SetPoint("CENTER", previewSlot, "CENTER", 0, 0)
    previewSlotLabel:SetText("色块")

    self._themePreview = {
        panel = previewPanel,
        title = previewTitle,
        text = previewText,
        dim = previewDim,
        btn = previewBtn,
        accent = previewAccent,
        slot = previewSlot,
        slotLabel = previewSlotLabel,
    }

    ---------------------------------------------------------------------------
    -- Hint
    ---------------------------------------------------------------------------
    CreateDesc(root, "提示: 更换主题后建议点击右下方 [保存并重载] 以确保所有界面生效。",
        14, -650, font, 500)

    ---------------------------------------------------------------------------
    -- Finalize
    ---------------------------------------------------------------------------
    themeScroll:UpdateRange()
    self.themeControls = controls
    self.themeScroll = themeScroll

    self:RefreshThemeSwatches()
    self:RefreshThemePreview()
end

function SFrames.ConfigUI:RefreshThemeSwatches()
    if not self._themeSwatches then return end
    local active = SFramesDB and SFramesDB.Theme and SFramesDB.Theme.preset or "pink"
    local useClass = SFramesDB and SFramesDB.Theme and SFramesDB.Theme.useClassTheme
    if useClass then
        active = SFrames.Theme:GetCurrentPreset()
    end
    for _, sw in ipairs(self._themeSwatches) do
        if sw.presetKey == active then
            sw:SetBackdropBorderColor(1, 0.85, 0, 1)
            if sw.selectFrame then sw.selectFrame:Show() end
        else
            sw:SetBackdropBorderColor(0.25, 0.25, 0.25, 1)
            if sw.selectFrame then sw.selectFrame:Hide() end
        end
    end
end

function SFrames.ConfigUI:RefreshThemePreview()
    local p = self._themePreview
    if not p then return end
    local A = SFrames.ActiveTheme

    if p.panel and p.panel.SetBackdropColor then
        p.panel:SetBackdropColor(A.panelBg[1], A.panelBg[2], A.panelBg[3], A.panelBg[4] or 0.95)
        p.panel:SetBackdropBorderColor(A.panelBorder[1], A.panelBorder[2], A.panelBorder[3], A.panelBorder[4] or 0.9)
    end
    if p.title then p.title:SetTextColor(A.title[1], A.title[2], A.title[3]) end
    if p.text then p.text:SetTextColor(A.text[1], A.text[2], A.text[3]) end
    if p.dim then p.dim:SetTextColor(A.dimText[1], A.dimText[2], A.dimText[3]) end
    if p.accent then p.accent:SetVertexColor(A.accent[1], A.accent[2], A.accent[3], A.accent[4] or 1) end
    if p.slot and p.slot.SetBackdropColor then
        p.slot:SetBackdropColor(A.slotBg[1], A.slotBg[2], A.slotBg[3], A.slotBg[4] or 0.9)
        p.slot:SetBackdropBorderColor(A.slotSelected[1], A.slotSelected[2], A.slotSelected[3], A.slotSelected[4] or 1)
    end
    if p.slotLabel then p.slotLabel:SetTextColor(A.nameText[1], A.nameText[2], A.nameText[3]) end
end

function SFrames.ConfigUI:RefreshIconSetButtons()
    if not self._iconSetBtns then return end
    local cur = SFramesDB and SFramesDB.Theme and SFramesDB.Theme.iconSet or "icon"
    for _, btn in ipairs(self._iconSetBtns) do
        if btn.setKey == cur then
            btn:SetBackdropBorderColor(1, 0.85, 0.6, 1)
            if btn.selectBorder then btn.selectBorder:Show() end
        else
            btn:SetBackdropBorderColor(0.3, 0.3, 0.35, 1)
            if btn.selectBorder then btn.selectBorder:Hide() end
        end
    end
end

function SFrames.ConfigUI:EnsurePage(mode)
    if self._pageBuilt[mode] then return end
    self._pageBuilt[mode] = true
    if mode == "ui" then self:BuildUIPage()
    elseif mode == "player" then self:BuildPlayerPage()
    elseif mode == "target" then self:BuildTargetPage()
    elseif mode == "party" then self:BuildPartyPage()
    elseif mode == "bags" then self:BuildBagPage()
    elseif mode == "raid" then self:BuildRaidPage()
    elseif mode == "char" then self:BuildCharPage()
    elseif mode == "actionbar" then self:BuildActionBarPage()
    elseif mode == "keybinds" then self:BuildKeybindsPage()
    elseif mode == "minimap" then self:BuildMinimapPage()
    elseif mode == "buff" then self:BuildBuffPage()
    elseif mode == "personalize" then self:BuildPersonalizePage()
    elseif mode == "theme" then self:BuildThemePage()
    end
end

function SFrames.ConfigUI:Build(mode)
    EnsureDB()
    self:EnsureFrame()

    local page = string.lower(mode or "ui")
    local validPages = { ui = true, player = true, target = true, bags = true, char = true, party = true, raid = true, actionbar = true, keybinds = true, minimap = true, buff = true, personalize = true, theme = true }
    if not validPages[page] then page = "ui" end

    if self.frame:IsShown() and self.activePage == page then
        self.frame:Hide()
        return
    end

    self:ShowPage(page)
    self.frame:Show()
    self.frame:Raise()
end

function SFrames.ConfigUI:OpenUI()
    self:Build("ui")
end

function SFrames.ConfigUI:OpenBags()
    self:Build("bags")
end
