
-- ============================================================
-- FILE: Welcome.lua
-- PURPOSE: First-run welcome wizard, "What's New" changelog
--   dialog, profile import/export UI, and the preset system that
--   applies named visual configurations to all addon settings.
-- HOW IT WORKS:
--   CreateWelcomeFrame() builds a multi-step wizard (~2200 lines).
--   Each step is a sub-frame; ShowStep() navigates between them.
--   ApplyPreset() writes a named set of defaults to MidnightUISettings
--   then calls ApplyAllSettings() to refresh all modules.
--   CreateImportFrame/CreateExportFrame wrap Profiles.ImportProfileString
--   and Profiles.ExportProfileString in copy-paste text boxes.
--   CreateWhatsNewFrame() shows a versioned changelog dialog.
--   TryShowWelcome/TryShowWhatsNew check Profiles seen-flags and
--   show only when needed.
-- DEPENDS ON:
--   _G.MidnightUI_Profiles         — export/import/seen-flag API
--   _G.MidnightUI_Settings         — InitializeSettings + Apply* funcs
--   _G.MidnightUI_Core.InitModules — module re-init after preset
--   _G.MidnightUI_MoverRegistry    — position apply after preset
--   _G.MidnightUI_ResolveFontPath  — font path resolver
--   All Apply* globals from Settings.lua
--   MidnightUISettings             (SavedVariable)
-- EXPORTS:
--   _G.MidnightUI_OpenURL(url)
--   _G.MidnightUI_ShowProfileImport()
--   _G.MidnightUI_ShowProfileExport()
--   _G.MidnightUI_WelcomeExitPreview()
--   _G.MidnightUI_ShowWelcome(force)
--   _G.MidnightUI_ReturnToWelcomeWizard()
--   _G.MidnightUI_TryShowWelcome()
--   _G.MidnightUI_TryShowWhatsNew()
--   _G.MidnightUI_ShowWhatsNew()
-- OWNED SAVEDVARIABLES:
--   MidnightUISettings (read/written through EnsureSettings and ApplyPreset)
--   Profiles.WelcomeSeen / Profiles.WhatsNewSeen (via Profiles module)
-- EVENTS REGISTERED: none — all UI driven by frame scripts
-- FRAMES CREATED:
--   Welcome wizard (singleton, lazy via CreateWelcomeFrame)
--   What's New dialog (singleton, lazy via CreateWhatsNewFrame)
--   Import/Export frames (singletons, lazy)
--   URL prompt dialog (singleton, lazy via EnsureURLPromptFrame)
-- GOTCHAS:
--   Profiles is captured at file-scope load time — if Profiles.lua
--   loads after Welcome.lua, the local Profiles reference will be nil.
--   TOC order ensures Profiles loads first.
--   ApplyAllSettings is blocked during InCombatLockdown.
-- ============================================================

local _G = _G
local CreateFrame = CreateFrame
local UIParent = UIParent
local C_Timer = C_Timer
local date = date
local math = math
local pcall = pcall
local tostring = tostring
local type = type
local pairs = pairs
local ipairs = ipairs

local Profiles = _G.MidnightUI_Profiles

local CURSEFORGE_URL = "https://www.curseforge.com/wow/addons/midnightui-midnight-ready"
local DISCORD_URL = "https://discord.gg/3AV6yUaYQ9"
local MPI_COMPANION_URL = "https://mpi.atyzi.com"
local TITLE_FONT = (_G.MidnightUI_ResolveFontPath and _G.MidnightUI_ResolveFontPath("Fonts\\FRIZQT__.TTF")) or "Fonts\\FRIZQT__.TTF"

local UI_COLORS = {
    bg = {0.05, 0.06, 0.11},
    panel = {0.075, 0.09, 0.15},
    inset = {0.08, 0.10, 0.17},
    border = {0.18, 0.22, 0.30},
    text = {0.92, 0.93, 0.96},
    muted = {0.58, 0.60, 0.65},
    accent = {0.00, 0.78, 1.00},
    accentDim = {0.00, 0.45, 0.62},
    success = {0.35, 0.88, 0.62},
    warning = {1.00, 0.72, 0.25},
}


-- ============================================================
-- SECTION: Module Init + URL Constants + Color Palette
-- URL constants are referenced by the URL prompt dialog and the
-- preset system. TITLE_FONT uses ResolveFontPath so it adapts to
-- any font override the player has set.
-- UI_COLORS is the single source of truth for all colours used
-- across every sub-frame in this file.
-- READS:  _G.MidnightUI_ResolveFontPath
-- WRITES: (pure declarations)
-- FUNCTIONS:
--   Log(msg)  — debug log via Diagnostics or MidnightUI_Debug
-- ============================================================
local function Log(msg)
    if _G.MidnightUI_Diagnostics and _G.MidnightUI_Diagnostics.LogDebugSource then
        _G.MidnightUI_Diagnostics.LogDebugSource("Welcome", msg)
        return
    end
    if _G.MidnightUI_Debug then
        _G.MidnightUI_Debug("[Welcome] " .. tostring(msg))
    end
end

-- ============================================================
-- SECTION: Shared UI Utilities
-- Small helpers used throughout every sub-frame in this file.
-- DescribeFrame(): diagnostic string for a frame — name/shown/
--   strata/level/size. Used in Log calls.
-- SafeCall(): pcall wrapper with Log on failure. Used by
--   ApplyAllSettings to safely call external module functions.
-- TrySetFont(): applies a font to a FontString/EditBox, logging
--   failures rather than crashing.
-- StyleButton(): applies the standard accent button look used by
--   all action buttons across the wizard and dialogs.
-- READS:  UI_COLORS
-- WRITES: (pure UI mutations)
-- FUNCTIONS:
--   DescribeFrame(frame)      → diagnostic string
--   SafeCall(tag, fn, ...)    — pcall wrapper with Log
--   TrySetFont(obj, path, size, flags) — safe font apply
--   StyleButton(btn, label, w, h) — accent button styling
-- ============================================================
local function DescribeFrame(frame)
    if not frame then return "frame=nil" end
    local name = (frame.GetName and frame:GetName()) or "<anonymous>"
    local shown = (frame.IsShown and frame:IsShown()) and "shown" or "hidden"
    local mouse = (frame.IsMouseEnabled and frame:IsMouseEnabled()) and "mouse" or "nomouse"
    local focus = (frame.HasFocus and frame:HasFocus()) and "focus" or "nofocus"
    local strata = (frame.GetFrameStrata and frame:GetFrameStrata()) or "?"
    local level = (frame.GetFrameLevel and frame:GetFrameLevel()) or "?"
    local width = frame.GetWidth and math.floor((frame:GetWidth() or 0) + 0.5) or 0
    local height = frame.GetHeight and math.floor((frame:GetHeight() or 0) + 0.5) or 0
    return name
        .. " shown=" .. shown
        .. " strata=" .. tostring(strata)
        .. " level=" .. tostring(level)
        .. " " .. mouse
        .. " " .. focus
        .. " size=" .. tostring(width) .. "x" .. tostring(height)
end

local function LogFrameState(tag, frame)
    Log(tag .. " | " .. DescribeFrame(frame))
end

local function LogMouseFocus(tag)
    if _G.GetMouseFocus then
        Log(tag .. " mouseFocus | " .. DescribeFrame(_G.GetMouseFocus()))
        return
    end
    Log(tag .. " mouseFocus | unavailable")
end

local function HookImportDropdownList(list, index)
    if not list or list._midnightImportDebugHooked then return end
    list._midnightImportDebugHooked = true
    list:HookScript("OnShow", function(self)
        LogFrameState("Import dropdown list " .. tostring(index) .. " shown", self)
    end)
    list:HookScript("OnHide", function(self)
        LogFrameState("Import dropdown list " .. tostring(index) .. " hidden", self)
    end)
    list:HookScript("OnMouseDown", function(self, button)
        Log("Import dropdown list " .. tostring(index) .. " mouse down button=" .. tostring(button))
        LogFrameState("Import dropdown list " .. tostring(index) .. " mouse target", self)
        LogMouseFocus("Import dropdown list " .. tostring(index))
    end)
end

local function LogImportDropdownLists(tag)
    for i = 1, 3 do
        local list = _G["DropDownList" .. i]
        if list then
            HookImportDropdownList(list, i)
            LogFrameState(tag .. " list " .. tostring(i), list)
        else
            Log(tag .. " list " .. tostring(i) .. " | missing")
        end
    end
end

local function SafeCall(tag, fn, ...)
    if type(fn) ~= "function" then return false end
    local ok, err = pcall(fn, ...)
    if not ok then
        Log(tag .. " failed: " .. tostring(err))
    end
    return ok
end

local function TrySetFont(fs, size, flags)
    if not fs or not fs.SetFont then return end
    local resolved = (_G.MidnightUI_ResolveFontPath and _G.MidnightUI_ResolveFontPath(TITLE_FONT)) or TITLE_FONT
    pcall(fs.SetFont, fs, resolved, size or 12, flags or "")
end

local function StyleButton(btn)
    if not btn then return end
    if _G.MidnightUI_Settings and _G.MidnightUI_Settings.StyleSettingsButton then
        _G.MidnightUI_Settings.StyleSettingsButton(btn)
    end
end


-- ============================================================
-- SECTION: Settings Initialization + Application
-- EnsureSettings(): guarantees every sub-table that the wizard
--   or preset system might write to exists in MidnightUISettings.
--   Returns the settings table for direct use by callers.
-- ApplyAllSettings(tag): refreshes all modules after a settings
--   change. Blocked during InCombatLockdown. Calls InitializeSettings,
--   ApplyGlobalTheme, ApplyMessengerSettings, and every per-module
--   Apply* function via SafeCall. Also calls MoverRegistry and
--   InitModules("WELCOME_PRESET") on Core.
-- READS:  _G.MidnightUISettings, _G.MidnightUI_Settings,
--         _G.MidnightUI_Core, _G.MidnightUI_MoverRegistry
-- WRITES: _G.MidnightUISettings (sub-table initialization)
-- FUNCTIONS:
--   EnsureSettings()       → MidnightUISettings
--   ApplyAllSettings(tag)  → bool (false if blocked by combat)
-- ============================================================
local function EnsureSettings()
    _G.MidnightUISettings = _G.MidnightUISettings or {}
    local s = _G.MidnightUISettings
    s.General = s.General or {}
    s.Messenger = s.Messenger or {}
    s.ActionBars = s.ActionBars or {}
    s.Minimap = s.Minimap or {}
    s.PlayerFrame = s.PlayerFrame or {}
    s.PlayerFrame.auras = s.PlayerFrame.auras or {}
    s.PlayerFrame.debuffs = s.PlayerFrame.debuffs or {}
    s.TargetFrame = s.TargetFrame or {}
    s.TargetFrame.auras = s.TargetFrame.auras or {}
    s.TargetFrame.debuffs = s.TargetFrame.debuffs or {}
    s.FocusFrame = s.FocusFrame or {}
    s.PetFrame = s.PetFrame or {}
    s.PartyFrames = s.PartyFrames or {}
    if s.PartyFrames.hideInRaid == nil then s.PartyFrames.hideInRaid = false end
    s.Combat = s.Combat or {}
    if s.Combat.debuffOverlayGlobalEnabled == nil then s.Combat.debuffOverlayGlobalEnabled = true end
    if s.Combat.debuffOverlayPlayerEnabled == nil then s.Combat.debuffOverlayPlayerEnabled = true end
    if s.Combat.debuffOverlayFocusEnabled == nil then s.Combat.debuffOverlayFocusEnabled = true end
    if s.Combat.debuffOverlayPartyEnabled == nil then s.Combat.debuffOverlayPartyEnabled = true end
    if s.Combat.debuffOverlayRaidEnabled == nil then s.Combat.debuffOverlayRaidEnabled = true end
    if s.Combat.debuffOverlayTargetOfTargetEnabled == nil then s.Combat.debuffOverlayTargetOfTargetEnabled = true end
    if s.Combat.debuffOverlayDefaultMigrationApplied ~= true then
        local legacyAllDisabled =
            s.Combat.debuffOverlayGlobalEnabled == false and
            s.Combat.debuffOverlayPlayerEnabled == false and
            s.Combat.debuffOverlayFocusEnabled == false and
            s.Combat.debuffOverlayPartyEnabled == false and
            s.Combat.debuffOverlayRaidEnabled == false and
            s.Combat.debuffOverlayTargetOfTargetEnabled == false
        local legacyGlobalOnlyDisabled =
            s.Combat.debuffOverlayGlobalEnabled == false and
            s.Combat.debuffOverlayPlayerEnabled ~= false and
            s.Combat.debuffOverlayFocusEnabled ~= false and
            s.Combat.debuffOverlayPartyEnabled ~= false and
            s.Combat.debuffOverlayRaidEnabled ~= false and
            s.Combat.debuffOverlayTargetOfTargetEnabled ~= false
        if legacyAllDisabled then
            s.Combat.debuffOverlayGlobalEnabled = true
            s.Combat.debuffOverlayPlayerEnabled = true
            s.Combat.debuffOverlayFocusEnabled = true
            s.Combat.debuffOverlayPartyEnabled = true
            s.Combat.debuffOverlayRaidEnabled = true
            s.Combat.debuffOverlayTargetOfTargetEnabled = true
        elseif legacyGlobalOnlyDisabled then
            s.Combat.debuffOverlayGlobalEnabled = true
        end
        s.Combat.debuffOverlayDefaultMigrationApplied = true
    end
    s.Nameplates = s.Nameplates or {}
    s.Nameplates.healthBar = s.Nameplates.healthBar or {}
    s.Nameplates.threatBar = s.Nameplates.threatBar or {}
    s.Nameplates.castBar = s.Nameplates.castBar or {}
    s.Nameplates.target = s.Nameplates.target or {}
    s.Nameplates.target.healthBar = s.Nameplates.target.healthBar or {}
    s.Nameplates.target.threatBar = s.Nameplates.target.threatBar or {}
    s.Nameplates.target.castBar = s.Nameplates.target.castBar or {}
    s.RaidFrames = s.RaidFrames or {}
    s.MainTankFrames = s.MainTankFrames or {}
    s.CastBars = s.CastBars or {}
    s.CastBars.player = s.CastBars.player or {}
    s.CastBars.target = s.CastBars.target or {}
    s.CastBars.focus = s.CastBars.focus or {}
    if s.CastBars.player.matchFrameWidth == nil then s.CastBars.player.matchFrameWidth = true end
    if s.CastBars.target.matchFrameWidth == nil then s.CastBars.target.matchFrameWidth = true end
    if s.CastBars.focus.matchFrameWidth == nil then s.CastBars.focus.matchFrameWidth = false end
    s.ConsumableBars = s.ConsumableBars or {}
    s.SoulShards = s.SoulShards or {}
    if s.SoulShards.enabled == nil then s.SoulShards.enabled = true end
    if not s.SoulShards.scale then s.SoulShards.scale = 100 end
    if not s.SoulShards.alpha then s.SoulShards.alpha = 1.0 end
    s.PetBar = s.PetBar or {}
    s.StanceBar = s.StanceBar or {}
    for i = 1, 8 do
        local key = "bar" .. i
        s.ActionBars[key] = s.ActionBars[key] or {}
        if s.ActionBars[key].enabled == nil then s.ActionBars[key].enabled = true end
        if not s.ActionBars[key].style then s.ActionBars[key].style = "Class Color" end
    end
    if s.ActionBars.globalStyle == nil then s.ActionBars.globalStyle = "Disabled" end
    if s.ConsumableBars.enabled == nil then s.ConsumableBars.enabled = true end
    if s.PetBar.enabled == nil then s.PetBar.enabled = true end
    if s.StanceBar.enabled == nil then s.StanceBar.enabled = true end
    return s
end


local function ApplyAllSettings(tag)
    if InCombatLockdown and InCombatLockdown() then
        Log("Apply deferred in combat for " .. tostring(tag))
        return false
    end

    local S = _G.MidnightUI_Settings
    if not S then return false end

    SafeCall("InitializeSettings", S.InitializeSettings)
    SafeCall("ApplyGlobalTheme", S.ApplyGlobalTheme)
    SafeCall("ApplyMessengerSettings", S.ApplyMessengerSettings)
    SafeCall("ApplyActionBarSettingsImmediate", S.ApplyActionBarSettingsImmediate)
    SafeCall("ApplyPlayerSettings", S.ApplyPlayerSettings)
    SafeCall("ApplyTargetSettings", S.ApplyTargetSettings)
    SafeCall("ApplyFocusSettings", S.ApplyFocusSettings)
    SafeCall("ApplyPartyFramesSettings", S.ApplyPartyFramesSettings)
    SafeCall("ApplyUnitFrameBarStyle", S.ApplyUnitFrameBarStyle)
    SafeCall("ApplyNameplateSettings", S.ApplyNameplateSettings)
    SafeCall("ApplyCastBarSettings", S.ApplyCastBarSettings)
    SafeCall("ApplyMinimapSettings", S.ApplyMinimapSettings)
    SafeCall("ApplyRaidFramesSettings", S.ApplyRaidFramesSettings)
    SafeCall("ApplyMainTankSettings", _G.MidnightUI_ApplyMainTankSettings)
    SafeCall("ApplyQuestObjectiveVisibility", S.ApplyQuestObjectiveVisibility)

    if _G.MidnightUI_Core and _G.MidnightUI_Core.InitModules then
        SafeCall("CoreInitModules", _G.MidnightUI_Core.InitModules, tag or "WELCOME_WIZARD")
    end
    if Profiles and Profiles.SaveCurrentProfile then
        Profiles.SaveCurrentProfile("welcome_wizard")
    end
    return true
end


-- ============================================================
-- SECTION: Base UI Component Builders
-- Reusable frame factories consumed by all sub-frames in this file.
-- CreatePanel():       BackdropTemplate frame with standard bg/border.
-- CreateHeaderLine():  1px horizontal divider texture.
-- CreateActionTile():  Clickable tile with icon, title, desc, hover
--   highlight. Used on the "Actions" step of the wizard.
-- CreateVisualCard():  Preview card with title, description, and a
--   rendered preview area. Used for visual preset selection.
-- GetCopyShortcutLabel(): returns "Cmd+C" or "Ctrl+C" by checking
--   for Mac key bindings.
-- READS:  UI_COLORS, TITLE_FONT
-- WRITES: (frame construction only)
-- FUNCTIONS:
--   CreatePanel(parent, w, h)               → frame
--   CreateHeaderLine(parent, yOff)          → texture
--   CreateActionTile(parent, cfg)           → tile frame
--   CreateVisualCard(parent, cfg)           → card frame
--   GetCopyShortcutLabel()                  → string
-- ============================================================
local function CreatePanel(parent, w, h)
    local frame = CreateFrame("Frame", nil, parent, "BackdropTemplate")
    frame:SetSize(w, h)
    frame:SetBackdrop({
        bgFile = "Interface\\Buttons\\WHITE8X8",
        edgeFile = "Interface\\Buttons\\WHITE8X8",
        edgeSize = 1,
        insets = { left = 1, right = 1, top = 1, bottom = 1 }
    })
    frame:SetBackdropColor(UI_COLORS.panel[1], UI_COLORS.panel[2], UI_COLORS.panel[3], 0.95)
    frame:SetBackdropBorderColor(UI_COLORS.border[1], UI_COLORS.border[2], UI_COLORS.border[3], 1)
    return frame
end

local function CreateHeaderLine(parent, y)
    local line = parent:CreateTexture(nil, "ARTWORK")
    line:SetPoint("TOPLEFT", 0, y)
    line:SetPoint("TOPRIGHT", 0, y)
    line:SetHeight(1)
    line:SetColorTexture(UI_COLORS.border[1], UI_COLORS.border[2], UI_COLORS.border[3], 0.9)
    return line
end

local function CreateActionTile(parent, title, body, buttonText, iconTexture, onClick, width, height)
    local tile = CreatePanel(parent, width or 268, height or 168)
    tile:SetBackdropColor(0.07, 0.11, 0.19, 0.92)

    local shine = tile:CreateTexture(nil, "ARTWORK")
    shine:SetTexture("Interface\\Buttons\\WHITE8X8")
    shine:SetPoint("TOPLEFT", 2, -2)
    shine:SetPoint("TOPRIGHT", -2, -2)
    shine:SetHeight(38)
    shine:SetVertexColor(0.10, 0.18, 0.30, 0.45)

    local icon = nil
    if iconTexture then
        icon = tile:CreateTexture(nil, "OVERLAY")
        icon:SetPoint("TOPLEFT", 12, -10)
        icon:SetSize(22, 22)
        icon:SetTexture(iconTexture)
        icon:SetVertexColor(UI_COLORS.accent[1], UI_COLORS.accent[2], UI_COLORS.accent[3], 0.9)
    end

    local t = tile:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    if icon then
        t:SetPoint("LEFT", icon, "RIGHT", 8, 0)
    else
        t:SetPoint("TOPLEFT", 12, -16)
    end
    t:SetText(title)
    t:SetTextColor(UI_COLORS.text[1], UI_COLORS.text[2], UI_COLORS.text[3])
    TrySetFont(t, 14, "OUTLINE")

    local d = tile:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    d:SetPoint("TOPLEFT", 12, -52)
    d:SetPoint("TOPRIGHT", -12, -52)
    d:SetPoint("BOTTOMLEFT", 12, 52)
    d:SetPoint("BOTTOMRIGHT", -12, 52)
    d:SetJustifyH("LEFT")
    d:SetText(body)
    d:SetTextColor(UI_COLORS.muted[1], UI_COLORS.muted[2], UI_COLORS.muted[3])
    TrySetFont(d, 13)

    local btn = CreateFrame("Button", nil, tile, "UIPanelButtonTemplate")
    btn:SetPoint("BOTTOMLEFT", 12, 12)
    btn:SetPoint("BOTTOMRIGHT", -12, 12)
    btn:SetHeight(30)
    btn:SetText(buttonText)
    StyleButton(btn)
    btn:SetScript("OnClick", onClick)
    tile._button = btn
    return tile
end



local _visualCardGroups = {}

