local addonName = ...

-- ============================================================
--  Colours
-- ============================================================
local C = {
    bg         = {0.05, 0.04, 0.07, 0.98},
    sidebar    = {0.03, 0.02, 0.05, 1.00},
    accent     = {0.65, 0.30, 1.00, 1.00},
    accentDim  = {0.40, 0.15, 0.65, 1.00},
    header     = {0.95, 0.92, 1.00, 1.00},
    sub        = {0.48, 0.42, 0.58, 1.00},
    text       = {0.82, 0.78, 0.90, 1.00},
    inputBg    = {0.08, 0.06, 0.12, 1.00},
    border     = {0.20, 0.14, 0.28, 1.00},
    row1       = {0.10, 0.07, 0.15, 0.70},
}

local FRAME_W   = 1040
local FRAME_H   = 680
local SIDEBAR_W = 240
local PAD       = 20

-- ============================================================
--  Helpers
-- ============================================================
local function Backdrop(f, bg, border)
    f:SetBackdrop({
        bgFile   = "Interface\\Buttons\\WHITE8X8",
        edgeFile = "Interface\\Buttons\\WHITE8X8",
        edgeSize = 1,
    })
    f:SetBackdropColor(bg[1], bg[2], bg[3], bg[4] or 1)
    if border then
        f:SetBackdropBorderColor(border[1], border[2], border[3], border[4] or 1)
    end
end

local function Tex(parent, r, g, b, a)
    local t = parent:CreateTexture(nil, "ARTWORK")
    t:SetColorTexture(r, g, b, a)
    return t
end

local function Label(parent, fontObj, text, r, g, b, a)
    local fs = parent:CreateFontString(nil, "OVERLAY", fontObj)
    fs:SetText(text or "")
    if r then fs:SetTextColor(r, g, b, a or 1) end
    fs:SetJustifyH("LEFT")
    return fs
end

-- ============================================================
--  Main window
-- ============================================================
local Main = CreateFrame("Frame", "CFLMainFrame", UIParent, "BackdropTemplate")
Main:SetSize(FRAME_W, FRAME_H)
Main:SetPoint("CENTER")
Main:SetFrameStrata("DIALOG")
Main:SetMovable(true)
Main:EnableMouse(true)
Main:RegisterForDrag("LeftButton")
Main:SetScript("OnDragStart", Main.StartMoving)
Main:SetScript("OnDragStop",  Main.StopMovingOrSizing)

table.insert(UISpecialFrames, "CFLMainFrame")
Main:Hide()
Main:SetBackdrop({
    bgFile   = "Interface\\Buttons\\WHITE8X8",
    edgeFile = "Interface\\Buttons\\WHITE8X8",
    edgeSize = 1,
})
Main:SetBackdropColor(C.bg[1], C.bg[2], C.bg[3], C.bg[4] or 1)
Main:SetBackdropBorderColor(0, 0, 0, 0)

local function AddBorderLine(side)
    local t = Main:CreateTexture(nil, "OVERLAY", nil, 7)
    t:SetColorTexture(C.accent[1], C.accent[2], C.accent[3], 0.9)
    if side == "top" then
        t:SetHeight(2); t:SetPoint("TOPLEFT", Main, "TOPLEFT", 0, 0); t:SetPoint("TOPRIGHT", Main, "TOPRIGHT", 0, 0)
    elseif side == "bottom" then
        t:SetHeight(2); t:SetPoint("BOTTOMLEFT", Main, "BOTTOMLEFT", 0, 0); t:SetPoint("BOTTOMRIGHT", Main, "BOTTOMRIGHT", 0, 0)
    elseif side == "left" then
        t:SetWidth(2); t:SetPoint("TOPLEFT", Main, "TOPLEFT", 0, 0); t:SetPoint("BOTTOMLEFT", Main, "BOTTOMLEFT", 0, 0)
    elseif side == "right" then
        t:SetWidth(2); t:SetPoint("TOPRIGHT", Main, "TOPRIGHT", 0, 0); t:SetPoint("BOTTOMRIGHT", Main, "BOTTOMRIGHT", 0, 0)
    end
end
AddBorderLine("top")
AddBorderLine("bottom")
AddBorderLine("left")
AddBorderLine("right")

local CloseBtn = CreateFrame("Button", nil, Main, "BackdropTemplate")
CloseBtn:SetSize(32, 32)
CloseBtn:SetPoint("TOPRIGHT", Main, "TOPRIGHT", -8, -8)
CloseBtn:SetFrameLevel(Main:GetFrameLevel() + 10)
Backdrop(CloseBtn, {0.15, 0.08, 0.04, 1}, C.border)
local closeFS = CloseBtn:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
closeFS:SetText("x")
closeFS:SetTextColor(C.sub[1], C.sub[2], C.sub[3])
closeFS:SetAllPoints(CloseBtn)
closeFS:SetJustifyH("CENTER")
CloseBtn:SetScript("OnEnter", function() closeFS:SetTextColor(1, 0.30, 0.20) end)
CloseBtn:SetScript("OnLeave", function() closeFS:SetTextColor(C.sub[1], C.sub[2], C.sub[3]) end)
CloseBtn:SetScript("OnClick", function() Main:Hide() end)

local Sidebar = CreateFrame("Frame", nil, Main, "BackdropTemplate")
Sidebar:SetWidth(SIDEBAR_W)
Sidebar:SetPoint("TOPLEFT",    Main, "TOPLEFT",    2, -2)
Sidebar:SetPoint("BOTTOMLEFT", Main, "BOTTOMLEFT", 2,  2)
Backdrop(Sidebar, C.sidebar, C.border)

local sideDiv = Tex(Sidebar, C.accent[1], C.accent[2], C.accent[3], 0.35)
sideDiv:SetWidth(1)
sideDiv:SetPoint("TOPRIGHT",    Sidebar, "TOPRIGHT",    0,  0)
sideDiv:SetPoint("BOTTOMRIGHT", Sidebar, "BOTTOMRIGHT", 0,  0)

local LOGO_H = 80
local logoGlow = Tex(Sidebar, C.accent[1], C.accent[2], C.accent[3], 0.08)
logoGlow:SetPoint("TOPLEFT",  Sidebar, "TOPLEFT",  0, 0)
logoGlow:SetPoint("TOPRIGHT", Sidebar, "TOPRIGHT", 0, 0)
logoGlow:SetHeight(LOGO_H)

local logoTitle = Label(Sidebar, "GameFontNormalHuge", "Custom Font Loader", C.accent[1], C.accent[2], C.accent[3])
logoTitle:SetPoint("TOPLEFT", Sidebar, "TOPLEFT", PAD, -14)
logoTitle:SetWidth(SIDEBAR_W - PAD * 2)
logoTitle:SetWordWrap(true)

local logoAuth = Label(Sidebar, "GameFontNormal", "Author: Exxos", C.sub[1], C.sub[2], C.sub[3])
logoAuth:SetPoint("TOPLEFT", logoTitle, "BOTTOMLEFT", 0, -4)

local logoDivLine = Tex(Sidebar, C.accent[1], C.accent[2], C.accent[3], 0.25)
logoDivLine:SetHeight(1)
logoDivLine:SetPoint("TOPLEFT",  Sidebar, "TOPLEFT",  0, -LOGO_H)
logoDivLine:SetPoint("TOPRIGHT", Sidebar, "TOPRIGHT", 0, -LOGO_H)

local verLabel = Sidebar:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
verLabel:SetPoint("BOTTOMLEFT", Sidebar, "BOTTOMLEFT", 8, 6)
verLabel:SetTextColor(C.sub[1], C.sub[2], C.sub[3])
verLabel:SetJustifyH("LEFT")
verLabel:SetText("v" .. (C_AddOns.GetAddOnMetadata(addonName, "Version") or "?"))

local NAV_Y   = LOGO_H + 40
local NAV_H   = 42
local activePane = nil

local function MakeNavBtn(label, yOffset)
    local btn = CreateFrame("Button", nil, Sidebar, "BackdropTemplate")
    btn:SetHeight(NAV_H)
    btn:SetPoint("TOPLEFT",  Sidebar, "TOPLEFT",  0, -(NAV_Y + yOffset))
    btn:SetPoint("TOPRIGHT", Sidebar, "TOPRIGHT", 0, -(NAV_Y + yOffset))

    local selBG = Tex(btn, C.accent[1], C.accent[2], C.accent[3], 0.00)
    selBG:SetAllPoints(btn)
    btn._selBG = selBG

    local selBar = Tex(btn, C.accent[1], C.accent[2], C.accent[3], 0.00)
    selBar:SetWidth(3)
    selBar:SetPoint("TOPLEFT",    btn, "TOPLEFT",    0, 0)
    selBar:SetPoint("BOTTOMLEFT", btn, "BOTTOMLEFT", 0, 0)
    btn._selBar = selBar

    local hl = Tex(btn, 1, 1, 1, 0.00)
    hl:SetAllPoints(btn)
    btn:SetScript("OnEnter", function(self)
        if activePane ~= self then hl:SetColorTexture(1, 1, 1, 0.05) end
    end)
    btn:SetScript("OnLeave", function() hl:SetColorTexture(1, 1, 1, 0.00) end)

    local lbl = Label(btn, "GameFontNormalLarge", label, C.sub[1], C.sub[2], C.sub[3])
    lbl:SetPoint("LEFT", btn, "LEFT", PAD + 10, 0)
    btn._lbl = lbl

    return btn
end

local NavInstructions = MakeNavBtn("Instructions", 0)
local NavFonts        = MakeNavBtn("Font Manager",  NAV_H)
local NavUtilities    = MakeNavBtn("Utilities",     NAV_H * 2)

local Pane = CreateFrame("Frame", nil, Main)
Pane:SetPoint("TOPLEFT",     Main, "TOPLEFT",     SIDEBAR_W + 1, -2)
Pane:SetPoint("BOTTOMRIGHT", Main, "BOTTOMRIGHT", -2,              2)

local HEADER_H = 66
local PaneHdr = CreateFrame("Frame", nil, Pane, "BackdropTemplate")
PaneHdr:SetHeight(HEADER_H)
PaneHdr:SetPoint("TOPLEFT",  Pane, "TOPLEFT",  0, 0)
PaneHdr:SetPoint("TOPRIGHT", Pane, "TOPRIGHT", 0, 0)
Backdrop(PaneHdr, {0.07, 0.05, 0.03, 1}, C.border)

local paneTitle = Label(PaneHdr, "GameFontNormalHuge", "", C.header[1], C.header[2], C.header[3])
paneTitle:SetPoint("BOTTOMLEFT", PaneHdr, "BOTTOMLEFT", PAD, 22)

local paneSub = Label(PaneHdr, "GameFontNormal", "", C.sub[1], C.sub[2], C.sub[3])
paneSub:SetPoint("BOTTOMLEFT", PaneHdr, "BOTTOMLEFT", PAD, 7)

local hdrDiv = Tex(Pane, C.border[1], C.border[2], C.border[3], 1)
hdrDiv:SetHeight(1)
hdrDiv:SetPoint("TOPLEFT",  Pane, "TOPLEFT",  0, -HEADER_H)
hdrDiv:SetPoint("TOPRIGHT", Pane, "TOPRIGHT", 0, -HEADER_H)

local CONTENT_TOP = HEADER_H + 1

local function ContentFrame()
    local f = CreateFrame("Frame", nil, Pane)
    f:SetPoint("TOPLEFT",     Pane, "TOPLEFT",     0, -CONTENT_TOP)
    f:SetPoint("BOTTOMRIGHT", Pane, "BOTTOMRIGHT", 0,  1)
    f:Hide()
    return f
end

local InstrFrame = ContentFrame()
local PANE_TXT_W = FRAME_W - SIDEBAR_W - PAD * 2

local function ILine(text, yOff, font, r, g, b)
    font = font or "GameFontNormal"
    r, g, b = r or C.text[1], g or C.text[2], b or C.text[3]
    local fs = InstrFrame:CreateFontString(nil, "OVERLAY", font)
    fs:SetText(text)
    fs:SetTextColor(r, g, b)
    fs:SetJustifyH("LEFT")
    fs:SetWidth(PANE_TXT_W)
    fs:SetPoint("TOPLEFT", InstrFrame, "TOPLEFT", PAD, -yOff)
    return fs
end

local function ISep(yOff, alpha)
    local t = Tex(InstrFrame, C.border[1], C.border[2], C.border[3], alpha or 0.8)
    t:SetHeight(1)
    t:SetPoint("TOPLEFT",  InstrFrame, "TOPLEFT",  PAD,    -yOff)
    t:SetPoint("TOPRIGHT", InstrFrame, "TOPRIGHT", -PAD,   -yOff)
    return t
end

ILine("How to add a new font", 18, "GameFontNormalLarge", C.accent[1], C.accent[2], C.accent[3])
ISep(46, 0.3)

