-- =============================================================
--  rCastbar_GUI.lua
--  Author  : Imithat (WoWInterface.com) - selias2k (curseforge.com)
--  WoW     : Retail 12.0  (Interface 120001)
-- =============================================================

local L = rCastbar_L or {}  -- locale table (loaded before this file)

-- Fallback #ae4500; live: rCastbarGuiTheme.ApplyPalette (class color)
local C = {
    primary    = {0.682, 0.271, 0.000},
    primaryHi  = {0.85,  0.42,  0.08},
    primaryDim = {0.30,  0.10,  0.00},
    bg         = {0.050, 0.035, 0.025, 0.98},
    sidebar    = {0.040, 0.028, 0.018, 1.0},
    content    = {0.060, 0.042, 0.030, 0.97},
    panel      = {0.090, 0.065, 0.045, 0.95},
    border     = {0.25,  0.14,  0.06,  1.0},
    text       = {0.95,  0.90,  0.85},
    textDim    = {0.50,  0.50,  0.50},
    textBright = {1.00,  1.00,  1.00},
    separator  = {0.25,  0.14,  0.06,  0.55},
    toggleOff  = {0.08, 0.06, 0.04},
    botFill    = {0.038, 0.038, 0.050, 1},
    botEdge    = {0.08, 0.10, 0.18, 1},
}

local FRAME_W_DEFAULT = 780
local FRAME_H_DEFAULT = math.floor(580 * 1.2 + 0.5) -- +20% height
local FRAME_W, FRAME_H = FRAME_W_DEFAULT, FRAME_H_DEFAULT
local TITLE_H    = 50
local SIDEBAR_W  = 155
local BOT_H      = 40
local PAD        = 14
local function CW() return FRAME_W - SIDEBAR_W - 10 end
local FONT       = "Fonts\\FRIZQT__.TTF"
local MEDIA      = "Interface\\AddOns\\rCastbar\\media\\"
local LOGO_RATIO = 1024 / 1536
local allToggleControls = {}
local allSliderControls = {}
local allColorPickerRows = {}

local DEFAULT_COLORS = {
    player = {
        color        = { r = 0,   g = 0.5, b = 1,   a = 0.8 },
        bgColor      = { r = 0.1, g = 0.1, b = 0.1, a = 0.7 },
        empowerTickColor = { r = 1, g = 1, b = 1, a = 1.0 },
        failColor    = { r = 1,   g = 0,   b = 0,   a = 1.0 },
        successColor = { r = 0,   g = 0.8, b = 0,   a = 1.0 },
    },
    target = {
        color         = { r = 0,   g = 0.5, b = 1,   a = 0.7 },
        bgColor       = { r = 0.1, g = 0.1, b = 0.1, a = 1.0 },
        empowerTickColor = { r = 1, g = 1, b = 1, a = 1.0 },
        shieldColor   = { r = 0.5, g = 0.5, b = 0.5, a = 1.0 },
        shieldBgColor = { r = 0.1, g = 0.1, b = 0.1, a = 0.7 },
        failColor     = { r = 1,   g = 0,   b = 0,   a = 1.0 },
        successColor  = { r = 0,   g = 0.8, b = 0,   a = 1.0 },
    },
}

local function copyColor(src)
    return { r = src.r, g = src.g, b = src.b, a = src.a }
end

-- Low-level helpers

local function Tex(p, l) return p:CreateTexture(nil, l or "ARTWORK") end
local function FS(p, t)  return p:CreateFontString(nil, "OVERLAY", t or "GameFontNormal") end

local function AddOnVersionFromToc(folder)
    local gm = C_AddOns and C_AddOns.GetAddOnMetadata or GetAddOnMetadata
    if type(gm) ~= "function" then return nil end
    local v = gm(folder, "Version")
    if type(v) == "string" and strtrim(v) ~= "" then return strtrim(v) end
    return nil
end

local function AttachGuiTitleVersion(parent, anchorTitle, folder, dr, dg, db)
    local ver = AddOnVersionFromToc(folder)
    if not ver then return end
    local fs = FS(parent, "GameFontDisableSmall")
    fs:SetPoint("LEFT", anchorTitle, "RIGHT", 8, -4)
    fs:SetTextColor(dr or 0.55, dg or 0.55, db or 0.58, 0.9)
    fs:SetText("v" .. ver)
end

local function FlatFrame(parent, r, g, b, a, br, bg_, bb, ba)
    local f = CreateFrame("Frame", nil, parent, "BackdropTemplate")
    f:SetBackdrop({bgFile="Interface\\Buttons\\WHITE8X8",edgeFile="Interface\\Buttons\\WHITE8X8",
        tile=false,edgeSize=1,insets={left=0,right=0,top=0,bottom=0}})
    f:SetBackdropColor(r or 0,g or 0,b or 0,a or 1)
    f:SetBackdropBorderColor(br or r or 0,bg_ or g or 0,bb or b or 0,ba or a or 1)
    return f
end

local function MakeBorder(frame, r, g, b)
    local function Ln(a1,a2,horiz)
        local t=frame:CreateTexture(nil,"OVERLAY"); t:SetPoint(a1); t:SetPoint(a2)
        if horiz then t:SetHeight(1) else t:SetWidth(1) end
        t:SetColorTexture(r,g,b,1)
    end
    Ln("TOPLEFT","TOPRIGHT",true); Ln("BOTTOMLEFT","BOTTOMRIGHT",true)
    Ln("TOPLEFT","BOTTOMLEFT",false); Ln("TOPRIGHT","BOTTOMRIGHT",false)
end

local _fadeTicker
local function StopFade() if _fadeTicker then _fadeTicker:Cancel(); _fadeTicker=nil end end
local function GUIFadeIn(f)
    StopFade(); f:SetAlpha(0); f:Show(); local t=0
    _fadeTicker=C_Timer.NewTicker(0.016,function()
        t=t+0.016; f:SetAlpha(math.min(t/0.18,1)); if t>=0.18 then StopFade() end
    end)
end
local function GUIFadeOut(f)
    StopFade(); local t=0; local s=f:GetAlpha()
    _fadeTicker=C_Timer.NewTicker(0.016,function()
        t=t+0.016; local p=math.min(t/0.14,1); f:SetAlpha(s*(1-p))
        if p>=1 then StopFade(); f:Hide(); f:SetAlpha(1) end
    end)
end

local function SectionHeader(parent, text, anchor, x, y)
    local lbl=FS(parent,"GameFontNormal")
    lbl:SetPoint("TOPLEFT",anchor or parent,"TOPLEFT",x or 0,y or 0)
    lbl:SetTextColor(C.primary[1],C.primary[2],C.primary[3],1); lbl:SetText(text)
    local line=Tex(parent,"BACKGROUND"); line:SetHeight(1)
    line:SetPoint("TOPLEFT",lbl,"BOTTOMLEFT",0,-3)
    line:SetPoint("TOPRIGHT",parent,"TOPRIGHT",-4,y or 0)
    line:SetGradient("HORIZONTAL",CreateColor(C.primary[1],C.primary[2],C.primary[3],0.7),
        CreateColor(C.primary[1]*0.22,C.primary[2]*0.22,C.primary[3]*0.22,0))
    return lbl
end

local BTN_PAD_X=18
local function StyledButton(parent,text,fixedW,h,onClick)
    local btn=CreateFrame("Button",nil,parent,"BackdropTemplate")
    btn:SetBackdrop({bgFile="Interface\\Buttons\\WHITE8X8",edgeFile="Interface\\Buttons\\WHITE8X8",
        tile=false,edgeSize=1,insets={left=0,right=0,top=0,bottom=0}})
    btn:SetBackdropColor(
        math.min(1, C.primary[1]*0.26+0.05), math.min(1, C.primary[2]*0.26+0.05), math.min(1, C.primary[3]*0.26+0.05), 1)
    btn:SetBackdropBorderColor(
        math.min(1, C.primary[1]*0.52+0.10), math.min(1, C.primary[2]*0.52+0.10), math.min(1, C.primary[3]*0.52+0.10), 1)
    local lbl=FS(btn); lbl:SetPoint("CENTER")
    lbl:SetTextColor(C.primary[1],C.primary[2],C.primary[3],1); lbl:SetText(text)
    local btnH=h or 26
    local function Sz()
        local w=fixedW; if not w then local sw=lbl:GetStringWidth(); w=(sw>0)and(sw+BTN_PAD_X)or 100 end
        btn:SetSize(w,btnH)
    end
    Sz(); btn:SetScript("OnShow",function(s) s:SetScript("OnShow",nil); Sz() end)
    btn:SetScript("OnEnter",function(s)
        s:SetBackdropColor(
            math.min(1, C.primary[1]*0.40+0.06), math.min(1, C.primary[2]*0.40+0.06), math.min(1, C.primary[3]*0.40+0.06), 1)
        s:SetBackdropBorderColor(C.primaryHi[1],C.primaryHi[2],C.primaryHi[3],1)
        lbl:SetTextColor(1,1,1,1)
    end)
    btn:SetScript("OnLeave",function(s)
        s:SetBackdropColor(
            math.min(1, C.primary[1]*0.26+0.05), math.min(1, C.primary[2]*0.26+0.05), math.min(1, C.primary[3]*0.26+0.05), 1)
        s:SetBackdropBorderColor(
            math.min(1, C.primary[1]*0.52+0.10), math.min(1, C.primary[2]*0.52+0.10), math.min(1, C.primary[3]*0.52+0.10), 1)
        lbl:SetTextColor(C.primary[1],C.primary[2],C.primary[3],1)
    end)
    btn:SetScript("OnClick",onClick); return btn,lbl
end

local function DangerButton(parent,text,fixedW,h,onClick)
    local btn=CreateFrame("Button",nil,parent,"BackdropTemplate")
    btn:SetBackdrop({bgFile="Interface\\Buttons\\WHITE8X8",edgeFile="Interface\\Buttons\\WHITE8X8",
        tile=false,edgeSize=1,insets={left=0,right=0,top=0,bottom=0}})
    btn:SetBackdropColor(0.55,0.08,0.08,1); btn:SetBackdropBorderColor(0.70,0.15,0.15,1)
    local lbl=FS(btn); lbl:SetPoint("CENTER"); lbl:SetTextColor(1,1,1,1); lbl:SetText(text)
    local btnH=h or 26
    local function Sz()
        local w=fixedW; if not w then local sw=lbl:GetStringWidth(); w=(sw>0)and(sw+BTN_PAD_X)or 100 end
        btn:SetSize(w,btnH)
    end
    Sz(); btn:SetScript("OnShow",function(s) s:SetScript("OnShow",nil); Sz() end)
    btn:SetScript("OnEnter",function(s) s:SetBackdropColor(0.80,0.14,0.14,1); s:SetBackdropBorderColor(0.95,0.25,0.25,1) end)
    btn:SetScript("OnLeave",function(s) s:SetBackdropColor(0.55,0.08,0.08,1); s:SetBackdropBorderColor(0.70,0.15,0.15,1) end)
    btn:SetScript("OnClick",onClick); return btn,lbl
end

local TOGGLE_W=38; local TOGGLE_H=19; local KNOB_SZ=13