local function CreateVisualCard(parent, opts)
    opts = opts or {}
    local w = opts.width or 380
    local h = opts.height or 200
    local accent = opts.accentColor or {UI_COLORS.accent[1], UI_COLORS.accent[2], UI_COLORS.accent[3]}

    local card = CreateFrame("Frame", nil, parent, "BackdropTemplate")
    card:SetSize(w, h)
    card:SetBackdrop({
        bgFile = "Interface\\Buttons\\WHITE8X8",
        edgeFile = "Interface\\Buttons\\WHITE8X8",
        edgeSize = 1,
        insets = { left = 1, right = 1, top = 1, bottom = 1 },
    })
    card:SetBackdropColor(UI_COLORS.inset[1], UI_COLORS.inset[2], UI_COLORS.inset[3], 0.9)
    card:SetBackdropBorderColor(UI_COLORS.border[1], UI_COLORS.border[2], UI_COLORS.border[3], 0.6)

    local topGrad = card:CreateTexture(nil, "ARTWORK")
    topGrad:SetPoint("TOPLEFT", 1, -1)
    topGrad:SetPoint("BOTTOMRIGHT", -1, 1)
    topGrad:SetTexture("Interface\\Buttons\\WHITE8X8")
    topGrad:SetGradient("VERTICAL",
        CreateColor(accent[1], accent[2], accent[3], 0),
        CreateColor(accent[1], accent[2], accent[3], 0.06)
    )

    local leftMark = card:CreateTexture(nil, "ARTWORK")
    leftMark:SetPoint("TOPLEFT", 1, -1)
    leftMark:SetPoint("BOTTOMLEFT", 1, 1)
    leftMark:SetWidth(2)
    leftMark:SetColorTexture(accent[1], accent[2], accent[3], 0.25)

    local selGlow = card:CreateTexture(nil, "BACKGROUND", nil, -1)
    selGlow:SetPoint("TOPLEFT", -3, 3)
    selGlow:SetPoint("BOTTOMRIGHT", 3, -3)
    selGlow:SetColorTexture(accent[1], accent[2], accent[3], 0.12)
    selGlow:Hide()

    local checkmark = card:CreateTexture(nil, "OVERLAY")
    checkmark:SetPoint("TOPRIGHT", -8, -6)
    checkmark:SetSize(10, 10)
    checkmark:SetColorTexture(accent[1], accent[2], accent[3], 0.95)
    checkmark:Hide()

    local iconTex
    local contentLeft = 16
    if opts.icon then
        iconTex = card:CreateTexture(nil, "OVERLAY")
        iconTex:SetPoint("TOPLEFT", 16, -4)
        iconTex:SetSize(32, 32)
        iconTex:SetTexture(opts.icon)
        iconTex:SetVertexColor(accent[1], accent[2], accent[3], 0.9)
        contentLeft = 58
    end

    local titleFS = card:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    titleFS:SetPoint("TOPLEFT", contentLeft, -10)
    titleFS:SetPoint("TOPRIGHT", -36, -10)
    titleFS:SetJustifyH("LEFT")
    titleFS:SetText(opts.title or "")
    titleFS:SetTextColor(UI_COLORS.text[1], UI_COLORS.text[2], UI_COLORS.text[3])
    TrySetFont(titleFS, 14, "OUTLINE")

    local taglineFS
    if opts.tagline then
        taglineFS = card:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
        taglineFS:SetPoint("TOPLEFT", contentLeft, -30)
        taglineFS:SetPoint("TOPRIGHT", -16, -30)
        taglineFS:SetJustifyH("LEFT")
        taglineFS:SetText(opts.tagline)
        taglineFS:SetTextColor(0.78, 0.80, 0.84)
        TrySetFont(taglineFS, 12)
    end

    if opts.tag then
        local tagFS = card:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
        tagFS:SetPoint("TOPRIGHT", -36, -30)
        tagFS:SetJustifyH("RIGHT")
        tagFS:SetText(opts.tag)
        tagFS:SetTextColor(accent[1], accent[2], accent[3], 0.8)
        TrySetFont(tagFS, 10)
    end

    if opts.description then
        local descFS = card:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
        descFS:SetPoint("TOPLEFT", 16, -48)
        descFS:SetPoint("BOTTOMRIGHT", -16, 6)
        descFS:SetJustifyH("LEFT")
        descFS:SetJustifyV("TOP")
        descFS:SetText(opts.description)
        descFS:SetTextColor(UI_COLORS.muted[1], UI_COLORS.muted[2], UI_COLORS.muted[3])
        TrySetFont(descFS, 11)
    end

    if opts.preview then
        pcall(opts.preview, card)
    end

    card._isSelected = false
    card._accent = accent
    card._selGlow = selGlow
    card._checkmark = checkmark
    card._leftMark = leftMark

    function card:Select()
        self._isSelected = true
        self:SetBackdropBorderColor(self._accent[1], self._accent[2], self._accent[3], 0.95)
        self._leftMark:SetColorTexture(self._accent[1], self._accent[2], self._accent[3], 0.95)
        self._selGlow:Show()
        self._checkmark:Show()
    end

    function card:Deselect()
        self._isSelected = false
        self:SetBackdropBorderColor(UI_COLORS.border[1], UI_COLORS.border[2], UI_COLORS.border[3], 0.6)
        self._leftMark:SetColorTexture(self._accent[1], self._accent[2], self._accent[3], 0.25)
        self._selGlow:Hide()
        self._checkmark:Hide()
    end

    function card:IsSelected()
        return self._isSelected
    end

    card:EnableMouse(true)
    card:SetScript("OnEnter", function(self)
        if not self._isSelected then
            self:SetBackdropColor(0.10, 0.13, 0.20, 0.95)
            self:SetBackdropBorderColor(UI_COLORS.border[1], UI_COLORS.border[2], UI_COLORS.border[3], 0.9)
            self._leftMark:SetColorTexture(self._accent[1], self._accent[2], self._accent[3], 0.55)
        end
    end)
    card:SetScript("OnLeave", function(self)
        if not self._isSelected then
            self:SetBackdropColor(UI_COLORS.inset[1], UI_COLORS.inset[2], UI_COLORS.inset[3], 0.9)
            self:SetBackdropBorderColor(UI_COLORS.border[1], UI_COLORS.border[2], UI_COLORS.border[3], 0.6)
            self._leftMark:SetColorTexture(self._accent[1], self._accent[2], self._accent[3], 0.25)
        end
    end)

    card:SetScript("OnMouseDown", function(self)
        if opts.group and _visualCardGroups[opts.group] then
            for _, other in ipairs(_visualCardGroups[opts.group].cards) do
                if other ~= self then other:Deselect() end
            end
        end
        self:Select()
        if opts.onClick then opts.onClick() end
    end)

    if opts.group then
        if not _visualCardGroups[opts.group] then
            _visualCardGroups[opts.group] = { cards = {} }
        end
        _visualCardGroups[opts.group].cards[#_visualCardGroups[opts.group].cards + 1] = card
    end

    return card
end


local urlPromptFrame

local function GetCopyShortcutLabel()
    local isMac = false
    if _G.IsMacClient then
        local ok, res = pcall(_G.IsMacClient)
        isMac = ok and res == true
    end
    return isMac and "Cmd + C" or "Ctrl + C"
end

-- ============================================================
-- SECTION: URL Prompt + Launcher
-- WoW cannot open URLs directly; instead we show a DIALOG-strata
-- frame with a read-only EditBox containing the URL so the player
-- can copy it manually.
-- EnsureURLPromptFrame(): lazy singleton. Contains an EditBox that
--   auto-selects all text on show. Escape/close button hide it.
-- OpenURL(url): calls EnsureURLPromptFrame, populates the EditBox,
--   and shows it. Exported as _G.MidnightUI_OpenURL for use by other
--   modules (e.g. Messenger social links).
-- urlPromptFrame is a module-upvalue singleton — only one instance
-- ever exists.
-- READS:  UI_COLORS, GetCopyShortcutLabel()
-- WRITES: urlPromptFrame (singleton lazy init)
-- FUNCTIONS:
--   EnsureURLPromptFrame()  → prompt frame (lazy singleton)
--   OpenURL(url)            — populate + show URL prompt
--   _G.MidnightUI_OpenURL   — exported alias for OpenURL
-- ============================================================
local function EnsureURLPromptFrame()
    if urlPromptFrame then return urlPromptFrame end

    local f = CreatePanel(UIParent, 980, 244)
    f:SetPoint("CENTER", UIParent, "CENTER", 0, 0)
    f:SetFrameStrata("DIALOG")
    f:SetFrameLevel(240)
    f:Hide()

    local title = f:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
    title:SetPoint("TOPLEFT", 16, -12)
    title:SetPoint("TOPRIGHT", -16, -12)
    title:SetJustifyH("LEFT")
    title:SetText("Copy this Link (" .. GetCopyShortcutLabel() .. ")")
    title:SetTextColor(UI_COLORS.text[1], UI_COLORS.text[2], UI_COLORS.text[3])
    TrySetFont(title, 20, "OUTLINE")

    local sub = f:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    sub:SetPoint("TOPLEFT", 16, -40)
    sub:SetPoint("TOPRIGHT", -16, -40)
    sub:SetJustifyH("LEFT")
    sub:SetText("Copy the link below and paste it into your browser.")
    sub:SetTextColor(UI_COLORS.muted[1], UI_COLORS.muted[2], UI_COLORS.muted[3])
    TrySetFont(sub, 14)

    local box = CreatePanel(f, 1, 108)
    box:SetPoint("TOPLEFT", 16, -66)
    box:SetPoint("TOPRIGHT", -16, -66)
    box:SetBackdropColor(UI_COLORS.inset[1], UI_COLORS.inset[2], UI_COLORS.inset[3], 0.95)

    local boxGlow = box:CreateTexture(nil, "BACKGROUND")
    boxGlow:SetTexture("Interface\\Buttons\\WHITE8X8")
    boxGlow:SetPoint("TOPLEFT", 1, -1)
    boxGlow:SetPoint("TOPRIGHT", -1, -1)
    boxGlow:SetHeight(26)
    boxGlow:SetVertexColor(0.10, 0.20, 0.34, 0.45)

    local scroll = CreateFrame("ScrollFrame", nil, box, "UIPanelScrollFrameTemplate")
    scroll:SetPoint("TOPLEFT", 8, -10)
    scroll:SetPoint("BOTTOMRIGHT", -28, 10)

    local edit = CreateFrame("EditBox", nil, scroll)
    edit:SetMultiLine(false)
    edit:SetFontObject("GameFontHighlight")
    if edit.SetFont then
        pcall(edit.SetFont, edit, TITLE_FONT, 16, "")
    end
    if edit.SetTextColor then
        edit:SetTextColor(UI_COLORS.text[1], UI_COLORS.text[2], UI_COLORS.text[3], 1)
    end
    if edit.SetJustifyH then
        edit:SetJustifyH("LEFT")
    end
    if edit.SetTextInsets then
        edit:SetTextInsets(8, 8, 4, 4)
    end
    edit:SetWidth(1600)
    edit:SetHeight(30)
    edit:SetAutoFocus(false)
    edit:SetText("")
    edit:SetScript("OnEscapePressed", function() f:Hide() end)
    edit:SetScript("OnEnterPressed", function(self)
        self:HighlightText()
        self:SetFocus()
    end)
    scroll:SetScrollChild(edit)

    local btnCopy = CreateFrame("Button", nil, f, "UIPanelButtonTemplate")
    btnCopy:SetSize(130, 30)
    btnCopy:SetPoint("BOTTOM", f, "BOTTOM", -72, 16)
    btnCopy:SetText("Copy")
    StyleButton(btnCopy)
    btnCopy:SetScript("OnClick", function()
        if not f._urlEdit then return end
        f._urlEdit:SetFocus()
        f._urlEdit:HighlightText()
    end)

    local btnClose = CreateFrame("Button", nil, f, "UIPanelButtonTemplate")
    btnClose:SetSize(130, 30)
    btnClose:SetPoint("LEFT", btnCopy, "RIGHT", 10, 0)
    btnClose:SetText("Close")
    StyleButton(btnClose)
    btnClose:SetScript("OnClick", function() f:Hide() end)

    local btnCorner = CreateFrame("Button", nil, f, "UIPanelButtonTemplate")
    btnCorner:SetSize(24, 24)
    btnCorner:SetPoint("TOPRIGHT", -8, -8)
    btnCorner:SetText("X")
    StyleButton(btnCorner)
    btnCorner:SetScript("OnClick", function() f:Hide() end)

    f._title = title
    f._urlEdit = edit
    f._urlValue = ""
    f.SetURL = function(self, url)
        local link = url or ""
        self._urlValue = link
        if self._title then
            self._title:SetText("Copy this Link (" .. GetCopyShortcutLabel() .. ")")
        end
        if self._urlEdit then
            self._urlEdit:SetText(link)
            if self._urlEdit.SetCursorPosition then
                self._urlEdit:SetCursorPosition(0)
            end
            self._urlEdit:HighlightText()
            self._urlEdit:SetFocus()
        end
    end

    f:SetScript("OnShow", function(self)
        if self._urlEdit then
            self._urlEdit:SetText(self._urlValue or "")
            self._urlEdit:HighlightText()
            self._urlEdit:SetFocus()
        end
    end)

    urlPromptFrame = f
    return urlPromptFrame
end

local function OpenURL(url)
    if not url or url == "" then return end
    local prompt = EnsureURLPromptFrame()
    if not prompt then return end
    prompt:SetURL(url)
    prompt:Show()
    prompt:Raise()
end
_G.MidnightUI_OpenURL = OpenURL

-- ============================================================
-- SECTION: Profile Import/Export UI
-- ResolveProfileOptions(): wraps Profiles.GetProfileOptions,
--   excluding the current character's key from the dropdown.
-- CreateImportFrame(): DIALOG-strata panel with a multiline EditBox
--   for pasting a MUI2 string. "Import" button calls
--   Profiles.ImportProfileString; on success shows a result message.
--   Supports both direct string paste and selecting a saved profile
--   from a dropdown of other characters' snapshots.
-- CreateExportFrame(): similar panel but pre-populated with
--   Profiles.ExportProfileString() on show, all text auto-selected.
-- importFrame/exportFrame are module-upvalue singletons.
-- _G.MidnightUI_ShowProfileImport/Export are the public entry points
-- called from the wizard "Copy Config" / "Paste Config" tiles.
-- READS:  _G.MidnightUI_Profiles (import/export/options)
-- WRITES: importFrame, exportFrame (singleton lazy init),
--         MidnightUISettings (via import)
-- FUNCTIONS:
--   ResolveProfileOptions()          → list table
--   CreateImportFrame()              → import panel (lazy singleton)
--   CreateExportFrame()              → export panel (lazy singleton)
--   _G.MidnightUI_ShowProfileImport() — show import panel
--   _G.MidnightUI_ShowProfileExport() — show export panel
-- ============================================================
local function ResolveProfileOptions()
    if not Profiles or not Profiles.GetProfileOptions then return {} end
    return Profiles.GetProfileOptions(Profiles.GetCharacterKey()) or {}
end


local importFrame
local exportFrame
local welcomeFrame

local function CreateImportFrame()
    local f = CreatePanel(UIParent, 680, 470)
    f:SetPoint("CENTER", UIParent, "CENTER", 0, 0)
    f:SetFrameStrata("FULLSCREEN_DIALOG")
    f:SetFrameLevel(240)
    f:EnableMouse(true)
    if f.SetToplevel then f:SetToplevel(true) end
    f:Hide()

    local title = f:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
    title:SetPoint("TOPLEFT", 16, -12)
    title:SetText("Import Profile")
    title:SetTextColor(UI_COLORS.text[1], UI_COLORS.text[2], UI_COLORS.text[3])

    local sub = f:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    sub:SetPoint("TOPLEFT", 16, -36)
    sub:SetPoint("TOPRIGHT", -16, -36)
    sub:SetText("Paste a profile string or pull from another one of your characters.")
    sub:SetTextColor(UI_COLORS.muted[1], UI_COLORS.muted[2], UI_COLORS.muted[3])

    local dropdown = CreateFrame("Frame", "MidnightUI_WelcomeImportDropdown", f, "UIDropDownMenuTemplate")
    dropdown:SetPoint("TOPLEFT", 10, -60)
    dropdown:SetFrameStrata("FULLSCREEN_DIALOG")
    dropdown:SetFrameLevel(f:GetFrameLevel() + 5)
    UIDropDownMenu_SetWidth(dropdown, 290)
    UIDropDownMenu_SetText(dropdown, "Import from character")

    local details = f:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    details:SetPoint("TOPLEFT", 330, -68)
    details:SetPoint("TOPRIGHT", -16, -68)
    details:SetJustifyH("LEFT")
    details:SetText("")
    details:SetTextColor(UI_COLORS.muted[1], UI_COLORS.muted[2], UI_COLORS.muted[3])

    local box = CreatePanel(f, 648, 270)
    box:SetPoint("TOPLEFT", 16, -110)
    box:SetBackdropColor(UI_COLORS.inset[1], UI_COLORS.inset[2], UI_COLORS.inset[3], 0.9)

    local scroll = CreateFrame("ScrollFrame", nil, box, "UIPanelScrollFrameTemplate")
    scroll:SetPoint("TOPLEFT", 4, -6)
    scroll:SetPoint("BOTTOMRIGHT", -26, 6)

    local edit = CreateFrame("EditBox", nil, scroll)
    edit:SetMultiLine(true)
    edit:SetFontObject("GameFontHighlightSmall")
    edit:SetWidth(600)
    edit:SetHeight(248)
    edit:SetAutoFocus(false)
    edit:EnableMouse(true)
    edit:SetText("")
    edit:SetScript("OnEscapePressed", function() edit:ClearFocus() end)
    edit:SetScript("OnMouseUp", function(self)
        LogFrameState("Import edit mouse up", self)
        FocusImportEdit("edit_mouse_up")
    end)
    scroll:SetScrollChild(edit)

    local status = f:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    status:SetPoint("BOTTOMLEFT", 16, 54)
    status:SetText("")
    status:SetTextColor(1, 0.4, 0.4)

    local btnImportCharacter = CreateFrame("Button", nil, f, "UIPanelButtonTemplate")
    btnImportCharacter:SetPoint("BOTTOMLEFT", 16, 18)
    btnImportCharacter:SetSize(190, 30)
    btnImportCharacter:SetText("Import Selected")
    StyleButton(btnImportCharacter)

    local btnImportString = CreateFrame("Button", nil, f, "UIPanelButtonTemplate")
    btnImportString:SetPoint("LEFT", btnImportCharacter, "RIGHT", 8, 0)
    btnImportString:SetSize(190, 30)
    btnImportString:SetText("Import Pasted String")
    StyleButton(btnImportString)

    local btnCancel = CreateFrame("Button", nil, f, "UIPanelButtonTemplate")
    btnCancel:SetPoint("BOTTOMRIGHT", -16, 18)
    btnCancel:SetSize(140, 30)
    btnCancel:SetText("Cancel")
    StyleButton(btnCancel)

    local options = {}
    local selectedKey = nil

    local function SetStatus(text, r, g, b)
        status:SetText(text or "")
        status:SetTextColor(r or 1, g or 0.4, b or 0.4)
    end

    local function SetStatusMethod(_, text, r, g, b)
        SetStatus(text, r, g, b)
    end

    local function FocusImportEdit(source)
        Log("Import edit focus requested source=" .. tostring(source))
        edit:SetFocus()
        LogFrameState("Import edit focus request result", edit)
    end

    local function LiftImportDropdowns()
        Log("Import dropdown lift requested")
        for i = 1, 3 do
            local list = _G["DropDownList" .. i]
            if list then
                list:SetFrameStrata("FULLSCREEN_DIALOG")
                list:SetFrameLevel(f:GetFrameLevel() + 10 + i)
                HookImportDropdownList(list, i)
            end
        end
        LogImportDropdownLists("Import dropdown lift result")
    end

    local function RefreshDropdown()
        options = ResolveProfileOptions()
        selectedKey = nil
        Log("Import dropdown refresh options=" .. tostring(#options))
        UIDropDownMenu_SetText(dropdown, "Import from character")
        details:SetText(#options == 0 and "No account profile found yet. Log into your other character once and it will show here." or "")

        UIDropDownMenu_Initialize(dropdown, function()
            Log("Import dropdown initialize options=" .. tostring(#options))
            if #options == 0 then
                local info = UIDropDownMenu_CreateInfo()
                info.text = "No other characters found"
                info.disabled = true
                UIDropDownMenu_AddButton(info)
                return
            end
            for _, entry in ipairs(options) do
                local info = UIDropDownMenu_CreateInfo()
                info.text = entry.label
                info.func = function()
                    selectedKey = entry.key
                    UIDropDownMenu_SetText(dropdown, entry.label)
                    local created = entry.createdAt and date("%b %d, %Y", entry.createdAt) or "Unknown"
                    details:SetText("Selected: " .. entry.key .. "\nCreated: " .. created)
                    Log("Import dropdown selected key=" .. tostring(entry.key) .. " label=" .. tostring(entry.label))
                end
                UIDropDownMenu_AddButton(info)
            end
        end)
        LiftImportDropdowns()
    end

    local dropdownButton = _G[dropdown:GetName() .. "Button"]
    if dropdownButton then
        dropdownButton:SetFrameStrata("FULLSCREEN_DIALOG")
        dropdownButton:SetFrameLevel(f:GetFrameLevel() + 8)
        dropdownButton:HookScript("OnMouseDown", function(self, button)
            Log("Import dropdown button mouse down button=" .. tostring(button))
            LogFrameState("Import dropdown button mouse down state", self)
            LogMouseFocus("Import dropdown button mouse down")
        end)
        dropdownButton:HookScript("OnMouseUp", function(self, button)
            Log("Import dropdown button mouse up button=" .. tostring(button))
            LogFrameState("Import dropdown button mouse up state", self)
            LogMouseFocus("Import dropdown button mouse up")
        end)
        dropdownButton:HookScript("OnClick", function()
            LogFrameState("Import dropdown button click", dropdownButton)
            LogFrameState("Import dropdown frame click", dropdown)
            if C_Timer and C_Timer.After then
                C_Timer.After(0, function()
                    Log("Import dropdown post-click lift timer fired")
                    LiftImportDropdowns()
                end)
            else
                LiftImportDropdowns()
            end
        end)
    end

    local function ImportDone(ok, err)
        if ok then
            SetStatus("Import successful", UI_COLORS.success[1], UI_COLORS.success[2], UI_COLORS.success[3])
            if Profiles and Profiles.MarkWelcomeSeen then Profiles.MarkWelcomeSeen() end
            if f._onImported then f._onImported() end
            f._suppressReturn = true
            f:Hide()
            return
        end
        SetStatus("Import failed. Check Diagnostics.", 1, 0.4, 0.4)
        Log("Import failed: " .. tostring(err))
    end

    btnImportCharacter:SetScript("OnClick", function()
        SetStatus("")
        Log("Import selected clicked key=" .. tostring(selectedKey))
        if not Profiles or not Profiles.ImportProfileFromKey then
            SetStatus("Profiles API is missing.")
            return
        end
        if not selectedKey then
            SetStatus("Pick a character profile first.")
            return
        end
        ImportDone(Profiles.ImportProfileFromKey(selectedKey))
    end)

    btnImportString:SetScript("OnClick", function()
        SetStatus("")
        Log("Import pasted clicked textLength=" .. tostring(string.len(edit:GetText() or "")))
        if not Profiles or not Profiles.ImportProfileString then
            SetStatus("Profiles API is missing.")
            return
        end
        local text = edit:GetText() or ""
        if text:gsub("%s+", "") == "" then
            SetStatus("Paste a profile string first.")
            return
        end
        ImportDone(Profiles.ImportProfileString(text))
    end)

    btnCancel:SetScript("OnClick", function()
        Log("Import dialog cancel clicked")
        f:Hide()
    end)

    f:SetScript("OnShow", function(self)
        LogFrameState("Import dialog shown", self)
        LogFrameState("Import dialog dropdown", dropdown)
        if dropdownButton then
            LogFrameState("Import dialog dropdown button", dropdownButton)
        end
        LogFrameState("Import dialog box", box)
        LogFrameState("Import dialog scroll", scroll)
        LogFrameState("Import dialog edit", edit)
        if welcomeFrame then
            LogFrameState("Import dialog welcome frame snapshot", welcomeFrame)
        end
        self:Raise()
        LiftImportDropdowns()
        if self.EditBox then
            self.EditBox:EnableMouse(true)
        end
    end)
    f:SetScript("OnHide", function(self)
        LogFrameState("Import dialog hidden", self)
        local returnFrame = self._returnFrame
        local suppressReturn = self._suppressReturn == true
        self._returnFrame = nil
        self._suppressReturn = false
        if not suppressReturn and returnFrame and returnFrame.Show then
            returnFrame:Show()
            if returnFrame.Raise then
                returnFrame:Raise()
            end
            LogFrameState("Import dialog restored return frame", returnFrame)
        else
            Log("Import dialog hide return suppressed=" .. tostring(suppressReturn) .. " returnFrame=" .. tostring(returnFrame ~= nil))
        end
    end)

    box:HookScript("OnMouseDown", function(self, button)
        Log("Import dialog box mouse down button=" .. tostring(button))
        LogFrameState("Import dialog box mouse target", self)
        LogMouseFocus("Import dialog box")
    end)
    box:HookScript("OnMouseUp", function()
        FocusImportEdit("box_mouse_up")
    end)
    scroll:HookScript("OnMouseDown", function(self, button)
        Log("Import dialog scroll mouse down button=" .. tostring(button))
        LogFrameState("Import dialog scroll mouse target", self)
        LogMouseFocus("Import dialog scroll")
        FocusImportEdit("scroll_mouse_down")
    end)
    scroll:HookScript("OnMouseUp", function()
        FocusImportEdit("scroll_mouse_up")
    end)
    edit:HookScript("OnMouseDown", function(self, button)
        Log("Import edit mouse down button=" .. tostring(button))
        LogFrameState("Import edit mouse down state", self)
        LogMouseFocus("Import edit")
    end)
    edit:HookScript("OnEditFocusGained", function(self)
        LogFrameState("Import edit focus gained", self)
    end)
    edit:HookScript("OnEditFocusLost", function(self)
        LogFrameState("Import edit focus lost", self)
    end)
    edit:HookScript("OnTextChanged", function(self, userInput)
        local textLength = string.len(self:GetText() or "")
        if userInput or textLength > 0 then
            Log("Import edit text changed userInput=" .. tostring(userInput) .. " length=" .. tostring(textLength))
        end
    end)

    LogFrameState("Import dialog created", f)
    LogFrameState("Import dialog dropdown created", dropdown)
    if dropdownButton then
        LogFrameState("Import dialog dropdown button created", dropdownButton)
    else
        Log("Import dialog dropdown button missing at creation")
    end
    LogFrameState("Import dialog box created", box)
    LogFrameState("Import dialog scroll created", scroll)
    LogFrameState("Import dialog edit created", edit)

    f.RefreshDropdown = RefreshDropdown
    f.EditBox = edit
    f.SetStatus = SetStatusMethod
    return f
end

local function CreateExportFrame()
    local f = CreatePanel(UIParent, 680, 430)
    f:SetPoint("CENTER", UIParent, "CENTER", 0, 0)
    f:SetFrameStrata("DIALOG")
    f:SetFrameLevel(120)
    f:Hide()

    local title = f:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
    title:SetPoint("TOPLEFT", 16, -12)
    title:SetText("Export Profile")
    title:SetTextColor(UI_COLORS.text[1], UI_COLORS.text[2], UI_COLORS.text[3])

    local sub = f:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    sub:SetPoint("TOPLEFT", 16, -36)
    sub:SetPoint("TOPRIGHT", -16, -36)
    sub:SetText("Copy this string and use it on another character or share it with another player.")
    sub:SetTextColor(UI_COLORS.muted[1], UI_COLORS.muted[2], UI_COLORS.muted[3])

    local meta = f:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    meta:SetPoint("TOPLEFT", 16, -56)
    meta:SetText("")
    meta:SetTextColor(UI_COLORS.accent[1], UI_COLORS.accent[2], UI_COLORS.accent[3])

    local box = CreatePanel(f, 648, 270)
    box:SetPoint("TOPLEFT", 16, -82)
    box:SetBackdropColor(UI_COLORS.inset[1], UI_COLORS.inset[2], UI_COLORS.inset[3], 0.9)

    local scroll = CreateFrame("ScrollFrame", nil, box, "UIPanelScrollFrameTemplate")
    scroll:SetPoint("TOPLEFT", 4, -6)
    scroll:SetPoint("BOTTOMRIGHT", -26, 6)

    local edit = CreateFrame("EditBox", nil, scroll)
    edit:SetMultiLine(true)
    edit:SetFontObject("GameFontHighlightSmall")
    edit:SetWidth(600)
    edit:SetAutoFocus(false)
    edit:SetText("")
    edit:SetScript("OnEscapePressed", function() edit:ClearFocus() end)
    scroll:SetScrollChild(edit)

    local btnRefresh = CreateFrame("Button", nil, f, "UIPanelButtonTemplate")
    btnRefresh:SetPoint("BOTTOMLEFT", 16, 18)
    btnRefresh:SetSize(140, 30)
    btnRefresh:SetText("Refresh")
    StyleButton(btnRefresh)

    local btnSelect = CreateFrame("Button", nil, f, "UIPanelButtonTemplate")
    btnSelect:SetPoint("LEFT", btnRefresh, "RIGHT", 8, 0)
    btnSelect:SetSize(140, 30)
    btnSelect:SetText("Select All")
    StyleButton(btnSelect)

    local btnClose = CreateFrame("Button", nil, f, "UIPanelButtonTemplate")
    btnClose:SetPoint("BOTTOMRIGHT", -16, 18)
    btnClose:SetSize(140, 30)
    btnClose:SetText("Close")
    StyleButton(btnClose)

    local function RefreshExport()
        local payload = (Profiles and Profiles.ExportProfileString and Profiles.ExportProfileString()) or "Profiles system is not available."
        edit:SetText(payload)
        meta:SetText("Payload length: " .. tostring(string.len(payload or "")))
        if Profiles and Profiles.SaveCurrentProfile then
            Profiles.SaveCurrentProfile("manual_export")
        end
    end

    btnRefresh:SetScript("OnClick", RefreshExport)
    btnSelect:SetScript("OnClick", function()
        edit:HighlightText()
        edit:SetFocus()
    end)
    btnClose:SetScript("OnClick", function() f:Hide() end)

    f.RefreshExport = RefreshExport
    f.EditBox = edit
    return f
end

function _G.MidnightUI_ShowProfileImport(onImported)
    if not importFrame then importFrame = CreateImportFrame() end
    importFrame._onImported = onImported
    importFrame._returnFrame = nil
    importFrame._suppressReturn = false
    Log("ShowProfileImport called onImported=" .. tostring(type(onImported) == "function"))
    if welcomeFrame then
        LogFrameState("ShowProfileImport welcome frame before handoff", welcomeFrame)
    else
        Log("ShowProfileImport welcome frame missing")
    end
    if welcomeFrame and welcomeFrame.IsShown and welcomeFrame:IsShown() then
        importFrame._returnFrame = welcomeFrame
        Log("ShowProfileImport hiding welcome frame for modal handoff")
        welcomeFrame:Hide()
    end
    importFrame:Show()
    importFrame:Raise()
    LogFrameState("ShowProfileImport import frame after show", importFrame)
    if importFrame.EditBox then importFrame.EditBox:SetText("") end
    importFrame:SetStatus("")
    if importFrame.RefreshDropdown then importFrame.RefreshDropdown() end
    LogImportDropdownLists("ShowProfileImport dropdown snapshot")
end

function _G.MidnightUI_ShowProfileExport()
    if not exportFrame then exportFrame = CreateExportFrame() end
    exportFrame:Show()
    exportFrame:Raise()
    if exportFrame.RefreshExport then exportFrame.RefreshExport() end
    exportFrame.EditBox:HighlightText()
end


-- ============================================================
-- SECTION: Preset System
-- ApplyPreset(name, skipApply) writes a named set of opinionated
-- defaults to MidnightUISettings covering every visual dimension:
-- bar style (gradient/flat/etc.), health bar texture, unit frame
-- visibility toggles, nameplate style, party layout, colors, etc.
-- Available presets: "Classic", "Minimal", "Vibrant", "Arc",
-- "Retro", "Pastel" and more.
-- After writing the settings, calls ApplyAllSettings("preset:<name>")
-- unless skipApply is true (used during wizard back-navigation).
-- Also resets mover positions via MoverRegistry.ResetAll when a
-- preset includes layout changes.
-- READS:  EnsureSettings() → MidnightUISettings sub-tables
-- WRITES: MidnightUISettings (extensive writes across all sub-tables)
-- FUNCTIONS:
--   ApplyPreset(name, skipApply)  — apply named preset + refresh
--   [inner helpers]: SetPartyStyle, SetBarStyle, etc. (upvalues)
-- ============================================================
local function ApplyPreset(name, skipApply)
    local s = EnsureSettings()
    local function SetPartyStyle(style)
        if _G.MidnightUI_SetPartyStyle then
            _G.MidnightUI_SetPartyStyle(style)
        end
        s.PartyFrames.style = style
    end

    local function SetTheme(theme)
        local actionBarStyleByTheme = {
            ["Default"] = "Disabled",
            ["Class Color"] = "Class Color",
            ["Faithful"] = "Faithful",
            ["Glass"] = "Glass",
        }
        s.GlobalStyle = theme
        s.Messenger.style = theme
        s.Minimap.infoBarStyle = theme
        s.ActionBars.globalStyle = actionBarStyleByTheme[theme] or "Disabled"
    end

    local function SetActionColorTheme(style)
        s.ActionBars.globalStyle = style
        s.Messenger.style = style
        s.Minimap.infoBarStyle = style
    end

    local function SetActionBarsEnabled(startIdx, endIdx, enabled)
        for i = startIdx, endIdx do
            local key = "bar" .. i
            s.ActionBars[key] = s.ActionBars[key] or {}
            s.ActionBars[key].enabled = (enabled == true)
        end
    end

    local function SetCoreToggles()
        s.PlayerFrame.enabled = true
        s.TargetFrame.enabled = true
        s.FocusFrame.enabled = true
        s.CastBars.player.enabled = true
        s.CastBars.target.enabled = true
        s.CastBars.focus.enabled = true
        s.Nameplates.enabled = true
        s.Nameplates.threatBar.enabled = true
        s.ConsumableBars.enabled = true
        s.PetBar.enabled = true
        s.StanceBar.enabled = true
    end

    local function ApplySharedDefaults()
        SetCoreToggles()
        s.PartyFrames.showTooltip = true
    end

    if name == "MidnightUI" then
        SetTheme("Default")
        s.ActionBars.globalStyle = "Glass"
        ApplySharedDefaults()
        s.General.unitFrameBarStyle = "Gradient"
        s.General.hideQuestObjectivesAlways = false
        s.General.hideQuestObjectivesInCombat = true
        s.ConsumableBars.hideInactive = false
        s.ConsumableBars.showInInstancesOnly = false
        SetActionBarsEnabled(1, 4, true)
        SetActionBarsEnabled(5, 8, false)

        s.PlayerFrame.width = 380
        s.PlayerFrame.height = 66
        s.PlayerFrame.scale = 100
        s.PlayerFrame.alpha = 0.95

        s.TargetFrame.width = 380
        s.TargetFrame.height = 66
        s.TargetFrame.scale = 100
        s.TargetFrame.alpha = 0.95
        s.TargetFrame.debuffs.filterMode = "AUTO"
        s.TargetFrame.debuffs.maxShown = 16

        s.FocusFrame.width = 320
        s.FocusFrame.height = 58
        s.FocusFrame.scale = 100
        s.FocusFrame.alpha = 0.95

        s.PartyFrames.layout = "Vertical"
        SetPartyStyle("Rendered")
        s.PartyFrames.width = 240
        s.PartyFrames.height = 58
        s.PartyFrames.spacingX = 8
        s.PartyFrames.spacingY = 8

        s.RaidFrames.groupBy = true
        s.RaidFrames.colorByGroup = true
        s.RaidFrames.groupBrackets = true
        s.RaidFrames.columns = 5
        s.RaidFrames.layoutStyle = "Detailed"
        s.RaidFrames.width = 92
        s.RaidFrames.height = 24
        s.RaidFrames.spacingX = 6
        s.RaidFrames.spacingY = 4

        s.MainTankFrames.width = 260
        s.MainTankFrames.height = 58
        s.MainTankFrames.spacing = 6
        s.MainTankFrames.scale = 1.0

        s.Nameplates.healthBar.width = 200
        s.Nameplates.healthBar.height = 20
        s.Nameplates.healthBar.alpha = 1.0
        s.Nameplates.healthBar.nameFontSize = 10
        s.Nameplates.healthBar.healthPctFontSize = 9
        s.Nameplates.healthBar.nameAlign = "LEFT"
        s.Nameplates.healthBar.healthPctDisplay = "RIGHT"
        s.Nameplates.threatBar.width = 200
        s.Nameplates.threatBar.height = 5
        s.Nameplates.threatBar.alpha = 1.0
        s.Nameplates.castBar.width = 200
        s.Nameplates.castBar.height = 16
        s.Nameplates.castBar.alpha = 1.0
        s.Nameplates.castBar.fontSize = 12
        s.Nameplates.target.healthBar.width = 240
        s.Nameplates.target.healthBar.height = 24
        s.Nameplates.target.threatBar.width = 240
        s.Nameplates.target.threatBar.height = 6
        s.Nameplates.target.castBar.width = 240
        s.Nameplates.target.castBar.height = 18

        s.CastBars.player.width = 360
        s.CastBars.player.height = 20
        s.CastBars.player.scale = 100
        s.CastBars.target.width = 360
        s.CastBars.target.height = 20
        s.CastBars.target.scale = 100
        s.CastBars.focus.width = 320
        s.CastBars.focus.height = 20
        s.CastBars.focus.scale = 100

        s.Combat.debuffBorderEnabled = true
        s.Combat.debuffOverlayGlobalEnabled = true
        s.Combat.dispelTrackingEnabled = true
        s.Combat.dispelTrackingMaxShown = 8
        s.General.healthTextMode = "percent"
    elseif name == "Raid Focused" then
        SetTheme("Glass")
        SetActionColorTheme("Glass")
        ApplySharedDefaults()
        s.General.unitFrameBarStyle = "Gradient"
        s.General.hideQuestObjectivesAlways = false
        s.General.hideQuestObjectivesInCombat = true
        s.ConsumableBars.hideInactive = true
        s.ConsumableBars.showInInstancesOnly = true
        SetActionBarsEnabled(1, 4, true)
        SetActionBarsEnabled(5, 8, false)

        s.PlayerFrame.width = 374
        s.PlayerFrame.height = 64
        s.PlayerFrame.scale = 100
        s.PlayerFrame.alpha = 0.95

        s.TargetFrame.width = 374
        s.TargetFrame.height = 64
        s.TargetFrame.scale = 100
        s.TargetFrame.alpha = 0.95
        s.TargetFrame.debuffs.filterMode = "AUTO"
        s.TargetFrame.debuffs.maxShown = 22

        s.FocusFrame.width = 312
        s.FocusFrame.height = 56
        s.FocusFrame.scale = 100
        s.FocusFrame.alpha = 0.95

        s.PartyFrames.layout = "Vertical"
        SetPartyStyle("Simple")
        s.PartyFrames.width = 228
        s.PartyFrames.height = 52
        s.PartyFrames.spacingX = 6
        s.PartyFrames.spacingY = 6

        s.RaidFrames.groupBy = true
        s.RaidFrames.colorByGroup = true
        s.RaidFrames.groupBrackets = true
        s.RaidFrames.columns = 8
        s.RaidFrames.layoutStyle = "Detailed"
        s.RaidFrames.width = 106
        s.RaidFrames.height = 28
        s.RaidFrames.spacingX = 4
        s.RaidFrames.spacingY = 3

        s.MainTankFrames.width = 282
        s.MainTankFrames.height = 62
        s.MainTankFrames.spacing = 8
        s.MainTankFrames.scale = 1.0

        s.Nameplates.healthBar.width = 212
        s.Nameplates.healthBar.height = 22
        s.Nameplates.healthBar.alpha = 1.0
        s.Nameplates.healthBar.nameFontSize = 11
        s.Nameplates.healthBar.healthPctFontSize = 10
        s.Nameplates.healthBar.nameAlign = "LEFT"
        s.Nameplates.healthBar.healthPctDisplay = "RIGHT"
        s.Nameplates.threatBar.width = 212
        s.Nameplates.threatBar.height = 6
        s.Nameplates.threatBar.alpha = 1.0
        s.Nameplates.castBar.width = 212
        s.Nameplates.castBar.height = 17
        s.Nameplates.castBar.alpha = 1.0
        s.Nameplates.castBar.fontSize = 13
        s.Nameplates.target.healthBar.width = 254
        s.Nameplates.target.healthBar.height = 26
        s.Nameplates.target.threatBar.width = 254
        s.Nameplates.target.threatBar.height = 7
        s.Nameplates.target.castBar.width = 254
        s.Nameplates.target.castBar.height = 19

        s.CastBars.player.width = 386
        s.CastBars.player.height = 22
        s.CastBars.player.scale = 108
        s.CastBars.target.width = 386
        s.CastBars.target.height = 22
        s.CastBars.target.scale = 108
        s.CastBars.focus.width = 312
        s.CastBars.focus.height = 22
        s.CastBars.focus.scale = 108

        s.PartyFrames.hideInRaid = true
        s.Combat.debuffBorderEnabled = true
        s.Combat.debuffOverlayGlobalEnabled = true
        s.Combat.debuffOverlayRaidEnabled = true
        s.Combat.dispelTrackingEnabled = true
        s.Combat.dispelTrackingMaxShown = 8
        s.General.healthTextMode = "percent"
    elseif name == "Mythic+" then
        SetTheme("Faithful")
        SetActionColorTheme("Glass")
        s.Messenger.style = "Default"
        ApplySharedDefaults()
        s.General.unitFrameBarStyle = "Gradient"
        s.General.hideQuestObjectivesAlways = false
        s.General.hideQuestObjectivesInCombat = true
        s.ConsumableBars.hideInactive = true
        s.ConsumableBars.showInInstancesOnly = true
        SetActionBarsEnabled(1, 4, true)
        SetActionBarsEnabled(5, 8, false)

        s.PlayerFrame.width = 384
        s.PlayerFrame.height = 66
        s.PlayerFrame.scale = 102
        s.PlayerFrame.alpha = 0.95

        s.TargetFrame.width = 384
        s.TargetFrame.height = 66
        s.TargetFrame.scale = 102
        s.TargetFrame.alpha = 0.95
        s.TargetFrame.debuffs.filterMode = "ALL"
        s.TargetFrame.debuffs.maxShown = 24

        s.FocusFrame.width = 324
        s.FocusFrame.height = 58
        s.FocusFrame.scale = 100
        s.FocusFrame.alpha = 0.95

        s.PartyFrames.layout = "Vertical"
        SetPartyStyle("Rendered")
        s.PartyFrames.width = 248
        s.PartyFrames.height = 62
        s.PartyFrames.spacingX = 8
        s.PartyFrames.spacingY = 7

        s.RaidFrames.groupBy = false
        s.RaidFrames.colorByGroup = false
        s.RaidFrames.groupBrackets = false
        s.RaidFrames.columns = 5
        s.RaidFrames.layoutStyle = "Simple"
        s.RaidFrames.width = 88
        s.RaidFrames.height = 22
        s.RaidFrames.spacingX = 6
        s.RaidFrames.spacingY = 4

        s.MainTankFrames.width = 266
        s.MainTankFrames.height = 60
        s.MainTankFrames.spacing = 7
        s.MainTankFrames.scale = 1.0

        s.Nameplates.healthBar.width = 220
        s.Nameplates.healthBar.height = 22
        s.Nameplates.healthBar.alpha = 1.0
        s.Nameplates.healthBar.nameFontSize = 11
        s.Nameplates.healthBar.healthPctFontSize = 10
        s.Nameplates.healthBar.nameAlign = "LEFT"
        s.Nameplates.healthBar.healthPctDisplay = "RIGHT"
        s.Nameplates.threatBar.width = 220
        s.Nameplates.threatBar.height = 6
        s.Nameplates.threatBar.alpha = 1.0
        s.Nameplates.castBar.width = 220
        s.Nameplates.castBar.height = 18
        s.Nameplates.castBar.alpha = 1.0
        s.Nameplates.castBar.fontSize = 13
        s.Nameplates.target.healthBar.width = 265
        s.Nameplates.target.healthBar.height = 28
        s.Nameplates.target.threatBar.width = 265
        s.Nameplates.target.threatBar.height = 7
        s.Nameplates.target.castBar.width = 265
        s.Nameplates.target.castBar.height = 20

        s.CastBars.player.width = 392
        s.CastBars.player.height = 22
        s.CastBars.player.scale = 110
        s.CastBars.target.width = 392
        s.CastBars.target.height = 22
        s.CastBars.target.scale = 110
        s.CastBars.focus.width = 324
        s.CastBars.focus.height = 22
        s.CastBars.focus.scale = 110

        s.Combat.debuffBorderEnabled = true
        s.Combat.debuffOverlayGlobalEnabled = true
        s.Combat.dispelTrackingEnabled = true
        s.Combat.dispelTrackingMaxShown = 8
        s.General.healthTextMode = "percent"
    else
        SetTheme("Class Color")
        SetActionColorTheme("Class Color")
        ApplySharedDefaults()
        s.General.unitFrameBarStyle = "Flat"
        s.General.hideQuestObjectivesAlways = true
        s.General.hideQuestObjectivesInCombat = true
        s.ConsumableBars.hideInactive = true
        s.ConsumableBars.showInInstancesOnly = false
        SetActionBarsEnabled(1, 4, true)
        SetActionBarsEnabled(5, 8, false)

        s.PlayerFrame.width = 388
        s.PlayerFrame.height = 68
        s.PlayerFrame.scale = 100
        s.PlayerFrame.alpha = 0.96

        s.TargetFrame.width = 388
        s.TargetFrame.height = 68
        s.TargetFrame.scale = 100
        s.TargetFrame.alpha = 0.96
        s.TargetFrame.debuffs.filterMode = "PLAYER"
        s.TargetFrame.debuffs.maxShown = 18

        s.FocusFrame.width = 330
        s.FocusFrame.height = 60
        s.FocusFrame.scale = 100
        s.FocusFrame.alpha = 0.96

        s.PartyFrames.layout = "Horizontal"
        SetPartyStyle("Rendered")
        s.PartyFrames.width = 232
        s.PartyFrames.height = 56
        s.PartyFrames.spacingX = 10
        s.PartyFrames.spacingY = 6

        s.RaidFrames.groupBy = false
        s.RaidFrames.colorByGroup = false
        s.RaidFrames.groupBrackets = false
        s.RaidFrames.columns = 6
        s.RaidFrames.layoutStyle = "Simple"
        s.RaidFrames.width = 90
        s.RaidFrames.height = 22
        s.RaidFrames.spacingX = 6
        s.RaidFrames.spacingY = 4

        s.MainTankFrames.width = 250
        s.MainTankFrames.height = 56
        s.MainTankFrames.spacing = 5
        s.MainTankFrames.scale = 1.0

        s.Nameplates.healthBar.width = 196
        s.Nameplates.healthBar.height = 19
        s.Nameplates.healthBar.alpha = 1.0
        s.Nameplates.healthBar.nameFontSize = 10
        s.Nameplates.healthBar.healthPctFontSize = 9
        s.Nameplates.healthBar.nameAlign = "CENTER"
        s.Nameplates.healthBar.healthPctDisplay = "RIGHT"
        s.Nameplates.threatBar.width = 196
        s.Nameplates.threatBar.height = 5
        s.Nameplates.threatBar.alpha = 1.0
        s.Nameplates.castBar.width = 196
        s.Nameplates.castBar.height = 15
        s.Nameplates.castBar.alpha = 1.0
        s.Nameplates.castBar.fontSize = 12
        s.Nameplates.target.healthBar.width = 232
        s.Nameplates.target.healthBar.height = 22
        s.Nameplates.target.threatBar.width = 232
        s.Nameplates.target.threatBar.height = 6
        s.Nameplates.target.castBar.width = 232
        s.Nameplates.target.castBar.height = 17

        s.CastBars.player.width = 350
        s.CastBars.player.height = 20
        s.CastBars.player.scale = 96
        s.CastBars.target.width = 350
        s.CastBars.target.height = 20
        s.CastBars.target.scale = 96
        s.CastBars.focus.width = 330
        s.CastBars.focus.height = 20
        s.CastBars.focus.scale = 96

        s.General.healthTextMode = "both"
        s.FocusFrame.scale = 110
        s.Combat.debuffBorderEnabled = true
        s.Combat.debuffOverlayGlobalEnabled = true
    end
    if not skipApply then
        ApplyAllSettings("WELCOME_PRESET")
    end
    Log("Preset applied: " .. tostring(name))
end


-- ============================================================
-- SECTION: Welcome Wizard
-- CreateWelcomeFrame() builds the full multi-step welcome wizard.
-- The wizard is a ~1200×730 (screen-clamped) DIALOG-strata frame.
-- Steps (pages) are individual sub-frames shown/hidden by ShowStep().
-- Navigation: back/next buttons + a step-dot indicator row.
-- Steps typically include: Welcome, Preset Picker, Unit Frames,
-- Action Bars, Chat, Import/Export, Done.
-- Each step is built inline as a local function or closure during
-- CreateWelcomeFrame's execution — all are upvalues of the factory.
-- ApplyPreset() is called when the player selects a preset card.
-- ExitPreviewMode() is exported as _G.MidnightUI_WelcomeExitPreview
-- so Settings_UI.lua can call it if the wizard is open when the
-- settings panel is closed.
-- The frame is a module-upvalue singleton (welcomeFrame); created
-- on first ShowWelcome call, then reused.
-- READS:  UI_COLORS, TITLE_FONT, ApplyPreset, EnsureSettings,
--         Profiles, _G.MidnightUI_ShowProfileImport/Export
-- WRITES: welcomeFrame (singleton), MidnightUISettings (via presets)
-- FUNCTIONS:
--   CreateWelcomeFrame()         → wizard frame (lazy singleton)
--   [upvalue]: ShowStep(n, init) — navigate to step n
--   [upvalue]: ExitPreviewMode() — restore pre-preview state
--   _G.MidnightUI_WelcomeExitPreview — exported alias
-- ============================================================
local function CreateWelcomeFrame()
    local screenW = (UIParent and UIParent.GetWidth and UIParent:GetWidth()) or 1920
    local screenH = (UIParent and UIParent.GetHeight and UIParent:GetHeight()) or 1080
    if screenW < 500 then screenW = 1920 end
    if screenH < 400 then screenH = 1080 end
    local maxSafeW = math.floor(screenW * 0.92)
    local maxSafeH = math.floor(screenH * 0.90)

    local BASE_W = math.max(1040, math.min(maxSafeW, 1320, math.floor(screenW * 0.62)))
    local BASE_H = math.max(660, math.min(maxSafeH, 780, math.floor(screenH * 0.70)))
    local EXPANDED_W = math.max(BASE_W + 80, math.min(maxSafeW, 1500, math.floor(screenW * 0.70)))
    local EXPANDED_H = math.max(BASE_H + 70, math.min(maxSafeH, 920, math.floor(screenH * 0.78)))
    local NUM_STEPS = 6
    local f = CreatePanel(UIParent, BASE_W, BASE_H)
    f:SetPoint("CENTER", UIParent, "CENTER", 0, 0)
    f:SetFrameStrata("DIALOG")
    f:SetFrameLevel(110)
    f:Hide()

    f:EnableKeyboard(true)
    f:SetScript("OnKeyDown", function(self, key)
        if key == "ESCAPE" then
            self:Hide()
            self:SetPropagateKeyboardInput(false)
        else
            self:SetPropagateKeyboardInput(true)
        end
    end)

    local state = {
        preset = "MidnightUI",
        theme = "Default",
        barStyle = "Gradient",
        frameSize = "Balanced",
        healthText = "percent",
        chatSystem = "midnight",
        chatStyle = "Default",
        minimapShape = "Circle",
        nameplatesEnabled = true,
    }


    f._ambient = f:CreateTexture(nil, "BACKGROUND")
    f._ambient:SetTexture("Interface\\Buttons\\WHITE8X8")
    f._ambient:SetPoint("TOPLEFT", 1, -1)
    f._ambient:SetPoint("BOTTOMRIGHT", -1, 1)
    f._ambient:SetVertexColor(0.03, 0.06, 0.11, 0.75)

    local leftRail = CreatePanel(f, 214, BASE_H)
    leftRail:SetPoint("TOPLEFT", 0, 0)
    leftRail:SetBackdropColor(0.04, 0.06, 0.12, 0.95)
    f._leftRail = leftRail

    leftRail._glow = leftRail:CreateTexture(nil, "ARTWORK")
    leftRail._glow:SetTexture("Interface\\Buttons\\WHITE8X8")
    leftRail._glow:SetPoint("TOPLEFT", 0, 0)
    leftRail._glow:SetPoint("TOPRIGHT", 0, 0)
    leftRail._glow:SetHeight(170)
    leftRail._glow:SetVertexColor(0.08, 0.15, 0.25, 0.55)

    leftRail._title = leftRail:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
    leftRail._title:SetPoint("TOPLEFT", 18, -20)
    leftRail._title:SetText("MIDNIGHT UI")
    leftRail._title:SetTextColor(UI_COLORS.text[1], UI_COLORS.text[2], UI_COLORS.text[3])
    TrySetFont(leftRail._title, 14)

    leftRail._sub = leftRail:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    leftRail._sub:SetPoint("TOPLEFT", 18, -44)
    leftRail._sub:SetText("SETUP WIZARD")
    leftRail._sub:SetTextColor(UI_COLORS.accent[1], UI_COLORS.accent[2], UI_COLORS.accent[3])

    leftRail._ringA = leftRail:CreateTexture(nil, "ARTWORK")
    leftRail._ringA:SetTexture("Interface\\Buttons\\WHITE8X8")
    if leftRail._ringA.SetMaskTexture then
        leftRail._ringA:SetMaskTexture("Interface\\CharacterFrame\\TempPortraitAlphaMask")
    end
    leftRail._ringA:SetSize(118, 118)
    leftRail._ringA:SetPoint("BOTTOM", leftRail, "BOTTOM", 2, 36)
    leftRail._ringA:SetVertexColor(0.00, 0.78, 1.00, 0.34)
    leftRail._ringA:SetBlendMode("ADD")

    leftRail._ringB = leftRail:CreateTexture(nil, "ARTWORK")
    leftRail._ringB:SetTexture("Interface\\Buttons\\WHITE8X8")
    if leftRail._ringB.SetMaskTexture then
        leftRail._ringB:SetMaskTexture("Interface\\CharacterFrame\\TempPortraitAlphaMask")
    end
    leftRail._ringB:SetSize(70, 70)
    leftRail._ringB:SetPoint("CENTER", leftRail._ringA, "CENTER", 0, 0)
    leftRail._ringB:SetVertexColor(0.15, 0.72, 1.00, 0.48)
    leftRail._ringB:SetBlendMode("ADD")
    local ringA = leftRail._ringA
    local ringB = leftRail._ringB

    local content = CreateFrame("Frame", nil, f)
    content:SetPoint("TOPLEFT", leftRail, "TOPRIGHT", 0, 0)
    content:SetPoint("BOTTOMRIGHT", 0, 0)

    content._headerBg = content:CreateTexture(nil, "BACKGROUND")
    content._headerBg:SetPoint("TOPLEFT", 0, 0)
    content._headerBg:SetPoint("TOPRIGHT", 0, 0)
    content._headerBg:SetHeight(68)
    content._headerBg:SetColorTexture(0.04, 0.06, 0.12, 0.92)
    CreateHeaderLine(content, -68)

    local title = content:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
    title:SetPoint("TOPLEFT", 20, -18)
    title:SetText("Welcome to MidnightUI")
    title:SetTextColor(UI_COLORS.text[1], UI_COLORS.text[2], UI_COLORS.text[3])
    TrySetFont(title, 18)

    local subtitle = content:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    subtitle:SetPoint("TOPLEFT", 20, -44)
    subtitle:SetText("Set up in 6 steps")
    subtitle:SetTextColor(UI_COLORS.muted[1], UI_COLORS.muted[2], UI_COLORS.muted[3])
    TrySetFont(subtitle, 12)

    local close = CreateFrame("Button", nil, f, "BackdropTemplate")
    close:SetSize(22, 22)
    close:SetPoint("TOPRIGHT", -10, -10)
    close:SetText("X")
    close:SetNormalFontObject("GameFontNormalSmall")
    StyleButton(close)
    close:SetScript("OnClick", function() f:Hide() end)

    local stepBar = CreateFrame("Frame", nil, leftRail)
    stepBar:SetPoint("TOPLEFT", 14, -72)
    stepBar:SetPoint("TOPRIGHT", -14, -72)
    stepBar:SetHeight(NUM_STEPS * 36 + 10)

    local stepButtons = {}
    local stepPages = {}
    local currentStep = 1
    local unlockedStep = 1

    local pageHost = CreateFrame("Frame", nil, content)
    pageHost:SetPoint("TOPLEFT", 16, -82)
    pageHost:SetPoint("BOTTOMRIGHT", -16, 60)

    local footer = CreateFrame("Frame", nil, content, "BackdropTemplate")
    footer:SetPoint("BOTTOMLEFT", 0, 0)
    footer:SetPoint("BOTTOMRIGHT", 0, 0)
    footer:SetHeight(56)
    footer:SetBackdrop({
        bgFile = "Interface\\Buttons\\WHITE8X8",
        edgeFile = "Interface\\Buttons\\WHITE8X8",
        edgeSize = 1,
        insets = { left = 0, right = 0, top = 1, bottom = 0 }
    })
    footer:SetBackdropColor(0.04, 0.06, 0.12, 0.9)
    footer:SetBackdropBorderColor(UI_COLORS.border[1], UI_COLORS.border[2], UI_COLORS.border[3], 0.75)

    local btnBack = CreateFrame("Button", nil, footer, "UIPanelButtonTemplate")
    btnBack:SetPoint("LEFT", 16, 0)
    btnBack:SetSize(120, 30)
    btnBack:SetText("Back")
    StyleButton(btnBack)

    local btnNext = CreateFrame("Button", nil, footer, "UIPanelButtonTemplate")
    btnNext:SetPoint("RIGHT", -16, 0)
    btnNext:SetSize(160, 30)
    btnNext:SetText("Next")
    StyleButton(btnNext)

    local footerStatus = footer:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    footerStatus:SetPoint("LEFT", btnBack, "RIGHT", 12, 0)
    footerStatus:SetPoint("RIGHT", btnNext, "LEFT", -12, 0)
    footerStatus:SetText("")
    footerStatus:SetJustifyH("CENTER")
    footerStatus:SetTextColor(UI_COLORS.muted[1], UI_COLORS.muted[2], UI_COLORS.muted[3])

    local function SetFooterStatus(text, r, g, b)
        footerStatus:SetText(text or "")
        footerStatus:SetTextColor(r or UI_COLORS.muted[1], g or UI_COLORS.muted[2], b or UI_COLORS.muted[3])
    end

    local stepCaptions = {
        [1] = "Profiles",
        [2] = "Playstyle",
        [3] = "Theme",
        [4] = "Frames",
        [5] = "Chat & Map",
        [6] = "Finish",
    }

    local function CreateStepButton(idx, label)
        local b = CreateFrame("Button", nil, stepBar, "BackdropTemplate")
        b:SetSize(172, 30)
        b:SetPoint("TOPLEFT", 0, -((idx - 1) * 36))
        b:SetBackdrop({ bgFile = "Interface\\Buttons\\WHITE8X8", edgeFile = "Interface\\Buttons\\WHITE8X8", edgeSize = 1 })
        b:SetBackdropColor(0.08, 0.11, 0.17, 0.84)
        b:SetBackdropBorderColor(0.15, 0.20, 0.28, 1)

        local glow = b:CreateTexture(nil, "ARTWORK")
        glow:SetTexture("Interface\\Buttons\\WHITE8X8")
        glow:SetPoint("TOPLEFT", 1, -1)
        glow:SetPoint("BOTTOMRIGHT", -1, 1)
        glow:SetVertexColor(0, 0, 0, 0)
        b._glow = glow

        local num = b:CreateFontString(nil, "OVERLAY", "GameFontNormal")
        num:SetPoint("LEFT", 10, 0)
        num:SetText(tostring(idx))
        num:SetTextColor(UI_COLORS.accent[1], UI_COLORS.accent[2], UI_COLORS.accent[3])
        b._num = num

        local txt = b:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
        txt:SetPoint("LEFT", num, "RIGHT", 10, 0)
        txt:SetText(label)
        txt:SetTextColor(UI_COLORS.muted[1], UI_COLORS.muted[2], UI_COLORS.muted[3])
        b._txt = txt

        return b
    end

    for i = 1, NUM_STEPS do
        stepButtons[i] = CreateStepButton(i, stepCaptions[i])
    end

    local onStepChanged = nil
    local previewDock = nil

    local function ExitPreviewMode()
        local st = EnsureSettings()
        st.Messenger.locked = true
        local settingsApi = _G.MidnightUI_Settings
        if settingsApi and settingsApi.GridFrame then settingsApi.GridFrame:Hide() end
        if settingsApi and settingsApi.MoveHUD then settingsApi.MoveHUD:Hide() end
        if _G.MidnightUI_MoveHUDRestore then _G.MidnightUI_MoveHUDRestore:Hide() end
        ApplyAllSettings("WELCOME_EXIT_PREVIEW")
        _G.MidnightUI_MovementModeFromWizard = false
        if previewDock then previewDock:Hide() end
        f:Show()
        f:Raise()
        if onStepChanged then onStepChanged(currentStep) end
    end

    local function EnsurePreviewDock()
        if previewDock then return previewDock end
        previewDock = CreatePanel(UIParent, 404, 62)
        previewDock:SetPoint("TOP", UIParent, "TOP", 0, -132)
        previewDock:SetFrameStrata("DIALOG")
        previewDock:SetFrameLevel(220)
        previewDock:SetBackdropColor(0.06, 0.10, 0.17, 0.95)

        local dockGlow = previewDock:CreateTexture(nil, "BACKGROUND")
        dockGlow:SetTexture("Interface\\Buttons\\WHITE8X8")
        dockGlow:SetPoint("TOPLEFT", 1, -1)
        dockGlow:SetPoint("TOPRIGHT", -1, -1)
        dockGlow:SetHeight(24)
        dockGlow:SetVertexColor(0.08, 0.18, 0.31, 0.55)

        local dockText = previewDock:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
        dockText:SetPoint("LEFT", 12, 0)
        dockText:SetPoint("RIGHT", -146, 0)
        dockText:SetJustifyH("LEFT")
        dockText:SetText("Preview mode is active.")
        dockText:SetTextColor(UI_COLORS.muted[1], UI_COLORS.muted[2], UI_COLORS.muted[3])
        TrySetFont(dockText, 12)
        previewDock._msg = dockText

        local btnReturn = CreateFrame("Button", nil, previewDock, "UIPanelButtonTemplate")
        btnReturn:SetPoint("RIGHT", -10, 0)
        btnReturn:SetSize(128, 30)
        btnReturn:SetText("Return to Setup")
        StyleButton(btnReturn)
        btnReturn:SetScript("OnClick", ExitPreviewMode)

        previewDock:Hide()
        return previewDock
    end

    local function RefreshStepButtons()
        for i = 1, NUM_STEPS do
            local b = stepButtons[i]
            if b then
                local locked = i > unlockedStep
                if i == currentStep then
                    b:SetBackdropColor(0.10, 0.18, 0.29, 1)
                    b._glow:SetVertexColor(0.00, 0.78, 1.00, 0.14)
                    b._txt:SetTextColor(1, 1, 1)
                elseif locked then
                    b:SetBackdropColor(0.07, 0.08, 0.12, 0.7)
                    b._glow:SetVertexColor(0, 0, 0, 0)
                    b._txt:SetTextColor(0.39, 0.41, 0.46)
                else
                    b:SetBackdropColor(0.08, 0.11, 0.17, 0.86)
                    b._glow:SetVertexColor(0, 0, 0, 0)
                    b._txt:SetTextColor(UI_COLORS.muted[1], UI_COLORS.muted[2], UI_COLORS.muted[3])
                end
            end
        end
    end

    local stepSubtitles = {
        [1] = "Import a profile or start from scratch",
        [2] = "Choose your gameplay focus",
        [3] = "Pick your visual theme and bar style",
        [4] = "Size your unit frames and health text",
        [5] = "Set up chat and minimap preferences",
        [6] = "Review your choices and finish setup",
    }

    local function ShowStep(idx, force)
        if idx < 1 or idx > NUM_STEPS then return end
        if not force and idx > unlockedStep then return end
        if not force and currentStep == 1 and idx > 1 then return end
        currentStep = idx
        if currentStep > unlockedStep then unlockedStep = currentStep end
        for i = 1, NUM_STEPS do
            if stepPages[i] then stepPages[i]:Hide() end
        end
        if stepPages[idx] then stepPages[idx]:Show() end
        subtitle:SetText(stepSubtitles[idx] or "")
        RefreshStepButtons()
        if onStepChanged then onStepChanged(idx) end
    end

    for i = 1, NUM_STEPS do
        stepButtons[i]:SetScript("OnClick", function()
            ShowStep(i, false)
        end)
    end

    local SyncPlaystyleSelection, SyncThemeSelection, SyncFrameSelection, SyncChatMapSelection
    local UpdateSummary, SetBarPreviewMode, UpdateNameplatePreview
    local UpdateFrameSizePreview
    local playstyleCards, themeCards, barStyleCards
    local frameSizeCards, healthTextCards
    local chatSystemCards, chatStyleCards, minimapCards, chatStyleSection
    local npCheck, npSliders, nameScaleSlider, valScaleSlider
    local RefreshQuickOptions
    local communityGlow
    local function Darken(v, f) local n = (v or 0) * f; if n > 1 then return 1 end; return n end
    local function Lift(v, a) local base = v or 0; local n = base + (1 - base) * (a or 0.035); if n > 1 then return 1 end; return n end
    local classR, classG, classB = 0.80, 0.35, 0.35
    do
        local _, engClass = UnitClass("player")
        if engClass and C_ClassColor and C_ClassColor.GetClassColor then
            local cc = C_ClassColor.GetClassColor(engClass)
            if cc then classR, classG, classB = cc.r, cc.g, cc.b end
        elseif engClass and RAID_CLASS_COLORS and RAID_CLASS_COLORS[engClass] then
            local cc = RAID_CLASS_COLORS[engClass]
            classR, classG, classB = cc.r, cc.g, cc.b
        end
    end

    do
    local p1 = CreateFrame("Frame", nil, pageHost)
    p1:SetAllPoints()
    stepPages[1] = p1

    local p1Tag = p1:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    p1Tag:SetPoint("TOPLEFT", 4, -6)
    p1Tag:SetText("STEP 1  PROFILES")
    p1Tag:SetTextColor(UI_COLORS.accent[1], UI_COLORS.accent[2], UI_COLORS.accent[3])
    TrySetFont(p1Tag, 12, "OUTLINE")

    local p1Head = p1:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
    p1Head:SetPoint("TOPLEFT", 4, -26)
    p1Head:SetText("Start with your profile")
    p1Head:SetTextColor(UI_COLORS.text[1], UI_COLORS.text[2], UI_COLORS.text[3])
    TrySetFont(p1Head, 20, "OUTLINE")

    local p1Sub = p1:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    p1Sub:SetPoint("TOPLEFT", 4, -52)
    p1Sub:SetPoint("TOPRIGHT", -4, -52)
    p1Sub:SetJustifyH("LEFT")
    p1Sub:SetText("Choose your path: import an existing profile, or begin fresh and fine-tune everything with live previews.")
    p1Sub:SetTextColor(UI_COLORS.muted[1], UI_COLORS.muted[2], UI_COLORS.muted[3])
    TrySetFont(p1Sub, 14)
    CreateHeaderLine(p1, -82)

    local actionRow = CreateFrame("Frame", nil, p1)
    actionRow:SetPoint("TOPLEFT", 4, -94)
    actionRow:SetPoint("TOPRIGHT", -4, -94)
    actionRow:SetHeight(206)

    local tileFresh = CreateActionTile(
        actionRow,
        "Start a New Profile",
        "Build a solid baseline now, then tune role, visuals, and overlays step by step.",
        "Start Fresh",
        626001,
        function()
            if f and f.AnimateSize then f:AnimateSize(EXPANDED_W, EXPANDED_H, 0.25) end
            unlockedStep = math.max(unlockedStep, 2)
            ShowStep(2, true)
        end,
        1, 206
    )
    tileFresh:ClearAllPoints()
    tileFresh:SetPoint("TOPLEFT", actionRow, "TOPLEFT", 0, 0)
    tileFresh:SetPoint("BOTTOMRIGHT", actionRow, "BOTTOM", -6, 0)

    local tileImport = CreateActionTile(
        actionRow,
        "Import a Profile",
        "Paste a profile code, or copy settings from another character on your account.",
        "Open Import",
        134939,
        function()
            _G.MidnightUI_ShowProfileImport(function()
                if Profiles and Profiles.MarkWelcomeSeen then Profiles.MarkWelcomeSeen() end
                f:Hide()
            end)
        end,
        1, 206
    )
    tileImport:ClearAllPoints()
    tileImport:SetPoint("TOPLEFT", actionRow, "TOP", 6, 0)
    tileImport:SetPoint("BOTTOMRIGHT", actionRow, "BOTTOMRIGHT", 0, 0)

    local quickBox = CreatePanel(p1, 1, 198)
    quickBox:SetPoint("TOPLEFT", 4, -304)
    quickBox:SetPoint("TOPRIGHT", -4, -304)
    quickBox:SetBackdropColor(UI_COLORS.inset[1], UI_COLORS.inset[2], UI_COLORS.inset[3], 0.92)

    local quickGlow = quickBox:CreateTexture(nil, "BACKGROUND")
    quickGlow:SetTexture("Interface\\Buttons\\WHITE8X8")
    quickGlow:SetPoint("TOPLEFT", 1, -1)
    quickGlow:SetPoint("TOPRIGHT", -1, -1)
    quickGlow:SetHeight(30)
    quickGlow:SetVertexColor(0.08, 0.16, 0.28, 0.45)

    local quickTitle = quickBox:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    quickTitle:SetPoint("TOPLEFT", 14, -10)
    quickTitle:SetText("Copy from an Existing Character.")
    quickTitle:SetTextColor(UI_COLORS.text[1], UI_COLORS.text[2], UI_COLORS.text[3])
    TrySetFont(quickTitle, 13, "OUTLINE")

    local quickBody = quickBox:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    quickBody:SetPoint("TOPLEFT", 14, -30)
    quickBody:SetPoint("TOPRIGHT", -14, -30)
    quickBody:SetJustifyH("LEFT")
    quickBody:SetText("Pick a saved profile, import it, then jump straight into fine-tuning.")
    quickBody:SetTextColor(UI_COLORS.muted[1], UI_COLORS.muted[2], UI_COLORS.muted[3])
    TrySetFont(quickBody, 12)

    local quickDrop = CreateFrame("Frame", "MidnightUI_WelcomeQuickImportDropdown", quickBox, "UIDropDownMenuTemplate")
    quickDrop:SetPoint("TOPLEFT", 10, -60)
    UIDropDownMenu_SetWidth(quickDrop, 330)
    UIDropDownMenu_SetText(quickDrop, "Choose character profile")

    local quickTips = quickBox:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    quickTips:SetPoint("TOPLEFT", quickDrop, "TOPRIGHT", 20, -2)
    quickTips:SetPoint("TOPRIGHT", -14, -60)
    quickTips:SetJustifyH("LEFT")
    quickTips:SetText("|cffffff00Tip:|r\nUse \"Start Fresh\" if you want to rebuild from scratch.\nUse import when you already trust another setup.")
    quickTips:SetTextColor(UI_COLORS.muted[1], UI_COLORS.muted[2], UI_COLORS.muted[3])
    TrySetFont(quickTips, 12)

    local quickStatus = quickBox:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    quickStatus:SetPoint("BOTTOMLEFT", 14, 14)
    quickStatus:SetText("")
    quickStatus:SetTextColor(1, 0.4, 0.4)
    TrySetFont(quickStatus, 12)

    local btnQuickImport = CreateFrame("Button", nil, quickBox, "UIPanelButtonTemplate")
    btnQuickImport:SetPoint("BOTTOMRIGHT", -14, 12)
    btnQuickImport:SetSize(162, 30)
    btnQuickImport:SetText("Import Selected")
    StyleButton(btnQuickImport)

    local btnQuickRefresh = CreateFrame("Button", nil, quickBox, "UIPanelButtonTemplate")
    btnQuickRefresh:SetPoint("RIGHT", btnQuickImport, "LEFT", -8, 0)
    btnQuickRefresh:SetSize(108, 30)
    btnQuickRefresh:SetText("Refresh List")
    StyleButton(btnQuickRefresh)

    local quickOptions = {}
    local quickSelectedKey = nil

    RefreshQuickOptions = function()
        quickOptions = ResolveProfileOptions()
        quickSelectedKey = nil
        UIDropDownMenu_SetText(quickDrop, "Choose character profile")
        quickStatus:SetText("")
        if #quickOptions == 0 then
            UIDropDownMenu_SetText(quickDrop, "No character profiles found yet")
            if quickDrop.Text then
                quickDrop.Text:SetTextColor(UI_COLORS.muted[1], UI_COLORS.muted[2], UI_COLORS.muted[3])
            end
        else
            UIDropDownMenu_SetText(quickDrop, "Choose character profile")
            if quickDrop.Text then
                quickDrop.Text:SetTextColor(UI_COLORS.text[1], UI_COLORS.text[2], UI_COLORS.text[3])
            end
        end

        UIDropDownMenu_Initialize(quickDrop, function()
            if #quickOptions == 0 then
                local info = UIDropDownMenu_CreateInfo()
                info.text = "No other character profiles found"
                info.disabled = true
                UIDropDownMenu_AddButton(info)
                return
            end
            for _, entry in ipairs(quickOptions) do
                local info = UIDropDownMenu_CreateInfo()
                info.text = entry.label
                info.func = function()
                    quickSelectedKey = entry.key
                    UIDropDownMenu_SetText(quickDrop, entry.label)
                    if quickDrop.Text then
                        quickDrop.Text:SetTextColor(UI_COLORS.text[1], UI_COLORS.text[2], UI_COLORS.text[3])
                    end
                    local created = entry.createdAt and date("%b %d, %Y", entry.createdAt) or "Unknown"
                    quickStatus:SetText("Selected: " .. entry.label .. "  Saved: " .. created)
                end
                UIDropDownMenu_AddButton(info)
            end
        end)
    end

    btnQuickImport:SetScript("OnClick", function()
        quickStatus:SetText("")
        if not quickSelectedKey then
            quickStatus:SetText("Select a profile first.")
            return
        end
        if not Profiles or not Profiles.ImportProfileFromKey then
            quickStatus:SetText("Profile system is not available.")
            return
        end
        local ok, err = Profiles.ImportProfileFromKey(quickSelectedKey)
        if ok then
            if Profiles.MarkWelcomeSeen then Profiles.MarkWelcomeSeen() end
            f:Hide()
            return
        end
        quickStatus:SetText("Import failed. Check Diagnostics for details.")
        Log("Quick import failed: " .. tostring(err))
    end)

    btnQuickRefresh:SetScript("OnClick", RefreshQuickOptions)

    end

    do
    local p2 = CreateFrame("Frame", nil, pageHost)
    p2:SetAllPoints()
    p2:Hide()
    stepPages[2] = p2

    local p2Tag = p2:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    p2Tag:SetPoint("TOPLEFT", 4, -6)
    p2Tag:SetText("STEP 2  PLAYSTYLE")
    p2Tag:SetTextColor(UI_COLORS.accent[1], UI_COLORS.accent[2], UI_COLORS.accent[3])
    TrySetFont(p2Tag, 12, "OUTLINE")

    local p2Head = p2:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
    p2Head:SetPoint("TOPLEFT", 4, -26)
    p2Head:SetText("What Do You Play?")
    p2Head:SetTextColor(UI_COLORS.text[1], UI_COLORS.text[2], UI_COLORS.text[3])
    TrySetFont(p2Head, 20, "OUTLINE")

    local p2Sub = p2:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    p2Sub:SetPoint("TOPLEFT", 4, -52)
    p2Sub:SetPoint("TOPRIGHT", -4, -52)
    p2Sub:SetJustifyH("LEFT")
    p2Sub:SetText("Pick your main activity and MidnightUI will tune frames, nameplates, debuff tracking, and party layout to match.")
    p2Sub:SetTextColor(UI_COLORS.muted[1], UI_COLORS.muted[2], UI_COLORS.muted[3])
    TrySetFont(p2Sub, 14)
    CreateHeaderLine(p2, -82)

    local p2Grid = CreateFrame("Frame", nil, p2)
    p2Grid:SetPoint("TOPLEFT", 4, -94)
    p2Grid:SetPoint("TOPRIGHT", -4, -94)
    p2Grid:SetHeight(320)

    playstyleCards = {}

    playstyleCards.mythicPlus = CreateVisualCard(p2Grid, {
        title = "Mythic+ & Dungeons",
        tagline = "Interrupts, debuff awareness, 5-player focus",
        description = "Bigger cast bars for timing kicks. Target debuffs show ALL effects -- Bursting stacks, boss DoTs, everything your healer needs to see. Detailed nameplates with cast bars you can actually read.",
        accentColor = {1.00, 0.58, 0.28},
        width = 380,
        height = 145,
        group = "playstyle",
        onClick = function()
            ApplyPreset("Mythic+", true)
            ApplyAllSettings("WIZARD_PLAYSTYLE")
            state.preset = "Mythic+"
        end,
    })
    playstyleCards.mythicPlus:ClearAllPoints()
    playstyleCards.mythicPlus:SetPoint("TOPLEFT", p2Grid, "TOPLEFT", 0, 0)
    playstyleCards.mythicPlus:SetPoint("RIGHT", p2Grid, "CENTER", -6, 0)
    playstyleCards.mythicPlus:SetHeight(145)

    playstyleCards.raiding = CreateVisualCard(p2Grid, {
        title = "Raiding",
        tagline = "Raid frame awareness, healer tools, organized groups",
        description = "Wider raid frames that fit 20+ players on screen. Health % visible at a glance, group brackets to tell groups apart. Party frames auto-hide during raids. Dispel tracking for healers.",
        accentColor = {0.70, 0.45, 0.90},
        width = 380,
        height = 145,
        group = "playstyle",
        onClick = function()
            ApplyPreset("Raid Focused", true)
            ApplyAllSettings("WIZARD_PLAYSTYLE")
            state.preset = "Raid Focused"
        end,
    })
    playstyleCards.raiding:ClearAllPoints()
    playstyleCards.raiding:SetPoint("TOPLEFT", p2Grid, "TOP", 6, 0)
    playstyleCards.raiding:SetPoint("TOPRIGHT", p2Grid, "TOPRIGHT", 0, 0)
    playstyleCards.raiding:SetHeight(145)

    playstyleCards.pvp = CreateVisualCard(p2Grid, {
        title = "PvP & Arena",
        tagline = "Fast target-switching, class identification, YOUR debuffs",
        description = "Class-colored everything for instant enemy identification. Health shown as values AND percent. Only YOUR debuffs on targets. Horizontal party layout for arena partners.",
        accentColor = {0.90, 0.30, 0.30},
        width = 380,
        height = 145,
        group = "playstyle",
        onClick = function()
            ApplyPreset("Class Themed", true)
            ApplyAllSettings("WIZARD_PLAYSTYLE")
            state.preset = "Class Themed"
        end,
    })
    playstyleCards.pvp:ClearAllPoints()
    playstyleCards.pvp:SetPoint("TOPLEFT", playstyleCards.mythicPlus, "BOTTOMLEFT", 0, -10)
    playstyleCards.pvp:SetPoint("RIGHT", p2Grid, "CENTER", -6, 0)
    playstyleCards.pvp:SetHeight(145)

    playstyleCards.casual = CreateVisualCard(p2Grid, {
        title = "Just Exploring",
        tagline = "Balanced and polished for everything",
        description = "Comfortable defaults for questing, dungeons, raids, and casual PvP. Polished gradient bars, medium-sized frames, everything enabled.",
        accentColor = {0.00, 0.78, 1.00},
        width = 380,
        height = 145,
        group = "playstyle",
        onClick = function()
            ApplyPreset("MidnightUI", true)
            ApplyAllSettings("WIZARD_PLAYSTYLE")
            state.preset = "MidnightUI"
        end,
    })
    playstyleCards.casual:ClearAllPoints()
    playstyleCards.casual:SetPoint("TOPLEFT", playstyleCards.raiding, "BOTTOMLEFT", 0, -10)
    playstyleCards.casual:SetPoint("TOPRIGHT", playstyleCards.raiding, "BOTTOMRIGHT", 0, -10)
    playstyleCards.casual:SetHeight(145)

    SyncPlaystyleSelection = function()
        local presetToCard = {
            ["Mythic+"] = playstyleCards.mythicPlus,
            ["Raid Focused"] = playstyleCards.raiding,
            ["Class Themed"] = playstyleCards.pvp,
            ["MidnightUI"] = playstyleCards.casual,
        }
        for _, card in pairs(playstyleCards) do card:Deselect() end
        local sel = presetToCard[state.preset]
        if sel then sel:Select() end
    end

    end

    do
    local p3 = CreateFrame("Frame", nil, pageHost)
    p3:SetAllPoints()
    p3:Hide()
    stepPages[3] = p3

    local p3Tag = p3:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    p3Tag:SetPoint("TOPLEFT", 4, -6)
    p3Tag:SetText("STEP 3  THEME")
    p3Tag:SetTextColor(UI_COLORS.accent[1], UI_COLORS.accent[2], UI_COLORS.accent[3])
    TrySetFont(p3Tag, 12, "OUTLINE")

    local p3Head = p3:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
    p3Head:SetPoint("TOPLEFT", 4, -26)
    p3Head:SetText("Pick Your Look")
    p3Head:SetTextColor(UI_COLORS.text[1], UI_COLORS.text[2], UI_COLORS.text[3])
    TrySetFont(p3Head, 20, "OUTLINE")

    local p3Sub = p3:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    p3Sub:SetPoint("TOPLEFT", 4, -52)
    p3Sub:SetPoint("TOPRIGHT", -4, -52)
    p3Sub:SetJustifyH("LEFT")
    p3Sub:SetText("Choose a theme for all your frames and panels, then pick how health bars look.")
    p3Sub:SetTextColor(UI_COLORS.muted[1], UI_COLORS.muted[2], UI_COLORS.muted[3])
    TrySetFont(p3Sub, 14)
    CreateHeaderLine(p3, -82)

    local p3Scroll = CreateFrame("ScrollFrame", nil, p3, "UIPanelScrollFrameTemplate")
    p3Scroll:SetPoint("TOPLEFT", 0, -90)
    p3Scroll:SetPoint("BOTTOMRIGHT", -20, 4)
    local p3Content = CreateFrame("Frame", nil, p3Scroll)
    p3Content:SetSize(math.max(700, EXPANDED_W - 350), 280)
    p3Scroll:SetScrollChild(p3Content)

    local themeHeader = p3Content:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    themeHeader:SetPoint("TOPLEFT", 4, 0)
    themeHeader:SetText("Theme")
    themeHeader:SetTextColor(UI_COLORS.text[1], UI_COLORS.text[2], UI_COLORS.text[3])
    TrySetFont(themeHeader, 14, "OUTLINE")


    themeCards = {}
    local themeGrid = CreateFrame("Frame", nil, p3Content)
    themeGrid:SetPoint("TOPLEFT", 4, -22)
    themeGrid:SetPoint("TOPRIGHT", -4, -22)
    themeGrid:SetHeight(110)

    local themeSwatches = {
        { name = "Default",     r = 0.08, g = 0.16, b = 0.38, tagline = "Deep midnight blue." },
        { name = "Class Color", r = classR, g = classG, b = classB, tagline = "Tinted to your class." },
        { name = "Faithful",    r = 0.55, g = 0.38, b = 0.22, tagline = "Warm, earthy tones." },
        { name = "Glass",       r = 0.35, g = 0.42, b = 0.55, tagline = "Sleek and minimal." },
    }

    local themeToActionStyle = {
        ["Default"] = "Glass",
        ["Class Color"] = "Class Color",
        ["Faithful"] = "Faithful",
        ["Glass"] = "Glass",
    }

    for idx, info in ipairs(themeSwatches) do
        local card = CreateVisualCard(themeGrid, {
            title = info.name,
            tagline = info.tagline,
            width = 190,
            height = 105,
            group = "theme",
            accentColor = {info.r, info.g, info.b},
            preview = function(c)
                local band = c:CreateTexture(nil, "ARTWORK", nil, 2)
                band:SetPoint("BOTTOMLEFT", 1, 1)
                band:SetPoint("BOTTOMRIGHT", -1, 1)
                band:SetHeight(38)
                band:SetColorTexture(info.r, info.g, info.b, 0.85)
                local bandFade = c:CreateTexture(nil, "ARTWORK", nil, 1)
                bandFade:SetPoint("BOTTOMLEFT", band, "TOPLEFT", 0, 0)
                bandFade:SetPoint("BOTTOMRIGHT", band, "TOPRIGHT", 0, 0)
                bandFade:SetHeight(12)
                bandFade:SetTexture("Interface\\Buttons\\WHITE8X8")
                bandFade:SetGradient("VERTICAL",
                    CreateColor(info.r, info.g, info.b, 0.85),
                    CreateColor(info.r, info.g, info.b, 0)
                )
            end,
            onClick = function()
                local s = EnsureSettings()
                s.GlobalStyle = info.name
                s.Messenger.style = info.name
                s.Minimap.infoBarStyle = info.name
                s.ActionBars.globalStyle = themeToActionStyle[info.name] or "Disabled"
                state.theme = info.name
                ApplyAllSettings("WIZARD_THEME")
            end,
        })
        themeCards[info.name] = card
    end
    do
        local tCards = { themeCards["Default"], themeCards["Class Color"], themeCards["Faithful"], themeCards["Glass"] }
        local function LayoutThemeCards()
            local gw = themeGrid:GetWidth()
            if gw < 100 then return end
            local gap = 10
            local cw = math.floor((gw - gap * 3) / 4)
            for i, c in ipairs(tCards) do
                c:ClearAllPoints()
                c:SetPoint("TOPLEFT", themeGrid, "TOPLEFT", (i - 1) * (cw + gap), 0)
                c:SetSize(cw, 105)
            end
        end
        themeGrid:HookScript("OnShow", LayoutThemeCards)
        themeGrid:HookScript("OnSizeChanged", LayoutThemeCards)
        C_Timer.After(0, LayoutThemeCards)
    end

    local barHeader = p3Content:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    barHeader:SetPoint("TOPLEFT", 4, -140)
    barHeader:SetText("Health Bar Style")
    barHeader:SetTextColor(UI_COLORS.text[1], UI_COLORS.text[2], UI_COLORS.text[3])
    TrySetFont(barHeader, 14, "OUTLINE")

    local barGrid = CreateFrame("Frame", nil, p3Content)
    barGrid:SetPoint("TOPLEFT", 4, -162)
    barGrid:SetPoint("TOPRIGHT", -4, -162)
    barGrid:SetHeight(66)

    barStyleCards = {}

    barStyleCards["Gradient"] = CreateVisualCard(barGrid, {
        title = "Gradient",
        tagline = "Polished 3D health bars with subtle shine.",
        width = 340,
        height = 62,
        group = "barStyle",
        accentColor = {0.30, 0.70, 0.50},
        onClick = function()
            local s = EnsureSettings()
            s.General.unitFrameBarStyle = "Gradient"
            state.barStyle = "Gradient"
            local S = _G.MidnightUI_Settings
            if S and S.ApplyUnitFrameBarStyle then SafeCall("ApplyUnitFrameBarStyle", S.ApplyUnitFrameBarStyle) end
        end,
    })
    barStyleCards["Gradient"]:ClearAllPoints()
    barStyleCards["Gradient"]:SetPoint("TOPLEFT", barGrid, "TOPLEFT", 0, 0)
    barStyleCards["Gradient"]:SetPoint("RIGHT", barGrid, "CENTER", -6, 0)
    barStyleCards["Gradient"]:SetHeight(62)

    barStyleCards["Flat"] = CreateVisualCard(barGrid, {
        title = "Flat",
        tagline = "Solid class-colored blocks. Clean, fast to read.",
        width = 340,
        height = 62,
        group = "barStyle",
        accentColor = {0.45, 0.45, 0.80},
        onClick = function()
            local s = EnsureSettings()
            s.General.unitFrameBarStyle = "Flat"
            state.barStyle = "Flat"
            local S = _G.MidnightUI_Settings
            if S and S.ApplyUnitFrameBarStyle then SafeCall("ApplyUnitFrameBarStyle", S.ApplyUnitFrameBarStyle) end
        end,
    })
    barStyleCards["Flat"]:ClearAllPoints()
    barStyleCards["Flat"]:SetPoint("TOPLEFT", barGrid, "TOP", 6, 0)
    barStyleCards["Flat"]:SetPoint("TOPRIGHT", barGrid, "TOPRIGHT", 0, 0)
    barStyleCards["Flat"]:SetHeight(62)

    local barPreviewHeader = p3Content:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    barPreviewHeader:SetPoint("TOPLEFT", 4, -236)
    barPreviewHeader:SetText("Preview")
    barPreviewHeader:SetTextColor(UI_COLORS.text[1], UI_COLORS.text[2], UI_COLORS.text[3])
    TrySetFont(barPreviewHeader, 14, "OUTLINE")

    local barPreview = CreatePanel(p3Content, 1, 130)
    barPreview:SetPoint("TOPLEFT", 4, -256)
    barPreview:SetPoint("TOPRIGHT", -4, -256)
    barPreview:SetBackdropColor(0.025, 0.03, 0.06, 0.98)
    barPreview:SetBackdropBorderColor(UI_COLORS.border[1], UI_COLORS.border[2], UI_COLORS.border[3], 0.3)


    local function CreateMockBar(parent, w, h, r, g, b, label, fillPct)
        local frame = CreateFrame("Frame", nil, parent)
        frame:SetSize(w, h)

        local bgTex = frame:CreateTexture(nil, "BACKGROUND")
        bgTex:SetAllPoints()
        bgTex:SetColorTexture(0.03, 0.04, 0.06, 0.95)

        local pct = fillPct or 0.78
        local fill = frame:CreateTexture(nil, "ARTWORK")
        fill:SetPoint("TOPLEFT", 0, 0)
        fill:SetPoint("BOTTOMLEFT", 0, 0)
        fill:SetWidth(math.max(2, math.floor(w * pct)))
        fill:SetColorTexture(r, g, b, 1)
        frame._fill = fill
        frame._fillPct = pct
        frame:HookScript("OnSizeChanged", function(self)
            local fw = self:GetWidth()
            if fw > 1 then self._fill:SetWidth(math.max(2, math.floor(fw * self._fillPct))) end
        end)

        local edgeR, edgeG, edgeB = Darken(r, 0.85), Darken(g, 0.85), Darken(b, 0.85)
        local midR, midG, midB = Lift(r, 0.02), Lift(g, 0.02), Lift(b, 0.02)
        local topH = math.max(1, math.floor(h * 0.43))
        local botH = math.max(1, math.floor(h * 0.57))
        local specH = math.max(1, math.floor(h * 0.35))

        local topHL = frame:CreateTexture(nil, "OVERLAY", nil, 2)
        topHL:SetPoint("TOPLEFT", fill, "TOPLEFT", 0, 0)
        topHL:SetPoint("TOPRIGHT", fill, "TOPRIGHT", 0, 0)
        topHL:SetHeight(topH)
        topHL:SetTexture("Interface\\Buttons\\WHITE8X8")
        topHL:SetBlendMode("DISABLE")
        topHL:SetGradient("VERTICAL", CreateColor(midR, midG, midB, 1), CreateColor(edgeR, edgeG, edgeB, 1))
        frame._topHL = topHL

        local botSH = frame:CreateTexture(nil, "OVERLAY", nil, 1)
        botSH:SetPoint("BOTTOMLEFT", fill, "BOTTOMLEFT", 0, 0)
        botSH:SetPoint("BOTTOMRIGHT", fill, "BOTTOMRIGHT", 0, 0)
        botSH:SetHeight(botH)
        botSH:SetTexture("Interface\\Buttons\\WHITE8X8")
        botSH:SetBlendMode("DISABLE")
        botSH:SetGradient("VERTICAL", CreateColor(edgeR, edgeG, edgeB, 1), CreateColor(midR, midG, midB, 1))
        frame._botSH = botSH

        local spec = frame:CreateTexture(nil, "OVERLAY", nil, 3)
        spec:SetPoint("TOPLEFT", fill, "TOPLEFT", 0, 0)
        spec:SetPoint("TOPRIGHT", fill, "TOPRIGHT", 0, 0)
        spec:SetHeight(specH)
        spec:SetTexture("Interface\\Buttons\\WHITE8X8")
        spec:SetBlendMode("ADD")
        spec:SetGradient("VERTICAL", CreateColor(1, 1, 1, 0), CreateColor(1, 1, 1, 0.06))
        frame._spec = spec

        if label then
            local lbl = frame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
            lbl:SetPoint("LEFT", 5, 0)
            lbl:SetText(label)
            lbl:SetTextColor(0.90, 0.91, 0.94)
            TrySetFont(lbl, math.max(7, math.floor(h * 0.5)))
        end

        return frame
    end

    local mc = {
        player = { classR, classG, classB },
        target = { 0.77, 0.12, 0.23 },
        party  = { {0.00,1.00,0.60}, {0.96,0.55,0.73}, {0.25,0.78,0.92} },
        raid   = { {1.00,0.49,0.04}, {0.00,0.44,0.87}, {0.67,0.83,0.45}, {0.53,0.53,0.93}, {1.00,0.96,0.41},
                   {0.77,0.12,0.23}, {0.20,0.58,0.50}, {0.96,0.55,0.73}, {0.00,1.00,0.60}, {0.78,0.61,0.43} },
    }

    local function MakeLabel(parent, text, x, y)
        local l = parent:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
        l:SetPoint("TOPLEFT", x, y)
        l:SetText(text)
        l:SetTextColor(0.50, 0.52, 0.58)
        TrySetFont(l, 9)
    end
    local COL_PAD = 16
    local COL_GAP_PX = 24

    local col1 = CreateFrame("Frame", nil, barPreview)
    col1:SetPoint("TOPLEFT", COL_PAD, 0)
    col1:SetPoint("BOTTOMLEFT", COL_PAD, 0)

    local col2 = CreateFrame("Frame", nil, barPreview)
    col2:SetPoint("TOP", 0, 0)
    col2:SetPoint("BOTTOM", 0, 0)

    local col3 = CreateFrame("Frame", nil, barPreview)
    col3:SetPoint("TOPRIGHT", -COL_PAD, 0)
    col3:SetPoint("BOTTOMRIGHT", -COL_PAD, 0)

    local sep1 = barPreview:CreateTexture(nil, "ARTWORK")
    sep1:SetWidth(1)
    sep1:SetColorTexture(0.12, 0.15, 0.22, 0.45)

    local sep2 = barPreview:CreateTexture(nil, "ARTWORK")
    sep2:SetWidth(1)
    sep2:SetColorTexture(0.12, 0.15, 0.22, 0.45)

    local function LayoutPreviewColumns()
        local pw = barPreview:GetWidth()
        if pw < 100 then return end
        local usable = pw - COL_PAD * 2 - COL_GAP_PX * 2
        local colW = math.floor(usable / 3)
        col1:SetWidth(colW)
        col2:SetWidth(colW)
        col3:SetWidth(colW)
        col1:ClearAllPoints()
        col1:SetPoint("TOPLEFT", COL_PAD, 0)
        col1:SetPoint("BOTTOMLEFT", COL_PAD, 0)
        col1:SetWidth(colW)
        col2:ClearAllPoints()
        col2:SetPoint("TOPLEFT", col1, "TOPRIGHT", COL_GAP_PX, 0)
        col2:SetPoint("BOTTOMLEFT", col1, "BOTTOMRIGHT", COL_GAP_PX, 0)
        col2:SetWidth(colW)
        col3:ClearAllPoints()
        col3:SetPoint("TOPLEFT", col2, "TOPRIGHT", COL_GAP_PX, 0)
        col3:SetPoint("BOTTOMLEFT", col2, "BOTTOMRIGHT", COL_GAP_PX, 0)
        col3:SetWidth(colW)
        sep1:ClearAllPoints()
        sep1:SetPoint("TOP", col1, "TOPRIGHT", math.floor(COL_GAP_PX / 2), -6)
        sep1:SetPoint("BOTTOM", col1, "BOTTOMRIGHT", math.floor(COL_GAP_PX / 2), 6)
        sep2:ClearAllPoints()
        sep2:SetPoint("TOP", col2, "TOPRIGHT", math.floor(COL_GAP_PX / 2), -6)
        sep2:SetPoint("BOTTOM", col2, "BOTTOMRIGHT", math.floor(COL_GAP_PX / 2), 6)
    end
    barPreview:HookScript("OnShow", LayoutPreviewColumns)
    barPreview:HookScript("OnSizeChanged", LayoutPreviewColumns)
    C_Timer.After(0, LayoutPreviewColumns)

    MakeLabel(col1, "PLAYER / TARGET", 0, -8)
    MakeLabel(col2, "PARTY", 0, -8)
    MakeLabel(col3, "RAID", 0, -8)

    local mockBars = {}

    mockBars[#mockBars+1] = CreateMockBar(col1, 10, 24, mc.player[1], mc.player[2], mc.player[3], "You", 0.82)
    mockBars[#mockBars]:SetPoint("TOPLEFT", 0, -22)
    mockBars[#mockBars]:SetPoint("TOPRIGHT", 0, -22)
    mockBars[#mockBars+1] = CreateMockBar(col1, 10, 6, 0.10, 0.32, 0.78, nil, 0.65)
    mockBars[#mockBars]:SetPoint("TOPLEFT", mockBars[#mockBars-1], "BOTTOMLEFT", 0, -1)
    mockBars[#mockBars]:SetPoint("TOPRIGHT", mockBars[#mockBars-1], "BOTTOMRIGHT", 0, -1)

    mockBars[#mockBars+1] = CreateMockBar(col1, 10, 24, mc.target[1], mc.target[2], mc.target[3], "Target", 0.45)
    mockBars[#mockBars]:SetPoint("TOPLEFT", mockBars[#mockBars-1], "BOTTOMLEFT", 0, -8)
    mockBars[#mockBars]:SetPoint("TOPRIGHT", mockBars[#mockBars-1], "BOTTOMRIGHT", 0, -8)
    mockBars[#mockBars+1] = CreateMockBar(col1, 10, 6, 0.10, 0.32, 0.78, nil, 0.90)
    mockBars[#mockBars]:SetPoint("TOPLEFT", mockBars[#mockBars-1], "BOTTOMLEFT", 0, -1)
    mockBars[#mockBars]:SetPoint("TOPRIGHT", mockBars[#mockBars-1], "BOTTOMRIGHT", 0, -1)

    local partyFills = { 0.92, 0.68, 0.55 }
    for i = 1, 3 do
        local c = mc.party[i]
        mockBars[#mockBars+1] = CreateMockBar(col2, 10, 20, c[1], c[2], c[3], nil, partyFills[i])
        mockBars[#mockBars]:SetPoint("TOPLEFT", 0, -22 - (i - 1) * 24)
        mockBars[#mockBars]:SetPoint("TOPRIGHT", 0, -22 - (i - 1) * 24)
    end

    local raidFills = { 0.95, 0.72, 0.60, 0.88, 0.33, 0.78, 0.50, 0.85, 0.42, 0.68 }
    local raidBarFrames = {}
    for row = 0, 1 do
        for col = 0, 4 do
            local idx = row * 5 + col + 1
            local rc = mc.raid[idx]
            mockBars[#mockBars+1] = CreateMockBar(col3, 10, 14, rc[1], rc[2], rc[3], nil, raidFills[idx])
            raidBarFrames[#raidBarFrames+1] = mockBars[#mockBars]
        end
    end
    local function LayoutRaidGrid()
        local cw = col3:GetWidth()
        if cw < 40 then return end
        local raidGap = 4
        local raidW = math.floor((cw - raidGap * 4) / 5)
        for row = 0, 1 do
            for col = 0, 4 do
                local f = raidBarFrames[row * 5 + col + 1]
                if f then
                    f:ClearAllPoints()
                    f:SetPoint("TOPLEFT", col3, "TOPLEFT", col * (raidW + raidGap), -22 - row * 18)
                    f:SetSize(raidW, 14)
                end
            end
        end
    end
    col3:HookScript("OnShow", LayoutRaidGrid)
    col3:HookScript("OnSizeChanged", LayoutRaidGrid)
    C_Timer.After(0, LayoutRaidGrid)

    SetBarPreviewMode = function(mode)
        for _, bar in ipairs(mockBars) do
            if mode == "Gradient" then
                if bar._topHL then bar._topHL:Show() end
                if bar._botSH then bar._botSH:Show() end
                if bar._spec then bar._spec:Show() end
            else
                if bar._topHL then bar._topHL:Hide() end
                if bar._botSH then bar._botSH:Hide() end
                if bar._spec then bar._spec:Hide() end
            end
        end
    end
    SetBarPreviewMode(state.barStyle)

    local origGradientClick = barStyleCards["Gradient"]._muiOrigClick
    barStyleCards["Gradient"]:SetScript("OnMouseDown", function(self)
        if _visualCardGroups["barStyle"] then
            for _, other in ipairs(_visualCardGroups["barStyle"].cards) do
                if other ~= self then other:Deselect() end
            end
        end
        self:Select()
        local s = EnsureSettings()
        s.General.unitFrameBarStyle = "Gradient"
        state.barStyle = "Gradient"
        local S = _G.MidnightUI_Settings
        if S and S.ApplyUnitFrameBarStyle then SafeCall("ApplyUnitFrameBarStyle", S.ApplyUnitFrameBarStyle) end
        SetBarPreviewMode("Gradient")
    end)

    barStyleCards["Flat"]:SetScript("OnMouseDown", function(self)
        if _visualCardGroups["barStyle"] then
            for _, other in ipairs(_visualCardGroups["barStyle"].cards) do
                if other ~= self then other:Deselect() end
            end
        end
        self:Select()
        local s = EnsureSettings()
        s.General.unitFrameBarStyle = "Flat"
        state.barStyle = "Flat"
        local S = _G.MidnightUI_Settings
        if S and S.ApplyUnitFrameBarStyle then SafeCall("ApplyUnitFrameBarStyle", S.ApplyUnitFrameBarStyle) end
        SetBarPreviewMode("Flat")
    end)

    p3Content:SetHeight(400)

    SyncThemeSelection = function()
        local st = EnsureSettings()
        local curTheme = st.GlobalStyle or "Default"
        state.theme = curTheme
        for name, card in pairs(themeCards) do
            if name == curTheme then card:Select() else card:Deselect() end
        end
        local curBar = (st.General and st.General.unitFrameBarStyle) or "Gradient"
        state.barStyle = curBar
        for name, card in pairs(barStyleCards) do
            if name == curBar then card:Select() else card:Deselect() end
        end
        SetBarPreviewMode(curBar)
    end

    end

    do
    local p4 = CreateFrame("Frame", nil, pageHost)
    p4:SetAllPoints(); p4:Hide(); stepPages[4] = p4

    local p4Tag = p4:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    p4Tag:SetPoint("TOPLEFT", 4, -6)
    p4Tag:SetText("STEP 4  FRAMES")
    p4Tag:SetTextColor(UI_COLORS.accent[1], UI_COLORS.accent[2], UI_COLORS.accent[3])
    TrySetFont(p4Tag, 12, "OUTLINE")

    local p4Head = p4:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
    p4Head:SetPoint("TOPLEFT", 4, -26)
    p4Head:SetText("Size Your Frames")
    p4Head:SetTextColor(UI_COLORS.text[1], UI_COLORS.text[2], UI_COLORS.text[3])
    TrySetFont(p4Head, 20, "OUTLINE")

    local p4Sub = p4:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    p4Sub:SetPoint("TOPLEFT", 4, -52)
    p4Sub:SetPoint("TOPRIGHT", -4, -52)
    p4Sub:SetJustifyH("LEFT")
    p4Sub:SetText("Pick a frame size preset for player, target, and focus frames. You can always fine-tune with /mui later.")
    p4Sub:SetTextColor(UI_COLORS.muted[1], UI_COLORS.muted[2], UI_COLORS.muted[3])
    TrySetFont(p4Sub, 14)

    local p4Scroll = CreateFrame("ScrollFrame", nil, p4, "UIPanelScrollFrameTemplate")
    p4Scroll:SetPoint("TOPLEFT", 0, -90)
    p4Scroll:SetPoint("BOTTOMRIGHT", -20, 4)
    local p4Content = CreateFrame("Frame", nil, p4Scroll)
    p4Content:SetSize(math.max(700, EXPANDED_W - 350), 690)
    p4Scroll:SetScrollChild(p4Content)

    local npSliderID = 0
    local function MakeInlineSlider(parent, label, min, max, step, initial, onChange)
        npSliderID = npSliderID + 1
        local row = CreateFrame("Frame", nil, parent)
        row:SetHeight(32)
        local lbl = row:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
        lbl:SetPoint("TOPLEFT", 0, 0)
        lbl:SetText(label)
        lbl:SetTextColor(0.72, 0.74, 0.80)
        TrySetFont(lbl, 10)
        local val = row:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
        val:SetPoint("TOPRIGHT", 0, 0)
        val:SetText(tostring(math.floor(initial)))
        val:SetTextColor(UI_COLORS.accent[1], UI_COLORS.accent[2], UI_COLORS.accent[3])
        TrySetFont(val, 10)
        local sl = CreateFrame("Slider", "MidnightUI_WizS4_" .. npSliderID, row, "OptionsSliderTemplate")
        sl:SetPoint("TOPLEFT", 0, -14); sl:SetPoint("TOPRIGHT", 0, -14); sl:SetHeight(12)
        sl:SetMinMaxValues(min, max); sl:SetValueStep(step); sl:SetObeyStepOnDrag(true); sl:SetValue(initial)
        local sn = sl:GetName() or ""
        if _G[sn.."Low"] then _G[sn.."Low"]:Hide() end
        if _G[sn.."High"] then _G[sn.."High"]:Hide() end
        if _G[sn.."Text"] then _G[sn.."Text"]:Hide() end
        sl:SetScript("OnValueChanged", function(_, v)
            val:SetText(tostring(math.floor(v)))
            if onChange then onChange(v) end
        end)
        row._slider = sl; row._val = val
        return row
    end

    local function MakePickButton(parent, text, w, h, onClick)
        local btn = CreateFrame("Button", nil, parent, "BackdropTemplate")
        btn:SetSize(w, h)
        btn:SetBackdrop({ bgFile="Interface\\Buttons\\WHITE8X8", edgeFile="Interface\\Buttons\\WHITE8X8", edgeSize=1 })
        btn:SetBackdropColor(UI_COLORS.inset[1], UI_COLORS.inset[2], UI_COLORS.inset[3], 0.9)
        btn:SetBackdropBorderColor(UI_COLORS.border[1], UI_COLORS.border[2], UI_COLORS.border[3], 0.5)
        local fs = btn:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
        fs:SetPoint("CENTER"); fs:SetText(text); fs:SetTextColor(0.85, 0.87, 0.92)
        TrySetFont(fs, 11)
        btn._fs = fs; btn._selected = false
        btn:SetScript("OnClick", function(self)
            if onClick then onClick(self) end
        end)
        btn:SetScript("OnEnter", function(self)
            if not self._selected then self:SetBackdropBorderColor(UI_COLORS.accent[1], UI_COLORS.accent[2], UI_COLORS.accent[3], 0.6) end
        end)
        btn:SetScript("OnLeave", function(self)
            if not self._selected then self:SetBackdropBorderColor(UI_COLORS.border[1], UI_COLORS.border[2], UI_COLORS.border[3], 0.5) end
        end)
        function btn:SetSelected(v)
            self._selected = v
            if v then
                self:SetBackdropBorderColor(UI_COLORS.accent[1], UI_COLORS.accent[2], UI_COLORS.accent[3], 0.9)
                self._fs:SetTextColor(1, 1, 1)
            else
                self:SetBackdropBorderColor(UI_COLORS.border[1], UI_COLORS.border[2], UI_COLORS.border[3], 0.5)
                self._fs:SetTextColor(0.85, 0.87, 0.92)
            end
        end
        return btn
    end

    local secATitle = p4Content:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    secATitle:SetPoint("TOPLEFT", 4, 0)
    secATitle:SetText("Health Bars")
    secATitle:SetTextColor(UI_COLORS.text[1], UI_COLORS.text[2], UI_COLORS.text[3])
    TrySetFont(secATitle, 13, "OUTLINE")
    local secALine = p4Content:CreateTexture(nil, "ARTWORK")
    secALine:SetPoint("LEFT", secATitle, "RIGHT", 10, 0)
    secALine:SetPoint("RIGHT", p4Content, "RIGHT", -4, 0)
    secALine:SetHeight(1)
    secALine:SetColorTexture(UI_COLORS.accent[1], UI_COLORS.accent[2], UI_COLORS.accent[3], 0.12)

    local secABody = CreateFrame("Frame", nil, p4Content)
    secABody:SetPoint("TOPLEFT", 4, -18)
    secABody:SetPoint("TOPRIGHT", -4, -18)
    secABody:SetHeight(240)

    local secALeft = CreateFrame("Frame", nil, secABody)
    local secARight = CreateFrame("Frame", nil, secABody)

    local function LayoutSecA()
        local w = secABody:GetWidth()
        if w < 100 then return end
        local leftW = math.floor(w * 0.55)
        secALeft:ClearAllPoints()
        secALeft:SetPoint("TOPLEFT", 0, 0); secALeft:SetPoint("BOTTOMLEFT", 0, 0); secALeft:SetWidth(leftW)
        secARight:ClearAllPoints()
        secARight:SetPoint("TOPLEFT", secALeft, "TOPRIGHT", 16, 0)
        secARight:SetPoint("BOTTOMRIGHT", 0, 0)
    end
    secABody:HookScript("OnShow", LayoutSecA)
    secABody:HookScript("OnSizeChanged", LayoutSecA)
    C_Timer.After(0, LayoutSecA)

    local fspPanel = CreateFrame("Frame", nil, secALeft)
    fspPanel:SetPoint("TOPLEFT", 0, -4); fspPanel:SetPoint("TOPRIGHT", 0, -4); fspPanel:SetHeight(120)

    local playerName = (UnitName and UnitName("player")) or "Player"
    local playerLevel = (UnitLevel and UnitLevel("player")) or 80

    local mockFrame = CreateFrame("Frame", nil, fspPanel, "BackdropTemplate")
    mockFrame:SetPoint("CENTER", fspPanel, "CENTER", 0, 0)
    mockFrame:SetSize(380, 66)
    mockFrame:SetBackdrop({ bgFile="Interface\\Buttons\\WHITE8X8", edgeFile="Interface\\Buttons\\WHITE8X8", edgeSize=1 })
    mockFrame:SetBackdropColor(0.1, 0.1, 0.1, 0.9)
    mockFrame:SetBackdropBorderColor(0, 0, 0, 1)

    local mockHealthBg = mockFrame:CreateTexture(nil, "BACKGROUND", nil, 1)
    mockHealthBg:SetPoint("TOPLEFT", 1, -1); mockHealthBg:SetPoint("TOPRIGHT", -1, -1)
    mockHealthBg:SetHeight(42); mockHealthBg:SetColorTexture(0.1, 0.1, 0.1, 1)

    local mockHealthFill = mockFrame:CreateTexture(nil, "ARTWORK")
    mockHealthFill:SetPoint("TOPLEFT", mockHealthBg, "TOPLEFT", 0, 0)
    mockHealthFill:SetSize(310, 42)
    mockHealthFill:SetTexture("Interface\\Buttons\\WHITE8X8")
    mockHealthFill:SetVertexColor(classR, classG, classB, 1)

    local mEdgeR, mEdgeG, mEdgeB = Darken(classR, 0.85), Darken(classG, 0.85), Darken(classB, 0.85)
    local mMidR, mMidG, mMidB = Lift(classR, 0.02), Lift(classG, 0.02), Lift(classB, 0.02)

    local mockTopHL = mockFrame:CreateTexture(nil, "OVERLAY", nil, 2)
    mockTopHL:SetPoint("TOPLEFT", mockHealthFill, 0, 0); mockTopHL:SetPoint("TOPRIGHT", mockHealthFill, 0, 0)
    mockTopHL:SetHeight(18); mockTopHL:SetTexture("Interface\\Buttons\\WHITE8X8"); mockTopHL:SetBlendMode("DISABLE")
    mockTopHL:SetGradient("VERTICAL", CreateColor(mMidR, mMidG, mMidB, 1), CreateColor(mEdgeR, mEdgeG, mEdgeB, 1))

    local mockBotSH = mockFrame:CreateTexture(nil, "OVERLAY", nil, 1)
    mockBotSH:SetPoint("BOTTOMLEFT", mockHealthFill, 0, 0); mockBotSH:SetPoint("BOTTOMRIGHT", mockHealthFill, 0, 0)
    mockBotSH:SetHeight(24); mockBotSH:SetTexture("Interface\\Buttons\\WHITE8X8"); mockBotSH:SetBlendMode("DISABLE")
    mockBotSH:SetGradient("VERTICAL", CreateColor(mEdgeR, mEdgeG, mEdgeB, 1), CreateColor(mMidR, mMidG, mMidB, 1))

    local mockSpec = mockFrame:CreateTexture(nil, "OVERLAY", nil, 3)
    mockSpec:SetPoint("TOPLEFT", mockHealthFill, 0, 0); mockSpec:SetPoint("TOPRIGHT", mockHealthFill, 0, 0)
    mockSpec:SetHeight(15); mockSpec:SetTexture("Interface\\Buttons\\WHITE8X8"); mockSpec:SetBlendMode("ADD")
    mockSpec:SetGradient("VERTICAL", CreateColor(1,1,1,0), CreateColor(1,1,1,0.06))

    local mockSep = mockFrame:CreateTexture(nil, "OVERLAY")
    mockSep:SetPoint("BOTTOMLEFT", mockHealthBg, 0, 0); mockSep:SetPoint("BOTTOMRIGHT", mockHealthBg, 0, 0)
    mockSep:SetHeight(1); mockSep:SetColorTexture(0, 0, 0, 1)

    local mockPowerBg = mockFrame:CreateTexture(nil, "BACKGROUND", nil, 1)
    mockPowerBg:SetPoint("TOPLEFT", mockSep, "BOTTOMLEFT", 0, 0)
    mockPowerBg:SetPoint("TOPRIGHT", mockSep, "BOTTOMRIGHT", 0, 0)
    mockPowerBg:SetPoint("BOTTOMRIGHT", mockFrame, "BOTTOMRIGHT", -1, 1)
    mockPowerBg:SetColorTexture(0, 0, 0, 0.5)

    local mockPowerFill = mockFrame:CreateTexture(nil, "ARTWORK")
    mockPowerFill:SetPoint("TOPLEFT", mockPowerBg, 0, 0); mockPowerFill:SetPoint("BOTTOMLEFT", mockPowerBg, 0, 0)
    mockPowerFill:SetWidth(270); mockPowerFill:SetColorTexture(0.0, 0.5, 1.0, 1)

    local pEdgeR, pEdgeG, pEdgeB = Darken(0.0, 0.85), Darken(0.5, 0.85), Darken(1.0, 0.85)
    local pMidR, pMidG, pMidB = Lift(0.0, 0.02), Lift(0.5, 0.02), Lift(1.0, 0.02)
    local mockPowerTopHL = mockFrame:CreateTexture(nil, "OVERLAY", nil, 2)
    mockPowerTopHL:SetPoint("TOPLEFT", mockPowerFill, 0, 0); mockPowerTopHL:SetPoint("TOPRIGHT", mockPowerFill, 0, 0)
    mockPowerTopHL:SetHeight(6); mockPowerTopHL:SetTexture("Interface\\Buttons\\WHITE8X8"); mockPowerTopHL:SetBlendMode("DISABLE")
    mockPowerTopHL:SetGradient("VERTICAL", CreateColor(pMidR,pMidG,pMidB,1), CreateColor(pEdgeR,pEdgeG,pEdgeB,1))
    local mockPowerBotSH = mockFrame:CreateTexture(nil, "OVERLAY", nil, 1)
    mockPowerBotSH:SetPoint("BOTTOMLEFT", mockPowerFill, 0, 0); mockPowerBotSH:SetPoint("BOTTOMRIGHT", mockPowerFill, 0, 0)
    mockPowerBotSH:SetHeight(8); mockPowerBotSH:SetTexture("Interface\\Buttons\\WHITE8X8"); mockPowerBotSH:SetBlendMode("DISABLE")
    mockPowerBotSH:SetGradient("VERTICAL", CreateColor(pEdgeR,pEdgeG,pEdgeB,1), CreateColor(pMidR,pMidG,pMidB,1))
    local mockPowerSpec = mockFrame:CreateTexture(nil, "OVERLAY", nil, 3)
    mockPowerSpec:SetPoint("TOPLEFT", mockPowerFill, 0, 0); mockPowerSpec:SetPoint("TOPRIGHT", mockPowerFill, 0, 0)
    mockPowerSpec:SetHeight(5); mockPowerSpec:SetTexture("Interface\\Buttons\\WHITE8X8"); mockPowerSpec:SetBlendMode("ADD")
    mockPowerSpec:SetGradient("VERTICAL", CreateColor(1,1,1,0), CreateColor(1,1,1,0.06))

    local mockLevelText = mockFrame:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    mockLevelText:SetPoint("LEFT", mockHealthBg, "LEFT", 10, 0)
    mockLevelText:SetText(tostring(playerLevel)); mockLevelText:SetTextColor(0.9,0.9,0.9,1)
    mockLevelText:SetShadowOffset(1,-1); mockLevelText:SetShadowColor(0,0,0,1); TrySetFont(mockLevelText, 11, "OUTLINE")

    local mockNameText = mockFrame:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    mockNameText:SetPoint("CENTER", mockHealthBg, "CENTER", 0, 0)
    mockNameText:SetText(playerName); mockNameText:SetTextColor(1,1,1,1)
    mockNameText:SetShadowOffset(1,-1); mockNameText:SetShadowColor(0,0,0,1); TrySetFont(mockNameText, 14, "OUTLINE")

    local mockHealthText = mockFrame:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    mockHealthText:SetPoint("RIGHT", mockHealthBg, "RIGHT", -10, 0)
    mockHealthText:SetText("82%"); mockHealthText:SetTextColor(1,1,1,1)
    mockHealthText:SetShadowOffset(2,-2); mockHealthText:SetShadowColor(0,0,0,1); TrySetFont(mockHealthText, 14, "OUTLINE")

    local mockPowerText = mockFrame:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    mockPowerText:SetPoint("CENTER", mockPowerBg, "CENTER", 0, 0)
    mockPowerText:SetText("8,420"); mockPowerText:SetTextColor(1,1,1,1)
    mockPowerText:SetShadowOffset(1,-1); mockPowerText:SetShadowColor(0,0,0,0.9); TrySetFont(mockPowerText, 11, "OUTLINE")

    local sizeHint = secALeft:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    sizeHint:SetPoint("TOP", fspPanel, "BOTTOM", 0, -4)
    sizeHint:SetText("Select a size preset from the right")
    sizeHint:SetTextColor(UI_COLORS.muted[1], UI_COLORS.muted[2], UI_COLORS.muted[3])
    TrySetFont(sizeHint, 9)

    local sizeLabel = secARight:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    sizeLabel:SetPoint("TOPLEFT", 0, -4)
    sizeLabel:SetText("Frame Size")
    sizeLabel:SetTextColor(0.72, 0.74, 0.80)
    TrySetFont(sizeLabel, 10)

    frameSizeCards = {}
    local frameSizePresets = {
        { name="Compact", pW=340, pH=56, pScale=90, tW=340, tH=56, tScale=90, fW=280, fH=48, fScale=80 },
        { name="Balanced", pW=380, pH=66, pScale=100, tW=380, tH=66, tScale=100, fW=320, fH=58, fScale=100 },
        { name="Spacious", pW=420, pH=76, pScale=110, tW=420, tH=76, tScale=110, fW=360, fH=68, fScale=110 },
    }

    local function ApplyFrameSizePreset(preset)
        local s = EnsureSettings()
        s.PlayerFrame.width=preset.pW; s.PlayerFrame.height=preset.pH; s.PlayerFrame.scale=preset.pScale
        s.TargetFrame.width=preset.tW; s.TargetFrame.height=preset.tH; s.TargetFrame.scale=preset.tScale
        s.FocusFrame.width=preset.fW; s.FocusFrame.height=preset.fH; s.FocusFrame.scale=preset.fScale
        state.frameSize = preset.name
        ApplyAllSettings("WIZARD_FRAMESIZE")
        if UpdateFrameSizePreview then UpdateFrameSizePreview() end
    end

    local sizeBtns = {}
    for i, preset in ipairs(frameSizePresets) do
        sizeBtns[i] = MakePickButton(secARight, preset.name, 80, 24, function()
            ApplyFrameSizePreset(preset)
            for j, b in ipairs(sizeBtns) do b:SetSelected(j == i) end
        end)
        sizeBtns[i]:SetPoint("TOPLEFT", (i-1) * 86, -18)
        frameSizeCards[preset.name] = sizeBtns[i]
    end

    nameScaleSlider = MakeInlineSlider(secARight, "Name Scale %", 50, 150, 5, 100, function(v)
        EnsureSettings().General.unitFrameNameScale = math.floor(v)
        if UpdateFrameSizePreview then UpdateFrameSizePreview() end
        local S = _G.MidnightUI_Settings
        if S and S.ApplySharedUnitFrameAppearance then SafeCall("ApplySharedAppearance", S.ApplySharedUnitFrameAppearance) end
    end)
    nameScaleSlider:SetPoint("TOPLEFT", secARight, "TOPLEFT", 0, -52)
    nameScaleSlider:SetPoint("TOPRIGHT", secARight, "TOPRIGHT", 0, -52)

    valScaleSlider = MakeInlineSlider(secARight, "Value Scale %", 50, 150, 5, 100, function(v)
        EnsureSettings().General.unitFrameValueScale = math.floor(v)
        if UpdateFrameSizePreview then UpdateFrameSizePreview() end
        local S = _G.MidnightUI_Settings
        if S and S.ApplySharedUnitFrameAppearance then SafeCall("ApplySharedAppearance", S.ApplySharedUnitFrameAppearance) end
    end)
    valScaleSlider:SetPoint("TOPLEFT", nameScaleSlider, "BOTTOMLEFT", 0, -6)
    valScaleSlider:SetPoint("TOPRIGHT", nameScaleSlider, "BOTTOMRIGHT", 0, -6)

    local htLabel = secARight:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    htLabel:SetPoint("TOPLEFT", valScaleSlider, "BOTTOMLEFT", 0, -10)
    htLabel:SetText("Health Display")
    htLabel:SetTextColor(0.72, 0.74, 0.80)
    TrySetFont(htLabel, 10)

    healthTextCards = {}
    local htOpts = {
        { name="Percent", display="75%", mode="percent" },
        { name="Value", display="150K/200K", mode="value" },
        { name="Both", display="Both", mode="both" },
    }
    local htBtns = {}
    for i, opt in ipairs(htOpts) do
        htBtns[i] = MakePickButton(secARight, opt.name, 80, 24, function()
            EnsureSettings().General.healthTextMode = opt.mode
            state.healthText = opt.mode
            if UpdateFrameSizePreview then UpdateFrameSizePreview() end
            ApplyAllSettings("WIZARD_HEALTHTEXT")
            for j, b in ipairs(htBtns) do b:SetSelected(j == i) end
        end)
        htBtns[i]:SetPoint("TOPLEFT", htLabel, "BOTTOMLEFT", (i-1) * 86, -4)
        healthTextCards[opt.mode] = htBtns[i]
    end

    UpdateFrameSizePreview = function()
        local st = EnsureSettings()
        local pW = (st.PlayerFrame and st.PlayerFrame.width) or 380
        local pH = (st.PlayerFrame and st.PlayerFrame.height) or 66
        local previewW = math.min(pW, math.max(200, (secALeft:GetWidth() or 400) - 20))
        local healthH = math.max(14, math.floor(pH * 42 / 66))
        local powerH = math.max(4, pH - healthH - 2)
        mockFrame:SetSize(previewW, pH)
        mockHealthBg:SetHeight(healthH)
        mockHealthFill:SetSize(math.max(4, math.floor((previewW-2)*0.82)), healthH)
        mockPowerFill:SetWidth(math.max(4, math.floor((previewW-2)*0.72)))
        mockTopHL:SetHeight(math.max(1, math.floor(healthH*0.43)))
        mockBotSH:SetHeight(math.max(1, math.floor(healthH*0.57)))
        mockSpec:SetHeight(math.max(1, math.floor(healthH*0.35)))
        mockPowerTopHL:SetHeight(math.max(1, math.floor(powerH*0.43)))
        mockPowerBotSH:SetHeight(math.max(1, math.floor(powerH*0.57)))
        mockPowerSpec:SetHeight(math.max(1, math.floor(powerH*0.35)))
        local barStyle = state.barStyle or (st.General and st.General.unitFrameBarStyle) or "Gradient"
        if barStyle == "Gradient" then
            mockTopHL:Show(); mockBotSH:Show(); mockSpec:Show()
            mockPowerTopHL:Show(); mockPowerBotSH:Show(); mockPowerSpec:Show()
        else
            mockTopHL:Hide(); mockBotSH:Hide(); mockSpec:Hide()
            mockPowerTopHL:Hide(); mockPowerBotSH:Hide(); mockPowerSpec:Hide()
        end
        local htMode = (st.General and st.General.healthTextMode) or "percent"
        if htMode == "percent" then mockHealthText:SetText("82%")
        elseif htMode == "value" then mockHealthText:SetText("164K / 200K")
        else mockHealthText:SetText("164K / 200K \194\183 82%") end
        local nScale = (st.General and st.General.unitFrameNameScale) or 100
        local vScale = (st.General and st.General.unitFrameValueScale) or 100
        TrySetFont(mockNameText, math.max(8, math.floor(12*nScale/100)), "OUTLINE")
        TrySetFont(mockLevelText, math.max(7, math.floor(11*vScale/100)), "OUTLINE")
        TrySetFont(mockHealthText, math.max(7, math.floor(11*vScale/100)), "OUTLINE")
    end
    C_Timer.After(0, function() if UpdateFrameSizePreview then UpdateFrameSizePreview() end end)


    local secBTitle = p4Content:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    secBTitle:SetPoint("TOPLEFT", secABody, "BOTTOMLEFT", 0, -14)
    secBTitle:SetText("Nameplates")
    secBTitle:SetTextColor(UI_COLORS.text[1], UI_COLORS.text[2], UI_COLORS.text[3])
    TrySetFont(secBTitle, 13, "OUTLINE")
    npCheck = CreateFrame("CheckButton", nil, p4Content, "InterfaceOptionsCheckButtonTemplate")
    npCheck:SetPoint("LEFT", secBTitle, "RIGHT", 16, 0)
    local secBLine = p4Content:CreateTexture(nil, "ARTWORK")
    secBLine:SetPoint("LEFT", npCheck.Text or npCheck, "RIGHT", 10, 0)
    secBLine:SetPoint("RIGHT", p4Content, "RIGHT", -4, 0)
    secBLine:SetHeight(1)
    secBLine:SetColorTexture(UI_COLORS.accent[1], UI_COLORS.accent[2], UI_COLORS.accent[3], 0.12)
    if npCheck.Text then
        npCheck.Text:SetText("Enable"); npCheck.Text:SetFontObject("GameFontHighlightSmall")
    end
    npCheck:SetChecked(true)

    local secBBody = CreateFrame("Frame", nil, p4Content)
    secBBody:SetPoint("TOPLEFT", secBTitle, "BOTTOMLEFT", 0, -6)
    secBBody:SetPoint("RIGHT", p4Content, "RIGHT", -4, 0)
    secBBody:SetHeight(220)

    local secBLeft = CreateFrame("Frame", nil, secBBody)
    local secBRight = CreateFrame("Frame", nil, secBBody)

    local function LayoutSecB()
        local w = secBBody:GetWidth()
        if w < 100 then return end
        local leftW = math.floor(w * 0.50)
        secBLeft:ClearAllPoints()
        secBLeft:SetPoint("TOPLEFT", 0, 0); secBLeft:SetPoint("BOTTOMLEFT", 0, 0); secBLeft:SetWidth(leftW)
        secBRight:ClearAllPoints()
        secBRight:SetPoint("TOPLEFT", secBLeft, "TOPRIGHT", 16, 0)
        secBRight:SetPoint("BOTTOMRIGHT", 0, 0)
    end
    secBBody:HookScript("OnShow", LayoutSecB)
    secBBody:HookScript("OnSizeChanged", LayoutSecB)
    C_Timer.After(0, LayoutSecB)

    local npPanel = CreateFrame("Frame", nil, secBLeft)
    npPanel:SetPoint("TOPLEFT", 0, -4); npPanel:SetPoint("TOPRIGHT", 0, -4); npPanel:SetHeight(100)

    local npMock = CreateFrame("Frame", nil, npPanel)
    npMock:SetPoint("CENTER", npPanel, "CENTER", 0, 0); npMock:SetSize(200, 50)

    local npCastBg = npMock:CreateTexture(nil, "BACKGROUND"); npCastBg:SetColorTexture(0.05,0.05,0.05,0.7)
    local npCastFill = npMock:CreateTexture(nil, "ARTWORK"); npCastFill:SetColorTexture(1.0,0.8,0.0,1)
    local npCastText = npMock:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    npCastText:SetText("Shadow Bolt"); npCastText:SetTextColor(1,1,1,0.9); TrySetFont(npCastText, 9, "OUTLINE")
    local npCastBorder = CreateFrame("Frame", nil, npMock, "BackdropTemplate")
    npCastBorder:SetBackdrop({edgeFile="Interface\\Buttons\\WHITE8X8",edgeSize=1}); npCastBorder:SetBackdropBorderColor(0,0,0,0.7)

    local npHealthBg = npMock:CreateTexture(nil, "BACKGROUND"); npHealthBg:SetColorTexture(0.05,0.05,0.05,0.6)
    local npHealthFill = npMock:CreateTexture(nil, "ARTWORK"); npHealthFill:SetColorTexture(0.80,0.14,0.14,1)
    local npHealthBorder = CreateFrame("Frame", nil, npMock, "BackdropTemplate")
    npHealthBorder:SetBackdrop({edgeFile="Interface\\Buttons\\WHITE8X8",edgeSize=1}); npHealthBorder:SetBackdropBorderColor(0,0,0,0.7)

    local npNameText = npMock:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    npNameText:SetText("Gul'dan"); npNameText:SetTextColor(1,1,1,1); TrySetFont(npNameText, 10, "OUTLINE")
    npNameText:SetShadowOffset(1,-1); npNameText:SetShadowColor(0,0,0,1)
    local npHPText = npMock:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    npHPText:SetText("72%"); npHPText:SetTextColor(1,1,1,0.9); TrySetFont(npHPText, 9, "OUTLINE")
    npHPText:SetShadowOffset(1,-1); npHPText:SetShadowColor(0,0,0,1)

    local npThreatBg = npMock:CreateTexture(nil, "BACKGROUND"); npThreatBg:SetColorTexture(0,0,0,0.5)
    local npThreatFill = npMock:CreateTexture(nil, "ARTWORK"); npThreatFill:SetColorTexture(classR,classG,classB,1)
    local npThreatBorder = CreateFrame("Frame", nil, npMock, "BackdropTemplate")
    npThreatBorder:SetBackdrop({edgeFile="Interface\\Buttons\\WHITE8X8",edgeSize=1}); npThreatBorder:SetBackdropBorderColor(0,0,0,0.7)

    local npTargetGlow = CreateFrame("Frame", nil, npMock, "BackdropTemplate")
    npTargetGlow:SetBackdrop({edgeFile="Interface\\Buttons\\WHITE8X8",edgeSize=2}); npTargetGlow:SetBackdropBorderColor(1.00,0.84,0.20,0.85)

    local function LayoutNameplateMock()
        local st3 = EnsureSettings(); local np = st3.Nameplates or {}
        local hb = np.healthBar or {}; local cb = np.castBar or {}; local tb = np.threatBar or {}
        local SP = 3
        local hpW = hb.width or 200; local hpH = hb.height or 20
        local castH = cb.height or 16; local threatH = tb.height or 5
        local nameFont = hb.nameFontSize or 10; local hpFont = hb.healthPctFontSize or 9
        local castFont = cb.fontSize or 12; local nameAlign = hb.nameAlign or "LEFT"
        local hpDisplay = hb.healthPctDisplay or "RIGHT"
        local threatEnabled = (tb.enabled ~= false)
        local totalH = castH + SP + hpH + (threatEnabled and (SP + threatH) or 0)
        npMock:SetSize(hpW, totalH)
        npCastBg:ClearAllPoints(); npCastBg:SetPoint("TOPLEFT", npMock, 0, 0); npCastBg:SetSize(hpW, castH)
        npCastFill:ClearAllPoints(); npCastFill:SetPoint("TOPLEFT", npCastBg, 0, 0)
        npCastFill:SetPoint("BOTTOMLEFT", npCastBg, 0, 0); npCastFill:SetWidth(math.floor(hpW*0.45))
        npCastText:ClearAllPoints(); npCastText:SetPoint("CENTER", npCastBg, 0, 0)
        TrySetFont(npCastText, math.max(7, math.floor(castFont*0.85)), "OUTLINE")
        npCastBorder:ClearAllPoints(); npCastBorder:SetPoint("TOPLEFT", npCastBg, -1, 1)
        npCastBorder:SetPoint("BOTTOMRIGHT", npCastBg, 1, -1)
        npHealthBg:ClearAllPoints(); npHealthBg:SetPoint("TOPLEFT", npCastBg, "BOTTOMLEFT", 0, -SP); npHealthBg:SetSize(hpW, hpH)
        npHealthFill:ClearAllPoints(); npHealthFill:SetPoint("TOPLEFT", npHealthBg, 0, 0)
        npHealthFill:SetPoint("BOTTOMLEFT", npHealthBg, 0, 0); npHealthFill:SetWidth(math.floor(hpW*0.72))
        npHealthBorder:ClearAllPoints(); npHealthBorder:SetPoint("TOPLEFT", npHealthBg, -1, 1)
        npHealthBorder:SetPoint("BOTTOMRIGHT", npHealthBg, 1, -1)
        npNameText:ClearAllPoints(); TrySetFont(npNameText, math.max(7, nameFont), "OUTLINE")
        if nameAlign == "CENTER" then npNameText:SetPoint("CENTER", npHealthBg, 0, 0)
        elseif nameAlign == "RIGHT" then npNameText:SetPoint("RIGHT", npHealthBg, "RIGHT", -6, 0)
        else npNameText:SetPoint("LEFT", npHealthBg, "LEFT", 6, 0) end
        npHPText:ClearAllPoints(); TrySetFont(npHPText, math.max(7, hpFont), "OUTLINE")
        if hpDisplay == "LEFT" then npHPText:SetPoint("LEFT", npHealthBg, "LEFT", 6, 0); npHPText:SetText("72%")
        elseif hpDisplay == "CENTER" then npHPText:SetPoint("CENTER", npHealthBg, 0, 0); npHPText:SetText("72%")
        elseif hpDisplay == "HIDDEN" then npHPText:SetText("")
        else npHPText:SetPoint("RIGHT", npHealthBg, "RIGHT", -6, 0); npHPText:SetText("72%") end
        if threatEnabled then
            npThreatBg:Show(); npThreatFill:Show(); npThreatBorder:Show()
            npThreatBg:ClearAllPoints(); npThreatBg:SetPoint("TOPLEFT", npHealthBg, "BOTTOMLEFT", 0, -SP); npThreatBg:SetSize(hpW, threatH)
            npThreatFill:ClearAllPoints(); npThreatFill:SetPoint("TOPLEFT", npThreatBg, 0, 0)
            npThreatFill:SetPoint("BOTTOMLEFT", npThreatBg, 0, 0); npThreatFill:SetWidth(math.floor(hpW*0.65))
            npThreatBorder:ClearAllPoints(); npThreatBorder:SetPoint("TOPLEFT", npThreatBg, -1, 1)
            npThreatBorder:SetPoint("BOTTOMRIGHT", npThreatBg, 1, -1)
        else npThreatBg:Hide(); npThreatFill:Hide(); npThreatBorder:Hide() end
        npTargetGlow:ClearAllPoints(); npTargetGlow:SetPoint("TOPLEFT", npHealthBg, -2, 2)
        npTargetGlow:SetPoint("BOTTOMRIGHT", npHealthBg, 2, -2)
        npPanel:SetAlpha((np.enabled ~= false) and 1 or 0.35)
    end

    UpdateNameplatePreview = function() LayoutNameplateMock() end

    local function ApplyNP()
        local S = _G.MidnightUI_Settings
        if S and S.ApplyNameplateSettings then SafeCall("ApplyNameplateSettings", S.ApplyNameplateSettings) end
        UpdateNameplatePreview()
    end

    npSliders = {}
    local stNP = EnsureSettings(); local npS0 = stNP.Nameplates or {}
    local npHB0 = npS0.healthBar or {}; local npCB0 = npS0.castBar or {}; local npTB0 = npS0.threatBar or {}

    npSliders.width = MakeInlineSlider(secBRight, "Health Width", 120, 320, 5, npHB0.width or 200, function(v)
        EnsureSettings().Nameplates.healthBar.width = math.floor(v); ApplyNP() end)
    npSliders.width:SetPoint("TOPLEFT", 0, -4); npSliders.width:SetPoint("TOPRIGHT", 0, -4)

    npSliders.height = MakeInlineSlider(secBRight, "Health Height", 8, 36, 1, npHB0.height or 20, function(v)
        EnsureSettings().Nameplates.healthBar.height = math.floor(v); ApplyNP() end)
    npSliders.height:SetPoint("TOPLEFT", npSliders.width, "BOTTOMLEFT", 0, -4)
    npSliders.height:SetPoint("TOPRIGHT", npSliders.width, "BOTTOMRIGHT", 0, -4)

    npSliders.nameFont = MakeInlineSlider(secBRight, "Name Font", 7, 18, 1, npHB0.nameFontSize or 10, function(v)
        EnsureSettings().Nameplates.healthBar.nameFontSize = math.floor(v); ApplyNP() end)
    npSliders.nameFont:SetPoint("TOPLEFT", npSliders.height, "BOTTOMLEFT", 0, -4)
    npSliders.nameFont:SetPoint("TOPRIGHT", npSliders.height, "BOTTOMRIGHT", 0, -4)

    npSliders.hpFont = MakeInlineSlider(secBRight, "HP% Font", 7, 18, 1, npHB0.healthPctFontSize or 9, function(v)
        EnsureSettings().Nameplates.healthBar.healthPctFontSize = math.floor(v); ApplyNP() end)
    npSliders.hpFont:SetPoint("TOPLEFT", npSliders.nameFont, "BOTTOMLEFT", 0, -4)
    npSliders.hpFont:SetPoint("TOPRIGHT", npSliders.nameFont, "BOTTOMRIGHT", 0, -4)

    npSliders.castH = MakeInlineSlider(secBRight, "Cast Height", 8, 28, 1, npCB0.height or 16, function(v)
        EnsureSettings().Nameplates.castBar.height = math.floor(v); ApplyNP() end)
    npSliders.castH:SetPoint("TOPLEFT", npSliders.hpFont, "BOTTOMLEFT", 0, -4)
    npSliders.castH:SetPoint("TOPRIGHT", npSliders.hpFont, "BOTTOMRIGHT", 0, -4)

    local npDropRow = CreateFrame("Frame", nil, secBRight)
    npDropRow:SetPoint("TOPLEFT", npSliders.castH, "BOTTOMLEFT", 0, -6)
    npDropRow:SetPoint("TOPRIGHT", npSliders.castH, "BOTTOMRIGHT", 0, -6)
    npDropRow:SetHeight(30)

    local npThreatLabel = npDropRow:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    npThreatLabel:SetPoint("LEFT", 0, 0); npThreatLabel:SetText("Threat:"); npThreatLabel:SetTextColor(0.72,0.74,0.80); TrySetFont(npThreatLabel, 9)
    local npThreatDrop = CreateFrame("Frame", "MidnightUI_WizNPThreat2", npDropRow, "UIDropDownMenuTemplate")
    npThreatDrop:SetPoint("LEFT", npThreatLabel, "RIGHT", -12, -2); UIDropDownMenu_SetWidth(npThreatDrop, 72)
    UIDropDownMenu_SetText(npThreatDrop, (npTB0.enabled ~= false) and "ON" or "OFF")
    UIDropDownMenu_Initialize(npThreatDrop, function()
        local cur = (EnsureSettings().Nameplates.threatBar or {}).enabled ~= false
        for _, opt in ipairs({"ON","OFF"}) do
            local info = UIDropDownMenu_CreateInfo(); info.text = opt; info.checked = (opt=="ON") == cur
            info.func = function() UIDropDownMenu_SetText(npThreatDrop, opt)
                EnsureSettings().Nameplates.threatBar.enabled = (opt=="ON"); CloseDropDownMenus(); ApplyNP() end
            UIDropDownMenu_AddButton(info) end end)

    local npAlignLabel = npDropRow:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    npAlignLabel:SetPoint("LEFT", npThreatDrop, "RIGHT", 0, 2); npAlignLabel:SetText("Name:"); npAlignLabel:SetTextColor(0.72,0.74,0.80); TrySetFont(npAlignLabel, 9)
    local npAlignDrop = CreateFrame("Frame", "MidnightUI_WizNPAlign2", npDropRow, "UIDropDownMenuTemplate")
    npAlignDrop:SetPoint("LEFT", npAlignLabel, "RIGHT", -12, -2); UIDropDownMenu_SetWidth(npAlignDrop, 72)
    UIDropDownMenu_SetText(npAlignDrop, npHB0.nameAlign or "LEFT")
    UIDropDownMenu_Initialize(npAlignDrop, function()
        local cur = (EnsureSettings().Nameplates.healthBar or {}).nameAlign or "LEFT"
        for _, a in ipairs({"LEFT","CENTER","RIGHT"}) do
            local info = UIDropDownMenu_CreateInfo(); info.text = a; info.checked = (a == cur)
            info.func = function() UIDropDownMenu_SetText(npAlignDrop, a)
                EnsureSettings().Nameplates.healthBar.nameAlign = a; CloseDropDownMenus(); ApplyNP() end
            UIDropDownMenu_AddButton(info) end end)

    local npHPPosLabel = npDropRow:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    npHPPosLabel:SetPoint("LEFT", npAlignDrop, "RIGHT", 0, 2); npHPPosLabel:SetText("HP%:"); npHPPosLabel:SetTextColor(0.72,0.74,0.80); TrySetFont(npHPPosLabel, 9)
    local npHPPosDrop = CreateFrame("Frame", "MidnightUI_WizNPHPPos2", npDropRow, "UIDropDownMenuTemplate")
    npHPPosDrop:SetPoint("LEFT", npHPPosLabel, "RIGHT", -12, -2); UIDropDownMenu_SetWidth(npHPPosDrop, 72)
    UIDropDownMenu_SetText(npHPPosDrop, npHB0.healthPctDisplay or "RIGHT")
    UIDropDownMenu_Initialize(npHPPosDrop, function()
        local cur = (EnsureSettings().Nameplates.healthBar or {}).healthPctDisplay or "RIGHT"
        for _, p in ipairs({"LEFT","CENTER","RIGHT","HIDDEN"}) do
            local info = UIDropDownMenu_CreateInfo(); info.text = p; info.checked = (p == cur)
            info.func = function() UIDropDownMenu_SetText(npHPPosDrop, p)
                EnsureSettings().Nameplates.healthBar.healthPctDisplay = p; CloseDropDownMenus(); ApplyNP() end
            UIDropDownMenu_AddButton(info) end end)

    npCheck:SetScript("OnClick", function(self)
        state.nameplatesEnabled = (self:GetChecked() == true)
        EnsureSettings().Nameplates.enabled = state.nameplatesEnabled; ApplyNP()
    end)

    p4Content:SetHeight(530)

    SyncFrameSelection = function()
        local st2 = EnsureSettings()
        local pW = (st2.PlayerFrame and st2.PlayerFrame.width) or 380
        local closest = "Balanced"
        if pW <= 350 then closest = "Compact" elseif pW >= 400 then closest = "Spacious" end
        state.frameSize = closest
        for name, btn in pairs(frameSizeCards) do if btn.SetSelected then btn:SetSelected(name == closest) end end
        local htMode = (st2.General and st2.General.healthTextMode) or "percent"
        state.healthText = htMode
        for mode, btn in pairs(healthTextCards) do if btn.SetSelected then btn:SetSelected(mode == htMode) end end
        nameScaleSlider._slider:SetValue((st2.General and st2.General.unitFrameNameScale) or 100)
        valScaleSlider._slider:SetValue((st2.General and st2.General.unitFrameValueScale) or 100)
        local npS2 = st2.Nameplates or {}; local npHB2 = npS2.healthBar or {}; local npCB2 = npS2.castBar or {}; local npTB2 = npS2.threatBar or {}
        state.nameplatesEnabled = (npS2.enabled ~= false); npCheck:SetChecked(state.nameplatesEnabled)
        if npSliders.width then npSliders.width._slider:SetValue(npHB2.width or 200) end
        if npSliders.height then npSliders.height._slider:SetValue(npHB2.height or 20) end
        if npSliders.nameFont then npSliders.nameFont._slider:SetValue(npHB2.nameFontSize or 10) end
        if npSliders.hpFont then npSliders.hpFont._slider:SetValue(npHB2.healthPctFontSize or 9) end
        if npSliders.castH then npSliders.castH._slider:SetValue(npCB2.height or 16) end
        UIDropDownMenu_SetText(npThreatDrop, (npTB2.enabled ~= false) and "ON" or "OFF")
        UIDropDownMenu_SetText(npAlignDrop, npHB2.nameAlign or "LEFT")
        UIDropDownMenu_SetText(npHPPosDrop, npHB2.healthPctDisplay or "RIGHT")
        UpdateFrameSizePreview(); UpdateNameplatePreview()
    end

    end

    do
    local p5 = CreateFrame("Frame", nil, pageHost)
    p5:SetAllPoints()
    p5:Hide()
    stepPages[5] = p5

    local p5Tag = p5:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    p5Tag:SetPoint("TOPLEFT", 4, -6)
    p5Tag:SetText("STEP 5  CHAT & MAP")
    p5Tag:SetTextColor(UI_COLORS.accent[1], UI_COLORS.accent[2], UI_COLORS.accent[3])
    TrySetFont(p5Tag, 12, "OUTLINE")

    local p5Head = p5:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
    p5Head:SetPoint("TOPLEFT", 4, -26)
    p5Head:SetText("Chat & Minimap")
    p5Head:SetTextColor(UI_COLORS.text[1], UI_COLORS.text[2], UI_COLORS.text[3])
    TrySetFont(p5Head, 20, "OUTLINE")

    local p5Sub = p5:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    p5Sub:SetPoint("TOPLEFT", 4, -52)
    p5Sub:SetPoint("TOPRIGHT", -4, -52)
    p5Sub:SetJustifyH("LEFT")
    p5Sub:SetText("Pick your chat system, chat panel style, and minimap shape.")
    p5Sub:SetTextColor(UI_COLORS.muted[1], UI_COLORS.muted[2], UI_COLORS.muted[3])
    TrySetFont(p5Sub, 14)
    CreateHeaderLine(p5, -82)

    local p5Scroll = CreateFrame("ScrollFrame", nil, p5, "UIPanelScrollFrameTemplate")
    p5Scroll:SetPoint("TOPLEFT", 0, -90)
    p5Scroll:SetPoint("BOTTOMRIGHT", -20, 4)
    local p5Content = CreateFrame("Frame", nil, p5Scroll)
    p5Content:SetSize(math.max(700, EXPANDED_W - 350), 340)
    p5Scroll:SetScrollChild(p5Content)

    local chatHeader = p5Content:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    chatHeader:SetPoint("TOPLEFT", 4, 0)
    chatHeader:SetText("Chat System")
    chatHeader:SetTextColor(UI_COLORS.text[1], UI_COLORS.text[2], UI_COLORS.text[3])
    TrySetFont(chatHeader, 14, "OUTLINE")

    local chatGrid = CreateFrame("Frame", nil, p5Content)
    chatGrid:SetPoint("TOPLEFT", 4, -22)
    chatGrid:SetPoint("TOPRIGHT", -4, -22)
    chatGrid:SetHeight(95)

    chatSystemCards = {}

    chatSystemCards["midnight"] = CreateVisualCard(chatGrid, {
        title = "MidnightUI Chat",
        tag = "(Recommended)",
        description = "Custom chat with tabbed channels, clean messages, and a Market tab.",
        width = 340,
        height = 90,
        group = "chatSystem",
        accentColor = {0.00, 0.78, 1.00},
        onClick = function()
            local s = EnsureSettings()
            s.Messenger.showDefaultChatInterface = false
            state.chatSystem = "midnight"
            local S = _G.MidnightUI_Settings
            if S and S.ApplyMessengerSettings then SafeCall("ApplyMessengerSettings", S.ApplyMessengerSettings) end
            if chatStyleSection then chatStyleSection:Show() end
        end,
    })
    chatSystemCards["midnight"]:ClearAllPoints()
    chatSystemCards["midnight"]:SetPoint("TOPLEFT", chatGrid, "TOPLEFT", 0, 0)
    chatSystemCards["midnight"]:SetPoint("RIGHT", chatGrid, "CENTER", -6, 0)
    chatSystemCards["midnight"]:SetHeight(90)

    chatSystemCards["blizzard"] = CreateVisualCard(chatGrid, {
        title = "Keep Blizzard Chat",
        description = "Stick with WoW's default chat window.",
        width = 340,
        height = 90,
        group = "chatSystem",
        accentColor = {0.50, 0.50, 0.55},
        onClick = function()
            local s = EnsureSettings()
            s.Messenger.showDefaultChatInterface = true
            state.chatSystem = "blizzard"
            local S = _G.MidnightUI_Settings
            if S and S.ApplyMessengerSettings then SafeCall("ApplyMessengerSettings", S.ApplyMessengerSettings) end
            if chatStyleSection then chatStyleSection:Hide() end
        end,
    })
    chatSystemCards["blizzard"]:ClearAllPoints()
    chatSystemCards["blizzard"]:SetPoint("TOPLEFT", chatGrid, "TOP", 6, 0)
    chatSystemCards["blizzard"]:SetPoint("TOPRIGHT", chatGrid, "TOPRIGHT", 0, 0)
    chatSystemCards["blizzard"]:SetHeight(90)

    chatStyleSection = CreateFrame("Frame", nil, p5Content)
    chatStyleSection:SetPoint("TOPLEFT", 4, -126)
    chatStyleSection:SetPoint("TOPRIGHT", -4, -126)
    chatStyleSection:SetHeight(90)

    local chatStyleHeader = chatStyleSection:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    chatStyleHeader:SetPoint("TOPLEFT", 0, 0)
    chatStyleHeader:SetText("Chat Style")
    chatStyleHeader:SetTextColor(UI_COLORS.text[1], UI_COLORS.text[2], UI_COLORS.text[3])
    TrySetFont(chatStyleHeader, 14, "OUTLINE")

    chatStyleCards = {}
    local chatStyleNames = { "Default", "Class Color", "Faithful", "Glass" }
    local chatStyleAccents = {
        { 0.05, 0.08, 0.18 },
        { classR, classG, classB },
        { 0.32, 0.22, 0.14 },
        { 0.20, 0.22, 0.28 },
    }

    for idx, styleName in ipairs(chatStyleNames) do
        local acc = chatStyleAccents[idx]
        local card = CreateVisualCard(chatStyleSection, {
            title = styleName,
            width = 120,
            height = 70,
            group = "chatStyle",
            accentColor = acc,
            onClick = function()
                local s = EnsureSettings()
                s.Messenger.style = styleName
                state.chatStyle = styleName
                local S = _G.MidnightUI_Settings
                if S and S.ApplyMessengerSettings then SafeCall("ApplyMessengerSettings", S.ApplyMessengerSettings) end
            end,
        })
        chatStyleCards[styleName] = card
    end
    do
        local csCards = { chatStyleCards["Default"], chatStyleCards["Class Color"], chatStyleCards["Faithful"], chatStyleCards["Glass"] }
        local function LayoutChatStyleCards()
            local gw = chatStyleSection:GetWidth()
            if gw < 100 then return end
            local gap = 10
            local cw = math.floor((gw - gap * 3) / 4)
            for i, c in ipairs(csCards) do
                c:ClearAllPoints()
                c:SetPoint("TOPLEFT", chatStyleSection, "TOPLEFT", (i - 1) * (cw + gap), -22)
                c:SetSize(cw, 60)
            end
        end
        chatStyleSection:HookScript("OnShow", LayoutChatStyleCards)
        chatStyleSection:HookScript("OnSizeChanged", LayoutChatStyleCards)
        C_Timer.After(0, LayoutChatStyleCards)
    end

    local mmHeader = p5Content:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    mmHeader:SetPoint("TOPLEFT", 4, -224)
    mmHeader:SetText("Minimap Shape")
    mmHeader:SetTextColor(UI_COLORS.text[1], UI_COLORS.text[2], UI_COLORS.text[3])
    TrySetFont(mmHeader, 14, "OUTLINE")

    local mmGrid = CreateFrame("Frame", nil, p5Content)
    mmGrid:SetPoint("TOPLEFT", 4, -244)
    mmGrid:SetPoint("TOPRIGHT", -4, -244)
    mmGrid:SetHeight(85)

    minimapCards = {}

    minimapCards["Circle"] = CreateVisualCard(mmGrid, {
        title = "Circle",
        width = 280,
        height = 80,
        group = "minimapShape",
        preview = function(c)
            local ring = c:CreateTexture(nil, "ARTWORK")
            ring:SetTexture("Interface\\Minimap\\UI-Minimap-Background")
            ring:SetSize(40, 40)
            ring:SetPoint("RIGHT", -20, -4)
            ring:SetVertexColor(UI_COLORS.accent[1], UI_COLORS.accent[2], UI_COLORS.accent[3], 0.6)
        end,
        onClick = function()
            local s = EnsureSettings()
            s.Minimap.shape = "Circle"
            state.minimapShape = "Circle"
            local S = _G.MidnightUI_Settings
            if S and S.ApplyMinimapSettings then SafeCall("ApplyMinimapSettings", S.ApplyMinimapSettings) end
        end,
    })
    minimapCards["Circle"]:ClearAllPoints()
    minimapCards["Circle"]:SetPoint("TOPLEFT", mmGrid, "TOPLEFT", 0, 0)
    minimapCards["Circle"]:SetPoint("RIGHT", mmGrid, "CENTER", -6, 0)
    minimapCards["Circle"]:SetHeight(80)

    minimapCards["Square"] = CreateVisualCard(mmGrid, {
        title = "Square",
        width = 280,
        height = 80,
        group = "minimapShape",
        preview = function(c)
            local sq = c:CreateTexture(nil, "ARTWORK")
            sq:SetSize(40, 40)
            sq:SetPoint("RIGHT", -20, -4)
            sq:SetColorTexture(UI_COLORS.accent[1], UI_COLORS.accent[2], UI_COLORS.accent[3], 0.6)
        end,
        onClick = function()
            local s = EnsureSettings()
            s.Minimap.shape = "Square"
            state.minimapShape = "Square"
            local S = _G.MidnightUI_Settings
            if S and S.ApplyMinimapSettings then SafeCall("ApplyMinimapSettings", S.ApplyMinimapSettings) end
        end,
    })
    minimapCards["Square"]:ClearAllPoints()
    minimapCards["Square"]:SetPoint("TOPLEFT", mmGrid, "TOP", 6, 0)
    minimapCards["Square"]:SetPoint("TOPRIGHT", mmGrid, "TOPRIGHT", 0, 0)
    minimapCards["Square"]:SetHeight(80)

    SyncChatMapSelection = function()
        local st = EnsureSettings()
        local useMUI = not (st.Messenger and st.Messenger.showDefaultChatInterface == true)
        state.chatSystem = useMUI and "midnight" or "blizzard"
        for key, card in pairs(chatSystemCards) do
            if key == state.chatSystem then card:Select() else card:Deselect() end
        end
        if useMUI then
            if chatStyleSection then chatStyleSection:Show() end
        else
            if chatStyleSection then chatStyleSection:Hide() end
        end
        local curStyle = (st.Messenger and st.Messenger.style) or "Default"
        state.chatStyle = curStyle
        for name, card in pairs(chatStyleCards) do
            if name == curStyle then card:Select() else card:Deselect() end
        end
        local shape = (st.Minimap and st.Minimap.shape) or "Circle"
        state.minimapShape = shape
        for name, card in pairs(minimapCards) do
            if name == shape then card:Select() else card:Deselect() end
        end
    end

    end

    do
    local p6 = CreateFrame("Frame", nil, pageHost)
    p6:SetAllPoints()
    p6:Hide()
    stepPages[6] = p6

    local p6Tag = p6:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    p6Tag:SetPoint("TOPLEFT", 4, -6)
    p6Tag:SetText("STEP 6  FINISH")
    p6Tag:SetTextColor(UI_COLORS.accent[1], UI_COLORS.accent[2], UI_COLORS.accent[3])
    TrySetFont(p6Tag, 12, "OUTLINE")

    local p6Head = p6:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
    p6Head:SetPoint("TOPLEFT", 4, -26)
    p6Head:SetText("You're All Set!")
    p6Head:SetTextColor(UI_COLORS.text[1], UI_COLORS.text[2], UI_COLORS.text[3])
    TrySetFont(p6Head, 20, "OUTLINE")

    local p6Sub = p6:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    p6Sub:SetPoint("TOPLEFT", 4, -52)
    p6Sub:SetPoint("TOPRIGHT", -4, -52)
    p6Sub:SetJustifyH("LEFT")
    p6Sub:SetText("Here is a summary of your choices. Type |cff00c8ff/mui|r to change anything later.")
    p6Sub:SetTextColor(UI_COLORS.muted[1], UI_COLORS.muted[2], UI_COLORS.muted[3])
    TrySetFont(p6Sub, 14)
    CreateHeaderLine(p6, -82)

    local summaryPanel = CreatePanel(p6, 640, 120)
    summaryPanel:SetPoint("TOPLEFT", 4, -94)
    summaryPanel:SetPoint("TOPRIGHT", -4, -94)
    summaryPanel:SetBackdropColor(0.05, 0.09, 0.16, 0.96)

    local summaryGlow = summaryPanel:CreateTexture(nil, "BACKGROUND")
    summaryGlow:SetTexture("Interface\\Buttons\\WHITE8X8")
    summaryGlow:SetPoint("TOPLEFT", 1, -1)
    summaryGlow:SetPoint("TOPRIGHT", -1, -1)
    summaryGlow:SetHeight(30)
    summaryGlow:SetVertexColor(0.00, 0.78, 1.00, 0.15)

    local summaryTitle = summaryPanel:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    summaryTitle:SetPoint("TOPLEFT", 14, -10)
    summaryTitle:SetText("Configuration Summary")
    summaryTitle:SetTextColor(UI_COLORS.text[1], UI_COLORS.text[2], UI_COLORS.text[3])
    TrySetFont(summaryTitle, 13, "OUTLINE")

    local snapColLeft = summaryPanel:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    snapColLeft:SetPoint("TOPLEFT", 14, -32)
    snapColLeft:SetPoint("BOTTOMLEFT", 14, 10)
    snapColLeft:SetPoint("RIGHT", summaryPanel, "CENTER", -4, 0)
    snapColLeft:SetJustifyH("LEFT")
    if snapColLeft.SetJustifyV then snapColLeft:SetJustifyV("TOP") end
    snapColLeft:SetText("")
    snapColLeft:SetTextColor(UI_COLORS.muted[1], UI_COLORS.muted[2], UI_COLORS.muted[3])
    TrySetFont(snapColLeft, 11)

    local snapColRight = summaryPanel:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    snapColRight:SetPoint("TOPRIGHT", -14, -32)
    snapColRight:SetPoint("BOTTOMRIGHT", -14, 10)
    snapColRight:SetPoint("LEFT", summaryPanel, "CENTER", 4, 0)
    snapColRight:SetJustifyH("LEFT")
    if snapColRight.SetJustifyV then snapColRight:SetJustifyV("TOP") end
    snapColRight:SetText("")
    snapColRight:SetTextColor(UI_COLORS.muted[1], UI_COLORS.muted[2], UI_COLORS.muted[3])
    TrySetFont(snapColRight, 11)

    UpdateSummary = function()
        local st = EnsureSettings()
        local pW = (st.PlayerFrame and st.PlayerFrame.width) or 380
        local pH = (st.PlayerFrame and st.PlayerFrame.height) or 66
        local tW = (st.TargetFrame and st.TargetFrame.width) or 380
        local tH = (st.TargetFrame and st.TargetFrame.height) or 66

        snapColLeft:SetText(
            "|cff7fc8ffPreset|r  " .. tostring(state.preset)
            .. "\n|cff7fc8ffTheme|r  " .. tostring(state.theme)
            .. "\n|cff7fc8ffBar Style|r  " .. tostring(state.barStyle)
            .. "\n|cff7fc8ffFrame Size|r  " .. tostring(state.frameSize)
        )
        snapColRight:SetText(
            "|cff7fc8ffHealth Text|r  " .. tostring(state.healthText)
            .. "\n|cff7fc8ffChat|r  " .. (state.chatSystem == "midnight" and "MidnightUI" or "Blizzard")
            .. "\n|cff7fc8ffMinimap|r  " .. tostring(state.minimapShape)
            .. "\n|cff7fc8ffNameplates|r  " .. (state.nameplatesEnabled and "On" or "Off")
        )
    end

    local muiReminder = p6:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    muiReminder:SetPoint("TOPLEFT", summaryPanel, "BOTTOMLEFT", 0, -12)
    muiReminder:SetPoint("TOPRIGHT", summaryPanel, "BOTTOMRIGHT", 0, -12)
    muiReminder:SetJustifyH("CENTER")
    muiReminder:SetText("Type |cff00c8ff/mui|r to open settings at any time.")
    muiReminder:SetTextColor(UI_COLORS.muted[1], UI_COLORS.muted[2], UI_COLORS.muted[3])
    TrySetFont(muiReminder, 13)

    local communityGrid = CreatePanel(p6, 1, 280)
    communityGrid:SetPoint("TOPLEFT", 4, -248)
    communityGrid:SetPoint("TOPRIGHT", -4, -248)
    communityGrid:SetBackdropColor(0.05, 0.09, 0.16, 0.96)

    communityGlow = communityGrid:CreateTexture(nil, "ARTWORK")
    communityGlow:SetTexture("Interface\\Buttons\\WHITE8X8")
    communityGlow:SetPoint("TOPLEFT", 1, -1)
    communityGlow:SetPoint("TOPRIGHT", -1, -1)
    communityGlow:SetHeight(100)
    communityGlow:SetVertexColor(0.00, 0.78, 1.00, 0.10)

    local communityTitle = communityGrid:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    communityTitle:SetPoint("TOPLEFT", 14, -10)
    communityTitle:SetText("Stay Connected")
    communityTitle:SetTextColor(UI_COLORS.text[1], UI_COLORS.text[2], UI_COLORS.text[3])
    TrySetFont(communityTitle, 14, "OUTLINE")

    local communityRow = CreateFrame("Frame", nil, communityGrid)
    communityRow:SetPoint("TOPLEFT", 14, -36)
    communityRow:SetPoint("TOPRIGHT", -14, -36)
    communityRow:SetHeight(230)

    local tileDiscord = CreateActionTile(
        communityRow,
        "Discord",
        "Report bugs, share feedback, and get help from other MidnightUI players.",
        "Join Discord",
        nil,
        function() OpenURL(DISCORD_URL) end,
        1, 220
    )
    tileDiscord:ClearAllPoints()
    tileDiscord:SetPoint("TOPLEFT", communityRow, "TOPLEFT", 0, 0)
    tileDiscord:SetPoint("BOTTOM", communityRow, "BOTTOM", 0, 0)
    tileDiscord:SetWidth(math.floor((EXPANDED_W - 280) / 3) - 12)
    do
        local discAccent = tileDiscord:CreateTexture(nil, "ARTWORK", nil, 2)
        discAccent:SetPoint("TOPLEFT", 1, -1)
        discAccent:SetPoint("TOPRIGHT", -1, -1)
        discAccent:SetHeight(3)
        discAccent:SetColorTexture(0.32, 0.69, 1.00, 0.80)
    end

    local tileCurse = CreateActionTile(
        communityRow,
        "CurseForge",
        "Rate and review MidnightUI to help other players find it.",
        "Open CurseForge",
        nil,
        function() OpenURL(CURSEFORGE_URL) end,
        1, 220
    )
    tileCurse:ClearAllPoints()
    tileCurse:SetPoint("TOPLEFT", tileDiscord, "TOPRIGHT", 10, 0)
    tileCurse:SetPoint("BOTTOM", communityRow, "BOTTOM", 0, 0)
    tileCurse:SetWidth(math.floor((EXPANDED_W - 280) / 3) - 12)
    do
        local curseAccent = tileCurse:CreateTexture(nil, "ARTWORK", nil, 2)
        curseAccent:SetPoint("TOPLEFT", 1, -1)
        curseAccent:SetPoint("TOPRIGHT", -1, -1)
        curseAccent:SetHeight(3)
        curseAccent:SetColorTexture(1.00, 0.56, 0.28, 0.80)
    end

    local tileMPI = CreateActionTile(
        communityRow,
        "MPI Companion",
        "Install the free desktop app that scores every player after each Mythic+ run.",
        "Download MPI",
        nil,
        function() OpenURL(MPI_COMPANION_URL) end,
        1, 220
    )
    tileMPI:ClearAllPoints()
    tileMPI:SetPoint("TOPLEFT", tileCurse, "TOPRIGHT", 10, 0)
    tileMPI:SetPoint("BOTTOM", communityRow, "BOTTOM", 0, 0)
    tileMPI:SetPoint("TOPRIGHT", communityRow, "TOPRIGHT", 0, 0)
    do
        local mpiAccent = tileMPI:CreateTexture(nil, "ARTWORK", nil, 2)
        mpiAccent:SetPoint("TOPLEFT", 1, -1)
        mpiAccent:SetPoint("TOPRIGHT", -1, -1)
        mpiAccent:SetHeight(3)
        mpiAccent:SetColorTexture(0.35, 0.88, 0.62, 0.80)
    end

    end

    onStepChanged = function(idx)
        btnBack:SetEnabled(idx > 1)
        if idx == 1 then
            btnNext:Hide()
            SetFooterStatus("Import a profile, or continue with a fresh setup.")
            RefreshQuickOptions()
        elseif idx == NUM_STEPS then
            btnNext:Show()
            btnNext:SetText("Finish Setup")
            btnNext:SetAlpha(0.85)
            SetFooterStatus("Review your choices, then finish setup.", 0.93, 0.88, 0.72)
            UpdateSummary()
        else
            btnNext:Show()
            btnNext:SetText("Next")
            btnNext:SetAlpha(1)
            if idx == 2 then
                SyncPlaystyleSelection()
                SetFooterStatus("Pick the playstyle that best describes how you play.")
            elseif idx == 3 then
                SyncThemeSelection()
                SetFooterStatus("Choose a theme and bar style for your UI.")
            elseif idx == 4 then
                SyncFrameSelection()
                SetFooterStatus("Pick frame sizes and health text display.")
            elseif idx == 5 then
                SyncChatMapSelection()
                SetFooterStatus("Set up chat and minimap preferences.")
            end
        end
    end

    btnBack:SetScript("OnClick", function()
        if currentStep > 1 then
            ShowStep(currentStep - 1, true)
        end
    end)

    btnNext:SetScript("OnClick", function()
        if currentStep == 1 and f and f.AnimateSize then
            f:AnimateSize(EXPANDED_W, EXPANDED_H, 0.25)
        end
        if currentStep < NUM_STEPS then
            unlockedStep = math.max(unlockedStep, currentStep + 1)
            ShowStep(currentStep + 1, true)
            return
        end
        if Profiles and Profiles.MarkWelcomeSeen then Profiles.MarkWelcomeSeen() end
        if Profiles and Profiles.SaveCurrentProfile then Profiles.SaveCurrentProfile("welcome_finish") end
        if previewDock then previewDock:Hide() end
        f:Hide()
        C_Timer.After(0.5, function()
            if _G.MidnightUI_TryShowWhatsNew then _G.MidnightUI_TryShowWhatsNew() end
        end)
    end)

    close:SetScript("OnClick", function()
        if Profiles and Profiles.MarkWelcomeSeen then Profiles.MarkWelcomeSeen() end
        if previewDock then previewDock:Hide() end
        f:Hide()
        C_Timer.After(0.5, function()
            if _G.MidnightUI_TryShowWhatsNew then _G.MidnightUI_TryShowWhatsNew() end
        end)
    end)

    local t = 0
    local baseA, baseB = 118, 70
    f:SetScript("OnUpdate", function(self, elapsed)
        t = t + (elapsed or 0)
        if self._sizeAnim then
            local a = self._sizeAnim
            a.t = a.t + (elapsed or 0)
            local p = math.min(1, a.t / a.dur)
            local ease = p * p * (3 - 2 * p)
            local w = a.fromW + (a.toW - a.fromW) * ease
            local h = a.fromH + (a.toH - a.fromH) * ease
            self:SetSize(w, h)
            if self._leftRail then self._leftRail:SetHeight(h) end
            if p >= 1 then
                self._sizeAnim = nil
            end
        end
        local pulseA = 0.22 + (math.sin(t * 1.2) * 0.06)
        local pulseB = 0.30 + (math.sin(t * 1.6 + 0.8) * 0.08)
        local scaleA = 1.00 + (math.sin(t * 0.7) * 0.02)
        local scaleB = 1.00 + (math.sin(t * 0.9 + 0.4) * 0.025)
        if ringA and ringB then
            ringA:SetAlpha(math.max(0.10, math.min(0.45, pulseA)))
            ringB:SetAlpha(math.max(0.12, math.min(0.55, pulseB)))
            ringA:SetSize(baseA * scaleA, baseA * scaleA)
            ringB:SetSize(baseB * scaleB, baseB * scaleB)
        end
        communityGlow:SetVertexColor(0.00, 0.78, 1.00, math.max(0.06, math.min(0.24, 0.14 + math.sin(t * 1.8) * 0.08)))
        if ringA and ringA.SetRotation then ringA:SetRotation(t * 0.05) end
        if ringB and ringB.SetRotation then ringB:SetRotation(-t * 0.08) end
    end)

    f.Refresh = function(self)
        if previewDock then previewDock:Hide() end
        RefreshQuickOptions()
        SyncPlaystyleSelection()
        SyncThemeSelection()
        SyncFrameSelection()
        SyncChatMapSelection()
        UpdateSummary()
    end

    f.ShowStep = function(self, idx, force)
        if idx == 2 and self.AnimateSize and not self._sizeExpanded then
            self._sizeExpanded = true
            self:AnimateSize(EXPANDED_W, EXPANDED_H, 0.25)
        end
        ShowStep(idx, force)
    end

    f.AnimateSize = function(self, w, h, dur)
        local cw, ch = self:GetSize()
        self._sizeAnim = {
            t = 0,
            dur = dur or 0.25,
            fromW = cw,
            fromH = ch,
            toW = w,
            toH = h,
        }
    end

    ShowStep(1, true)

    _G.MidnightUI_WelcomeExitPreview = ExitPreviewMode

    return f
end

-- ============================================================
-- SECTION: Public API — Welcome Show Guards
-- These are the public entry points other modules call to show
-- the wizard or check if it should be shown.
-- ShowWelcome(force): lazy-create wizard; if force=false, skip if
--   the player has already seen the welcome screen this char.
-- ReturnToWelcomeWizard(): re-opens wizard and navigates back to
--   step 1 (used by Settings_UI "Show Welcome" button).
-- TryShowWelcome(): checks Profiles.HasSeenWelcome; shows wizard
--   only on first login for this character.
-- READS:  Profiles.HasSeenWelcome, welcomeFrame
-- WRITES: welcomeFrame (lazy init via CreateWelcomeFrame)
-- FUNCTIONS:
--   _G.MidnightUI_ShowWelcome(force)
--   _G.MidnightUI_ReturnToWelcomeWizard()
--   _G.MidnightUI_TryShowWelcome()
-- ============================================================
function _G.MidnightUI_ShowWelcome(force)
    if not welcomeFrame then welcomeFrame = CreateWelcomeFrame() end
    local seen = false
    if Profiles and Profiles.HasSeenWelcome then
        local ok, res = pcall(Profiles.HasSeenWelcome)
        if ok then seen = (res == true) end
    end
    if not force and seen then
        if _G.MidnightUI_Diagnostics and _G.MidnightUI_Diagnostics.LogDebugSource and _G.MidnightUI_Diagnostics.IsEnabled and _G.MidnightUI_Diagnostics.IsEnabled() then
            _G.MidnightUI_Diagnostics.LogDebugSource("Welcome", "ShowWelcome blocked: HasSeenWelcome=true")
        elseif _G.MidnightUI_Debug then
            _G.MidnightUI_Debug("[Welcome] ShowWelcome blocked: HasSeenWelcome=true")
        end
        return
    end
    if not force and Profiles and Profiles.MarkWelcomeSeen then Profiles.MarkWelcomeSeen() end
    welcomeFrame:Show()
    welcomeFrame:Raise()
    if welcomeFrame.Refresh then welcomeFrame:Refresh() end
    if welcomeFrame.ShowStep then welcomeFrame:ShowStep(1, true) end
end

function _G.MidnightUI_ReturnToWelcomeWizard()
    if _G.MidnightUI_WelcomeExitPreview then
        _G.MidnightUI_WelcomeExitPreview()
    elseif welcomeFrame then
        welcomeFrame:Show()
        welcomeFrame:Raise()
    end
end

function _G.MidnightUI_TryShowWelcome()
    if not Profiles or not Profiles.HasSeenWelcome then return end
    local seen = false
    local okSeen, resSeen = pcall(Profiles.HasSeenWelcome)
    if okSeen then seen = (resSeen == true) end
    if seen then
        return
    end
    if InCombatLockdown and InCombatLockdown() then
        Log("Welcome deferred, player in combat")
        if C_Timer and C_Timer.After then
            C_Timer.After(2, function()
                if not InCombatLockdown or not InCombatLockdown() then
                    _G.MidnightUI_ShowWelcome()
                end
            end)
        end
        return
    end
    local ok, err = pcall(_G.MidnightUI_ShowWelcome)
    if not ok then
        if _G.MidnightUI_Diagnostics and _G.MidnightUI_Diagnostics.LogDebugSource and _G.MidnightUI_Diagnostics.IsEnabled and _G.MidnightUI_Diagnostics.IsEnabled() then
            _G.MidnightUI_Diagnostics.LogDebugSource("Welcome", "TryShowWelcome: ShowWelcome error: " .. tostring(err))
        elseif _G.MidnightUI_Debug then
            _G.MidnightUI_Debug("[Welcome] TryShowWelcome: ShowWelcome error: " .. tostring(err))
        end
    end
end



-- ============================================================
-- SECTION: What's New Dialog
-- A versioned changelog dialog shown once per character per addon
-- version. WHATS_NEW_VERSION is bumped manually when changelog
-- content changes.
-- CreateWhatsNewFrame(): 740×600 draggable DIALOG-strata panel
--   with scrollable changelog content and a dismiss button.
--   Calls Profiles.MarkWhatsNewSeen on close.
-- TryShowWhatsNew(): checks Profiles.HasSeenWhatsNew(version);
--   shows dialog only if version is new for this character.
-- ShowWhatsNew(): unconditional show (used by Settings_UI button).
-- whatsNewFrame is a module-upvalue singleton (lazy init).
-- READS:  Profiles.HasSeenWhatsNew, Profiles.MarkWhatsNewSeen
-- WRITES: whatsNewFrame (lazy singleton)
-- FUNCTIONS:
--   CreateWhatsNewFrame()        → dialog frame (lazy singleton)
--   _G.MidnightUI_TryShowWhatsNew()
--   _G.MidnightUI_ShowWhatsNew()
-- ============================================================
local WHATS_NEW_VERSION = "2.4.0"

local whatsNewFrame = nil

local function CreateWhatsNewFrame()
    local W, H = 740, 600
    local f = CreatePanel(UIParent, W, H)
    f:SetPoint("CENTER", UIParent, "CENTER", 0, 20)
    f:SetFrameStrata("DIALOG")
    f:SetFrameLevel(100)
    f:SetMovable(true)
    f:EnableMouse(true)
    f:RegisterForDrag("LeftButton")
    f:SetScript("OnDragStart", f.StartMoving)
    f:SetScript("OnDragStop", f.StopMovingOrSizing)
    f:Hide()

    local topBar = f:CreateTexture(nil, "ARTWORK")
    topBar:SetTexture("Interface\\Buttons\\WHITE8X8")
    topBar:SetPoint("TOPLEFT", 1, -1)
    topBar:SetPoint("TOPRIGHT", -1, -1)
    topBar:SetHeight(3)
    topBar:SetVertexColor(0.00, 0.78, 1.00, 0.90)

    local title = f:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
    title:SetPoint("TOPLEFT", 24, -18)
    TrySetFont(title, 24, "OUTLINE")
    title:SetText("What's New in MidnightUI")
    title:SetTextColor(1, 1, 1)

    local verLine = f:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    verLine:SetPoint("TOPLEFT", 24, -50)
    TrySetFont(verLine, 12)
    verLine:SetText("v" .. WHATS_NEW_VERSION .. " — here's what we've been working on.")
    verLine:SetTextColor(UI_COLORS.muted[1], UI_COLORS.muted[2], UI_COLORS.muted[3])

    local close = CreateFrame("Button", nil, f)
    close:SetSize(28, 28)
    close:SetPoint("TOPRIGHT", -12, -12)
    local closeText = close:CreateFontString(nil, "OVERLAY")
    TrySetFont(closeText, 18, "OUTLINE")
    closeText:SetPoint("CENTER")
    closeText:SetText("x")
    closeText:SetTextColor(0.6, 0.6, 0.6)
    close:SetScript("OnEnter", function() closeText:SetTextColor(1, 0.3, 0.3) end)
    close:SetScript("OnLeave", function() closeText:SetTextColor(0.6, 0.6, 0.6) end)

    CreateHeaderLine(f, -72)


    local CARD_PAD = 20
    local CARD_TOP = -82
    local CARD_W = math.floor((W - CARD_PAD * 4) / 3)
    local CARD_H = 400

    local function MakeShowcaseCard(parent, xOffset, accentR, accentG, accentB, tagText, titleText, bodyLines, ctaText, ctaAction)
        local card = CreatePanel(parent, CARD_W, CARD_H)
        card:SetPoint("TOPLEFT", CARD_PAD + xOffset, CARD_TOP)
        card:SetBackdropColor(0.05, 0.07, 0.13, 0.98)

        local cAccent = card:CreateTexture(nil, "ARTWORK")
        cAccent:SetTexture("Interface\\Buttons\\WHITE8X8")
        cAccent:SetPoint("TOPLEFT", 1, -1)
        cAccent:SetPoint("TOPRIGHT", -1, -1)
        cAccent:SetHeight(3)
        cAccent:SetVertexColor(accentR, accentG, accentB, 0.90)

        local glow = card:CreateTexture(nil, "BACKGROUND")
        glow:SetTexture("Interface\\Buttons\\WHITE8X8")
        glow:SetPoint("TOPLEFT", 1, -1)
        glow:SetPoint("TOPRIGHT", -1, -1)
        glow:SetHeight(80)
        glow:SetVertexColor(accentR, accentG, accentB, 0.06)

        local tag = card:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
        tag:SetPoint("TOPLEFT", 14, -14)
        TrySetFont(tag, 8, "OUTLINE")
        tag:SetText(tagText)
        tag:SetTextColor(accentR, accentG, accentB)

        local cTitle = card:CreateFontString(nil, "OVERLAY", "GameFontNormal")
        cTitle:SetPoint("TOPLEFT", 14, -32)
        cTitle:SetPoint("TOPRIGHT", -14, -32)
        TrySetFont(cTitle, 15, "")
        cTitle:SetText(titleText)
        cTitle:SetTextColor(1, 1, 1)
        cTitle:SetJustifyH("LEFT")

        local yOff = -60
        for _, line in ipairs(bodyLines) do
            local para = card:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
            para:SetPoint("TOPLEFT", 14, yOff)
            para:SetPoint("TOPRIGHT", -14, yOff)
            para:SetJustifyH("LEFT")
            TrySetFont(para, 11)
            para:SetText(line)
            para:SetTextColor(UI_COLORS.muted[1], UI_COLORS.muted[2], UI_COLORS.muted[3])
            para:SetSpacing(2)
            local h = para:GetStringHeight() or 30
            yOff = yOff - h - 10
        end

        if ctaText then
            local btn = CreateFrame("Button", nil, card, "UIPanelButtonTemplate")
            btn:SetPoint("BOTTOMLEFT", 12, 14)
            btn:SetPoint("BOTTOMRIGHT", -12, 14)
            btn:SetHeight(32)
            btn:SetText(ctaText)
            StyleButton(btn)

            local btnGlow = btn:CreateTexture(nil, "BACKGROUND")
            btnGlow:SetTexture("Interface\\Buttons\\WHITE8X8")
            btnGlow:SetPoint("TOPLEFT", 1, -1)
            btnGlow:SetPoint("BOTTOMRIGHT", -1, 1)
            btnGlow:SetVertexColor(accentR, accentG, accentB, 0.14)
            btn:HookScript("OnEnter", function() btnGlow:SetVertexColor(accentR, accentG, accentB, 0.28) end)
            btn:HookScript("OnLeave", function() btnGlow:SetVertexColor(accentR, accentG, accentB, 0.14) end)

            btn:SetScript("OnClick", function()
                if ctaAction == "groupfinder" then
                    if PVEFrame_ToggleFrame then
                        PVEFrame_ToggleFrame("GroupFinderFrame")
                    end
                    f:Hide()
                elseif ctaAction == "website" then
                    if _G.MidnightUI_OpenURL then
                        _G.MidnightUI_OpenURL("https://mpi.atyzi.com")
                    end
                elseif ctaAction == "guild" then
                    if ToggleGuildFrame then
                        ToggleGuildFrame()
                    elseif GuildFrame_Toggle then
                        GuildFrame_Toggle()
                    end
                    f:Hide()
                end
            end)
        end

        return card
    end

    MakeShowcaseCard(f, 0,
        0.32, 0.69, 1.00,
        "REDESIGNED",
        "Group Finder Redesign",
        {
            "The group finder has been completely rebuilt for Mythic+ pushers. Browse keys by dungeon, key level, and role slot. See group composition at a glance, one-click apply.",
            "Applications track live, so you always know who invited you, who declined, and who's still sitting on you.",
            "Turned it off? Re-enable it under |cffFFD100Settings → General → Group Finder|r and tick |cffFFFFFFEnable Midnight Group Finder|r.",
        },
        "Open Group Finder",
        "groupfinder"
    )

    MakeShowcaseCard(f, CARD_W + CARD_PAD,
        0.90, 0.35, 0.35,
        "NEW FEATURE",
        "Debuff & Interrupt Tracking",
        {
            "Your health bar now glows with a color-coded border the moment you're hit by a Magic, Curse, Disease, Poison, Bleed, or Enrage effect, so you know at a glance what to dispel or trinket out.",
            "A row of kick icons above your frame shows every interrupter in your party with their cooldowns ticking live. The next kicker up is highlighted so the rotation never breaks.",
            "Disabled them? The glow border lives under |cffFFD100Settings → Combat → Debuffs|r (|cffFFFFFFPlayer Glow Border|r). The kick tracker is in the |cffFFD100Overlay Manager|r as |cffFFFFFFInterrupt Tracker|r.",
        },
        nil,
        nil
    )

    MakeShowcaseCard(f, (CARD_W + CARD_PAD) * 2,
        0.61, 0.48, 0.93,
        "NEW FEATURE",
        "M+ Player Scores",
        {
            "Every player in your group now gets a performance score from 0-100 after each Mythic+ run.",
            "Scores measure five things: how well you dodge mechanics, how often you stay alive, your damage output, how much you interrupt, and how consistent you are.",
            "Install the free |cff9b7aedMPI Companion|r desktop app and your scores upload to |cff9b7aedmpi.atyzi.com|r — a full dashboard with run replays, death analysis, boss breakdowns, and tips to improve.",
        },
        "Get MPI Companion",
        "website"
    )


    local gotIt = CreateFrame("Button", nil, f, "UIPanelButtonTemplate")
    gotIt:SetPoint("BOTTOMLEFT", 24, 16)
    gotIt:SetPoint("BOTTOMRIGHT", -24, 16)
    gotIt:SetHeight(36)
    gotIt:SetText("Got It — Let's Go!")
    StyleButton(gotIt)

    local function CloseWhatsNew()
        if Profiles and Profiles.MarkWhatsNewSeen then
            Profiles.MarkWhatsNewSeen(WHATS_NEW_VERSION)
        end
        f:Hide()
    end

    close:SetScript("OnClick", CloseWhatsNew)
    gotIt:SetScript("OnClick", CloseWhatsNew)
    f:SetScript("OnKeyDown", function(self, key)
        if key == "ESCAPE" then
            CloseWhatsNew()
            self:SetPropagateKeyboardInput(false)
        else
            self:SetPropagateKeyboardInput(true)
        end
    end)

    return f
end

function _G.MidnightUI_TryShowWhatsNew()
    if not Profiles then return end

    if Profiles.HasSeenWelcome then
        local ok, seen = pcall(Profiles.HasSeenWelcome)
        if ok and not seen then return end
    end

    if Profiles.HasSeenWhatsNew then
        local ok, seen = pcall(Profiles.HasSeenWhatsNew, WHATS_NEW_VERSION)
        if ok and seen then return end
    end

    if InCombatLockdown and InCombatLockdown() then
        if C_Timer and C_Timer.After then
            C_Timer.After(3, function()
                if not InCombatLockdown or not InCombatLockdown() then
                    _G.MidnightUI_TryShowWhatsNew()
                end
            end)
        end
        return
    end

    if not whatsNewFrame then
        whatsNewFrame = CreateWhatsNewFrame()
    end
    whatsNewFrame:Show()
    whatsNewFrame:Raise()
end

function _G.MidnightUI_ShowWhatsNew()
    if not whatsNewFrame then
        whatsNewFrame = CreateWhatsNewFrame()
    end
    whatsNewFrame:Show()
    whatsNewFrame:Raise()
end