ILine("Step 1 — Place your font file", 58, "GameFontNormalLarge", C.header[1], C.header[2], C.header[3])
ILine("Copy your .ttf font file(s) into the addon Fonts folder:", 84)
ILine("    World of Warcraft\\_retail_\\Interface\\AddOns\\CustomFontLoader\\Fonts\\", 106,
    "GameFontNormal", C.accent[1], C.accent[2] * 0.85, C.accent[3] * 0.4)
ILine("Avoid spaces in filenames — use hyphens instead (e.g. My-Font-Bold.ttf).", 132)
ISep(156)

ILine("Step 2 — Reload", 172, "GameFontNormalLarge", C.header[1], C.header[2], C.header[3])
ILine("WoW must load the files before they can be registered. Type  /reload  or use the button below:", 198)

local reloadBtn = CreateFrame("Button", nil, InstrFrame, "BackdropTemplate")
reloadBtn:SetSize(110, 26)
reloadBtn:SetPoint("TOPLEFT", InstrFrame, "TOPLEFT", PAD, -216)
Backdrop(reloadBtn, {0.30, 0.10, 0.55, 1.00}, {0.50, 0.20, 0.80, 1.00})
local reloadLbl = reloadBtn:CreateFontString(nil, "OVERLAY", "GameFontNormal")
reloadLbl:SetText("Reload UI")
reloadLbl:SetTextColor(0.90, 0.80, 1.00)
reloadLbl:SetAllPoints(reloadBtn)
reloadLbl:SetJustifyH("CENTER")
reloadBtn:SetScript("OnEnter", function(self) self:SetBackdropColor(C.accent[1], C.accent[2], C.accent[3], 1) end)
reloadBtn:SetScript("OnLeave", function(self) self:SetBackdropColor(0.30, 0.10, 0.55, 1.00) end)
reloadBtn:SetScript("OnClick", function()
    CFLOpenOnLoad = true
    ReloadUI()
end)

ILine("If you use the button above, this addon will reopen automatically after the reload.", 272)
ISep(292)

ILine("Step 3 — Register in the Font Manager", 308, "GameFontNormalLarge", C.header[1], C.header[2], C.header[3])
ILine("Switch to the Font Manager tab, type the filename without the extension, then click Add Font.", 334)
ILine("The font is immediately available in EllesmereUI, Plater, BigWigs, and any other SharedMedia addon.", 360)
ISep(386)

ILine("Notes", 402, "GameFontNormalLarge", C.header[1], C.header[2], C.header[3])
ILine("   -  Fonts are saved automatically and re-registered every time you log in.", 428)
ILine("   -  Only .ttf files are supported — .otf files will not load in WoW.", 452)
ILine("   -  If you delete a font file, the game will not recognize the removal until a full restart.", 476)
ILine("   -  After a full restart, missing fonts are automatically cleaned up on login.", 500)

local FontFrame = ContentFrame()
local PANE_INNER = FRAME_W - SIDEBAR_W - 1
local ROW_H      = 30

local ListHeader = CreateFrame("Frame", nil, FontFrame)
ListHeader:SetHeight(44)
ListHeader:SetPoint("TOPLEFT",  FontFrame, "TOPLEFT",  0, 0)
ListHeader:SetPoint("TOPRIGHT", FontFrame, "TOPRIGHT", 0, 0)

local listLabel = Label(ListHeader, "GameFontNormalLarge", "Registered Fonts", C.text[1], C.text[2], C.text[3])
listLabel:SetPoint("LEFT", ListHeader, "LEFT", PAD, 0)

local countLabel = Label(ListHeader, "GameFontNormalSmall", "", C.sub[1], C.sub[2], C.sub[3])
countLabel:SetPoint("RIGHT", ListHeader, "RIGHT", -PAD, 0)
countLabel:SetJustifyH("RIGHT")

local listDivTop = Tex(FontFrame, C.border[1], C.border[2], C.border[3], 0.8)
listDivTop:SetHeight(1)
listDivTop:SetPoint("TOPLEFT",  FontFrame, "TOPLEFT",  PAD,  -44)
listDivTop:SetPoint("TOPRIGHT", FontFrame, "TOPRIGHT", -PAD, -44)

local ADD_PANEL_H = 170
local AddPanel = CreateFrame("Frame", nil, FontFrame, "BackdropTemplate")
AddPanel:SetHeight(ADD_PANEL_H)
AddPanel:SetPoint("BOTTOMLEFT",  FontFrame, "BOTTOMLEFT",  0, 1)
AddPanel:SetPoint("BOTTOMRIGHT", FontFrame, "BOTTOMRIGHT", 0, 1)
AddPanel:SetBackdrop({bgFile = "Interface\\Buttons\\WHITE8X8"})
AddPanel:SetBackdropColor(0.04, 0.03, 0.07, 1)

local addPanelDiv = Tex(FontFrame, C.accent[1], C.accent[2], C.accent[3], 0.25)
addPanelDiv:SetHeight(1)
addPanelDiv:SetPoint("BOTTOMLEFT",  FontFrame, "BOTTOMLEFT",  PAD,  ADD_PANEL_H)
addPanelDiv:SetPoint("BOTTOMRIGHT", FontFrame, "BOTTOMRIGHT", -PAD, ADD_PANEL_H)

local addTitle = Label(AddPanel, "GameFontNormalLarge", "Add New Font", C.header[1], C.header[2], C.header[3])
addTitle:SetPoint("TOPLEFT", AddPanel, "TOPLEFT", PAD, -14)

local addInstr = Label(AddPanel, "GameFontNormal",
    "Place the .ttf file in the Fonts folder, enter the filename below, then click Add Font.", C.sub[1], C.sub[2], C.sub[3])
addInstr:SetPoint("TOPLEFT", addTitle, "BOTTOMLEFT", 0, -6)
addInstr:SetWidth(PANE_INNER - PAD * 2)

local lblFile = Label(AddPanel, "GameFontNormal", "Font Filename", C.sub[1], C.sub[2], C.sub[3])
lblFile:SetPoint("TOPLEFT", addInstr, "BOTTOMLEFT", 0, -10)

local BTN_W        = 100
local FILE_INPUT_W = PANE_INNER - PAD * 2 - BTN_W - PAD - 8

local function MakeEditBox(parent, width, anchorPt, anchorTo, relPt, ox, oy)
    local eb = CreateFrame("EditBox", nil, parent, "BackdropTemplate")
    eb:SetHeight(32)
    eb:SetWidth(width)
    eb:SetPoint(anchorPt, anchorTo, relPt, ox, oy)
    Backdrop(eb, C.inputBg, C.border)
    eb:SetFontObject("GameFontNormal")
    eb:SetTextColor(C.text[1], C.text[2], C.text[3])
    eb:SetTextInsets(10, 10, 0, 0)
    eb:SetAutoFocus(false)
    eb:SetMaxLetters(256)
    eb:SetScript("OnEditFocusGained", function(self) self:SetBackdropBorderColor(C.accent[1], C.accent[2], C.accent[3], 0.9) end)
    eb:SetScript("OnEditFocusLost", function(self) self:SetBackdropBorderColor(C.border[1], C.border[2], C.border[3], 1) end)
    eb:SetScript("OnEscapePressed", function(self) self:ClearFocus() end)
    eb:SetScript("OnEnterPressed", function(self) self:ClearFocus() end)
    return eb
end

local fileBox = MakeEditBox(AddPanel, FILE_INPUT_W, "TOPLEFT", lblFile, "BOTTOMLEFT", 0, -4)

local AddBtn = CreateFrame("Button", nil, AddPanel, "BackdropTemplate")
AddBtn:SetSize(BTN_W, 32)
AddBtn:SetPoint("BOTTOMLEFT", fileBox, "BOTTOMRIGHT", 8, 0)
Backdrop(AddBtn, {0.30, 0.10, 0.55, 1.00}, {0.50, 0.20, 0.80, 1.00})
local addBtnLbl = AddBtn:CreateFontString(nil, "OVERLAY", "GameFontNormal")
addBtnLbl:SetText("Add Font")
addBtnLbl:SetTextColor(0.90, 0.80, 1.00)
addBtnLbl:SetAllPoints(AddBtn)
addBtnLbl:SetJustifyH("CENTER")
AddBtn:SetScript("OnEnter", function(self) self:SetBackdropColor(C.accent[1], C.accent[2], C.accent[3], 1) end)
AddBtn:SetScript("OnLeave", function(self) self:SetBackdropColor(0.30, 0.10, 0.55, 1.00) end)

local statusFS = Label(AddPanel, "GameFontNormal", "", C.sub[1], C.sub[2], C.sub[3])
statusFS:SetPoint("TOPLEFT", fileBox, "BOTTOMLEFT", 0, -6)
statusFS:SetWidth(PANE_INNER - PAD * 2)

local function SetStatus(msg, ok)
    statusFS:SetText(msg)
    if ok then
        statusFS:SetTextColor(C.accent[1], C.accent[2], C.accent[3])
    else
        statusFS:SetTextColor(1, 0.35, 0.20)
    end
end

local scrollFrame = CreateFrame("ScrollFrame", nil, FontFrame, "UIPanelScrollFrameTemplate")
scrollFrame:SetPoint("TOPLEFT",     FontFrame, "TOPLEFT",     PAD,       -46)
scrollFrame:SetPoint("BOTTOMRIGHT", FontFrame, "BOTTOMRIGHT", -(PAD + 22), ADD_PANEL_H + 2)

local scrollChild = CreateFrame("Frame", nil, scrollFrame)
scrollChild:SetWidth(1)
scrollChild:SetHeight(1)
scrollFrame:SetScrollChild(scrollChild)

local probeString = nil
local function GetProbe()
    if not probeString then
        probeString = UIParent:CreateFontString(nil, "ARTWORK")
        probeString:Hide()
    end
    return probeString
end

local function FontFileExists(path)
    local probe = GetProbe()
    local ok = pcall(function() probe:SetFont(path, 12) end)
    return ok
end

local function SyncFonts()
    local LSM = LibStub and LibStub("LibSharedMedia-3.0", true)
    if not LSM then return 0, 0 end
    if not CFLPendingFonts then CFLPendingFonts = {} end

    local removed = 0
    for i = #CFLPendingFonts, 1, -1 do
        local entry = CFLPendingFonts[i]
        if FontFileExists(entry.path) then
            LSM:Register("font", entry.name, entry.path)
        else
            local hash = LSM:HashTable("font")
            if hash[entry.name] == entry.path then
                hash[entry.name] = nil
            end
            table.remove(CFLPendingFonts, i)
            removed = removed + 1
        end
    end

    local total = 0
    for _ in pairs(LSM:HashTable("font")) do total = total + 1 end
    return total, removed
end