local function ModernToggle(parent,label,anchor,xOfs,yOfs,getter,setter,desc)
    local rowH=desc and 52 or 30
    local con=CreateFrame("Frame",nil,parent); con:SetSize(CW()-20,rowH)
    con:SetPoint("TOPLEFT",anchor or parent,"TOPLEFT",xOfs or 0,yOfs or 0)
    local track=CreateFrame("Button",nil,con,"BackdropTemplate")
    track:SetSize(TOGGLE_W,TOGGLE_H)
    track:SetPoint("LEFT",con,"LEFT",0,desc and 8 or 0)
    track:SetBackdrop({bgFile="Interface\\Buttons\\WHITE8X8",edgeFile="Interface\\Buttons\\WHITE8X8",
        tile=false,edgeSize=1,insets={left=0,right=0,top=0,bottom=0}})
    local knob=CreateFrame("Frame",nil,track,"BackdropTemplate")
    knob:SetSize(KNOB_SZ,KNOB_SZ)
    knob:SetBackdrop({bgFile="Interface\\Buttons\\WHITE8X8",edgeFile="Interface\\Buttons\\WHITE8X8",
        tile=false,edgeSize=1,insets={left=0,right=0,top=0,bottom=0}})
    local lbl=FS(con)
    lbl:SetPoint("LEFT",track,"RIGHT",8,desc and 8 or 0)
    lbl:SetPoint("RIGHT",con,"RIGHT",-4,0)
    lbl:SetJustifyH("LEFT"); lbl:SetTextColor(unpack(C.text)); lbl:SetText(label)
    if desc then
        local d=FS(con,"GameFontHighlightSmall")
        d:SetPoint("TOPLEFT",lbl,"BOTTOMLEFT",0,-2); d:SetPoint("RIGHT",con,"RIGHT",-4,0)
        d:SetJustifyH("LEFT"); d:SetTextColor(unpack(C.textDim)); d:SetText(desc)
    end
    local function SetVisual(on)
        if on then
            track:SetBackdropColor(C.primary[1],C.primary[2],C.primary[3],1)
            track:SetBackdropBorderColor(C.primaryHi[1],C.primaryHi[2],C.primaryHi[3],1)
            knob:ClearAllPoints(); knob:SetPoint("RIGHT",track,"RIGHT",-3,0)
            knob:SetBackdropColor(1,1,1,1); knob:SetBackdropBorderColor(C.primaryHi[1],C.primaryHi[2],C.primaryHi[3],1)
            lbl:SetTextColor(unpack(C.textBright))
        else
            track:SetBackdropColor(C.toggleOff[1],C.toggleOff[2],C.toggleOff[3],1)
            track:SetBackdropBorderColor(unpack(C.border))
            knob:ClearAllPoints(); knob:SetPoint("LEFT",track,"LEFT",3,0)
            knob:SetBackdropColor(0.50,0.50,0.55,1); knob:SetBackdropBorderColor(0.32,0.32,0.38,1)
            lbl:SetTextColor(unpack(C.text))
        end
    end
    local function Refresh() SetVisual(getter()==true or getter()==1) end; Refresh()
    track:SetScript("OnEnter",function()
        if getter()==true or getter()==1 then track:SetBackdropBorderColor(C.primaryHi[1],C.primaryHi[2],C.primaryHi[3],1)
        else track:SetBackdropBorderColor(C.primary[1]*0.75,C.primary[2]*0.5,C.primary[3]*0.75,1) end
    end)
    track:SetScript("OnLeave",function()
        if getter()==true or getter()==1 then
            track:SetBackdropBorderColor(C.primaryHi[1],C.primaryHi[2],C.primaryHi[3],1)
        else track:SetBackdropBorderColor(unpack(C.border)) end
    end)
    track:SetScript("OnClick",function()
        local v=not(getter()==true or getter()==1); SetVisual(v); setter(v)
    end)
    con.Refresh=Refresh
    tinsert(allToggleControls, con)
    return con
end

local function ModernSlider(parent,label,desc,minV,maxV,step,anchor,yOfs,getter,setter,fmt)
    local con=CreateFrame("Frame",nil,parent); con:SetSize(CW()-20,60)
    con:SetPoint("TOPLEFT",anchor or parent,"TOPLEFT",0,yOfs or 0)
    local lbl=FS(con); lbl:SetPoint("TOPLEFT",0,0); lbl:SetTextColor(unpack(C.text)); lbl:SetText(label)
    local valLbl=FS(con,"GameFontNormalSmall"); valLbl:SetPoint("TOPRIGHT",0,-1)
    valLbl:SetTextColor(C.primary[1],C.primary[2],C.primary[3],1)
    if desc then
        local d=FS(con,"GameFontHighlightSmall")
        d:SetPoint("TOPLEFT",lbl,"BOTTOMLEFT",0,-1); d:SetTextColor(unpack(C.textDim)); d:SetText(desc)
    end
    local track=Tex(con,"BACKGROUND"); track:SetHeight(3)
    track:SetPoint("BOTTOMLEFT",con,"BOTTOMLEFT",6,14); track:SetPoint("BOTTOMRIGHT",con,"BOTTOMRIGHT",-6,14)
    track:SetColorTexture(
        math.min(1, C.primary[1]*0.14+0.08), math.min(1, C.primary[2]*0.14+0.08), math.min(1, C.primary[3]*0.14+0.08), 1)
    local fill=Tex(con,"ARTWORK"); fill:SetHeight(3); fill:SetPoint("LEFT",track,"LEFT",0,0)
    fill:SetColorTexture(C.primary[1],C.primary[2],C.primary[3],0.85)
    local sl=CreateFrame("Slider",nil,con); sl:SetHeight(18)
    sl:SetPoint("LEFT",track,"LEFT",0,0); sl:SetPoint("RIGHT",track,"RIGHT",0,0)
    sl:SetMinMaxValues(minV,maxV); sl:SetValueStep(step); sl:SetObeyStepOnDrag(true)
    sl:SetValue(getter()); sl:SetOrientation("HORIZONTAL")
    local thumb=Tex(sl,"OVERLAY"); thumb:SetSize(10,14)
    thumb:SetTexture("Interface\\Buttons\\WHITE8X8")
    thumb:SetVertexColor(C.primary[1],C.primary[2],C.primary[3],1)
    sl:SetThumbTexture(thumb)
    local function UpdateFill()
        local p=math.max(0,(sl:GetValue()-minV)/math.max(maxV-minV,0.001))
        local w=track:GetWidth(); if w and w>0 then fill:SetWidth(w*p) end
    end
    local function UpdateVal()
        local v=sl:GetValue(); valLbl:SetText(fmt and fmt(v) or string.format("%.2f",v))
        UpdateFill(); setter(v)
    end
    sl:SetScript("OnValueChanged",UpdateVal); sl:SetScript("OnSizeChanged",UpdateFill)
    sl:SetScript("OnEnter",function() thumb:SetVertexColor(C.primaryHi[1],C.primaryHi[2],C.primaryHi[3],1) end)
    sl:SetScript("OnLeave",function() thumb:SetVertexColor(C.primary[1],C.primary[2],C.primary[3],1) end)
    UpdateVal(); con.slider=sl; con.Refresh=function() sl:SetValue(getter()); UpdateVal() end
    tinsert(allSliderControls, con)
    return con
end

local function ColorPickerRow(parent,label,getter,setter,anchor,yOfs)
    local con=CreateFrame("Frame",nil,parent); con:SetSize(CW()-20,28)
    con:SetPoint("TOPLEFT",anchor or parent,"TOPLEFT",0,yOfs or 0)
    local lbl=FS(con); lbl:SetPoint("LEFT",0,0); lbl:SetTextColor(unpack(C.text)); lbl:SetText(label)
    local box=CreateFrame("Button",nil,con,"BackdropTemplate")
    box:SetSize(48,20); box:SetPoint("RIGHT",0,0)
    box:SetBackdrop({edgeFile="Interface\\Buttons\\WHITE8X8",tile=false,edgeSize=1,
        insets={left=0,right=0,top=0,bottom=0}})
    box:SetBackdropBorderColor(unpack(C.border))
    local bbg=box:CreateTexture(nil,"OVERLAY"); bbg:SetAllPoints()
    local alphaStrip=box:CreateTexture(nil,"OVERLAY"); alphaStrip:SetSize(8,20); alphaStrip:SetPoint("RIGHT",0,0)
    local function UpdatePreview()
        local cc=getter()
        if cc then bbg:SetColorTexture(cc.r,cc.g,cc.b,1); alphaStrip:SetColorTexture(cc.r*0.3,cc.g*0.3,cc.b*0.3,cc.a or 1)
        else bbg:SetColorTexture(0.4,0.4,0.4,1) end
    end
    UpdatePreview()
    box:SetScript("OnEnter",function() box:SetBackdropBorderColor(C.primary[1],C.primary[2],C.primary[3],1) end)
    box:SetScript("OnLeave",function() box:SetBackdropBorderColor(unpack(C.border)) end)
    box:SetScript("OnClick",function()
        local cc=getter(); if not cc then return end
        local function Apply(r,g,b,a) setter({r=r,g=g,b=b,a=a}); UpdatePreview() end
        ColorPickerFrame:SetupColorPickerAndShow({
            r=cc.r,g=cc.g,b=cc.b,hasOpacity=true,opacity=1-(cc.a or 1),
            swatchFunc=function()
                local r,g,b=ColorPickerFrame:GetColorRGB(); local a=1-ColorPickerFrame:GetColorAlpha(); Apply(r,g,b,a)
            end,
            opacityFunc=function()
                local r,g,b=ColorPickerFrame:GetColorRGB(); local a=1-ColorPickerFrame:GetColorAlpha(); Apply(r,g,b,a)
            end,
            cancelFunc=function(prev) Apply(prev.r,prev.g,prev.b,1-(prev.opacity or 0)) end,
        })
    end)
    con.UpdatePreview=UpdatePreview
    tinsert(allColorPickerRows, con)
    return con
end

-- Custom scrollbar (wider thumb) — UIPanelScrollFrameTemplate often ~2–4 px wide in 12.x.
local function MakeScrollContent(parent)
    local TRACK_W = 8
    local container = CreateFrame("Frame", nil, parent)
    container:SetAllPoints(parent)
    local sf = CreateFrame("ScrollFrame", nil, container)
    sf:SetPoint("TOPLEFT", container, "TOPLEFT", 2, -40)
    sf:SetPoint("BOTTOMRIGHT", container, "BOTTOMRIGHT", -(TRACK_W + 6), 4)
    local sc = CreateFrame("Frame", nil, sf)
    sc:SetWidth(math.max(100, CW() - 24))
    sc:SetHeight(2000)
    sf:SetScrollChild(sc)
    local function OnScrollFrameSized()
        local w = sf:GetWidth()
        if w and w > 40 then sc:SetWidth(math.max(100, w - 4)) end
    end
    sf:SetScript("OnSizeChanged", OnScrollFrameSized)

    local track = CreateFrame("Frame", nil, container)
    track:SetWidth(TRACK_W)
    track:SetPoint("TOPRIGHT", container, "TOPRIGHT", -2, -40)
    track:SetPoint("BOTTOMRIGHT", container, "BOTTOMRIGHT", -2, 4)
    local trackTex = track:CreateTexture(nil, "BACKGROUND")
    trackTex:SetAllPoints(track)
    trackTex:SetColorTexture(
        math.min(1, 0.08 + C.primary[1]*0.05), math.min(1, 0.08 + C.primary[2]*0.05), math.min(1, 0.08 + C.primary[3]*0.05), 1)

    local thumb = CreateFrame("Button", nil, track, "BackdropTemplate")
    thumb:SetWidth(TRACK_W)
    thumb:SetBackdrop({ bgFile = "Interface\\Buttons\\WHITE8X8", edgeFile = "Interface\\Buttons\\WHITE8X8",
        edgeSize = 1, insets = { left = 0, right = 0, top = 0, bottom = 0 } })
    local function ScrollThumbBase()
        local r, g, b = C.primary[1], C.primary[2], C.primary[3]
        return
            math.min(1, r * 0.5 + 0.10),
            math.min(1, g * 0.5 + 0.10),
            math.min(1, b * 0.5 + 0.10)
    end
    local function ScrollThumbBorder()
        local r, g, b = C.primary[1], C.primary[2], C.primary[3]
        return
            math.min(1, r * 0.62 + 0.06),
            math.min(1, g * 0.62 + 0.06),
            math.min(1, b * 0.62 + 0.06)
    end
    do
        local tr, tg, tb = ScrollThumbBase()
        thumb:SetBackdropColor(tr, tg, tb, 1)
        local br, bg, bb = ScrollThumbBorder()
        thumb:SetBackdropBorderColor(br, bg, bb, 1)
    end
    thumb:SetPoint("TOPLEFT", track, "TOPLEFT", 0, 0)

    local function UpdateThumb()
        local trackH = track:GetHeight()
        if not trackH or trackH <= 0 then return end
        local viewH = sf:GetHeight()
        local contentH = sc:GetHeight()
        if contentH <= viewH then
            thumb:Hide()
            return
        end
        thumb:Show()
        local ratio = viewH / contentH
        local thumbH = math.max(22, trackH * ratio)
        thumb:SetHeight(thumbH)
        local maxScroll = contentH - viewH
        local cur = sf:GetVerticalScroll()
        local pct = maxScroll > 0 and (cur / maxScroll) or 0
        local maxY = trackH - thumbH
        thumb:ClearAllPoints()
        thumb:SetPoint("TOPLEFT", track, "TOPLEFT", 0, -(pct * maxY))
    end

    sf:EnableMouseWheel(true)
    sf:SetScript("OnMouseWheel", function(self, d)
        local viewH = self:GetHeight()
        local contentH = sc:GetHeight()
        local maxScroll = math.max(0, contentH - viewH)
        self:SetVerticalScroll(math.max(0, math.min(maxScroll, self:GetVerticalScroll() - d * 30)))
        UpdateThumb()
    end)

    local dragStartY, dragStartScroll
    thumb:RegisterForClicks("LeftButtonUp")
    thumb:EnableMouse(true)
    thumb:SetScript("OnMouseDown", function(self, btn2)
        if btn2 ~= "LeftButton" then return end
        local _, my = GetCursorPosition()
        local eff = UIParent:GetEffectiveScale()
        dragStartY = my / eff
        dragStartScroll = sf:GetVerticalScroll()
        self:SetScript("OnUpdate", function()
            local _, cy = GetCursorPosition()
            cy = cy / eff
            local delta = dragStartY - cy
            local trackH = track:GetHeight()
            local viewH = sf:GetHeight()
            local contentH = sc:GetHeight()
            local maxScroll = math.max(0, contentH - viewH)
            local thumbH = self:GetHeight()
            local maxY = trackH - thumbH
            if maxY <= 0 then return end
            sf:SetVerticalScroll(math.max(0, math.min(maxScroll, dragStartScroll + (delta / maxY) * maxScroll)))
            UpdateThumb()
        end)
    end)
    thumb:SetScript("OnMouseUp", function(self) self:SetScript("OnUpdate", nil) end)
    thumb:SetScript("OnEnter", function(self)
        self:SetBackdropColor(C.primaryHi[1], C.primaryHi[2], C.primaryHi[3], 1)
        self:SetBackdropBorderColor(1, 1, 1, 0.35)
    end)
    thumb:SetScript("OnLeave", function(self)
        local tr, tg, tb = ScrollThumbBase()
        self:SetBackdropColor(tr, tg, tb, 1)
        local br, bg, bb = ScrollThumbBorder()
        self:SetBackdropBorderColor(br, bg, bb, 1)
    end)

    track:EnableMouse(true)
    track:SetScript("OnMouseDown", function(self, btn2)
        if btn2 ~= "LeftButton" then return end
        local _, my = GetCursorPosition()
        my = my / UIParent:GetEffectiveScale()
        local trackH = self:GetHeight()
        local viewH = sf:GetHeight()
        local contentH = sc:GetHeight()
        local maxScroll = math.max(0, contentH - viewH)
        local _, screenCY = self:GetCenter()
        local trackTop = (screenCY or 0) + trackH / 2
        local relY = trackTop - my
        local pct = math.max(0, math.min(1, relY / trackH))
        sf:SetVerticalScroll(pct * maxScroll)
        UpdateThumb()
    end)

    sf:SetScript("OnScrollRangeChanged", function() UpdateThumb() end)
    sc:SetScript("OnSizeChanged", function() UpdateThumb() end)
    sf:SetScript("OnSizeChanged", function() UpdateThumb() end)
    container:HookScript("OnSizeChanged", function() UpdateThumb() end)
    C_Timer.After(0, UpdateThumb)

    return sc, sf
end

local function LiveApplyColor(unit,colorKey)
    local bar=_G["rCastbar_"..(unit:sub(1,1):upper()..unit:sub(2))]
    if not bar then return end
    local cc=rCastbarDB[unit][colorKey]; if not cc then return end
    if colorKey=="color" then bar:SetStatusBarColor(cc.r,cc.g,cc.b,cc.a or 1)
    elseif colorKey=="bgColor" then if bar.bg then bar.bg:SetVertexColor(cc.r,cc.g,cc.b,cc.a or 1) end end
    if colorKey=="empowerTickColor" and bar.empTickPool then
        for _, tick in ipairs(bar.empTickPool) do
            if tick.SetTextColor then
                tick:SetTextColor(cc.r or 0, cc.g or 0, cc.b or 0, cc.a or 1)
            end
        end
    end
end

-- Helper: update the Text RIGHT anchor when showTime visibility changes
local function updateBarTextAnchor(bar, showTime)
    if not bar or not bar.Text then return end
    local rightOfs = showTime and -55 or -5
    bar.Text:ClearAllPoints()
    bar.Text:SetPoint("LEFT",  bar, "LEFT",  5,        0)
    bar.Text:SetPoint("RIGHT", bar, "RIGHT", rightOfs, 0)
end

local function RefreshAllStateControls()
    for _, c in ipairs(allToggleControls) do
        if c and c.Refresh then c:Refresh() end
    end
    for _, c in ipairs(allSliderControls) do
        if c and c.Refresh then c:Refresh() end
    end
end

local activeDropdown=nil
local function CloseDropdown() if activeDropdown then activeDropdown:Hide(); activeDropdown=nil end end

-- ======================================================
-- Bearbeitungsmodus: Raster, Positionsanzeige, Pfeiltasten
-- ======================================================
local editModeGrid, editModeTopBar, editModeKeyButton
local rCastbar_SelectedContainer = nil

local function GetUnitFromContainer(cont)
    if not cont or not cont.GetName then return nil end
    local n = cont:GetName() or ""
    if n:find("Player") then return "player" end
    if n:find("Target") then return "target" end
    return nil
end

local function UpdateAnchorPositionDisplay(cont)
    if not cont or not cont._editFrame then return end
    local ef = cont._editFrame
    if not ef.xText or not ef.yText then return end
    local point, relFrame, relPoint, x, y = cont:GetPoint()
    local xVal = string.format("%.0f", x or 0)
    local yVal = string.format("%.0f", y or 0)
    ef.xText:SetFormattedText("X = %s", xVal)
    ef.yText:SetFormattedText("Y = %s", yVal)
end

function rCastbar_NudgeSelected(dx, dy)
    local cont = rCastbar_SelectedContainer
    if not cont or not cont:IsShown() then return end
    local unit = GetUnitFromContainer(cont)
    if not unit or not rCastbarDB or not rCastbarDB[unit] then return end
    local cfg = rCastbarDB[unit]
    local point, relFrame, relPoint, x, y = cont:GetPoint()
    x, y = (x or 0) + dx, (y or 0) + dy
    if rCastbarDB and rCastbarDB.snapToGrid then
        x = math.floor(x / 10 + 0.5) * 10
        y = math.floor(y / 10 + 0.5) * 10
    end
    cfg.x, cfg.y = math.floor(x + 0.5), math.floor(y + 0.5)
    cont:ClearAllPoints()
    cont:SetPoint(point or cfg.point, relFrame or _G[cfg.relFrame] or UIParent, relPoint or cfg.relPoint, cfg.x, cfg.y)
    UpdateAnchorPositionDisplay(cont)
end

local function CreateEditModeGrid()
    if editModeGrid then return editModeGrid end
    if rCastbarGuiTheme and rCastbarGuiTheme.ApplyPalette then
        rCastbarGuiTheme.ApplyPalette(C)
    end
    local rh = rCastbarGuiTheme and rCastbarGuiTheme.RGBToWoWColorHex
        and rCastbarGuiTheme.RGBToWoWColorHex(C.primary[1], C.primary[2], C.primary[3])
        or "ae4500"
    local grid = CreateFrame("Frame", "rCastbarEditGrid", UIParent)
    grid:SetAllPoints(UIParent); grid:SetFrameStrata("BACKGROUND"); grid:SetFrameLevel(1); grid:Hide()
    local dim = Tex(grid, "BACKGROUND"); dim:SetAllPoints(); dim:SetColorTexture(0,0,0,0.35)
    local cols, rows = 64, 36
    local w = GetScreenWidth() / cols
    local h = GetScreenHeight() / rows
    for i = 0, cols do
        local t = Tex(grid, "ARTWORK")
        if i == cols/2 then
            -- Mittellinie: GUI-Akzentfarbe (primary)
            t:SetColorTexture(C.primary[1], C.primary[2], C.primary[3], 0.75)
        else
            -- Restliche Linien: dezente Akzentfarbe
            t:SetColorTexture(C.primary[1], C.primary[2], C.primary[3], 0.12)
        end
        t:SetPoint("TOPLEFT", grid, "TOPLEFT", i * w - 1, 0)
        t:SetPoint("BOTTOMRIGHT", grid, "BOTTOMLEFT", i * w + 1, 0)
    end
    for i = 0, rows do
        local t = Tex(grid, "ARTWORK")
        if i == rows/2 then
            -- Mittellinie: GUI-Akzentfarbe (primary)
            t:SetColorTexture(C.primary[1], C.primary[2], C.primary[3], 0.75)
        else
            -- Restliche Linien: dezente Akzentfarbe
            t:SetColorTexture(C.primary[1], C.primary[2], C.primary[3], 0.12)
        end
        t:SetPoint("TOPLEFT", grid, "TOPLEFT", 0, -i * h + 1)
        t:SetPoint("BOTTOMRIGHT", grid, "TOPRIGHT", 0, -i * h - 1)
    end
    local wm = FS(grid, "GameFontNormalSmall")
    wm:SetFont(FONT, 11, "OUTLINE"); wm:SetPoint("BOTTOM", UIParent, "BOTTOM", 0, 6)
    wm:SetText("|cff" .. rh .. "r|rCastbar  –  EDIT MODE"); wm:SetTextColor(1,1,1,0.55)
    local topLabel = FS(grid, "GameFontNormalLarge")
    topLabel:SetPoint("TOP", UIParent, "TOP", 0, -40)
    topLabel:SetText("|cff" .. rh .. "r|rCastbar  –  EDIT MODE"); topLabel:SetTextColor(1, 1, 1, 0.9)
    editModeGrid = grid; return grid
end

local function ShowEditGrid() CreateEditModeGrid(); editModeGrid:Show() end
local function HideEditGrid() if editModeGrid then editModeGrid:Hide() end end