local function PopulateFontList()
    for _, c in ipairs({scrollChild:GetChildren()}) do
        c:Hide(); c:SetParent(nil)
    end
    for _, r in ipairs({scrollChild:GetRegions()}) do r:Hide() end

    local LSM   = LibStub and LibStub("LibSharedMedia-3.0", true)
    local fonts = LSM and LSM:HashTable("font") or {}

    local sorted = {}
    for name in pairs(fonts) do sorted[#sorted + 1] = name end
    table.sort(sorted)

    countLabel:SetText(#sorted .. " font(s) registered")

    local vw = scrollFrame:GetWidth()
    scrollChild:SetWidth(vw)
    scrollChild:SetHeight(math.max(#sorted * ROW_H, 1))

    for i, name in ipairs(sorted) do
        local path    = fonts[name]
        local missing = path and not FontFileExists(path)

        local row = CreateFrame("Frame", nil, scrollChild, "BackdropTemplate")
        row:SetHeight(ROW_H)
        row:SetPoint("TOPLEFT",  scrollChild, "TOPLEFT",  0, -(i - 1) * ROW_H)
        row:SetPoint("TOPRIGHT", scrollChild, "TOPRIGHT", 0, -(i - 1) * ROW_H)
        if i % 2 == 1 then Backdrop(row, C.row1, {0, 0, 0, 0}) end

        local dot = row:CreateTexture(nil, "ARTWORK")
        dot:SetSize(5, 5)
        if missing then dot:SetColorTexture(0.8, 0.2, 0.2, 0.8) else dot:SetColorTexture(C.accent[1], C.accent[2], C.accent[3], 0.8) end
        dot:SetPoint("LEFT", row, "LEFT", 10, 0)

        local nameLbl = row:CreateFontString(nil, "OVERLAY", "GameFontNormal")
        nameLbl:SetText(name)
        if missing then nameLbl:SetTextColor(0.6, 0.2, 0.2) else nameLbl:SetTextColor(C.text[1], C.text[2], C.text[3]) end
        nameLbl:SetPoint("LEFT", dot, "RIGHT", 6, 0)
        nameLbl:SetJustifyH("LEFT")

        if missing then
            local missingLbl = row:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
            missingLbl:SetText("file not found")
            missingLbl:SetTextColor(0.6, 0.2, 0.2)
            missingLbl:SetPoint("RIGHT", row, "RIGHT", -8, 0)
            missingLbl:SetJustifyH("RIGHT")
        elseif path then
            local prev = row:CreateFontString(nil, "OVERLAY")
            local ok = pcall(function() prev:SetFont(path, 12) end)
            if ok then
                prev:SetText("Aa Bb Cc 123")
                prev:SetTextColor(C.sub[1], C.sub[2], C.sub[3])
                prev:SetPoint("RIGHT", row, "RIGHT", -8, 0)
                prev:SetJustifyH("RIGHT")
            else
                prev:Hide()
            end
        end
    end

    if #sorted == 0 then
        local empty = scrollChild:CreateFontString(nil, "OVERLAY", "GameFontNormal")
        empty:SetText("No fonts registered yet.  Add one below.")
        empty:SetTextColor(C.sub[1], C.sub[2], C.sub[3])
        empty:SetPoint("TOPLEFT", scrollChild, "TOPLEFT", 8, -10)
    end
end

AddBtn:SetScript("OnClick", function()
    local LSM  = LibStub and LibStub("LibSharedMedia-3.0", true)
    local file = fileBox:GetText():match("^%s*(.-)%s*$")

    if not LSM then SetStatus("LibSharedMedia-3.0 not found.", false); return end
    if file == "" then SetStatus("Enter a font filename.", false); return end

    local ext = file:match("%.([^%.]+)$")
    if ext and ext:lower() ~= "ttf" then
        SetStatus('".' .. ext .. '" is not a valid format for WoW. Only .ttf files are supported.', false)
        return
    end

    file = file:gsub("%.ttf$", ""):gsub("%.TTF$", "")
    local fileName   = file .. ".ttf"
    local displayName = file  
    local fullPath   = "Interface\\AddOns\\CustomFontLoader\\Fonts\\" .. fileName

    if not FontFileExists(fullPath) then
        SetStatus('"' .. fileName .. '" not found in the Fonts folder. Check the filename and try again.', false)
        return
    end

    if not CFLPendingFonts then CFLPendingFonts = {} end

    local exists = false
    for _, e in ipairs(CFLPendingFonts) do
        if e.name == displayName then e.path = fullPath; exists = true; break end
    end
    if not exists then table.insert(CFLPendingFonts, {name = displayName, path = fullPath}) end

    LSM:Register("font", displayName, fullPath)

    local total, removed = SyncFonts()
    fileBox:SetText("")
    PopulateFontList()

    local msg = '"' .. displayName .. '" added.'
    if removed > 0 then msg = msg .. "  Removed " .. removed .. " missing font(s)." end
    SetStatus(msg, true)
    print("|cFFAA44FFCustomFontLoader:|r Registered font: " .. displayName)
end)

local UtilFrame  

local function SelectTab(navBtn, contentFrame, title, sub)
    InstrFrame:Hide()
    FontFrame:Hide()
    if UtilFrame then UtilFrame:Hide() end

    for _, btn in ipairs({NavInstructions, NavFonts, NavUtilities}) do
        btn._selBG:SetColorTexture(C.accent[1], C.accent[2], C.accent[3], 0.00)
        btn._selBar:SetColorTexture(C.accent[1], C.accent[2], C.accent[3], 0.00)
        btn._lbl:SetTextColor(C.sub[1], C.sub[2], C.sub[3])
    end

    navBtn._selBG:SetColorTexture(C.accent[1], C.accent[2], C.accent[3], 0.12)
    navBtn._selBar:SetColorTexture(C.accent[1], C.accent[2], C.accent[3], 1.00)
    navBtn._lbl:SetTextColor(C.accent[1], C.accent[2], C.accent[3])
    activePane = navBtn

    paneTitle:SetText(title)
    paneSub:SetText(sub)
    contentFrame:Show()
end

NavInstructions:SetScript("OnClick", function() SelectTab(NavInstructions, InstrFrame, "Instructions", "How to add fonts to CustomFontLoader") end)
NavFonts:SetScript("OnClick", function() SelectTab(NavFonts, FontFrame, "Font Manager", "Fonts registered with LibSharedMedia-3.0"); PopulateFontList() end)
NavUtilities:SetScript("OnClick", function() SelectTab(NavUtilities, UtilFrame, "Utilities", "HUD display options") end)
Main:SetScript("OnShow", function() SelectTab(NavInstructions, InstrFrame, "Instructions", "How to add fonts to CustomFontLoader") end)

local loader = CreateFrame("Frame")
loader:RegisterEvent("ADDON_LOADED")
loader:RegisterEvent("PLAYER_LOGIN")
loader:SetScript("OnEvent", function(self, event, addon)
    if event == "ADDON_LOADED" then
        if addon ~= addonName then return end
        local total, removed = SyncFonts()
        if removed > 0 then print("|cFFAA44FFCustomFontLoader:|r Removed " .. removed .. " font(s) with missing files.  " .. total .. " font(s) active.") end

        if not CFLUtilSettings then CFLUtilSettings = {} end
        local D = CFLUtilSettings
        if D.ibShow      == nil then D.ibShow      = false end
        if D.ibGold      == nil then D.ibGold      = true  end
        if D.ibFriends   == nil then D.ibFriends   = true  end
        if D.ibClock     == nil then D.ibClock     = true  end
        if D.ibFPS       == nil then D.ibFPS       = true  end
        if D.ibMS        == nil then D.ibMS        = true  end
        if D.ibDur       == nil then D.ibDur       = true  end
        if D.ibParty     == nil then D.ibParty     = true  end
        if D.ibScreenshot== nil then D.ibScreenshot= true  end
        if D.ibHeroDC    == nil then D.ibHeroDC    = true  end
        if D.ibMythDC    == nil then D.ibMythDC    = true  end
        if D.ibVoidcore  == nil then D.ibVoidcore  = true  end
        if D.ib24h       == nil then D.ib24h       = false end  -- false = 12h
        if D.ibLocalTime == nil then D.ibLocalTime = true  end  -- true = local time
        if D.hudLocked   == nil then D.hudLocked   = false end
        if D.hudX        == nil then D.hudX        = 0     end
        if D.hudY        == nil then D.hudY        = 30    end
        if D.hudScale    == nil then D.hudScale    = 1.0   end

        local defText  = {font="Expressway", size=14, r=0.82, g=0.78, b=0.90, outline="OUTLINE"}
        local defAccnt = {font="Expressway", size=25, r=0.65, g=0.30, b=1.00, outline="OUTLINE"}
        if not D.ibGoldCfg      then D.ibGoldCfg      = {font=defText.font, size=14, r=1.00, g=0.82, b=0.00, outline="OUTLINE"} end
        if not D.ibFriendsCfg   then D.ibFriendsCfg   = {font=defText.font, size=12, r=defText.r, g=defText.g, b=defText.b, outline=""} end
        if not D.ibPartyCfg     then D.ibPartyCfg     = {font=defText.font, size=12, r=defText.r, g=defText.g, b=defText.b, outline=""} end
        if not D.ibClockCfg     then D.ibClockCfg     = {font=defAccnt.font, size=25, r=defAccnt.r, g=defAccnt.g, b=defAccnt.b, outline="OUTLINE"} end
        if not D.ibFPScfg       then D.ibFPScfg       = {font=defText.font, size=14, r=defText.r, g=defText.g, b=defText.b, outline="OUTLINE"} end
        if not D.ibHeroDCcfg    then D.ibHeroDCcfg    = {font=defText.font, size=14, r=defText.r, g=defText.g, b=defText.b, outline="OUTLINE"} end
        if not D.ibMythDCcfg    then D.ibMythDCcfg    = {font=defText.font, size=14, r=defText.r, g=defText.g, b=defText.b, outline="OUTLINE"} end
        if not D.ibVoidcoreCfg  then D.ibVoidcoreCfg  = {font=defText.font, size=14, r=defText.r, g=defText.g, b=defText.b, outline="OUTLINE"} end

        CFL_RefreshUtilUI()

        if CFLOpenOnLoad then
            CFLOpenOnLoad = nil
            C_Timer.After(0.5, function() Main:Show() end)
        end

    elseif event == "PLAYER_LOGIN" then
        if CFLUtilSettings then CFL_ApplyHUDState() end
    end
end)

-- ============================================================
--  UTILITIES — HUD
-- ============================================================

local function ResolveFont(fontName)
    local LSM = LibStub and LibStub("LibSharedMedia-3.0", true)
    if LSM then
        local path = LSM:Fetch("font", fontName or "")
        if path then return path end
    end
    return fontName or ""
end

local OUTLINE_OPTIONS = {
    { value = "",                           label = "None" },
    { value = "OUTLINE",                    label = "Outline" },
    { value = "THICKOUTLINE",               label = "Thick Outline" },
    { value = "MONOCHROME",                 label = "Monochrome" },
    { value = "OUTLINE, MONOCHROME",       label = "Outline + Monochrome" },
    { value = "THICKOUTLINE, MONOCHROME",  label = "Thick Outline + Monochrome" },
}

local function OutlineLabelFor(value)
    for _, opt in ipairs(OUTLINE_OPTIONS) do
        if opt.value == (value or "") then return opt.label end
    end
    return "None"
end

local RefreshClkBtns

-- Currency IDs — Midnight Season 1
local CURRENCY_HERO_DAWNCREST = 3345   -- Hero Dawncrest
local CURRENCY_MYTH_DAWNCREST = 3347   -- Myth Dawncrest
local CURRENCY_VOIDCORE       = 3418   -- Nebulous Voidcore

local ICON_FRIENDS    = "Interface\\Icons\\achievement_guildperk_everybodysfriend"
local ICON_PARTY      = "Interface\\Icons\\ui_greenflag"
local ICON_SCREENSHOT = "Interface\\Icons\\Inv_misc_selfiecamera_red_camera"
local HUD = CreateFrame("Button", "CFLHudFrame", UIParent)
HUD:SetSize(300, 24)
HUD:SetPoint("TOP", UIParent, "TOP", 0, -30)
HUD:SetFrameStrata("MEDIUM")
HUD:SetMovable(true)
HUD:EnableMouse(true)
HUD:SetClampedToScreen(true)
HUD:Hide()

local function HandleDrag(btn)
    btn:RegisterForDrag("LeftButton")
    btn:SetScript("OnDragStart", function()
        if CFLUtilSettings and CFLUtilSettings.hudLocked then return end
        HUD:StartMoving()
    end)
    btn:SetScript("OnDragStop", function()
        HUD:StopMovingOrSizing()
        if CFLUtilSettings then
            local x, y = HUD:GetCenter()
            CFLUtilSettings.hudX = x - UIParent:GetWidth()  / 2
            CFLUtilSettings.hudY = UIParent:GetHeight() - y - HUD:GetHeight() / 2
        end
    end)
end
HandleDrag(HUD)

local ibEls     = {}
local ibTickers = {}

local function TakeCleanScreenshot()
    UIErrorsFrame:Hide()
    Screenshot()
    C_Timer.After(0.5, function() UIErrorsFrame:Show() end)
end

local function ClassColor()
    local _, cls = UnitClass("player")
    if cls and RAID_CLASS_COLORS and RAID_CLASS_COLORS[cls] then
        local c = RAID_CLASS_COLORS[cls]; return c.r, c.g, c.b
    end
    return C.text[1], C.text[2], C.text[3]
end

local function StopIBTickers()
    for _, t in ipairs(ibTickers) do t:Cancel() end
    ibTickers = {}
end

local function GetOrMakeIconBtn(key, iconID, size, template)
    size = size or 20
    if not ibEls[key] then
        local btn = CreateFrame("Button", nil, HUD, template)
        btn:SetSize(size, size)
        local tex = btn:CreateTexture(nil, "ARTWORK")
        tex:SetAllPoints(btn)
        tex:SetTexture(iconID)
        btn._tex = tex
        local hl = btn:CreateTexture(nil, "HIGHLIGHT")
        hl:SetAllPoints(btn)
        hl:SetTexture("Interface\\Buttons\\ButtonHilight-Square")
        hl:SetBlendMode("ADD")
        HandleDrag(btn)
        ibEls[key] = btn
    end
    return ibEls[key]
end

local function GetOrMakeFrame(key)
    if not ibEls[key] then
        local f = CreateFrame("Button", nil, HUD)
        f:EnableMouse(true)
        HandleDrag(f)
        ibEls[key] = f
    end
    return ibEls[key]
end

function CFL_ApplyHUDState()
    local D = CFLUtilSettings
    if not D then return end
    StopIBTickers()
    for _, el in pairs(ibEls) do el:ClearAllPoints(); el:Hide() end

    if not D.ibShow then HUD:Hide(); return end
    HUD:Show()
    HUD:SetScale(D.hudScale or 1.0)
    HUD:ClearAllPoints()
    HUD:SetPoint("TOP", UIParent, "TOP", D.hudX or 0, -(D.hudY or 30))

    local cr, cg, cb = ClassColor()
    local pr, pg, pb = C.accent[1], C.accent[2], C.accent[3]
    local ICON = 20
    local GAP  = 6

    -- Elements are collected into left/right lists first, then positioned in a
    -- second pass so both wings are equidistant from the clock centre.
    -- Left wing: Gold → Friends → Party → Screenshot (closest to clock)
    -- Right wing: Currencies (closest to clock → far right)
    local leftEls  = {}   -- { el, w, h }
    local clockEl  = nil; local clockW = 0; local clockH_val = 0
    local rightEls = {}   -- { el, w, h }

    local function QLft(el, w, h) leftEls[#leftEls+1]  = { el=el, w=w, h=h or ICON } end
    local function QRgt(el, w, h) rightEls[#rightEls+1] = { el=el, w=w, h=h or ICON } end

    -- ── 1. Gold (far left) ───────────────────────────────────────────────────
    if D.ibGold then
        local blk = GetOrMakeFrame("gold")
        if not blk._lbl then
            blk._lbl = blk:CreateFontString(nil, "OVERLAY")
            blk._lbl:SetJustifyH("LEFT")
            blk._lbl:SetPoint("CENTER", blk, "CENTER", 0, 0)
            blk:SetScript("OnEnter", function(s)
                GameTooltip:SetOwner(s, "ANCHOR_NONE")
                GameTooltip:SetPoint("TOPLEFT", s, "BOTTOMLEFT", 0, -24)
                GameTooltip:SetText("Gold", 1, 1, 1)
                GameTooltip:Show()
            end)
            blk:SetScript("OnLeave", function() GameTooltip:Hide() end)
        end
        local gCfg = D.ibGoldCfg or {}
        pcall(function() blk._lbl:SetFont(ResolveFont(gCfg.font), gCfg.size or 14, "OUTLINE") end)
        blk._lbl:SetTextColor(1, 1, 1)  -- colors are embedded in format strings
        local function UpdGold()
            local copper = GetMoney() or 0
            local g = math.floor(copper / 10000)
            local s = math.floor((copper % 10000) / 100)
            local c = copper % 100
            local txt
            if g > 0 then
                txt = string.format("|cFFFFD700%dg|r", g)
                if s > 0 or c > 0 then txt = txt .. string.format(" |cFFC0C0C0%ds|r", s) end
            elseif s > 0 then
                txt = string.format("|cFFC0C0C0%ds|r", s)
                if c > 0 then txt = txt .. string.format(" |cFFCD7F32%dc|r", c) end
            else
                txt = string.format("|cFFCD7F32%dc|r", c)
            end
            blk._lbl:SetText(txt)
        end
        UpdGold()
        ibTickers[#ibTickers+1] = C_Timer.NewTicker(5, UpdGold)
        local w = math.max(blk._lbl:GetStringWidth() + 4, 40)
        blk:SetSize(w, ICON)
        QLft(blk, w, ICON)
    end

    -- ── 2. Friends (left side) ───────────────────────────────────────────────
    if D.ibFriends then
        local btn = GetOrMakeIconBtn("friends", ICON_FRIENDS, ICON)
        if not btn._count then
            btn._count = btn:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
            btn._count:SetPoint("BOTTOMRIGHT", btn, "BOTTOMRIGHT", 6, -3)
        end
        -- Tooltip: show in-game BNet friends with class colors
        btn:SetScript("OnEnter", function(s)
            GameTooltip:SetOwner(s, "ANCHOR_NONE")
            GameTooltip:SetPoint("TOPLEFT", s, "BOTTOMLEFT", 0, -24)
            -- Collect BNet friends actually playing WoW right now
            local ingame = {}
            if BNGetNumFriends then
                local numTotal = select(1, BNGetNumFriends()) or 0
                for i = 1, numTotal do
                    local accountInfo = C_BattleNet.GetFriendAccountInfo(i)
                    if accountInfo and accountInfo.gameAccountInfo then
                        local ga = accountInfo.gameAccountInfo
                        if ga.isOnline and ga.clientProgram == BNET_CLIENT_WOW then
                            local btag = accountInfo.battleTag or accountInfo.accountName or "Unknown"
                            local char = ga.characterName or btag
                            local zone = ga.areaName or ""
                            -- Class color: ga.className is the English class name key
                            local classHex = "FFFFFF"
                            if ga.className and RAID_CLASS_COLORS then
                                -- RAID_CLASS_COLORS is keyed by uppercase English class name
                                local clsUpper = ga.className:upper():gsub(" ", "")
                                -- Try direct lookup first, then scan for match
                                local clr = RAID_CLASS_COLORS[clsUpper]
                                if not clr then
                                    for k, v in pairs(RAID_CLASS_COLORS) do
                                        if k:upper():gsub(" ", "") == clsUpper then clr = v; break end
                                    end
                                end
                                if clr then
                                    classHex = string.format("%02X%02X%02X",
                                        math.floor(clr.r * 255),
                                        math.floor(clr.g * 255),
                                        math.floor(clr.b * 255))
                                end
                            end
                            -- BNet blue: #1D8DBA
                            local charStr = "|cFF" .. classHex .. char .. "|r"
                            local btagStr = "|cFF1D8DBA" .. btag .. "|r"
                            local entry = charStr .. " " .. btagStr
                            if zone ~= "" then
                                entry = entry .. " |cFF888888- " .. zone .. "|r"
                            end
                            ingame[#ingame+1] = entry
                        end
                    end
                end
            end
            local total = btn._count:GetText() or "0"
            GameTooltip:SetText("Friends Online: " .. total, 1, 1, 1)
            if #ingame > 0 then
                GameTooltip:AddLine("In WoW:", 0.65, 0.30, 1.00)
                for _, line in ipairs(ingame) do
                    GameTooltip:AddLine(line, 1, 1, 1, true)
                end
            else
                GameTooltip:AddLine("No friends in WoW right now", 0.6, 0.6, 0.6)
            end
            GameTooltip:Show()
        end)
        btn:SetScript("OnLeave", function() GameTooltip:Hide() end)
        btn:SetScript("OnClick", function()
            ToggleFriendsFrame(1)   -- 1 = Friends tab
        end)

        local fCfg = D.ibFriendsCfg or {}
        pcall(function() btn._count:SetFont(ResolveFont(fCfg.font), fCfg.size or 12, fCfg.outline or "") end)
        btn._count:SetTextColor(fCfg.r or cr, fCfg.g or cg, fCfg.b or cb)
        local function Upd()
            local n = 0
            for i = 1, C_FriendList.GetNumFriends() do
                local info = C_FriendList.GetFriendInfoByIndex(i)
                if info and info.connected then n = n + 1 end
            end
            btn._count:SetText(tostring(n + ((BNGetNumFriends and select(1,BNGetNumFriends())) or 0)))
        end
        Upd(); ibTickers[#ibTickers+1] = C_Timer.NewTicker(10, Upd)
        QLft(btn, ICON, ICON)
    end

    -- ── 3. Party (left side, closest to clock) ───────────────────────────────
    if D.ibParty then
        local btn = GetOrMakeIconBtn("party", ICON_PARTY, ICON)
        if not btn._count then
            btn._count = btn:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
            btn._count:SetPoint("BOTTOMRIGHT", btn, "BOTTOMRIGHT", 6, -3)
            btn:SetScript("OnEnter", function(s)
                local isRaid = IsInRaid()
                local tip = isRaid and "Raid Size" or "Party Size"
                GameTooltip:SetOwner(s, "ANCHOR_NONE")
                GameTooltip:SetPoint("TOPLEFT", s, "BOTTOMLEFT", 0, -24)
                GameTooltip:SetText(tip, 1, 1, 1)
                GameTooltip:Show()
            end)
            btn:SetScript("OnLeave", function() GameTooltip:Hide() end)
            btn:SetScript("OnClick", function()
                -- Open Social panel on the Group/Raid tab (tab 3)
                ToggleFriendsFrame(3)
            end)
        end
        local pCfg = D.ibPartyCfg or {}
        pcall(function() btn._count:SetFont(ResolveFont(pCfg.font), pCfg.size or 12, pCfg.outline or "") end)
        btn._count:SetTextColor(pCfg.r or cr, pCfg.g or cg, pCfg.b or cb)
        local function Upd()
            local n
            if IsInRaid() then
                n = GetNumGroupMembers()          -- full raid size (1-40)
            elseif IsInGroup() then
                n = GetNumGroupMembers()          -- party size includes player (2-5)
            else
                n = 1                             -- solo
            end
            btn._count:SetText(tostring(n))
        end
        Upd(); ibTickers[#ibTickers+1] = C_Timer.NewTicker(3, Upd)
        QLft(btn, ICON, ICON)
    end

    -- ── 4. Screenshot (left side, closest to clock) ───────────────────────────
    if D.ibScreenshot then
        local btn = GetOrMakeIconBtn("screenshot", ICON_SCREENSHOT, ICON)
        if not btn._ready then
            btn:SetScript("OnClick", TakeCleanScreenshot)
            btn:SetScript("OnEnter", function(s) GameTooltip:SetOwner(s,"ANCHOR_NONE"); GameTooltip:SetPoint("TOPLEFT",s,"BOTTOMLEFT",0,-24); GameTooltip:SetText("Screenshot (silent)",1,1,1); GameTooltip:Show() end)
            btn:SetScript("OnLeave", function() GameTooltip:Hide() end)
            btn._ready = true
        end
        QLft(btn, ICON, ICON)
    end

    -- ── 5. Clock / FPS / MS / Dur (centre) ───────────────────────────────────
    if D.ibClock or D.ibFPS or D.ibMS or D.ibDur then
        local blk = GetOrMakeFrame("clockblock")
        if not blk._clk then
            blk._clk   = blk:CreateFontString(nil, "OVERLAY")
            blk._stats = blk:CreateFontString(nil, "OVERLAY")
            blk._clk:SetJustifyH("CENTER")
            blk._clk:SetPoint("TOP",    blk, "TOP",    0, 0)
            blk._stats:SetJustifyH("CENTER")
            blk._stats:SetPoint("BOTTOM", blk, "BOTTOM", 0, 0)
            blk:SetScript("OnClick", function()
                if CFLUtilSettings and CFLUtilSettings.ibClock then
                    CFLUtilSettings.ib24h = not (CFLUtilSettings.ib24h or false)
                    if RefreshClkBtns then RefreshClkBtns() end
                    CFL_ApplyHUDState()
                end
            end)
            blk:SetScript("OnEnter", function()
                if CFLUtilSettings and CFLUtilSettings.ibClock then
                    GameTooltip:SetOwner(blk, "ANCHOR_NONE")
                    GameTooltip:SetPoint("TOPLEFT", blk, "BOTTOMLEFT", 0, -24)
                    GameTooltip:SetText("Click to toggle 12h/24h", 1, 1, 1)
                    GameTooltip:Show()
                end
            end)
            blk:SetScript("OnLeave", function() GameTooltip:Hide() end)
        end

        local clkCfg = D.ibClockCfg or {}
        if D.ibClock then
            pcall(function() blk._clk:SetFont(ResolveFont(clkCfg.font), clkCfg.size or 25, clkCfg.outline or "OUTLINE") end)
            blk._clk:SetTextColor(clkCfg.r or pr, clkCfg.g or pg, clkCfg.b or pb)
            blk._clk:Show()
        else
            blk._clk:SetText(""); blk._clk:Hide()
        end

        local stCfg = D.ibFPScfg or {}
        if D.ibFPS or D.ibMS or D.ibDur then
            pcall(function() blk._stats:SetFont(ResolveFont(stCfg.font), stCfg.size or 14, stCfg.outline or "OUTLINE") end)
            blk._stats:SetTextColor(pr, pg, pb); blk._stats:Show()
        else
            blk._stats:SetText(""); blk._stats:Hide()
        end

        local function UpdClk()
            if not (CFLUtilSettings and CFLUtilSettings.ibClock) then return end
            local h, m
            if (CFLUtilSettings or {}).ibLocalTime then
                local t = date("*t"); h, m = t.hour, t.min
            else
                h, m = GetGameTime()
            end
            if (CFLUtilSettings or {}).ib24h then
                blk._clk:SetText(string.format("%02d:%02d", h, m))
            else
                local sfx = h >= 12 and "PM" or "AM"
                h = h % 12; if h == 0 then h = 12 end
                blk._clk:SetText(string.format("%d:%02d %s", h, m, sfx))
            end
        end
        local function UpdStats()
            local D2 = CFLUtilSettings or {}; local parts = {}
            if D2.ibFPS then parts[#parts+1] = string.format("%d fps", math.floor(GetFramerate())) end
            if D2.ibMS  then local _,_,ms=GetNetStats(); parts[#parts+1]=string.format("%d ms",ms or 0) end
            if D2.ibDur then
                local cur,mx=0,0
                for s=0,19 do local c2,m2=GetInventoryItemDurability(s); if c2 and m2 and m2>0 then cur=cur+c2;mx=mx+m2 end end
                if mx>0 then
                    local pct=math.floor((cur/mx)*100)
                    parts[#parts+1]=pct<=20 and string.format("|cFFFF4444%d%% dur|r",pct) or string.format("%d%% dur",pct)
                else parts[#parts+1]="-- dur" end
            end
            blk._stats:SetText(table.concat(parts,"  "))
        end
        UpdClk(); UpdStats()
        ibTickers[#ibTickers+1] = C_Timer.NewTicker(5, UpdClk)
        ibTickers[#ibTickers+1] = C_Timer.NewTicker(2, UpdStats)

        local clkH = D.ibClock and (clkCfg.size or 25)+2 or 0
        local stH  = (D.ibFPS or D.ibMS or D.ibDur) and (stCfg.size or 14)+2 or 0
        clockW = math.max(
            D.ibClock and blk._clk:GetStringWidth()+4 or 0,
            (D.ibFPS or D.ibMS or D.ibDur) and blk._stats:GetStringWidth()+4 or 0, 50)
        clockH_val = math.max(clkH+stH, ICON)
        blk:SetSize(clockW, clockH_val)
        clockEl = blk
    end


    local CURR_LIST = {
        { key="herodc",   id=CURRENCY_HERO_DAWNCREST, tip="Hero Dawncrest"    },
        { key="mythdc",   id=CURRENCY_MYTH_DAWNCREST,  tip="Myth Dawncrest"    },
        { key="voidcore", id=CURRENCY_VOIDCORE,         tip="Nebulous Voidcore" },
    }
    local showKey = { herodc="ibHeroDC", mythdc="ibMythDC", voidcore="ibVoidcore" }
    local cfgKey  = { herodc="ibHeroDCcfg", mythdc="ibMythDCcfg", voidcore="ibVoidcoreCfg" }

    for _, cur in ipairs(CURR_LIST) do
        if D[showKey[cur.key]] then
            local blk = GetOrMakeFrame("curr_"..cur.key)
            if not blk._icon then
                blk._icon = blk:CreateTexture(nil, "ARTWORK")
                blk._icon:SetSize(ICON, ICON)
                blk._icon:SetPoint("LEFT", blk, "LEFT", 0, 0)
                blk._lbl = blk:CreateFontString(nil, "OVERLAY")
                blk._lbl:SetJustifyH("LEFT")
                blk._lbl:SetPoint("LEFT", blk._icon, "RIGHT", 3, 0)
                blk:SetScript("OnEnter", function(s)
                    GameTooltip:SetOwner(s, "ANCHOR_NONE")
                    GameTooltip:SetPoint("TOPLEFT", s, "BOTTOMLEFT", 0, -24)
                    local info = C_CurrencyInfo.GetCurrencyInfo(cur.id)
                    GameTooltip:SetText(info and info.name or cur.tip, 1, 1, 1)
                    if info and info.maximumQuantity and info.maximumQuantity > 0 then
                        GameTooltip:AddLine(string.format("%d / %d", info.quantity, info.maximumQuantity), 0.8, 0.8, 0.8)
                    end
                    GameTooltip:Show()
                end)
                blk:SetScript("OnLeave", function() GameTooltip:Hide() end)
            end
            local info = C_CurrencyInfo.GetCurrencyInfo(cur.id)
            if info and info.iconFileID then blk._icon:SetTexture(info.iconFileID) end
            local cCfg = D[cfgKey[cur.key]] or {}
            pcall(function() blk._lbl:SetFont(ResolveFont(cCfg.font), cCfg.size or 14, cCfg.outline or "OUTLINE") end)
            blk._lbl:SetTextColor(cCfg.r or C.text[1], cCfg.g or C.text[2], cCfg.b or C.text[3])
            local captID = cur.id; local captBlk = blk
            local function UpdCurr()
                local ci = C_CurrencyInfo.GetCurrencyInfo(captID)
                captBlk._lbl:SetText(tostring(ci and ci.quantity or 0))
            end
            UpdCurr()
            ibTickers[#ibTickers+1] = C_Timer.NewTicker(10, UpdCurr)
            local txtW = blk._lbl:GetStringWidth()
            local blkW = ICON + 3 + math.max(txtW, 14)
            blk:SetSize(blkW, ICON)
            QRgt(blk, blkW, ICON)
        end
    end

    -- ── Final layout pass ─────────────────────────────────────────────────────
    -- Measure widths of each side so we can centre the clock and keep both
    -- flanking groups equidistant from it.
    local function sideWidth(list)
        local w = 0
        for i, e in ipairs(list) do w = w + e.w + (i < #list and GAP or 0) end
        return w
    end

    local leftW  = sideWidth(leftEls)
    local rightW = sideWidth(rightEls)
    -- Same gap on both sides of the clock. No mirroring — each side uses its
    -- own actual width so the right wing starts immediately after the clock gap.
    local SIDE_GAP = GAP * 2
    local totalW = leftW + SIDE_GAP + clockW + SIDE_GAP + rightW
    HUD:SetWidth(math.max(totalW, 80))

    -- Left wing: placed left-to-right starting at x=0
    local xL = 0
    for _, e in ipairs(leftEls) do
        e.el:SetSize(e.w, e.h)
        e.el:ClearAllPoints()
        e.el:SetPoint("LEFT", HUD, "LEFT", xL, 0)
        e.el:Show()
        xL = xL + e.w + GAP
    end

    -- Clock: immediately after left wing + SIDE_GAP
    if clockEl then
        clockEl:ClearAllPoints()
        clockEl:SetPoint("LEFT", HUD, "LEFT", leftW + SIDE_GAP, 0)
        clockEl:SetSize(clockW, clockH_val)
        clockEl:Show()
    end

    -- Right wing: immediately after clock + SIDE_GAP
    local xR = leftW + SIDE_GAP + clockW + SIDE_GAP
    for _, e in ipairs(rightEls) do
        e.el:SetSize(e.w, e.h)
        e.el:ClearAllPoints()
        e.el:SetPoint("LEFT", HUD, "LEFT", xR, 0)
        e.el:Show()
        xR = xR + e.w + GAP
    end
end
-- ============================================================
--  UTILITIES — Settings UI
-- ============================================================
UtilFrame = ContentFrame()
local UP_U         = PAD
local statWidgets = {}   

local function RefreshWidgetDisplay(cfgKey)
    local D = CFLUtilSettings
    if not D then return end
    local w   = statWidgets[cfgKey]
    local cfg = D[cfgKey]
    if not (w and cfg) then return end
    if w.ddBtn  and w.ddBtn._lbl  then w.ddBtn._lbl:SetText(cfg.font or "Expressway") end
    if w.sizeEB then w.sizeEB:SetTextSafe(tostring(cfg.size or 12)) end
    if w.swatch and w.swatch._tex then
        w.swatch._tex:SetColorTexture(cfg.r or C.text[1], cfg.g or C.text[2], cfg.b or C.text[3], 1)
    end
    if w.hexBox then
        w.hexBox:SetText(string.format("%02X%02X%02X",
            math.floor((cfg.r or C.text[1])*255),
            math.floor((cfg.g or C.text[2])*255),
            math.floor((cfg.b or C.text[3])*255)))
    end
    if w.outlineDD and w.outlineDD._lbl then
        w.outlineDD._lbl:SetText(OutlineLabelFor(cfg.outline))
    end
end

local sharedDD = CreateFrame("Frame", "CFLFontDropdown", UIParent, "BackdropTemplate")
sharedDD:SetFrameStrata("TOOLTIP")
sharedDD:SetWidth(200)
sharedDD:Hide()
sharedDD:EnableMouse(true)
Backdrop(sharedDD, C.sidebar, C.border)

local ddCloseBtn = CreateFrame("Button", nil, UIParent)
ddCloseBtn:SetAllPoints(UIParent)
ddCloseBtn:SetFrameStrata("HIGH")
ddCloseBtn:EnableMouse(true)
ddCloseBtn:Hide()
ddCloseBtn:SetScript("OnClick", function() sharedDD:Hide(); ddCloseBtn:Hide() end)

local ddSF = CreateFrame("ScrollFrame", nil, sharedDD, "UIPanelScrollFrameTemplate")
ddSF:SetPoint("TOPLEFT",     sharedDD, "TOPLEFT",     2, -2)
ddSF:SetPoint("BOTTOMRIGHT", sharedDD, "BOTTOMRIGHT", -20, 2)
local ddSC = CreateFrame("Frame", nil, ddSF)
ddSC:SetWidth(1); ddSC:SetHeight(1)
ddSF:SetScrollChild(ddSC)
sharedDD:SetFrameLevel(ddCloseBtn:GetFrameLevel() + 1)

local function MakeUtilEditBox(parent, width, anchorPt, anchorTo, relPt, ox, oy)
    local eb = CreateFrame("EditBox", nil, parent, "BackdropTemplate")
    eb:SetHeight(32)
    eb:SetWidth(width)
    eb:SetPoint(anchorPt, anchorTo, relPt, ox, oy)
    Backdrop(eb, C.inputBg, C.border)
    eb:SetFontObject("GameFontNormal")
    eb:SetTextColor(C.text[1], C.text[2], C.text[3])
    eb:SetTextInsets(10, 10, 0, 0)
    eb:SetAutoFocus(false)
    eb:SetMaxLetters(256)
    eb:SetScript("OnEditFocusGained", function(self) self:SetBackdropBorderColor(C.accent[1], C.accent[2], C.accent[3], 0.9) end)
    eb:SetScript("OnEditFocusLost", function(self) self:SetBackdropBorderColor(C.border[1], C.border[2], C.border[3], 1) end)
    eb:SetScript("OnEscapePressed", function(self) self:ClearFocus() end)
    eb:SetScript("OnEnterPressed",  function(self) self:ClearFocus() end)
    return eb
end

local function MakeFontDropdown(parent, cfgKey, onApply, anchorFrame, ax, ay)
    local btn = CreateFrame("Button", nil, parent, "BackdropTemplate")
    btn:SetSize(160, 24)
    btn:SetPoint("LEFT", anchorFrame, "RIGHT", ax or 8, ay or 0)
    Backdrop(btn, C.inputBg, C.border)
    local btnLbl = btn:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    btnLbl:SetAllPoints(btn); btnLbl:SetJustifyH("CENTER")
    btnLbl:SetTextColor(C.text[1], C.text[2], C.text[3])
    btnLbl:SetText("Expressway")
    btn._lbl = btnLbl
    btn:SetScript("OnClick", function(self)
        if sharedDD:IsShown() and sharedDD._owner == self then
            sharedDD:Hide(); ddCloseBtn:Hide(); return
        end
        local LSM = LibStub and LibStub("LibSharedMedia-3.0", true)
        local fonts = {}; local seen = {}
        for _, n in ipairs({"GameFontNormal","GameFontNormalLarge","GameFontNormalSmall"}) do
            if not seen[n] then seen[n]=true; fonts[#fonts+1]=n end
        end
        if LSM then
            for n in pairs(LSM:HashTable("font")) do
                if not seen[n] then seen[n]=true; fonts[#fonts+1]=n end
            end
        end
        table.sort(fonts)
        for _, c in ipairs({ddSC:GetChildren()}) do c:Hide(); c:SetParent(nil) end
        local ITEM_H = 22
        sharedDD:SetHeight(math.min(#fonts,10)*ITEM_H+4)
        ddSC:SetWidth(sharedDD:GetWidth()-20)
        ddSC:SetHeight(#fonts*ITEM_H)
        local D = CFLUtilSettings
        local currentFont = (D and D[cfgKey] and D[cfgKey].font) or ""
        for i, name in ipairs(fonts) do
            local row = CreateFrame("Button", nil, ddSC)
            row:SetHeight(ITEM_H)
            row:SetPoint("TOPLEFT",  ddSC, "TOPLEFT",  0, -(i-1)*ITEM_H)
            row:SetPoint("TOPRIGHT", ddSC, "TOPRIGHT", 0, -(i-1)*ITEM_H)
            local rl = row:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
            rl:SetText(name); rl:SetPoint("LEFT", row, "LEFT", 6, 0)
            local isC = (name == currentFont)
            rl:SetTextColor(isC and C.accent[1] or C.text[1], isC and C.accent[2] or C.text[2], isC and C.accent[3] or C.text[3])
            row:SetScript("OnEnter", function() rl:SetTextColor(C.accent[1],C.accent[2],C.accent[3]) end)
            row:SetScript("OnLeave", function()
                local d2 = CFLUtilSettings and CFLUtilSettings[cfgKey]
                local c2 = d2 and (name == d2.font)
                rl:SetTextColor(c2 and C.accent[1] or C.text[1], c2 and C.accent[2] or C.text[2], c2 and C.accent[3] or C.text[3])
            end)
            row:SetScript("OnClick", function()
                if CFLUtilSettings and CFLUtilSettings[cfgKey] then CFLUtilSettings[cfgKey].font = name end
                btnLbl:SetText(name)
                if onApply then onApply() end
                sharedDD:Hide(); ddCloseBtn:Hide()
            end)
        end
        sharedDD:ClearAllPoints()
        sharedDD:SetPoint("TOPLEFT", self, "BOTTOMLEFT", 0, -2)
        sharedDD._owner = self; sharedDD:Show(); ddCloseBtn:Show()
        ddSF:SetVerticalScroll(0)
    end)
    return btn
end

local function MakeOutlineDropdown(parent, cfgKey, onApply, anchorFrame, ax, ay)
    local btn = CreateFrame("Button", nil, parent, "BackdropTemplate")
    btn:SetSize(120, 24)
    btn:SetPoint("LEFT", anchorFrame, "RIGHT", ax or 8, ay or 0)
    Backdrop(btn, C.inputBg, C.border)
    local btnLbl = btn:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    btnLbl:SetAllPoints(btn); btnLbl:SetJustifyH("CENTER")
    btnLbl:SetTextColor(C.text[1], C.text[2], C.text[3])
    btnLbl:SetText("None")
    btn._lbl = btnLbl
    btn:SetScript("OnClick", function(self)
        if sharedDD:IsShown() and sharedDD._owner == self then
            sharedDD:Hide(); ddCloseBtn:Hide(); return
        end
        for _, c in ipairs({ddSC:GetChildren()}) do c:Hide(); c:SetParent(nil) end
        local ITEM_H = 22; local count = #OUTLINE_OPTIONS
        sharedDD:SetHeight(math.min(count,10)*ITEM_H+4)
        ddSC:SetWidth(sharedDD:GetWidth()-20); ddSC:SetHeight(count*ITEM_H)
        local D = CFLUtilSettings
        local currentVal = (D and D[cfgKey] and D[cfgKey].outline) or ""
        for i, opt in ipairs(OUTLINE_OPTIONS) do
            local row = CreateFrame("Button", nil, ddSC)
            row:SetHeight(ITEM_H)
            row:SetPoint("TOPLEFT",  ddSC, "TOPLEFT",  0, -(i-1)*ITEM_H)
            row:SetPoint("TOPRIGHT", ddSC, "TOPRIGHT", 0, -(i-1)*ITEM_H)
            local rl = row:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
            rl:SetText(opt.label); rl:SetPoint("LEFT", row, "LEFT", 6, 0)
            local isC = (opt.value == currentVal)
            rl:SetTextColor(isC and C.accent[1] or C.text[1], isC and C.accent[2] or C.text[2], isC and C.accent[3] or C.text[3])
            row:SetScript("OnEnter", function() rl:SetTextColor(C.accent[1],C.accent[2],C.accent[3]) end)
            row:SetScript("OnLeave", function()
                local d2 = CFLUtilSettings and CFLUtilSettings[cfgKey]
                local c2 = d2 and (opt.value == (d2.outline or ""))
                rl:SetTextColor(c2 and C.accent[1] or C.text[1], c2 and C.accent[2] or C.text[2], c2 and C.accent[3] or C.text[3])
            end)
            row:SetScript("OnClick", function()
                if CFLUtilSettings and CFLUtilSettings[cfgKey] then CFLUtilSettings[cfgKey].outline = opt.value end
                btnLbl:SetText(opt.label)
                if onApply then onApply() end
                sharedDD:Hide(); ddCloseBtn:Hide()
            end)
        end
        sharedDD:ClearAllPoints()
        sharedDD:SetPoint("TOPLEFT", self, "BOTTOMLEFT", 0, -2)
        sharedDD._owner = self; sharedDD:Show(); ddCloseBtn:Show()
        ddSF:SetVerticalScroll(0)
    end)
    return btn
end

local function MakeSizeSpinner(parent, cfgKey, onApply, anchorFrame, ax, ay)
    local eb = MakeUtilEditBox(parent, 44, "LEFT", anchorFrame, "RIGHT", ax or 8, ay or 0)
    eb:SetText("12"); eb:SetMaxLetters(3)
    local _sup = false
    local function Apply()
        if _sup then return end
        local v = tonumber(eb:GetText())
        local cfg = CFLUtilSettings and CFLUtilSettings[cfgKey]
        if v and v >= 6 and v <= 48 then
            if cfg then cfg.size = v end
            if onApply then onApply() end
        else
            _sup = true; eb:SetText(tostring(cfg and cfg.size or 12)); _sup = false
        end
    end
    eb.SetTextSafe = function(self, val) _sup=true; self:SetText(val); _sup=false end
    eb:SetScript("OnEnterPressed", function(self) self:ClearFocus(); Apply() end)
    eb:SetScript("OnEditFocusLost", Apply)
    return eb
end

local function MakeColorPicker(parent, cfgKey, onApply, anchorFrame, ax, ay)
    local swatch = CreateFrame("Button", nil, parent, "BackdropTemplate")
    swatch:SetSize(24, 24)
    swatch:SetPoint("LEFT", anchorFrame, "RIGHT", ax or 8, ay or 0)
    Backdrop(swatch, {0,0,0,0}, C.border)
    local swTex = swatch:CreateTexture(nil, "ARTWORK")
    swTex:SetPoint("TOPLEFT", swatch, "TOPLEFT", 2, -2)
    swTex:SetPoint("BOTTOMRIGHT", swatch, "BOTTOMRIGHT", -2, 2)
    swTex:SetColorTexture(C.text[1], C.text[2], C.text[3], 1)
    swatch._tex = swTex
    swatch:SetScript("OnEnter", function(self) self:SetBackdropBorderColor(C.accent[1],C.accent[2],C.accent[3],1) end)
    swatch:SetScript("OnLeave", function(self) self:SetBackdropBorderColor(C.border[1],C.border[2],C.border[3],1) end)
    local hexBox = MakeUtilEditBox(parent, 72, "LEFT", swatch, "RIGHT", 6, 0)
    hexBox:SetText("D1C7E6"); hexBox:SetMaxLetters(6)
    
    local function ApplyHex()
        local hex = hexBox:GetText():match("^%s*(.-)%s*$"):upper()
        if #hex == 6 then
            local r=tonumber(hex:sub(1,2),16); local g=tonumber(hex:sub(3,4),16); local b=tonumber(hex:sub(5,6),16)
            if r and g and b then
                local D = CFLUtilSettings
                if D and D[cfgKey] then D[cfgKey].r=r/255; D[cfgKey].g=g/255; D[cfgKey].b=b/255 end
                swTex:SetColorTexture(r/255,g/255,b/255,1)
                if onApply then onApply() end
            end
        end
    end
    hexBox:SetScript("OnEnterPressed", ApplyHex)
    hexBox:SetScript("OnEditFocusLost", ApplyHex)
    
    swatch:SetScript("OnClick", function()
        local D = CFLUtilSettings; local cfg = D and D[cfgKey]
        local r0 = cfg and cfg.r or C.text[1]
        local g0 = cfg and cfg.g or C.text[2]
        local b0 = cfg and cfg.b or C.text[3]
        
        ColorPickerFrame:SetupColorPickerAndShow({
            r = r0, g = g0, b = b0,
            hasOpacity = false,
            swatchFunc = function()
                local r, g, b = ColorPickerFrame:GetColorRGB()
                if r then
                    if CFLUtilSettings and CFLUtilSettings[cfgKey] then
                        CFLUtilSettings[cfgKey].r, CFLUtilSettings[cfgKey].g, CFLUtilSettings[cfgKey].b = r, g, b
                    end
                    swTex:SetColorTexture(r, g, b, 1)
                    hexBox:SetText(string.format("%02X%02X%02X", math.floor(r*255), math.floor(g*255), math.floor(b*255)))
                    if onApply then onApply() end
                end
            end,
            cancelFunc = function()
                if CFLUtilSettings and CFLUtilSettings[cfgKey] then
                    CFLUtilSettings[cfgKey].r, CFLUtilSettings[cfgKey].g, CFLUtilSettings[cfgKey].b = r0, g0, b0
                end
                swTex:SetColorTexture(r0, g0, b0, 1)
                hexBox:SetText(string.format("%02X%02X%02X", math.floor(r0*255), math.floor(g0*255), math.floor(b0*255)))
                if onApply then onApply() end
            end
        })
    end)
    return swatch, hexBox
end

local function MakeCheckbox(parent, label, yOff, onToggle)
    local cb = CreateFrame("CheckButton", nil, parent, "UICheckButtonTemplate")
    cb:SetSize(24, 24)
    cb:SetPoint("TOPLEFT", parent, "TOPLEFT", UP_U, -yOff)
    cb:SetScript("OnClick", function(self)
        self._checked = self:GetChecked()
        if onToggle then onToggle(self._checked) end
    end)
    local lbl = parent:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    lbl:SetText(label)
    lbl:SetTextColor(C.text[1], C.text[2], C.text[3])
    lbl:SetPoint("LEFT", cb, "RIGHT", 4, 0)
    cb.SetCheckedState = function(self, v) self._checked=v; self:SetChecked(v) end
    return cb, lbl
end

-- Stripped HUDTextBlock for gold/currency: font + size only, no color/outline pickers.
local function HUDTextBlockFontOnly(scrollChild, yBase, label, cfgKey, showKey)
    local function onApply()
        if not CFLUtilSettings then return end
        CFL_ApplyHUDState()
        RefreshWidgetDisplay(cfgKey)
    end

    local cb = CreateFrame("CheckButton", nil, scrollChild, "UICheckButtonTemplate")
    cb:SetSize(24, 24)
    cb:SetPoint("TOPLEFT", scrollChild, "TOPLEFT", UP_U, -yBase)
    cb:SetScript("OnClick", function(self)
        if CFLUtilSettings then CFLUtilSettings[showKey] = self:GetChecked() end
        CFL_ApplyHUDState()
    end)
    cb.SetCheckedState = function(self, v) self._checked=v; self:SetChecked(v) end
    local cbLbl = scrollChild:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    cbLbl:SetText(label); cbLbl:SetTextColor(C.text[1],C.text[2],C.text[3])
    cbLbl:SetPoint("LEFT", cb, "RIGHT", 4, 0)
    cb:SetCheckedState(false)

    local fontLbl = scrollChild:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    fontLbl:SetText("Font:"); fontLbl:SetTextColor(C.sub[1],C.sub[2],C.sub[3])
    fontLbl:SetPoint("TOPLEFT", scrollChild, "TOPLEFT", UP_U + 30, -(yBase + 28))

    local ddBtn = MakeFontDropdown(scrollChild, cfgKey, onApply, fontLbl, 6, 0)

    local sizeLbl = scrollChild:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    sizeLbl:SetText("Size:"); sizeLbl:SetTextColor(C.sub[1],C.sub[2],C.sub[3])
    sizeLbl:SetPoint("LEFT", ddBtn, "RIGHT", 14, 0)

    local sizeEB = MakeSizeSpinner(scrollChild, cfgKey, onApply, sizeLbl, 6, 0)

    statWidgets[cfgKey] = {
        cb=cb, cbLbl=cbLbl, fontLbl=fontLbl, ddBtn=ddBtn,
        sizeLbl=sizeLbl, sizeEB=sizeEB, showKey=showKey,
    }
    return yBase + 62
end

local function HUDTextBlock(scrollChild, yBase, label, cfgKey, showKey, defaultCfg)
    local function onApply()
        if not CFLUtilSettings then return end
        CFL_ApplyHUDState()
        RefreshWidgetDisplay(cfgKey)
    end

    local cb = CreateFrame("CheckButton", nil, scrollChild, "UICheckButtonTemplate")
    cb:SetSize(24, 24)
    cb:SetPoint("TOPLEFT", scrollChild, "TOPLEFT", UP_U, -yBase)
    cb:SetScript("OnClick", function(self)
        if CFLUtilSettings then CFLUtilSettings[showKey] = self:GetChecked() end
        CFL_ApplyHUDState()
    end)
    cb.SetCheckedState = function(self, v) self._checked=v; self:SetChecked(v) end
    local cbLbl = scrollChild:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    cbLbl:SetText(label); cbLbl:SetTextColor(C.text[1],C.text[2],C.text[3])
    cbLbl:SetPoint("LEFT", cb, "RIGHT", 4, 0)
    cb:SetCheckedState(false)

    local fontLbl = scrollChild:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    fontLbl:SetText("Font:"); fontLbl:SetTextColor(C.sub[1],C.sub[2],C.sub[3])
    fontLbl:SetPoint("TOPLEFT", scrollChild, "TOPLEFT", UP_U + 30, -(yBase + 28))

    local ddBtn = MakeFontDropdown(scrollChild, cfgKey, onApply, fontLbl, 6, 0)
    local sizeLbl = scrollChild:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    sizeLbl:SetText("Size:"); sizeLbl:SetTextColor(C.sub[1],C.sub[2],C.sub[3])
    sizeLbl:SetPoint("LEFT", ddBtn, "RIGHT", 14, 0)

    local sizeEB = MakeSizeSpinner(scrollChild, cfgKey, onApply, sizeLbl, 6, 0)
    local colorLbl = scrollChild:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    colorLbl:SetText("Color:"); colorLbl:SetTextColor(C.sub[1],C.sub[2],C.sub[3])
    colorLbl:SetPoint("LEFT", sizeEB, "RIGHT", 14, 0)

    local swatch, hexBox = MakeColorPicker(scrollChild, cfgKey, onApply, colorLbl, 6, 0)
    local outlineDD = MakeOutlineDropdown(scrollChild, cfgKey, onApply, hexBox, 10, 0)

    statWidgets[cfgKey] = {
        cb=cb, cbLbl=cbLbl, fontLbl=fontLbl, ddBtn=ddBtn,
        sizeLbl=sizeLbl, sizeEB=sizeEB, colorLbl=colorLbl,
        swatch=swatch, hexBox=hexBox, showKey=showKey, outlineDD=outlineDD,
    }
    return yBase + 62
end

-- Forward declarations for scale slider/editbox (created later in the UI build)
local scaleSlider, scaleEB
local _scaleSup = false

function CFL_RefreshUtilUI()
    local D = CFLUtilSettings
    if not D then return end

    for cfgKey, w in pairs(statWidgets) do
        if cfgKey:sub(1,1) ~= "_" and w then
            RefreshWidgetDisplay(cfgKey)
            if w.cb then w.cb:SetCheckedState(D[w.showKey] or false) end
        end
    end

    local ibKeyMap = {
        _ibShowCB  = "ibShow",     _ibGold    = "ibGold",
        _ibFriends = "ibFriends",  _ibClock   = "ibClock",
        _ibFPS     = "ibFPS",      _ibMS      = "ibMS",
        _ibDur     = "ibDur",      _ibParty   = "ibParty",
        _ibSS      = "ibScreenshot", _ibHeroDC = "ibHeroDC",
        _ibMythDC  = "ibMythDC",   _ibVoidcore = "ibVoidcore",
        _ibLockCB  = "hudLocked",
    }
    for wKey, dKey in pairs(ibKeyMap) do
        local w = statWidgets[wKey]
        if w and w.cb then w.cb:SetChecked(D[dKey] or false) end
    end

    if RefreshClkBtns then RefreshClkBtns() end
    if scaleSlider then
        local v = D.hudScale or 1.0
        _scaleSup = true
        scaleSlider:SetValue(v)
        if scaleEB then scaleEB:SetText(string.format("%.2f", v)) end
        _scaleSup = false
    end
end

local utilScroll = CreateFrame("ScrollFrame", nil, UtilFrame, "UIPanelScrollFrameTemplate")
utilScroll:SetPoint("TOPLEFT",     UtilFrame, "TOPLEFT",     0, 0)
utilScroll:SetPoint("BOTTOMRIGHT", UtilFrame, "BOTTOMRIGHT", -22, 0)

local utilSC = CreateFrame("Frame", nil, utilScroll)
utilSC:SetWidth(FRAME_W - SIDEBAR_W - 44)
utilSC:SetHeight(1)  
utilScroll:SetScrollChild(utilSC)

local function SLabel(text, yOff, font, r, g, b)
    local fs = utilSC:CreateFontString(nil, "OVERLAY", font or "GameFontNormal")
    fs:SetText(text)
    fs:SetTextColor(r or C.text[1], g or C.text[2], b or C.text[3])
    fs:SetJustifyH("LEFT")
    fs:SetPoint("TOPLEFT", utilSC, "TOPLEFT", UP_U, -yOff)
    return fs
end

local function SSep(yOff, alpha)
    local t = Tex(utilSC, C.border[1], C.border[2], C.border[3], alpha or 0.8)
    t:SetHeight(1)
    t:SetPoint("TOPLEFT",  utilSC, "TOPLEFT",  UP_U,  -yOff)
    t:SetPoint("TOPRIGHT", utilSC, "TOPRIGHT", -UP_U-20, -yOff)
    return t
end

SLabel("HUD", 16, "GameFontNormalLarge", C.accent[1], C.accent[2], C.accent[3])
SSep(40, 0.3)
SLabel("A compact top-of-screen bar with live stats, clock, and quick-action buttons.", 50, "GameFontNormal", C.sub[1], C.sub[2], C.sub[3])
SSep(72, 0.3)

local hudShowCB = MakeCheckbox(utilSC, "Show HUD", 0, function(checked)
    if CFLUtilSettings then CFLUtilSettings.ibShow = checked end
    CFL_ApplyHUDState()
end)
hudShowCB:ClearAllPoints()
hudShowCB:SetPoint("TOPLEFT", utilSC, "TOPLEFT", UP_U, -84)
statWidgets["_ibShowCB"] = { cb = hudShowCB, showKey = "ibShow" }

local TOGGLE_Y  = 116
local TOGGLE_Y2 = TOGGLE_Y + 28   -- second row of checkboxes

local function IBToggleSC(label, key, xOff, row)
    local yOff = (row == 2) and TOGGLE_Y2 or TOGGLE_Y
    local cb = CreateFrame("CheckButton", nil, utilSC, "UICheckButtonTemplate")
    cb:SetSize(20, 20)
    cb:SetPoint("TOPLEFT", utilSC, "TOPLEFT", UP_U + xOff, -yOff)
    cb:SetScript("OnClick", function(self)
        if CFLUtilSettings then CFLUtilSettings[key] = self:GetChecked() end
        CFL_ApplyHUDState()
    end)
    cb.SetCheckedState = function(self, v) self:SetChecked(v) end
    local lbl = utilSC:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    lbl:SetText(label); lbl:SetTextColor(C.text[1],C.text[2],C.text[3])
    lbl:SetPoint("LEFT", cb, "RIGHT", 2, 0)
    return cb
end

local togLbl = utilSC:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
togLbl:SetText("Show:"); togLbl:SetTextColor(C.sub[1],C.sub[2],C.sub[3])
togLbl:SetPoint("TOPLEFT", utilSC, "TOPLEFT", UP_U, -(TOGGLE_Y + 2))

-- Row 1: Currency, Friends, Clock, FPS, MS, Durability
local ibCB_gold    = IBToggleSC("Currency",    "ibGold",        48, 1)
local ibCB_friends = IBToggleSC("Friends",     "ibFriends",    130, 1)
local ibCB_clock   = IBToggleSC("Clock",       "ibClock",      210, 1)
local ibCB_fps     = IBToggleSC("FPS",         "ibFPS",        278, 1)
local ibCB_ms      = IBToggleSC("MS",          "ibMS",         322, 1)
local ibCB_dur     = IBToggleSC("Durability",  "ibDur",        366, 1)
-- Row 2: Party, Screenshot, Hero DC, Myth DC, Voidcore
local ibCB_party   = IBToggleSC("Party",       "ibParty",       48, 2)
local ibCB_ss      = IBToggleSC("Screenshot",  "ibScreenshot", 118, 2)
local ibCB_herodc  = IBToggleSC("Hero DC",     "ibHeroDC",     222, 2)
local ibCB_mythdc  = IBToggleSC("Myth DC",     "ibMythDC",     302, 2)
local ibCB_voidcore= IBToggleSC("Voidcore",    "ibVoidcore",   382, 2)

statWidgets["_ibGold"]     = { cb = ibCB_gold,     showKey = "ibGold"      }
statWidgets["_ibFriends"]  = { cb = ibCB_friends,  showKey = "ibFriends"   }
statWidgets["_ibClock"]    = { cb = ibCB_clock,     showKey = "ibClock"    }
statWidgets["_ibFPS"]      = { cb = ibCB_fps,       showKey = "ibFPS"      }
statWidgets["_ibMS"]       = { cb = ibCB_ms,        showKey = "ibMS"       }
statWidgets["_ibDur"]      = { cb = ibCB_dur,       showKey = "ibDur"      }
statWidgets["_ibParty"]    = { cb = ibCB_party,     showKey = "ibParty"    }
statWidgets["_ibSS"]       = { cb = ibCB_ss,        showKey = "ibScreenshot"}
statWidgets["_ibHeroDC"]   = { cb = ibCB_herodc,    showKey = "ibHeroDC"   }
statWidgets["_ibMythDC"]   = { cb = ibCB_mythdc,    showKey = "ibMythDC"   }
statWidgets["_ibVoidcore"] = { cb = ibCB_voidcore,  showKey = "ibVoidcore" }

SSep(170, 0.3)
SLabel("Text Style", 182, "GameFontNormalLarge", C.accent[1], C.accent[2], C.accent[3])
SLabel("Customize font, size, and color for each HUD element.", 206, "GameFontNormal", C.sub[1], C.sub[2], C.sub[3])
SSep(228, 0.3)

local nextY = 240
nextY = HUDTextBlockFontOnly(utilSC, nextY, "Currency",     "ibGoldCfg",    "ibGold")
SSep(nextY - 8, 0.2)
nextY = HUDTextBlock(utilSC, nextY, "Friends count",  "ibFriendsCfg", "ibFriends",  {r=C.text[1],g=C.text[2],b=C.text[3],size=12,outline=""})
SSep(nextY - 8, 0.2)
nextY = HUDTextBlock(utilSC, nextY, "Clock",          "ibClockCfg",   "ibClock",    {r=C.accent[1],g=C.accent[2],b=C.accent[3],size=25,outline="OUTLINE"})
SSep(nextY - 8, 0.2)
nextY = HUDTextBlock(utilSC, nextY, "Stats (FPS / MS / Dur)", "ibFPScfg", "ibFPS",      {r=C.text[1],g=C.text[2],b=C.text[3],size=14,outline="OUTLINE"})
SSep(nextY - 8, 0.2)
nextY = HUDTextBlock(utilSC, nextY, "Party count",    "ibPartyCfg",   "ibParty",    {r=C.text[1],g=C.text[2],b=C.text[3],size=12,outline=""})
SSep(nextY - 8, 0.2)
nextY = HUDTextBlock(utilSC, nextY, "Hero Dawncrest", "ibHeroDCcfg",  "ibHeroDC",   {r=C.text[1],g=C.text[2],b=C.text[3],size=14,outline="OUTLINE"})
SSep(nextY - 8, 0.2)
nextY = HUDTextBlock(utilSC, nextY, "Myth Dawncrest", "ibMythDCcfg",  "ibMythDC",   {r=C.text[1],g=C.text[2],b=C.text[3],size=14,outline="OUTLINE"})
SSep(nextY - 8, 0.2)
nextY = HUDTextBlock(utilSC, nextY, "Nebulous Voidcore","ibVoidcoreCfg","ibVoidcore",{r=C.text[1],g=C.text[2],b=C.text[3],size=14,outline="OUTLINE"})

SSep(nextY + 4, 0.3)
local CLK_Y = nextY + 16
SLabel("Clock Format", CLK_Y, "GameFontNormalLarge", C.accent[1], C.accent[2], C.accent[3])
SSep(CLK_Y + 24, 0.3)

local clkFmtLbl = utilSC:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
clkFmtLbl:SetText("Format:"); clkFmtLbl:SetTextColor(C.sub[1],C.sub[2],C.sub[3])
clkFmtLbl:SetPoint("TOPLEFT", utilSC, "TOPLEFT", UP_U, -(CLK_Y + 36))

local clkBtn12 = CreateFrame("Button", nil, utilSC, "BackdropTemplate")
clkBtn12:SetSize(44, 20); clkBtn12:SetPoint("LEFT", clkFmtLbl, "RIGHT", 8, 0)
Backdrop(clkBtn12, C.inputBg, C.border)
local clkLbl12 = clkBtn12:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
clkLbl12:SetText("12h"); clkLbl12:SetAllPoints(clkBtn12); clkLbl12:SetJustifyH("CENTER"); clkLbl12:SetTextColor(C.text[1],C.text[2],C.text[3])

local clkBtn24 = CreateFrame("Button", nil, utilSC, "BackdropTemplate")
clkBtn24:SetSize(44, 20); clkBtn24:SetPoint("LEFT", clkBtn12, "RIGHT", 4, 0)
Backdrop(clkBtn24, C.inputBg, C.border)
local clkLbl24 = clkBtn24:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
clkLbl24:SetText("24h"); clkLbl24:SetAllPoints(clkBtn24); clkLbl24:SetJustifyH("CENTER"); clkLbl24:SetTextColor(C.text[1],C.text[2],C.text[3])

-- Source row: Server / Local  (must be created before RefreshClkBtns closure so they're valid upvalues)
local clkSrcLbl = utilSC:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
clkSrcLbl:SetText("Source:"); clkSrcLbl:SetTextColor(C.sub[1],C.sub[2],C.sub[3])
clkSrcLbl:SetPoint("LEFT", clkFmtLbl, "RIGHT", 130, 0)

local clkBtnServer = CreateFrame("Button", nil, utilSC, "BackdropTemplate")
clkBtnServer:SetSize(60, 20); clkBtnServer:SetPoint("LEFT", clkSrcLbl, "RIGHT", 8, 0)
Backdrop(clkBtnServer, C.inputBg, C.border)
local clkLblServer = clkBtnServer:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
clkLblServer:SetText("Server"); clkLblServer:SetAllPoints(clkBtnServer); clkLblServer:SetJustifyH("CENTER"); clkLblServer:SetTextColor(C.text[1],C.text[2],C.text[3])

local clkBtnLocal = CreateFrame("Button", nil, utilSC, "BackdropTemplate")
clkBtnLocal:SetSize(50, 20); clkBtnLocal:SetPoint("LEFT", clkBtnServer, "RIGHT", 4, 0)
Backdrop(clkBtnLocal, C.inputBg, C.border)
local clkLblLocal = clkBtnLocal:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
clkLblLocal:SetText("Local"); clkLblLocal:SetAllPoints(clkBtnLocal); clkLblLocal:SetJustifyH("CENTER"); clkLblLocal:SetTextColor(C.text[1],C.text[2],C.text[3])

RefreshClkBtns = function()
    local use24    = CFLUtilSettings and CFLUtilSettings.ib24h
    local useLocal = CFLUtilSettings and CFLUtilSettings.ibLocalTime
    if use24 then
        clkBtn24:SetBackdropColor(C.accent[1]*0.5,C.accent[2]*0.5,C.accent[3]*0.5,1); clkLbl24:SetTextColor(C.accent[1],C.accent[2],C.accent[3])
        clkBtn12:SetBackdropColor(C.inputBg[1],C.inputBg[2],C.inputBg[3],1); clkLbl12:SetTextColor(C.text[1],C.text[2],C.text[3])
    else
        clkBtn12:SetBackdropColor(C.accent[1]*0.5,C.accent[2]*0.5,C.accent[3]*0.5,1); clkLbl12:SetTextColor(C.accent[1],C.accent[2],C.accent[3])
        clkBtn24:SetBackdropColor(C.inputBg[1],C.inputBg[2],C.inputBg[3],1); clkLbl24:SetTextColor(C.text[1],C.text[2],C.text[3])
    end
    if useLocal then
        clkBtnLocal:SetBackdropColor(C.accent[1]*0.5,C.accent[2]*0.5,C.accent[3]*0.5,1); clkLblLocal:SetTextColor(C.accent[1],C.accent[2],C.accent[3])
        clkBtnServer:SetBackdropColor(C.inputBg[1],C.inputBg[2],C.inputBg[3],1); clkLblServer:SetTextColor(C.text[1],C.text[2],C.text[3])
    else
        clkBtnServer:SetBackdropColor(C.accent[1]*0.5,C.accent[2]*0.5,C.accent[3]*0.5,1); clkLblServer:SetTextColor(C.accent[1],C.accent[2],C.accent[3])
        clkBtnLocal:SetBackdropColor(C.inputBg[1],C.inputBg[2],C.inputBg[3],1); clkLblLocal:SetTextColor(C.text[1],C.text[2],C.text[3])
    end
end

clkBtn12:SetScript("OnClick", function() if CFLUtilSettings then CFLUtilSettings.ib24h = false end; RefreshClkBtns(); CFL_ApplyHUDState() end)
clkBtn24:SetScript("OnClick", function() if CFLUtilSettings then CFLUtilSettings.ib24h = true end; RefreshClkBtns(); CFL_ApplyHUDState() end)
clkBtnServer:SetScript("OnClick", function() if CFLUtilSettings then CFLUtilSettings.ibLocalTime = false end; RefreshClkBtns(); CFL_ApplyHUDState() end)
clkBtnLocal:SetScript("OnClick", function() if CFLUtilSettings then CFLUtilSettings.ibLocalTime = true end; RefreshClkBtns(); CFL_ApplyHUDState() end)


local POS_Y = CLK_Y + 80
SSep(POS_Y - 12, 0.3)
SLabel("Position & Layout", POS_Y, "GameFontNormalLarge", C.accent[1], C.accent[2], C.accent[3])
SSep(POS_Y + 24, 0.3)

local hudResetBtn = CreateFrame("Button", nil, utilSC, "BackdropTemplate")
hudResetBtn:SetSize(160, 26)
hudResetBtn:SetPoint("TOPLEFT", utilSC, "TOPLEFT", UP_U, -(POS_Y + 36))
Backdrop(hudResetBtn, {0.10,0.06,0.18,1}, C.border)
local hudResetLbl = hudResetBtn:CreateFontString(nil, "OVERLAY", "GameFontNormal")
hudResetLbl:SetText("Reset HUD Position"); hudResetLbl:SetTextColor(C.accent[1],C.accent[2],C.accent[3])
hudResetLbl:SetAllPoints(hudResetBtn); hudResetLbl:SetJustifyH("CENTER")
hudResetBtn:SetScript("OnEnter", function(self) self:SetBackdropColor(0.20,0.10,0.32,1) end)
hudResetBtn:SetScript("OnLeave", function(self) self:SetBackdropColor(0.10,0.06,0.18,1) end)
hudResetBtn:SetScript("OnClick", function()
    if CFLUtilSettings then CFLUtilSettings.hudX=0; CFLUtilSettings.hudY=30; CFL_ApplyHUDState() end
end)

local hudLockCB = MakeCheckbox(utilSC, "Lock Position", 0, function(checked)
    if CFLUtilSettings then CFLUtilSettings.hudLocked = checked end
end)
hudLockCB:ClearAllPoints()
hudLockCB:SetPoint("LEFT", hudResetBtn, "RIGHT", 16, 0)
statWidgets["_ibLockCB"] = { cb = hudLockCB, showKey = "hudLocked" }

scaleSlider = CreateFrame("Slider", nil, utilSC, "OptionsSliderTemplate")
scaleSlider:SetPoint("LEFT", hudLockCB, "RIGHT", 110, 0)
scaleSlider:SetMinMaxValues(0.5, 2.0)
scaleSlider:SetValueStep(0.05)
scaleSlider:SetObeyStepOnDrag(true)
scaleSlider:SetValue(CFLUtilSettings and CFLUtilSettings.hudScale or 1.0)
scaleSlider.Text:SetText("HUD Scale")
scaleSlider.Low:SetText("0.5")
scaleSlider.High:SetText("2.0")

scaleEB = MakeUtilEditBox(utilSC, 52, "LEFT", scaleSlider, "RIGHT", 10, 0)
scaleEB:SetMaxLetters(4)
scaleEB:SetText(string.format("%.2f", CFLUtilSettings and CFLUtilSettings.hudScale or 1.0))

-- _scaleSup declared above CFL_RefreshUtilUI

scaleSlider:SetScript("OnValueChanged", function(self, value)
    if CFLUtilSettings then CFLUtilSettings.hudScale = value end
    if HUD then HUD:SetScale(value) end
    if not _scaleSup then
        _scaleSup = true
        scaleEB:SetText(string.format("%.2f", value))
        _scaleSup = false
    end
end)

local function ApplyScaleEB()
    if _scaleSup then return end
    local v = tonumber(scaleEB:GetText())
    if v then
        v = math.max(0.5, math.min(2.0, v))
        -- round to nearest 0.05 step
        v = math.floor(v / 0.05 + 0.5) * 0.05
        _scaleSup = true
        scaleSlider:SetValue(v)
        scaleEB:SetText(string.format("%.2f", v))
        _scaleSup = false
        if CFLUtilSettings then CFLUtilSettings.hudScale = v end
        if HUD then HUD:SetScale(v) end
    else
        -- restore last valid value
        local cur = CFLUtilSettings and CFLUtilSettings.hudScale or 1.0
        scaleEB:SetText(string.format("%.2f", cur))
    end
end

scaleEB:SetScript("OnEnterPressed", function(self) self:ClearFocus(); ApplyScaleEB() end)
scaleEB:SetScript("OnEditFocusLost", ApplyScaleEB)

local TOTAL_CONTENT_H = POS_Y + 80
utilSC:SetHeight(TOTAL_CONTENT_H)

-- ============================================================
--  Slash commands & Modern Interface Category Options
-- ============================================================
SLASH_CFL1 = "/cfl"
SlashCmdList["CFL"] = function()
    if Main:IsShown() then Main:Hide() else Main:Show() end
end

local OptionsFrame = CreateFrame("Frame")
OptionsFrame.name = "Custom Font Loader"

local category = Settings.RegisterCanvasLayoutCategory(OptionsFrame, OptionsFrame.name)
Settings.RegisterAddOnCategory(category)

-- ============================================================
--  Minimap Button
-- ============================================================
local MinimapBtn = CreateFrame("Button", "CFLMinimapButton", Minimap)
MinimapBtn:SetSize(32, 32)
MinimapBtn:SetFrameStrata("MEDIUM")
MinimapBtn:SetFrameLevel(8)
MinimapBtn:SetClampedToScreen(true)
MinimapBtn:SetMovable(true)
MinimapBtn:RegisterForClicks("anyUp")
MinimapBtn:RegisterForDrag("LeftButton")

local bg = MinimapBtn:CreateTexture(nil, "BACKGROUND")
bg:SetSize(25, 25)
bg:SetPoint("CENTER", 0, 0)
bg:SetTexture("Interface\\Minimap\\UI-Minimap-Background")
bg:SetVertexColor(0, 0, 0, 1)

local icon = MinimapBtn:CreateTexture(nil, "ARTWORK")
icon:SetSize(18, 18)
icon:SetPoint("CENTER", 0, 0)
icon:SetTexture("Interface\\AddOns\\CustomFontLoader\\cfl.png")

local overlay = MinimapBtn:CreateTexture(nil, "OVERLAY")
overlay:SetSize(53, 53)
overlay:SetPoint("TOPLEFT", MinimapBtn, "TOPLEFT", 0, 0)
overlay:SetTexture("Interface\\Minimap\\MiniMap-TrackingBorder")

MinimapBtn:SetHighlightTexture("Interface\\Minimap\\UI-Minimap-ZoomButton-Highlight")

local isDragging = false
MinimapBtn:SetScript("OnEnter", function(self)
    if isDragging then return end
    GameTooltip:SetOwner(self, "ANCHOR_NONE")
    GameTooltip:SetPoint("TOPRIGHT", self, "TOPLEFT", -2, 0)
    GameTooltip:SetText("Custom Font Loader", 0.65, 0.30, 1.00)
    GameTooltip:AddLine("Left-click: Toggle UI", 0.82, 0.78, 0.90)
    GameTooltip:AddLine("Drag: Reposition", 0.82, 0.78, 0.90)
    GameTooltip:Show()
end)
MinimapBtn:SetScript("OnLeave", function() GameTooltip:Hide() end)
MinimapBtn:SetScript("OnClick", function()
    if Main:IsShown() then Main:Hide() else Main:Show() end
end)

local function UpdateMinimapPos(angle)
    local mw, mh   = Minimap:GetWidth(), Minimap:GetHeight()
    local radius   = (math.max(mw, mh) / 2) + 5
    local rad      = math.rad(angle)
    MinimapBtn:ClearAllPoints()
    MinimapBtn:SetPoint("CENTER", Minimap, "CENTER", math.cos(rad) * radius, math.sin(rad) * radius)
end

local function DragOnUpdate()
    local mx, my   = Minimap:GetCenter()
    local cx, cy   = GetCursorPosition()
    local scale    = Minimap:GetEffectiveScale()
    cx, cy         = cx / scale, cy / scale
    CFLMinimapAngle = math.deg(math.atan2(cy - my, cx - mx))
    UpdateMinimapPos(CFLMinimapAngle)
end

MinimapBtn:SetScript("OnDragStart", function(self)
    isDragging = true
    self:LockHighlight()
    self:SetScript("OnUpdate", DragOnUpdate)
    GameTooltip:Hide()
end)

MinimapBtn:SetScript("OnDragStop", function(self)
    self:SetScript("OnUpdate", nil)
    self:UnlockHighlight()
    isDragging = false
    UpdateMinimapPos(CFLMinimapAngle)
end)

local initLoader = CreateFrame("Frame")
initLoader:RegisterEvent("ADDON_LOADED")
initLoader:SetScript("OnEvent", function(self, event, addon)
    if addon ~= addonName then return end
    if not CFLMinimapAngle then CFLMinimapAngle = 220 end
    UpdateMinimapPos(CFLMinimapAngle)
end)