local function CreateEditModeTopBar()
    if editModeTopBar then return editModeTopBar end
    local bar = CreateFrame("Frame", "rCastbarEditModeTopBar", UIParent, "BackdropTemplate")
    bar:SetSize(260, 36); bar:SetPoint("TOP", UIParent, "TOP", 0, -80)
    bar:SetBackdrop({bgFile="Interface\\Buttons\\WHITE8X8", edgeFile="Interface\\Buttons\\WHITE8X8",
        tile=false, edgeSize=1, insets={left=0,right=0,top=0,bottom=0}})
    bar:SetBackdropColor(0, 0, 0, 0.7)
    bar:SetBackdropBorderColor(C.primary[1], C.primary[2], C.primary[3], 1)
    local btn = CreateFrame("Button", nil, bar, "UIPanelButtonTemplate")
    btn:SetPoint("CENTER"); btn:SetSize(220, 22); btn:SetText((L["EditMode_Disable"] or ""):gsub("%s*|cff00ff00%[^%]]+%]|r", ""))
    btn:SetScript("OnClick", function()
        if InCombatLockdown() then return end
        local editBtn = _G.rCastbarEditBtn
        if editBtn and editBtn._editOn then
            editBtn._editOn = false
            if editBtn.editBtnLbl then editBtn.editBtnLbl:SetText(L["EditMode_Enable"]) end
            editBtn:SetBackdropColor(
                math.min(1, C.primary[1]*0.26+0.05), math.min(1, C.primary[2]*0.26+0.05), math.min(1, C.primary[3]*0.26+0.05), 1)
            for _, cont in ipairs({_G.rCastbar_Player_Container, _G.rCastbar_Target_Container}) do
                if cont and cont._editFrame then cont._editFrame:Hide() end
            end
            HideEditGrid(); if editModeTopBar then editModeTopBar:Hide() end
            if editModeKeyButton then ClearOverrideBindings(editModeKeyButton) end
            if rCastbar_ShowPreview then rCastbar_ShowPreview(false) end
            -- Open config frame by global name; configFrame may not be captured correctly in this closure.
            local cf = _G["rCastbarConfigFrame"] or configFrame
            if cf then
                if cf.FadeIn then
                    cf:FadeIn()
                else
                    cf:Show()
                end
            end
        end
    end)
    editModeTopBar = bar; return bar
end

-- ======================================================
-- MAIN FRAME
-- ======================================================
local configFrame; local activeTabIdx=0

local function BuildConfigFrame()
    if configFrame then return configFrame end
    if rCastbarGuiTheme and rCastbarGuiTheme.ApplyPalette then
        rCastbarGuiTheme.ApplyPalette(C)
    end
    if not rCastbarDB then rCastbarDB = {} end
    if rCastbarDB.guiFrameW and rCastbarDB.guiFrameH then
        FRAME_W = rCastbarDB.guiFrameW
        FRAME_H = rCastbarDB.guiFrameH
    else
        FRAME_W, FRAME_H = FRAME_W_DEFAULT, FRAME_H_DEFAULT
    end
    local frame=CreateFrame("Frame","rCastbarConfigFrame",UIParent,"BackdropTemplate")
    frame:SetSize(FRAME_W,FRAME_H); frame:SetPoint("CENTER")
    frame:SetMovable(true); frame:EnableMouse(true)
    frame:SetFrameStrata("DIALOG"); frame:SetClampedToScreen(true); frame:Hide()
    frame:SetBackdrop({bgFile="Interface\\Buttons\\WHITE8X8",edgeFile="Interface\\Buttons\\WHITE8X8",
        tile=false,edgeSize=2,insets={left=0,right=0,top=0,bottom=0}})
    frame:SetBackdropColor(unpack(C.bg))
    frame:SetBackdropBorderColor(
        math.min(1, C.primary[1]*0.50+0.10), math.min(1, C.primary[2]*0.50+0.10), math.min(1, C.primary[3]*0.50+0.10), 1)
    tinsert(UISpecialFrames,"rCastbarConfigFrame")
    frame:SetScript("OnHide",CloseDropdown)
    frame:HookScript("OnShow", function()
        RefreshAllStateControls()
    end)
    function frame:FadeIn()  GUIFadeIn(self)  end
    function frame:FadeOut() GUIFadeOut(self) end

    -- Titelleiste (Klassenfarbe, Verlauf)
    local titleBg=FlatFrame(frame,
        math.min(1, C.primary[1]*0.20+0.04), math.min(1, C.primary[2]*0.20+0.04), math.min(1, C.primary[3]*0.20+0.04), 1,
        math.min(1, C.primary[1]*0.48+0.08), math.min(1, C.primary[2]*0.48+0.08), math.min(1, C.primary[3]*0.48+0.08), 1)
    titleBg:SetPoint("TOPLEFT",frame,"TOPLEFT",2,-2); titleBg:SetPoint("TOPRIGHT",frame,"TOPRIGHT",-2,-2); titleBg:SetHeight(TITLE_H)
    local ta=Tex(titleBg); ta:SetWidth(3); ta:SetPoint("TOPLEFT",titleBg,"TOPLEFT",0,0); ta:SetPoint("BOTTOMLEFT",titleBg,"BOTTOMLEFT",0,0)
    ta:SetColorTexture(C.primary[1],C.primary[2],C.primary[3],1)
    local titleIcon=Tex(titleBg,"OVERLAY"); titleIcon:SetSize(28,28)
    titleIcon:SetPoint("LEFT",titleBg,"LEFT",14,0)
    titleIcon:SetTexture(237017)
    titleIcon:SetTexCoord(0.08,0.92,0.08,0.92)
    local titleLbl=FS(titleBg,"GameFontNormalHuge"); titleLbl:SetPoint("LEFT",titleIcon,"RIGHT",10,0)
    local th = rCastbarGuiTheme and rCastbarGuiTheme.RGBToWoWColorHex
        and rCastbarGuiTheme.RGBToWoWColorHex(C.primary[1], C.primary[2], C.primary[3])
        or "ae4500"
    titleLbl:SetTextColor(1,1,1,1); titleLbl:SetText("|cff" .. th .. "r|rCastbar")
    AttachGuiTitleVersion(titleBg, titleLbl, "rCastbar", C.textDim[1], C.textDim[2], C.textDim[3])
    local tgl=Tex(titleBg); tgl:SetHeight(2)
    tgl:SetPoint("BOTTOMLEFT",titleBg,"BOTTOMLEFT",0,0); tgl:SetPoint("BOTTOMRIGHT",titleBg,"BOTTOMRIGHT",0,0)
    tgl:SetGradient("HORIZONTAL",CreateColor(C.primary[1],C.primary[2],C.primary[3],1),
        CreateColor(C.primary[1]*0.22,C.primary[2]*0.22,C.primary[3]*0.22,0))
    local closeBtn=CreateFrame("Button",nil,titleBg); closeBtn:SetSize(28,28); closeBtn:SetPoint("RIGHT",titleBg,"RIGHT",-12,0)
    closeBtn:SetFrameLevel(titleBg:GetFrameLevel()+5)
    local closeBg=Tex(closeBtn,"BACKGROUND"); closeBg:SetAllPoints(); closeBg:SetColorTexture(0,0,0,0)
    local closeLbl=closeBtn:CreateFontString(nil,"OVERLAY"); closeLbl:SetFont(FONT,16,"OUTLINE")
    closeLbl:SetPoint("CENTER",0,1); closeLbl:SetText("x"); closeLbl:SetTextColor(1,1,1,1)
    closeBtn:SetScript("OnEnter",function() closeBg:SetColorTexture(C.primaryHi[1],C.primaryHi[2],C.primaryHi[3],1) end)
    closeBtn:SetScript("OnLeave",function() closeBg:SetColorTexture(0,0,0,0) end)
    closeBtn:SetScript("OnClick",function() frame:FadeOut() end)

    -- Sidebar
    local sidebar=FlatFrame(frame,C.sidebar[1],C.sidebar[2],C.sidebar[3],C.sidebar[4])
    sidebar:SetPoint("TOPLEFT",titleBg,"BOTTOMLEFT",2,0); sidebar:SetPoint("BOTTOMLEFT",frame,"BOTTOMLEFT",2,BOT_H+2); sidebar:SetWidth(SIDEBAR_W)
    local sbl=Tex(sidebar); sbl:SetWidth(1); sbl:SetPoint("TOPRIGHT",sidebar,"TOPRIGHT",0,0); sbl:SetPoint("BOTTOMRIGHT",sidebar,"BOTTOMRIGHT",0,0)
    sbl:SetColorTexture(
        math.min(1, C.primary[1]*0.38+0.08), math.min(1, C.primary[2]*0.38+0.08), math.min(1, C.primary[3]*0.38+0.08), 1)

    -- Content area
    local contentBg=FlatFrame(frame,C.content[1],C.content[2],C.content[3],C.content[4],C.border[1]*0.5,C.border[2]*0.5,C.border[3]*0.5,C.border[4])
    contentBg:SetPoint("TOPLEFT",sidebar,"TOPRIGHT",1,0); contentBg:SetPoint("BOTTOMRIGHT",frame,"BOTTOMRIGHT",-2,BOT_H+2)
    local contentHeader=FS(contentBg,"GameFontNormalLarge"); contentHeader:SetPoint("TOPLEFT",contentBg,"TOPLEFT",PAD,-12)
    contentHeader:SetTextColor(C.primary[1],C.primary[2],C.primary[3],0.70)
    local chLine=Tex(contentBg,"BACKGROUND"); chLine:SetHeight(1)
    chLine:SetPoint("TOPLEFT",contentBg,"TOPLEFT",PAD,-30); chLine:SetPoint("TOPRIGHT",contentBg,"TOPRIGHT",-PAD,-30)
    chLine:SetColorTexture(C.primary[1],C.primary[2],C.primary[3],0.18)

    -- Bottom bar
    local bot=FlatFrame(frame,
        (C.botFill and C.botFill[1]) or 0.035, (C.botFill and C.botFill[2]) or 0.025, (C.botFill and C.botFill[3]) or 0.050, (C.botFill and C.botFill[4]) or 1,
        (C.botEdge and C.botEdge[1]) or 0.07, (C.botEdge and C.botEdge[2]) or 0.06, (C.botEdge and C.botEdge[3]) or 0.12, (C.botEdge and C.botEdge[4]) or 1)
    bot:SetPoint("BOTTOMLEFT",frame,"BOTTOMLEFT",2,2); bot:SetPoint("BOTTOMRIGHT",frame,"BOTTOMRIGHT",-2,2); bot:SetHeight(BOT_H)
    local botLine=Tex(bot); botLine:SetHeight(1); botLine:SetPoint("TOPLEFT",bot,"TOPLEFT",0,0); botLine:SetPoint("TOPRIGHT",bot,"TOPRIGHT",0,0)
    botLine:SetColorTexture(C.primary[1],C.primary[2],C.primary[3],0.28)

    -- GUI Scale
    do
        local STEP=0.05; local SMIN=0.5; local SMAX=2.0
        local function GS() return(rCastbarDB and rCastbarDB.guiScale)or 1.0 end
        local function AS(v) v=math.max(SMIN,math.min(SMAX,v)); v=math.floor(v/STEP+0.5)*STEP; v=math.floor(v*100+0.5)/100
            if rCastbarDB then rCastbarDB.guiScale=v end; frame:SetScale(v) end
        local scaleLbl=FS(bot,"GameFontHighlightSmall"); scaleLbl:SetPoint("LEFT",bot,"LEFT",10,0)
        scaleLbl:SetTextColor(unpack(C.textDim)); scaleLbl:SetText(L["GUI Scale"])
        local valBox=CreateFrame("EditBox",nil,bot,"BackdropTemplate"); valBox:SetSize(46,22)
        valBox:SetBackdrop({bgFile="Interface\\Buttons\\WHITE8X8",edgeFile="Interface\\Buttons\\WHITE8X8",edgeSize=1,insets={left=0,right=0,top=0,bottom=0}})
        valBox:SetBackdropColor(0.04,0.04,0.05,0.9); valBox:SetBackdropBorderColor(
            math.min(1, C.primary[1]*0.48+0.10), math.min(1, C.primary[2]*0.48+0.10), math.min(1, C.primary[3]*0.48+0.10), 1)
        valBox:SetFont(FONT,11,""); valBox:SetTextColor(C.primaryHi[1],C.primaryHi[2],C.primaryHi[3],1); valBox:SetJustifyH("CENTER"); valBox:SetMaxLetters(5); valBox:SetAutoFocus(false)
        local function SyncBox() valBox:SetText(string.format("%.2f",GS())); frame:SetScale(GS()) end
        local function MkArr(sym,dir)
            local b=CreateFrame("Button",nil,bot,"BackdropTemplate"); b:SetSize(20,22)
            b:SetBackdrop({bgFile="Interface\\Buttons\\WHITE8X8",edgeFile="Interface\\Buttons\\WHITE8X8",edgeSize=1,insets={left=1,right=1,top=1,bottom=1}})
            b:SetBackdropColor(
                (C.toggleOff and C.toggleOff[1]) or 0.08, (C.toggleOff and C.toggleOff[2]) or 0.08, (C.toggleOff and C.toggleOff[3]) or 0.09, 1)
            b:SetBackdropBorderColor(
                math.min(1, C.primary[1]*0.42+0.08), math.min(1, C.primary[2]*0.42+0.08), math.min(1, C.primary[3]*0.42+0.08), 1)
            local t2=b:CreateFontString(nil,"OVERLAY","GameFontNormal"); t2:SetAllPoints(); t2:SetText(sym)
            t2:SetTextColor(C.primaryHi[1],C.primaryHi[2],C.primaryHi[3],1)
            b:SetScript("OnEnter",function(s) s:SetBackdropColor(
                math.min(1, C.primary[1]*0.22+0.08), math.min(1, C.primary[2]*0.22+0.08), math.min(1, C.primary[3]*0.22+0.08), 1)
                s:SetBackdropBorderColor(C.primaryHi[1],C.primaryHi[2],C.primaryHi[3],1) end)
            b:SetScript("OnLeave",function(s) s:SetBackdropColor(
                (C.toggleOff and C.toggleOff[1]) or 0.08, (C.toggleOff and C.toggleOff[2]) or 0.08, (C.toggleOff and C.toggleOff[3]) or 0.09, 1)
                s:SetBackdropBorderColor(
                    math.min(1, C.primary[1]*0.42+0.08), math.min(1, C.primary[2]*0.42+0.08), math.min(1, C.primary[3]*0.42+0.08), 1); s.held=false end)
            local ticker
            b:SetScript("OnClick",function() AS(GS()+dir*STEP); SyncBox() end)
            b:SetScript("OnMouseDown",function() b.held=true; C_Timer.After(0.4,function() if not b.held then return end
                ticker=C_Timer.NewTicker(0.08,function() if b.held then AS(GS()+dir*STEP); SyncBox() else ticker:Cancel(); ticker=nil end end) end) end)
            b:SetScript("OnMouseUp",function() b.held=false; if ticker then ticker:Cancel(); ticker=nil end end)
            return b
        end
        local btnMinus=MkArr("<",-1); btnMinus:SetPoint("LEFT",scaleLbl,"RIGHT",8,0)
        valBox:SetPoint("LEFT",btnMinus,"RIGHT",2,0); local btnPlus=MkArr(">",1); btnPlus:SetPoint("LEFT",valBox,"RIGHT",2,0)
        valBox:SetScript("OnMouseWheel",function(_,d) AS(GS()+d*STEP); SyncBox() end)
        local function CT()
            local v=tonumber(valBox:GetText()); if v then AS(v) end; SyncBox(); valBox:ClearFocus()
        end
        valBox:SetScript("OnEnterPressed",CT); valBox:SetScript("OnTabPressed",CT)
        valBox:SetScript("OnEditFocusLost",function() C_Timer.After(0,CT) end)
        valBox:SetScript("OnEscapePressed",function() SyncBox(); valBox:ClearFocus() end)
        frame:HookScript("OnShow",SyncBox); frame:HookScript("OnHide",function() valBox:ClearFocus() end); SyncBox()
        frame.SyncScale = SyncBox
    end

    local closeBot=StyledButton(bot,L["Close"],nil,26,function() frame:FadeOut() end)
    closeBot:SetPoint("RIGHT",bot,"RIGHT",-10,0)

    -- Tabs
    local TAB_DEFS={
        {id="general", label=L["General"], icon="Interface\\Icons\\INV_Misc_Note_06"},
        {id="player",  label=L["Player"],  icon="Interface\\Icons\\Achievement_Character_Human_Male"},
        {id="target",  label=L["Target"],  icon="Interface\\Icons\\Ability_Warrior_OffensiveStance"},
    }
    local TAB_H=40; local TAB_GAP=1; local tabs={}; local tabPanels={}

    local function SelectTab(idx)
        activeTabIdx=idx
        for i,tab in ipairs(tabs) do
            local sel=(i==idx)
            if sel then
                tab.accent:Show()
                tab.bgTex:SetColorTexture(
                    math.min(1, C.primary[1]*0.22+0.06), math.min(1, C.primary[2]*0.22+0.06), math.min(1, C.primary[3]*0.22+0.06), 1)
                tab.icon:SetVertexColor(C.primaryHi[1], C.primaryHi[2], C.primaryHi[3], 1)
                tab.lbl:SetTextColor(1, 0.98, 0.98, 1)
            else
                tab.accent:Hide()
                tab.bgTex:SetColorTexture(C.sidebar[1], C.sidebar[2], C.sidebar[3], 1)
                tab.icon:SetVertexColor(0.42, 0.44, 0.55, 1)
                tab.lbl:SetTextColor(0.68, 0.70, 0.80, 1)
            end
            tabPanels[i]:SetShown(sel)
        end
        contentHeader:SetText(TAB_DEFS[idx].label)
    end

    for i,td in ipairs(TAB_DEFS) do
        local tab=CreateFrame("Button",nil,sidebar); tab:SetHeight(TAB_H)
        tab:SetPoint("TOPLEFT",sidebar,"TOPLEFT",0,-(i-1)*(TAB_H+TAB_GAP)); tab:SetPoint("TOPRIGHT",sidebar,"TOPRIGHT",-1,-(i-1)*(TAB_H+TAB_GAP))
        tab.bgTex=Tex(tab,"BACKGROUND"); tab.bgTex:SetAllPoints(); tab.bgTex:SetColorTexture(C.sidebar[1],C.sidebar[2],C.sidebar[3],1)
        tab.accent=Tex(tab,"OVERLAY"); tab.accent:SetWidth(3); tab.accent:SetPoint("TOPLEFT",tab,"TOPLEFT",0,0); tab.accent:SetPoint("BOTTOMLEFT",tab,"BOTTOMLEFT",0,0)
        tab.accent:SetColorTexture(C.primary[1],C.primary[2],C.primary[3],1); tab.accent:Hide()
        local sep=Tex(tab,"BORDER"); sep:SetHeight(1); sep:SetPoint("BOTTOMLEFT",tab,"BOTTOMLEFT",0,0); sep:SetPoint("BOTTOMRIGHT",tab,"BOTTOMRIGHT",0,0)
        sep:SetColorTexture(C.separator[1], C.separator[2], C.separator[3], C.separator[4] or 0.55)
        tab.icon=Tex(tab,"OVERLAY"); tab.icon:SetSize(18,18); tab.icon:SetPoint("LEFT",tab,"LEFT",14,0); tab.icon:SetTexture(td.icon)
        tab.icon:SetVertexColor(0.42, 0.44, 0.55, 1)
        tab.lbl=FS(tab,"GameFontNormalSmall"); tab.lbl:SetPoint("LEFT",tab.icon,"RIGHT",8,0); tab.lbl:SetPoint("RIGHT",tab,"RIGHT",-6,0)
        tab.lbl:SetJustifyH("LEFT"); tab.lbl:SetTextColor(0.68, 0.70, 0.80, 1); tab.lbl:SetText(td.label)
        local ii=i
        tab:SetScript("OnEnter",function(s)
            if activeTabIdx~=ii then
                s.bgTex:SetColorTexture(
                    math.min(1, C.primary[1]*0.14+0.04), math.min(1, C.primary[2]*0.14+0.04), math.min(1, C.primary[3]*0.14+0.04), 1)
                s.lbl:SetTextColor(C.primaryHi[1], C.primaryHi[2], C.primaryHi[3], 1)
                s.icon:SetVertexColor(C.primary[1], C.primary[2], C.primary[3], 1)
            end
        end)
        tab:SetScript("OnLeave",function(s)
            if activeTabIdx~=ii then
                s.bgTex:SetColorTexture(C.sidebar[1], C.sidebar[2], C.sidebar[3], 1)
                s.lbl:SetTextColor(0.68, 0.70, 0.80, 1)
                s.icon:SetVertexColor(0.42, 0.44, 0.55, 1)
            end
        end)
        tab:SetScript("OnClick",function() SelectTab(ii) end); tabs[i]=tab
        local panel=CreateFrame("Frame",nil,contentBg)
        panel:SetPoint("TOPLEFT",contentBg,"TOPLEFT",0,0); panel:SetPoint("BOTTOMRIGHT",contentBg,"BOTTOMRIGHT",0,0); panel:Hide()
        tabPanels[i]=panel
    end

    do
        local ICON_SZ = 56
        local classIconHolder = CreateFrame("Frame", nil, sidebar)
        classIconHolder:SetSize(ICON_SZ, ICON_SZ)
        classIconHolder:SetPoint("BOTTOM", sidebar, "BOTTOM", 0, 10)
        local ciTex = classIconHolder:CreateTexture(nil, "ARTWORK", nil, 2)
        ciTex:SetAllPoints()
        ciTex:SetVertexColor(1, 1, 1, 1)
        if not (rCastbarGuiTheme and rCastbarGuiTheme.ApplyCustomClassIconToTexture and rCastbarGuiTheme.ApplyCustomClassIconToTexture(ciTex)) then
            classIconHolder:Hide()
        else
            classIconHolder:EnableMouse(true)
            classIconHolder:SetScript("OnEnter", function(self)
                GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
                local _, className = UnitClass("player")
                GameTooltip:SetText(className or "", C.primary[1], C.primary[2], C.primary[3], 1)
                GameTooltip:Show()
            end)
            classIconHolder:SetScript("OnLeave", function() GameTooltip:Hide() end)
        end
    end

    if RR_ConfigResizeGrip_Attach then
        RR_ConfigResizeGrip_Attach({
            frame = frame,
            titleBg = titleBg,
            dbTable = rCastbarDB,
            texturePath = "Interface\\AddOns\\rCastbar\\media\\resize.tga",
            gripName = "rCastbarConfigResizeGrip",
            defW = FRAME_W_DEFAULT,
            defH = FRAME_H_DEFAULT,
            primary = { C.primary[1], C.primary[2], C.primary[3] },
            primaryHi = { C.primaryHi[1], C.primaryHi[2], C.primaryHi[3] },
            tooltipText = L["GUI_RESIZE_GRIP"],
            syncScaleName = "SyncScale",
            onSaved = function()
                FRAME_W = rCastbarDB.guiFrameW or FRAME_W
                FRAME_H = rCastbarDB.guiFrameH or FRAME_H
            end,
            onLiveResize = function(w, h)
                FRAME_W, FRAME_H = w, h
                local cw = CW() - 20
                for _, c in ipairs(allToggleControls) do
                    if c and c.SetSize then c:SetSize(cw, c:GetHeight()); if c.Refresh then c:Refresh() end end
                end
                for _, c in ipairs(allSliderControls) do
                    if c and c.SetSize then c:SetSize(cw, c:GetHeight()); if c.Refresh then c:Refresh() end end
                end
                for _, c in ipairs(allColorPickerRows) do
                    if c and c.SetSize then c:SetSize(cw, c:GetHeight()); if c.UpdatePreview then c:UpdatePreview() end end
                end
                if frame._rCastbarGeneralLogo then
                    local lw = math.min(CW() - 40, 450)
                    frame._rCastbarGeneralLogo:SetSize(lw, math.floor(lw * LOGO_RATIO + 0.5))
                end
                for _, panel in ipairs(tabPanels) do
                    if panel.sf and panel.sc then
                        local wf = panel.sf:GetWidth()
                        if wf and wf > 40 then panel.sc:SetWidth(math.max(100, wf - 4)) end
                    end
                    if panel.UpdateThumb then panel.UpdateThumb() end
                end
            end,
        })
    end

    configFrame=frame; frame._tabs=tabs; frame._tabPanels=tabPanels; frame._SelectTab=SelectTab
    return frame,tabs,tabPanels,SelectTab
end

-- ======================================================
-- UNIT PANEL
-- ======================================================
local function BuildUnitPanel(parent, unit)
    local sc,sf=MakeScrollContent(parent)
    local P2=12; local y=-P2

    local function getBar()  return _G["rCastbar_"..(unit:sub(1,1):upper()..unit:sub(2))] end
    local function getCont() return _G["rCastbar_"..(unit:sub(1,1):upper()..unit:sub(2)).."_Container"] end

    -- Layout
    SectionHeader(sc, L["Layout"], sc, P2, y); y=y-26

    ModernToggle(sc, L["Enabled"], sc, P2, y,
        function() return rCastbarDB[unit].enabled end,
        function(v) rCastbarDB[unit].enabled=v end,
        L["Enabled_Desc"])
    y=y-56

    -- Width
    -- NOTE: when dynamicWidth is ON, the slider value is the minimum width only;
    --       we do NOT directly resize the bar (it resizes on next cast start).
    ModernSlider(sc, L["Width"], L["Width_Desc"], 80, 500, 1, sc, y,
        function() return rCastbarDB[unit].width end,
        function(v)
            rCastbarDB[unit].width = v
            if not rCastbarDB[unit].dynamicWidth then
                local bar=getBar(); local cont=getCont()
                if bar then bar:SetWidth(v); if bar.UpdateArtwork then bar:UpdateArtwork(v) end end
                if cont then cont:SetWidth(v+20) end
            end
        end,
        function(v) return string.format(L["Width_Fmt"], v) end)
    y=y-72

    -- Dynamic Width
    ModernToggle(sc, L["DynamicWidth"], sc, P2, y,
        function() return rCastbarDB[unit].dynamicWidth end,
        function(v) rCastbarDB[unit].dynamicWidth=v end,
        L["DynamicWidth_Desc"])
    y=y-56

    -- Font Size
    ModernSlider(sc, L["FontSize"], L["FontSize_Desc"], 7, 20, 1, sc, y,
        function() return rCastbarDB[unit].fontSize end,
        function(v)
            rCastbarDB[unit].fontSize=v
            local bar=getBar()
            if bar then
                if bar.Text then bar.Text:SetFont(MEDIA.."Expressway.ttf",v,"THINOUTLINE") end
                if bar.Time then bar.Time:SetFont(MEDIA.."Expressway.ttf",v,"THINOUTLINE") end
            end
        end,
        function(v) return string.format(L["FontSize_Fmt"], v) end)
    y=y-72

    -- Fail display duration
    ModernSlider(sc, L["TimeToHold"], L["TimeToHold_Desc"], 0, 5, 0.1, sc, y,
        function() return rCastbarDB[unit].timeToHold end,
        function(v) rCastbarDB[unit].timeToHold=v end,
        function(v) return string.format(L["TimeToHold_Fmt"], v) end)
    y=y-72

    -- Show Icon
    ModernToggle(sc, L["ShowIcon"], sc, P2, y,
        function() return rCastbarDB[unit].showIcon end,
        function(v)
            rCastbarDB[unit].showIcon=v
            local bar=getBar()
            if bar and bar.Icon then
                if v then bar.Icon:Show() else bar.Icon:Hide() end
            end
        end,
        L["ShowIcon_Desc"])
    y=y-56

    -- Show Spell Name
    ModernToggle(sc, L["ShowSpellName"], sc, P2, y,
        function() return rCastbarDB[unit].showSpellName end,
        function(v)
            rCastbarDB[unit].showSpellName = v
            local bar=getBar()
            if bar and bar.Text then
                if v then bar.Text:Show() else bar.Text:Hide() end
            end
        end,
        L["ShowSpellName_Desc"])
    y=y-56

    -- Show Cast Time
    ModernToggle(sc, L["ShowTime"], sc, P2, y,
        function() return rCastbarDB[unit].showTime end,
        function(v)
            rCastbarDB[unit].showTime = v
            local bar=getBar()
            if bar then
                if bar.Time then
                    if v then bar.Time:Show() else bar.Time:Hide() end
                end
                -- Adjust Text RIGHT anchor to use freed space
                updateBarTextAnchor(bar, v)
            end
        end,
        L["ShowTime_Desc"])
    y=y-56

    -- Latency (player only)
    if unit == "player" then
        ModernToggle(sc, L["ShowLatency"], sc, P2, y,
            function() return rCastbarDB.player.latency end,
            function(v) rCastbarDB.player.latency=v end,
            L["ShowLatency_Desc"])
        y=y-56
    end

    -- Fade
    y=y-8; SectionHeader(sc, L["Fade"], sc, P2, y); y=y-26

    ModernToggle(sc, L["FadeEnable"], sc, P2, y,
        function() return rCastbarDB[unit].fadeEnable end,
        function(v) rCastbarDB[unit].fadeEnable=v end,
        L["FadeEnable_Desc"])
    y=y-56

    ModernSlider(sc, L["FadeIn"], L["FadeIn_Desc"], 0, 1, 0.05, sc, y,
        function() return rCastbarDB[unit].fadeInTime end,
        function(v) rCastbarDB[unit].fadeInTime=v end,
        function(v) return string.format(L["FadeTime_Fmt"], v) end)
    y=y-72

    ModernSlider(sc, L["FadeOut"], L["FadeOut_Desc"], 0, 2, 0.05, sc, y,
        function() return rCastbarDB[unit].fadeOutTime end,
        function(v) rCastbarDB[unit].fadeOutTime=v end,
        function(v) return string.format(L["FadeTime_Fmt"], v) end)
    y=y-72

    -- Successful Cast
    y=y-8; SectionHeader(sc, L["SuccessSection"], sc, P2, y); y=y-26

    ModernToggle(sc, L["CastSuccess"], sc, P2, y,
        function() return rCastbarDB[unit].castSuccess end,
        function(v) rCastbarDB[unit].castSuccess=v end,
        L["CastSuccess_Desc"])
    y=y-56

    -- Colors
    y=y-8; SectionHeader(sc, L["Colors"], sc, P2, y); y=y-26

    local colorNote=FS(sc,"GameFontHighlightSmall"); colorNote:SetPoint("TOPLEFT",sc,"TOPLEFT",P2,y)
    colorNote:SetTextColor(
        math.min(1, C.primary[1]*0.55+0.2), math.min(1, C.primary[2]*0.55+0.2), math.min(1, C.primary[3]*0.55+0.2), 0.85)
    colorNote:SetText(L["Colors_Note"]); y=y-20

    -- Class color toggle
    ModernToggle(sc, L["ClassColor"], sc, P2, y,
        function() return rCastbarDB[unit].classColor end,
        function(v)
            rCastbarDB[unit].classColor=v
            local bar=_G["rCastbar_"..(unit:sub(1,1):upper()..unit:sub(2))]
            if bar then
                if v then
                    local _,classFile=UnitClass(unit)
                    local cc=classFile and RAID_CLASS_COLORS[classFile]
                    if cc then bar:SetStatusBarColor(cc.r,cc.g,cc.b,rCastbarDB[unit].color.a) end
                else
                    local col=rCastbarDB[unit].color
                    bar:SetStatusBarColor(col.r,col.g,col.b,col.a)
                end
            end
        end,
        L["ClassColor_Desc"])
    y=y-56

    local colorRows={}

    local r1=ColorPickerRow(sc, L["ColorCast"],
        function() return rCastbarDB[unit].color end,
        function(t) rCastbarDB[unit].color=t; LiveApplyColor(unit,"color") end,
        sc,y); colorRows[#colorRows+1]=r1; y=y-32

    local r2=ColorPickerRow(sc, L["ColorBg"],
        function() return rCastbarDB[unit].bgColor end,
        function(t) rCastbarDB[unit].bgColor=t; LiveApplyColor(unit,"bgColor") end,
        sc,y); colorRows[#colorRows+1]=r2; y=y-32

    local r3=ColorPickerRow(sc, L["ColorEmpowerTick"],
        function() return rCastbarDB[unit].empowerTickColor end,
        function(t) rCastbarDB[unit].empowerTickColor=t; LiveApplyColor(unit,"empowerTickColor") end,
        sc,y); colorRows[#colorRows+1]=r3; y=y-32

    local r4=ColorPickerRow(sc, L["ColorFail"],
        function() return rCastbarDB[unit].failColor end,
        function(t) rCastbarDB[unit].failColor=t end,
        sc,y); colorRows[#colorRows+1]=r4; y=y-32

    local r5=ColorPickerRow(sc, L["ColorSuccess"],
        function() return rCastbarDB[unit].successColor end,
        function(t) rCastbarDB[unit].successColor=t end,
        sc,y); colorRows[#colorRows+1]=r5; y=y-32

    if unit == "target" then
        local r6=ColorPickerRow(sc, L["ColorShield"],
            function() return rCastbarDB.target.shieldColor end,
            function(t) rCastbarDB.target.shieldColor=t end,
            sc,y); colorRows[#colorRows+1]=r6; y=y-32
        local r7=ColorPickerRow(sc, L["ColorShieldBg"],
            function() return rCastbarDB.target.shieldBgColor end,
            function(t) rCastbarDB.target.shieldBgColor=t end,
            sc,y); colorRows[#colorRows+1]=r7; y=y-32
    end

    local resetColorBtn=StyledButton(sc, L["ResetColors"], nil, 24, function()
        local def=DEFAULT_COLORS[unit]; if not def then return end
        rCastbarDB[unit].color        = copyColor(def.color)
        rCastbarDB[unit].bgColor      = copyColor(def.bgColor)
        rCastbarDB[unit].empowerTickColor = copyColor(def.empowerTickColor)
        rCastbarDB[unit].failColor    = copyColor(def.failColor)
        rCastbarDB[unit].successColor = copyColor(def.successColor)
        LiveApplyColor(unit,"color"); LiveApplyColor(unit,"bgColor"); LiveApplyColor(unit,"empowerTickColor")
        if unit=="target" and def.shieldColor then
            rCastbarDB.target.shieldColor   = copyColor(def.shieldColor)
            rCastbarDB.target.shieldBgColor = copyColor(def.shieldBgColor)
        end
        for _,row in ipairs(colorRows) do if row.UpdatePreview then row:UpdatePreview() end end
    end)
    resetColorBtn:SetPoint("TOPLEFT",sc,"TOPLEFT",P2,y); y=y-32

    -- Position
    y=y-8; SectionHeader(sc, L["Position"], sc, P2, y); y=y-26

    local posNote=FS(sc,"GameFontHighlightSmall"); posNote:SetPoint("TOPLEFT",sc,"TOPLEFT",P2,y)
    posNote:SetTextColor(unpack(C.textDim)); posNote:SetText(L["Position_Note"]); y=y-20

    ModernSlider(sc, L["XOffset"], nil, -1000, 1000, 1, sc, y,
        function() return rCastbarDB[unit].x end,
        function(v)
            rCastbarDB[unit].x=v; local cont=getCont()
            if cont then cont:ClearAllPoints(); cont:SetPoint(rCastbarDB[unit].point,
                _G[rCastbarDB[unit].relFrame] or UIParent,rCastbarDB[unit].relPoint,v,rCastbarDB[unit].y) end
        end,
        function(v) return string.format("%d",v) end)
    y=y-72

    ModernSlider(sc, L["YOffset"], nil, -800, 800, 1, sc, y,
        function() return rCastbarDB[unit].y end,
        function(v)
            rCastbarDB[unit].y=v; local cont=getCont()
            if cont then cont:ClearAllPoints(); cont:SetPoint(rCastbarDB[unit].point,
                _G[rCastbarDB[unit].relFrame] or UIParent,rCastbarDB[unit].relPoint,rCastbarDB[unit].x,v) end
        end,
        function(v) return string.format("%d",v) end)
    y=y-72

    y=y-8
    local sepLine=Tex(sc,"BACKGROUND"); sepLine:SetHeight(1)
    sepLine:SetPoint("TOPLEFT",sc,"TOPLEFT",P2,y); sepLine:SetPoint("TOPRIGHT",sc,"TOPRIGHT",-P2,y)
    sepLine:SetColorTexture(C.primary[1],C.primary[2],C.primary[3],0.18); y=y-14
    local applyBtn=StyledButton(sc, L["ReloadUI"], nil, 28, function() ReloadUI() end)
    applyBtn:SetPoint("TOPLEFT",sc,"TOPLEFT",P2,y); y=y-38

    sc:SetHeight(-y+P2); return sc,sf
end

-- ======================================================
-- GENERAL TAB
-- ======================================================
local function BuildGeneralTab(parent)
    local sc,sf=MakeScrollContent(parent)
    local P2=12; local y=-P2

    local LOGO_W=math.min(CW()-40,450)
    local LOGO_H=math.floor(LOGO_W*LOGO_RATIO)
    local logo=Tex(sc,"ARTWORK"); logo:SetTexture(MEDIA.."logo_big"); logo:SetSize(LOGO_W,LOGO_H)
    logo:SetPoint("TOP",sc,"TOP",0,y); y=y-LOGO_H-20
    do
        local cf = _G["rCastbarConfigFrame"]
        if cf then cf._rCastbarGeneralLogo = logo end
    end

    SectionHeader(sc, L["EditMode"], sc, P2, y); y=y-26
    local editDesc=FS(sc,"GameFontHighlightSmall"); editDesc:SetPoint("TOPLEFT",sc,"TOPLEFT",P2,y)
    editDesc:SetTextColor(unpack(C.textDim)); editDesc:SetText(L["EditMode_Note"]); y=y-20

    local editBtn,editBtnLbl=StyledButton(sc, L["EditMode_Enable"], nil, 30, nil)
    editBtn.editBtnLbl = editBtnLbl
    _G.rCastbarEditBtn = editBtn
    editBtn:SetPoint("TOPLEFT",sc,"TOPLEFT",P2,y)
    editBtn:SetScript("OnClick",function()
        if InCombatLockdown() then return end
        local pC=_G["rCastbar_Player_Container"]; local tC=_G["rCastbar_Target_Container"]
        if editBtn._editOn then
            editBtn._editOn=false; editBtnLbl:SetText(L["EditMode_Enable"])
            editBtn:SetBackdropColor(
                math.min(1, C.primary[1]*0.26+0.05), math.min(1, C.primary[2]*0.26+0.05), math.min(1, C.primary[3]*0.26+0.05), 1)
            for _,cont in ipairs({pC,tC}) do if cont and cont._editFrame then cont._editFrame:Hide() end end
            HideEditGrid(); if editModeTopBar then editModeTopBar:Hide() end
            if editModeKeyButton then ClearOverrideBindings(editModeKeyButton) end
            if rCastbar_ShowPreview then rCastbar_ShowPreview(false) end
            if configFrame then configFrame:Show() end
        else
            editBtn._editOn=true; editBtnLbl:SetText(L["EditMode_Disable"])
            editBtn:SetBackdropColor(
                math.min(1, C.primary[1]*0.42+0.08), math.min(1, C.primary[2]*0.42+0.08), math.min(1, C.primary[3]*0.42+0.08), 1)
            local function MkOv(cont)
                if not cont then return end
                if not cont._editFrame then
                    local ef=CreateFrame("Frame",nil,cont)
                    ef:SetAllPoints(cont)
                    ef:SetFrameStrata("HIGH")
                    ef:SetFrameLevel(cont:GetFrameLevel()+5)
                    ef:EnableMouse(false)
                    local ov=ef:CreateTexture(nil,"OVERLAY",nil,7); ov:SetAllPoints()
                    ov:SetColorTexture(C.primary[1],C.primary[2],C.primary[3],0.18)
                    local ovLbl=ef:CreateFontString(nil,"OVERLAY"); ovLbl:SetFont(FONT,10,"OUTLINE"); ovLbl:SetPoint("CENTER",0,8)
                    ovLbl:SetTextColor(1,1,1,0.9); ovLbl:SetText("SHIFT + DRAG")
                    local xText=ef:CreateFontString(nil,"OVERLAY","GameFontHighlightSmall")
                    xText:SetPoint("TOP",cont,"BOTTOM",0,-2); xText:SetTextColor(C.primaryHi[1],C.primaryHi[2],C.primaryHi[3],1)
                    local yText=ef:CreateFontString(nil,"OVERLAY","GameFontHighlightSmall")
                    yText:SetPoint("TOP",xText,"BOTTOM",0,-2); yText:SetTextColor(C.primaryHi[1],C.primaryHi[2],C.primaryHi[3],1)
                    ef.xText, ef.yText = xText, yText
                    cont._editFrame=ef
                    cont:HookScript("OnDragStop",function() UpdateAnchorPositionDisplay(cont) end)
                    cont:HookScript("OnMouseDown",function() rCastbar_SelectedContainer=cont end)
                end
                cont._editFrame:Show()
                UpdateAnchorPositionDisplay(cont)
            end
            MkOv(pC); MkOv(tC)
            if rCastbar_ShowPreview then rCastbar_ShowPreview(true) end
            ShowEditGrid()
            CreateEditModeTopBar(); editModeTopBar:Show()
            if configFrame then configFrame:Hide() end
            if not editModeKeyButton then
                editModeKeyButton=CreateFrame("Button","rCastbarEditKeyHandler",UIParent)
                editModeKeyButton:SetScript("OnClick",function(_,btn)
                    if not rCastbar_NudgeSelected then return end
                    local step=IsShiftKeyDown() and 5 or 1
                    local dx,dy=0,0
                    if btn=="UP" then dy=step elseif btn=="DOWN" then dy=-step
                    elseif btn=="LEFT" then dx=-step elseif btn=="RIGHT" then dx=step
                    else return end
                    rCastbar_NudgeSelected(dx,dy)
                end)
            end
            ClearOverrideBindings(editModeKeyButton)
            SetOverrideBindingClick(editModeKeyButton,false,"UP","rCastbarEditKeyHandler","UP")
            SetOverrideBindingClick(editModeKeyButton,false,"DOWN","rCastbarEditKeyHandler","DOWN")
            SetOverrideBindingClick(editModeKeyButton,false,"LEFT","rCastbarEditKeyHandler","LEFT")
            SetOverrideBindingClick(editModeKeyButton,false,"RIGHT","rCastbarEditKeyHandler","RIGHT")
        end
        C_Timer.After(0,function() if editBtnLbl then local sw=editBtnLbl:GetStringWidth()
            if sw and sw>0 then editBtn:SetWidth(sw+BTN_PAD_X) end end end)
    end)
    y=y-38

    ModernToggle(sc, L["SnapToGrid"], sc, P2, y,
        function() return rCastbarDB and rCastbarDB.snapToGrid == true end,
        function(v) if rCastbarDB then rCastbarDB.snapToGrid = v end end,
        L["SnapToGrid_Desc"])
    y=y-56

    -- Minimap Icon Toggle
    y=y-8; SectionHeader(sc, L["ShowMinimapIcon"], sc, P2, y); y=y-26

    local mmToggle = ModernToggle(sc, L["ShowMinimapIcon"], sc, P2, y,
        function()
            if rCastbarDB and rCastbarDB.minimap then return not rCastbarDB.minimap.hide end
            return true
        end,
        function(v) end,  -- setter: handled by the modal dialog below
        L["ShowMinimapIcon_Desc"])
    y=y-56

    -- Modal reload dialog (built once, shown on toggle click)
    local pendingHide = false
    local mmDlg = CreateFrame("Frame", "rCastbarMinimapReloadDialog", UIParent, "BackdropTemplate")
    mmDlg:SetSize(420, 150); mmDlg:SetPoint("CENTER")
    mmDlg:SetFrameStrata("FULLSCREEN_DIALOG"); mmDlg:SetFrameLevel(300)
    mmDlg:Hide(); mmDlg:EnableMouse(true)
    mmDlg:SetBackdrop({bgFile="Interface\\Buttons\\WHITE8X8", edgeFile="Interface\\Buttons\\WHITE8X8",
        tile=false, edgeSize=2, insets={left=0,right=0,top=0,bottom=0}})
    mmDlg:SetBackdropColor(0.04, 0.04, 0.06, 0.97)
    mmDlg:SetBackdropBorderColor(
        math.min(1, C.primary[1]*0.50+0.10), math.min(1, C.primary[2]*0.50+0.10), math.min(1, C.primary[3]*0.50+0.10), 1)

    local mmDlgHdr = Tex(mmDlg, "BORDER")
    mmDlgHdr:SetPoint("TOPLEFT"); mmDlgHdr:SetPoint("TOPRIGHT"); mmDlgHdr:SetHeight(32)
    mmDlgHdr:SetColorTexture(
        math.min(1, C.primary[1]*0.20+0.04), math.min(1, C.primary[2]*0.20+0.04), math.min(1, C.primary[3]*0.20+0.04), 1)

    local mmDlgTitle = FS(mmDlg, "GameFontNormalLarge")
    mmDlgTitle:SetPoint("TOPLEFT", 14, -8)
    mmDlgTitle:SetText(L["Minimap_ReloadTitle"])
    mmDlgTitle:SetTextColor(C.primary[1], C.primary[2], C.primary[3])

    local mmDlgBody = FS(mmDlg, "GameFontNormal")
    mmDlgBody:SetPoint("TOPLEFT", 14, -44); mmDlgBody:SetWidth(392)
    mmDlgBody:SetJustifyH("LEFT"); mmDlgBody:SetText(L["Minimap_ReloadBody"])
    mmDlgBody:SetTextColor(unpack(C.text))

    StyledButton(mmDlg, L["Minimap_BtnCancel"], nil, 28, function()
        -- Revert toggle visual back to current saved state
        if mmToggle and mmToggle.Refresh then mmToggle.Refresh() end
        mmDlg:Hide()
    end):SetPoint("BOTTOMLEFT", 14, 12)

    StyledButton(mmDlg, L["Minimap_BtnReload"], nil, 28, function()
        if rCastbarDB and rCastbarDB.minimap then
            rCastbarDB.minimap.hide = pendingHide
        end
        mmDlg:Hide()
        ReloadUI()
    end):SetPoint("BOTTOMRIGHT", -14, 12)

    -- Intercept the toggle track click to show the dialog instead
    local mmTrack = select(1, mmToggle:GetChildren())
    if mmTrack then
        mmTrack:SetScript("OnClick", function()
            if not rCastbarDB or not rCastbarDB.minimap then return end
            pendingHide = not rCastbarDB.minimap.hide
            mmDlg:Show()
        end)
    end

    y=y-8; SectionHeader(sc, L["Reset"], sc, P2, y); y=y-26
    local resetNote=FS(sc,"GameFontHighlightSmall"); resetNote:SetPoint("TOPLEFT",sc,"TOPLEFT",P2,y)
    resetNote:SetTextColor(unpack(C.textDim)); resetNote:SetText(L["Reset_Note"]); y=y-20
    local resetBtn=DangerButton(sc, L["ResetBtn"], nil, 28, function()
        if rCastbarDB then rCastbarDB.player=nil; rCastbarDB.target=nil; rCastbarDB.guiScale=nil end; ReloadUI()
    end)
    resetBtn:SetPoint("TOPLEFT",sc,"TOPLEFT",P2,y); y=y-38

    y=y-8
    local sep=Tex(sc,"BACKGROUND"); sep:SetHeight(1)
    sep:SetPoint("TOPLEFT",sc,"TOPLEFT",P2,y); sep:SetPoint("TOPRIGHT",sc,"TOPRIGHT",-P2,y)
    sep:SetColorTexture(C.primary[1],C.primary[2],C.primary[3],0.15); y=y-12
    local infoLbl=FS(sc,"GameFontHighlightSmall"); infoLbl:SetPoint("TOPLEFT",sc,"TOPLEFT",P2,y)
    infoLbl:SetTextColor(unpack(C.textDim)); infoLbl:SetText(L["Commands"]); y=y-20

    sc:SetHeight(-y+P2)
end

-- ======================================================
-- BLIZZARD PANEL
-- ======================================================
local function CreateBlizzardPanel()
    if rCastbarGuiTheme and rCastbarGuiTheme.ApplyPalette then
        rCastbarGuiTheme.ApplyPalette(C)
    end
    local panel=CreateFrame("Frame","rCastbarInterfacePanel"); panel.name="rCastbar"
    local BW=300; local BH=math.floor(BW*LOGO_RATIO)
    local logo=panel:CreateTexture(nil,"ARTWORK"); logo:SetSize(BW,BH); logo:SetPoint("TOP",panel,"TOP",0,-28); logo:SetTexture(MEDIA.."logo_big")
    local titleLbl=panel:CreateFontString(nil,"ARTWORK","GameFontNormalHuge"); titleLbl:SetPoint("TOP",logo,"BOTTOM",0,-10)
    local bh = rCastbarGuiTheme and rCastbarGuiTheme.RGBToWoWColorHex
        and rCastbarGuiTheme.RGBToWoWColorHex(C.primary[1], C.primary[2], C.primary[3])
        or "ae4500"
    titleLbl:SetTextColor(C.primary[1],C.primary[2],C.primary[3],1); titleLbl:SetText("|cff" .. bh .. "r|rCastbar")
    AttachGuiTitleVersion(panel, titleLbl, "rCastbar", C.textDim[1], C.textDim[2], C.textDim[3])
    local subLbl=panel:CreateFontString(nil,"ARTWORK","GameFontNormalSmall"); subLbl:SetPoint("TOP",titleLbl,"BOTTOM",0,-4)
    subLbl:SetTextColor(
        math.min(1, C.primaryDim[1]+0.35), math.min(1, C.primaryDim[2]+0.35), math.min(1, C.primaryDim[3]+0.35), 1); subLbl:SetText(L["BlizzSubtitle"])
    local descLbl=panel:CreateFontString(nil,"ARTWORK","GameFontHighlightSmall"); descLbl:SetPoint("TOP",subLbl,"BOTTOM",0,-8)
    descLbl:SetWidth(420); descLbl:SetJustifyH("CENTER"); descLbl:SetTextColor(
        math.min(1, C.primaryDim[1]+0.40), math.min(1, C.primaryDim[2]+0.40), math.min(1, C.primaryDim[3]+0.40), 1); descLbl:SetText(L["BlizzDesc"])
    local openBtn=CreateFrame("Button",nil,panel,"BackdropTemplate"); openBtn:SetSize(220,38); openBtn:SetPoint("TOP",descLbl,"BOTTOM",0,-18)
    openBtn:SetBackdrop({bgFile="Interface\\Buttons\\WHITE8X8",edgeFile="Interface\\Buttons\\WHITE8X8",tile=false,edgeSize=1,insets={left=0,right=0,top=0,bottom=0}})
    openBtn:SetBackdropColor(
        math.min(1, C.primary[1]*0.26+0.05), math.min(1, C.primary[2]*0.26+0.05), math.min(1, C.primary[3]*0.26+0.05), 1)
    openBtn:SetBackdropBorderColor(
        math.min(1, C.primary[1]*0.52+0.10), math.min(1, C.primary[2]*0.52+0.10), math.min(1, C.primary[3]*0.52+0.10), 1)
    local openLbl=openBtn:CreateFontString(nil,"OVERLAY","GameFontNormal"); openLbl:SetPoint("CENTER")
    openLbl:SetTextColor(C.primary[1],C.primary[2],C.primary[3],1); openLbl:SetText(L["OpenSettings"])
    openBtn:SetScript("OnEnter",function(s) s:SetBackdropColor(
        math.min(1, C.primary[1]*0.42+0.08), math.min(1, C.primary[2]*0.42+0.08), math.min(1, C.primary[3]*0.42+0.08), 1)
        s:SetBackdropBorderColor(C.primaryHi[1],C.primaryHi[2],C.primaryHi[3],1); openLbl:SetTextColor(1,1,1,1) end)
    openBtn:SetScript("OnLeave",function(s) s:SetBackdropColor(
        math.min(1, C.primary[1]*0.26+0.05), math.min(1, C.primary[2]*0.26+0.05), math.min(1, C.primary[3]*0.26+0.05), 1)
        s:SetBackdropBorderColor(
            math.min(1, C.primary[1]*0.52+0.10), math.min(1, C.primary[2]*0.52+0.10), math.min(1, C.primary[3]*0.52+0.10), 1); openLbl:SetTextColor(C.primary[1],C.primary[2],C.primary[3],1) end)
    openBtn:SetScript("OnClick",function()
        C_Timer.After(0,function()
            if SettingsPanel and SettingsPanel:IsShown() then HideUIPanel(SettingsPanel) end
            if configFrame then configFrame:FadeIn() end
        end)
    end)
    if Settings and Settings.RegisterCanvasLayoutCategory then
        local cat=Settings.RegisterCanvasLayoutCategory(panel,"rCastbar"); cat.ID="rCastbar"; Settings.RegisterAddOnCategory(cat)
    elseif InterfaceOptions_AddCategory then panel.parent="AddOns"; InterfaceOptions_AddCategory(panel) end
end

-- ======================================================
-- INIT
-- ======================================================
local initFrame=CreateFrame("Frame"); initFrame:RegisterEvent("PLAYER_LOGIN")
initFrame:SetScript("OnEvent",function(self,event)
    if event~="PLAYER_LOGIN" then return end; self:UnregisterEvent("PLAYER_LOGIN")
    -- Re-bind L in case locale was loaded after this file's top-level assignment
    L = rCastbar_L or L or {}
    if rCastbarDB and not rCastbarDB.guiScale then rCastbarDB.guiScale=1.0 end
    local frame,tabs,tabPanels,SelectTab=BuildConfigFrame()
    BuildGeneralTab(tabPanels[1]); BuildUnitPanel(tabPanels[2],"player"); BuildUnitPanel(tabPanels[3],"target")
    CreateBlizzardPanel()
    frame:HookScript("OnShow",function(f) f:SetScale((rCastbarDB and rCastbarDB.guiScale)or 1.0) end)
    SelectTab(1)
end)

SLASH_RCASTBAR1="/rcastbar"; SLASH_RCASTBAR2="/rcb"
SlashCmdList["RCASTBAR"]=function(msg)
    msg=(msg or ""):lower():trim()
    if msg=="edit" or msg=="move" or msg=="toggle" then
        if InCombatLockdown() then return end
        local editBtn=_G.rCastbarEditBtn
        if editBtn and editBtn:GetScript("OnClick") then editBtn:Click() end
    else
        if configFrame then if configFrame:IsShown() then configFrame:FadeOut() else configFrame:FadeIn() end end
    end
end

-- ======================================================
-- MINIMAP ICON (LibDBIcon-1.0)
-- ======================================================
do
    local LDB     = LibStub and LibStub("LibDataBroker-1.1", true)
    local LDBIcon = LibStub and LibStub("LibDBIcon-1.0",      true)

    if LDB and LDBIcon then
        local broker = LDB:NewDataObject("rCastbar", {
            type  = "launcher",
            icon  = 237017,
            label = "rCastbar",
            OnClick = function(_, btn)
                if not configFrame then return end
                if btn == "RightButton" then return end
                if configFrame:IsShown() then configFrame:FadeOut() else configFrame:FadeIn() end
            end,
            OnTooltipShow = function(tt)
                if rCastbarGuiTheme and rCastbarGuiTheme.ApplyPalette then
                    rCastbarGuiTheme.ApplyPalette(C)
                end
                local mh = rCastbarGuiTheme and rCastbarGuiTheme.RGBToWoWColorHex
                    and rCastbarGuiTheme.RGBToWoWColorHex(C.primary[1], C.primary[2], C.primary[3])
                    or "ae4500"
                tt:AddLine("|cff" .. mh .. "r|rCastbar")
                tt:AddLine(" ")
                tt:AddLine(L["Tooltip_Click"], 0.9, 0.9, 0.9)
                tt:AddLine(L["Tooltip_Drag"],  0.6, 0.6, 0.6)
            end,
        })

        local minimapInitFrame = CreateFrame("Frame")
        minimapInitFrame:RegisterEvent("PLAYER_LOGIN")
        minimapInitFrame:SetScript("OnEvent", function(self)
            self:UnregisterEvent("PLAYER_LOGIN")
            if not rCastbarDB then rCastbarDB = {} end
            if not rCastbarDB.minimap then
                rCastbarDB.minimap = { minimapPos = 225, hide = false }
            end
            LDBIcon:Register("rCastbar", broker, rCastbarDB.minimap)
        end)
    end
end

function rCastbar_OnAddonCompartmentClick(_, btn)
    if btn == "RightButton" or not configFrame then return end
    if configFrame:IsShown() then configFrame:FadeOut() else configFrame:FadeIn() end
end

function rCastbar_OnAddonCompartmentEnter(_, tt)
    if not tt or not tt.AddLine then return end
    if rCastbarGuiTheme and rCastbarGuiTheme.ApplyPalette then
        rCastbarGuiTheme.ApplyPalette(C)
    end
    local ch = rCastbarGuiTheme and rCastbarGuiTheme.RGBToWoWColorHex
        and rCastbarGuiTheme.RGBToWoWColorHex(C.primary[1], C.primary[2], C.primary[3])
        or "ae4500"
    local loc = rCastbar_L or L or {}
    tt:AddLine("|cff" .. ch .. "r|rCastbar")
    tt:AddLine(" ")
    tt:AddLine(loc["Tooltip_Click"] or "Click to open settings", 0.9, 0.9, 0.9)
    tt:AddLine(loc["Tooltip_Drag"] or "", 0.6, 0.6, 0.6)
    if tt.Show then tt:Show() end
end

function rCastbar_OnAddonCompartmentLeave()
    GameTooltip:Hide()
end
