-- rCompassCastbar — GUI
-- Orange accent, modern toggles, custom dropdown, fade in/out
local A = ...
local L = rCompassCastbarL
-- Large logo (media/logo_big.tga); WoW loads without file extension
local MEDIA_LOGO_BIG = "Interface\\AddOns\\rCompassCastbar\\media\\logo_big"

-- ── COLOR SCHEME ─────────────────────────────────────────────────────────────
-- Fallback Addon-Orange; live: rCompassCastbarGuiTheme.ApplyPalette(C)
local C = {
  primary    = {0.965, 0.533, 0.000},
  primaryHi  = {1.000, 0.700, 0.250},
  primaryDim = {0.450, 0.220, 0.000},
  bg         = {0.060, 0.060, 0.060, 0.98},
  sidebar    = {0.048, 0.048, 0.048, 1.0},
  content    = {0.070, 0.070, 0.070, 0.97},
  border     = {0.20,  0.20,  0.20,  1.0},
  text       = {0.90,  0.90,  0.90},
  textDim    = {0.50,  0.50,  0.50},
  textBright = {1.00,  1.00,  1.00},
  separator  = {0.25,  0.25,  0.25,  0.55},
  toggleOff  = {0.10,  0.10,  0.10},
  warn       = {0.90,  0.55,  0.10,  1.0},
  warnBg     = {0.18,  0.10,  0.02,  1.0},
  warnBorder = {0.70,  0.38,  0.05,  1.0},
}

-- ── LAYOUT ───────────────────────────────────────────────────────────────────
local FRAME_W_DEFAULT = 720
local FRAME_H_DEFAULT = 580
local FRAME_W, FRAME_H = FRAME_W_DEFAULT, FRAME_H_DEFAULT
local TITLE_H   = 50
local SIDEBAR_W = 170
local BOT_H     = 40
local PAD       = 16
local function CW() return FRAME_W - SIDEBAR_W - 8 end
local _origCreateFrame = _G.CreateFrame
local SECTION_HEADER_H = 44
local SECTION_CARD_GAP = 8
local SECTION_ANIM_DUR = 0.18
local CloseActiveDropdown = function() end

-- Sidebar-Tab „Icon“: Spell-Icon (FileID), nicht Fragezeichen-Platzhalter
local GUI_TAB_ICON_SPELL_ID = 4238934
local function GetSpellIconTextureForTab(spellId)
  if C_Spell and C_Spell.GetSpellTexture then
    local fileID = C_Spell.GetSpellTexture(spellId)
    if fileID and fileID ~= 0 then return fileID end
  end
  return "Interface\\Icons\\INV_Misc_QuestionMark"
end

-- ── FORWARD DECLARATIONS ────────────────────────────────────────────────────
local configFrame
local allCheckboxes = {}
local allSliders = {}
local colorSwatchesRegistry

local function _WrapRegionSetPoint(region, pane, sec)
  if not region or not pane or not sec then return end
  local origSP = region.SetPoint
  region.SetPoint = function(self, pt, a, b, c, d)
    if a == pane then
      a = sec.settingsContainer
      if type(b) == "string" then
        d = (d or 0) - sec.startY + SECTION_HEADER_H
      elseif type(b) == "number" then
        c = (c or 0) - sec.startY + SECTION_HEADER_H
      end
    end
    return origSP(self, pt, a, b, c, d)
  end
end

local function CreateFrame(frameType, name, parent, template, id)
  local pane = parent
  local sec = pane and pane._activeSection
  local redirect = sec and sec.settingsContainer and not pane._suspendSectionTracking
  if redirect then parent = sec.settingsContainer end
  local f = _origCreateFrame(frameType, name, parent, template, id)
  if redirect then _WrapRegionSetPoint(f, pane, sec) end
  return f
end

-- ── RELOAD-DIALOG ────────────────────────────────────────────────────────────
-- One-shot dialog when a setting requires /reload
local reloadDialogFrame

local function ShowReloadDialog(onConfirm, onCancel)
  if rCompassCastbarGuiTheme and rCompassCastbarGuiTheme.ApplyPalette then
    rCompassCastbarGuiTheme.ApplyPalette(C)
  end
  if not reloadDialogFrame then
    local d = CreateFrame("Frame", "rCCBReloadDialog", UIParent, "BackdropTemplate")
    d:SetSize(380, 120)
    d:SetPoint("CENTER", UIParent, "CENTER", 0, 60)
    d:SetFrameStrata("FULLSCREEN_DIALOG")
    d:SetFrameLevel(200)
    d:SetBackdrop({bgFile="Interface\\Buttons\\WHITE8X8", edgeFile="Interface\\Buttons\\WHITE8X8",
      tile=false, edgeSize=2, insets={left=0,right=0,top=0,bottom=0}})
    d:SetBackdropColor(C.warnBg[1], C.warnBg[2], C.warnBg[3], C.warnBg[4])
    d:SetBackdropBorderColor(C.warnBorder[1], C.warnBorder[2], C.warnBorder[3], 1)

    -- Warn-Stripe oben
    local stripe = d:CreateTexture(nil, "BACKGROUND")
    stripe:SetHeight(3)
    stripe:SetPoint("TOPLEFT",  d, "TOPLEFT",  0, 0)
    stripe:SetPoint("TOPRIGHT", d, "TOPRIGHT", 0, 0)
    stripe:SetGradient("HORIZONTAL",
      CreateColor(C.warn[1], C.warn[2], C.warn[3], 1),
      CreateColor(C.warn[1]*0.22, C.warn[2]*0.22, C.warn[3]*0.22, 0))

    -- Icon
    local ico = d:CreateTexture(nil, "ARTWORK")
    ico:SetSize(22, 22)
    ico:SetPoint("TOPLEFT", d, "TOPLEFT", 12, -14)
    ico:SetTexture("Interface\\DialogFrame\\UI-Dialog-Icon-AlertNew")

    -- Nachricht
    local msg = d:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    msg:SetPoint("TOPLEFT", ico, "TOPRIGHT", 8, 0)
    msg:SetPoint("RIGHT", d, "RIGHT", -12, 0)
    msg:SetJustifyH("LEFT")
    msg:SetTextColor(C.warn[1], C.warn[2], C.warn[3], 1)
    msg:SetText(L["Reload Dialog Text"])

    -- Sub-text
    local sub = d:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    sub:SetPoint("TOPLEFT", msg, "BOTTOMLEFT", 0, -4)
    sub:SetPoint("RIGHT", d, "RIGHT", -12, 0)
    sub:SetJustifyH("LEFT")
    sub:SetTextColor(unpack(C.textDim))
    sub:SetText(L["Reload Dialog Subtext"])

    -- Buttons
    local function MkBtn(text, w, col, onClick)
      local btn = CreateFrame("Button", nil, d, "BackdropTemplate")
      btn:SetSize(w, 26)
      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, col[1]*0.28+0.04), math.min(1, col[2]*0.28+0.04), math.min(1, col[3]*0.28+0.04), 1)
      btn:SetBackdropBorderColor(
        math.min(1, col[1]*0.52+0.08), math.min(1, col[2]*0.52+0.08), math.min(1, col[3]*0.52+0.08), 1)
      local lbl = btn:CreateFontString(nil, "OVERLAY", "GameFontNormal")
      lbl:SetPoint("CENTER"); lbl:SetText(text)
      lbl:SetTextColor(col[1], col[2], col[3], 1)
      btn:SetScript("OnEnter", function(s)
        s:SetBackdropColor(
          math.min(1, col[1]*0.42+0.08), math.min(1, col[2]*0.42+0.08), math.min(1, col[3]*0.42+0.08), 1)
        s:SetBackdropBorderColor(col[1], col[2], col[3], 1)
        lbl:SetTextColor(1, 1, 1, 1)
      end)
      btn:SetScript("OnLeave", function(s)
        s:SetBackdropColor(
          math.min(1, col[1]*0.28+0.04), math.min(1, col[2]*0.28+0.04), math.min(1, col[3]*0.28+0.04), 1)
        s:SetBackdropBorderColor(
          math.min(1, col[1]*0.52+0.08), math.min(1, col[2]*0.52+0.08), math.min(1, col[3]*0.52+0.08), 1)
        lbl:SetTextColor(col[1], col[2], col[3], 1)
      end)
      btn:SetScript("OnClick", onClick)
      return btn
    end

    local reloadText = L["Reload Dialog Confirm"]
    local cancelText = L["Reload Dialog Cancel"]

    local btnReload = MkBtn(reloadText, 150, { C.warn[1], C.warn[2], C.warn[3] }, function()
      d:Hide()
      ReloadUI()
    end)
    btnReload:SetPoint("BOTTOMRIGHT", d, "BOTTOMRIGHT", -12, 10)

    local btnCancel = MkBtn(cancelText, 90, {0.55, 0.55, 0.55}, function()
      d:Hide()
      if d._onCancel then d._onCancel() end
    end)
    btnCancel:SetPoint("RIGHT", btnReload, "LEFT", -8, 0)

    d:Hide()
    reloadDialogFrame = d
  end

  reloadDialogFrame._onConfirm = onConfirm
  reloadDialogFrame._onCancel  = onCancel

  -- Fade in
  reloadDialogFrame:SetAlpha(0)
  reloadDialogFrame:Show()
  local t = 0
  local ticker
  ticker = C_Timer.NewTicker(0.016, function()
    t = t + 0.016
    reloadDialogFrame:SetAlpha(math.min(t / 0.18, 1))
    if t >= 0.18 then ticker:Cancel() end
  end)
end

-- ── COLOR PICKER HELPER ──────────────────────────────────────────────────────
local function RCCB_OpenColorPicker(r, g, b, onAccept)
  if ColorPickerFrame.SetupColorPickerAndShow then
    local prevR, prevG, prevB = r, g, b
    ColorPickerFrame:SetupColorPickerAndShow({
      swatchFunc  = function()
        local nr, ng, nb = ColorPickerFrame:GetColorRGB()
        onAccept(nr, ng, nb)
      end,
      cancelFunc = function() onAccept(prevR, prevG, prevB) end,
      hasOpacity = false, r = r, g = g, b = b,
    })
  else
    ColorPickerFrame.func = function()
      local nr, ng, nb = ColorPickerFrame:GetColorRGB()
      onAccept(nr, ng, nb)
    end
    ColorPickerFrame.cancelFunc = function() onAccept(r, g, b) end
    ColorPickerFrame.hasOpacity = false
    ColorPickerFrame:SetColorRGB(r, g, b)
    ShowUIPanel(ColorPickerFrame)
  end
end

local function MakeColorSwatch(parent, r, g, b, w, h)
  local btn = CreateFrame("Button", nil, parent, "BackdropTemplate")
  btn:SetSize(w or 22, h or 22)
  btn:SetBackdrop({bgFile="Interface\\Buttons\\WHITE8X8", edgeFile="Interface\\Buttons\\WHITE8X8",
    tile=false, edgeSize=1, insets={left=1,right=1,top=1,bottom=1}})
  btn:SetBackdropColor(r, g, b, 1)
  btn:SetBackdropBorderColor(0.25, 0.25, 0.25, 1)
  btn.r, btn.g, btn.b = r, g, b
  function btn:SetColor(nr, ng, nb)
    self.r, self.g, self.b = nr, ng, nb
    self:SetBackdropColor(nr, ng, nb, 1)
  end
  btn:SetScript("OnEnter", function(s)
    s:SetBackdropBorderColor(C.primaryHi[1], C.primaryHi[2], C.primaryHi[3], 1)
  end)
  btn:SetScript("OnLeave", function(s) s:SetBackdropBorderColor(0.25, 0.25, 0.25, 1) end)
  return btn
end

-- ── LOW-LEVEL HELPERS ────────────────────────────────────────────────────────
local function Tex(parent, layer)
  local pane = parent
  local sec = pane and pane._activeSection
  if sec and sec.settingsContainer and not pane._suspendSectionTracking then
    local t = sec.settingsContainer:CreateTexture(nil, layer or "ARTWORK")
    _WrapRegionSetPoint(t, pane, sec)
    return t
  end
  return parent:CreateTexture(nil, layer or "ARTWORK")
end

local function FS(parent, template)
  local pane = parent
  local sec = pane and pane._activeSection
  if sec and sec.settingsContainer and not pane._suspendSectionTracking then
    local fs = sec.settingsContainer:CreateFontString(nil, "OVERLAY", template or "GameFontNormal")
    _WrapRegionSetPoint(fs, pane, sec)
    return fs
  end
  return parent:CreateFontString(nil, "OVERLAY", template 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 = _origCreateFrame("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 _SectionEase(t)
  if t < 0.5 then return 2 * t * t end
  return 1 - ((-2 * t + 2) ^ 2) / 2
end

local function _Section_AnimateCard(pane, sec, targetH)
  local card = sec.card
  if not card then return end
  card._animFrame = card._animFrame or _origCreateFrame("Frame", nil, card)
  local af = card._animFrame
  local startH = card:GetHeight()
  local elapsed = 0
  if math.abs(targetH - startH) < 0.5 then
    card:SetHeight(targetH)
    if sec.settingsContainer then sec.settingsContainer:SetAlpha(sec.collapsed and 0 or 1) end
    return
  end
  af:SetScript("OnUpdate", function(self, dt)
    elapsed = elapsed + (dt or 0)
    local t = math.min(1, elapsed / SECTION_ANIM_DUR)
    local p = _SectionEase(t)
    local cur = startH + (targetH - startH) * p
    card:SetHeight(cur)
    if sec.settingsContainer then sec.settingsContainer:SetAlpha(sec.collapsed and (1 - p) or p) end
    if pane._RecomputeContentH then pane._RecomputeContentH() end
    if pane._contentH then pane:SetHeight(pane._contentH) end
    if pane._updateThumb then pane._updateThumb() end
    if t >= 1 then
      self:SetScript("OnUpdate", nil)
      card:SetHeight(targetH)
      if sec.settingsContainer then sec.settingsContainer:SetAlpha(sec.collapsed and 0 or 1) end
    end
  end)
end

local function _Section_SetCollapsed(pane, sec, collapsed, skipAnim)
  if sec.collapsed == collapsed then return end
  sec.collapsed = collapsed
  if sec.chevron and sec.chevron.SetAtlas then
    sec.chevron:SetAtlas(collapsed and "friendslist-categorybutton-arrow-right" or "friendslist-categorybutton-arrow-down")
  end
  local targetH = collapsed and SECTION_HEADER_H or (sec.fullHeight or (SECTION_HEADER_H + 120))
  if skipAnim then
    if sec.card then sec.card:SetHeight(targetH) end
    if sec.settingsContainer then sec.settingsContainer:SetAlpha(collapsed and 0 or 1) end
    if pane._RecomputeContentH then pane._RecomputeContentH() end
    if pane._contentH then pane:SetHeight(pane._contentH) end
    if pane._updateThumb then pane._updateThumb() end
  else
    _Section_AnimateCard(pane, sec, targetH)
  end
end

local function _Section_FinalizePane(pane)
  if not pane._sections or #pane._sections == 0 then return end
  if pane._sectionsFinalized then return end
  pane._sectionsFinalized = true
  local paneEndY = -(pane._sectionsEndY or 120)
  for i, sec in ipairs(pane._sections) do
    local nextStart = pane._sections[i + 1] and pane._sections[i + 1].startY or paneEndY
    local raw = math.abs(sec.startY - nextStart) - SECTION_CARD_GAP
    local fullH = math.max(SECTION_HEADER_H + 8, raw)
    sec.fullHeight = fullH
    if sec.card then sec.card:SetHeight(sec.collapsed and SECTION_HEADER_H or fullH) end
    if sec.settingsContainer then sec.settingsContainer:SetAlpha(sec.collapsed and 0 or 1) end
  end
  pane._RecomputeContentH = function()
    local first = pane._sections[1]
    local total = first and math.abs(first.startY) or 0
    for i, sec in ipairs(pane._sections) do
      total = total + (sec.collapsed and SECTION_HEADER_H or (sec.fullHeight or SECTION_HEADER_H))
      if i < #pane._sections then total = total + SECTION_CARD_GAP end
    end
    pane._contentH = total + 40
  end
  pane._RecomputeContentH()
end

local function EnablePaneSections(pane)
  if pane._sectionsEnabled then return end
  pane._sectionsEnabled = true
  pane._suspendSectionTracking = false
  pane._sections = {}
  pane._lastCard = nil
  pane._activeSection = nil
  local origSetH = pane.SetHeight
  pane.SetHeight = function(self, h)
    if self._sections and #self._sections > 0 and not self._sectionsFinalized then
      self._sectionsEndY = (h or 0) - 40
      _Section_FinalizePane(self)
      h = self._contentH or h
    end
    return origSetH(self, h)
  end
end

local function SectionHeader(parent, text, anchor, xOfs, yOfs)
  if parent._sectionsEnabled then
    local prevSuspend = parent._suspendSectionTracking
    parent._suspendSectionTracking = true
    local card = _origCreateFrame("Frame", nil, parent, "BackdropTemplate")
    card:SetClipsChildren(true)
    if parent._lastCard then
      card:SetPoint("TOPLEFT", parent._lastCard, "BOTTOMLEFT", 0, -SECTION_CARD_GAP)
      card:SetPoint("TOPRIGHT", parent._lastCard, "BOTTOMRIGHT", 0, -SECTION_CARD_GAP)
    else
      card:SetPoint("TOPLEFT", parent, "TOPLEFT", 0, yOfs or 0)
      card:SetPoint("TOPRIGHT", parent, "TOPRIGHT", 0, yOfs or 0)
    end
    card:SetHeight(SECTION_HEADER_H)
    local cBg = card:CreateTexture(nil, "BACKGROUND"); cBg:SetAllPoints(); cBg:SetColorTexture(0.09, 0.09, 0.11, 0.55)
    local header = _origCreateFrame("Button", nil, card)
    header:SetPoint("TOPLEFT", card, "TOPLEFT", 0, 0); header:SetPoint("TOPRIGHT", card, "TOPRIGHT", 0, 0)
    header:SetHeight(SECTION_HEADER_H); header:SetFrameLevel(card:GetFrameLevel() + 5)
    local lbl = card:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
    lbl:SetPoint("TOPLEFT", card, "TOPLEFT", (xOfs or 0) + 12, -math.floor((SECTION_HEADER_H - 12) / 2))
    lbl:SetTextColor(C.primary[1], C.primary[2], C.primary[3], 1); lbl:SetText(text)
    local chev = card:CreateTexture(nil, "OVERLAY")
    chev:SetSize(10, 10); chev:SetPoint("TOPRIGHT", card, "TOPRIGHT", -14, -math.floor((SECTION_HEADER_H - 10) / 2))
    if chev.SetAtlas then chev:SetAtlas("friendslist-categorybutton-arrow-right") end
    chev:SetVertexColor(C.primary[1], C.primary[2], C.primary[3], 0.85)
    local sc = _origCreateFrame("Frame", nil, card)
    sc:SetPoint("TOPLEFT", card, "TOPLEFT", 0, -SECTION_HEADER_H); sc:SetPoint("TOPRIGHT", card, "TOPRIGHT", 0, -SECTION_HEADER_H)
    sc:SetHeight(4000)
    sc:SetAlpha(0)
    local sec = { label = lbl, chevron = chev, card = card, settingsContainer = sc, startY = yOfs or 0, collapsed = true }
    tinsert(parent._sections, sec)
    parent._lastCard = card
    parent._activeSection = sec
    parent._suspendSectionTracking = prevSuspend
    header:SetScript("OnClick", function()
      if CloseActiveDropdown then CloseActiveDropdown() end
      _Section_FinalizePane(parent)
      _Section_SetCollapsed(parent, sec, not sec.collapsed)
    end)
    return lbl
  end
  local lbl = FS(parent, "GameFontNormal")
  lbl:SetPoint("TOPLEFT", anchor or parent, "TOPLEFT", xOfs or 0, yOfs 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",    0, yOfs or 0)
  line:SetGradient("HORIZONTAL",
    CreateColor(C.primary[1], C.primary[2], C.primary[3], 0.7),
    CreateColor(C.primary[1]*0.18, C.primary[2]*0.18, C.primary[3]*0.18, 0))
  return lbl
end

local function HRule(parent, anchor, yOfs)
  if parent._sectionsEnabled then return end
  local t = Tex(parent, "BACKGROUND")
  t:SetHeight(1)
  t:SetPoint("TOPLEFT",  anchor or parent, "TOPLEFT",  0, yOfs or 0)
  t:SetPoint("TOPRIGHT", parent,           "TOPRIGHT", 0, yOfs or 0)
  t:SetColorTexture(unpack(C.separator))
  return t
end

-- ── STYLED BUTTON ────────────────────────────────────────────────────────────
local function StyledButton(parent, text, w, h, onClick)
  local BTN_PAD_X = 20
  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 function ApplySize()
    local btnH = h or 26
    local btnW = w
    if not btnW then
      local sw = lbl:GetStringWidth()
      btnW = (sw > 0) and (sw + BTN_PAD_X) or 110
    end
    btn:SetSize(btnW, btnH)
  end
  ApplySize()
  btn:SetScript("OnShow", function(self) self:SetScript("OnShow", nil); ApplySize() 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
end

-- ── MODERN TOGGLE ────────────────────────────────────────────────────────────
local TOGGLE_W = 36
local TOGGLE_H = 18
local KNOB_SZ  = 12

-- needsReload: true → show reload dialog after toggle
-- liveSet: optional function run immediately on toggle (live effect)
local function ModernCheckbox(parent, label, desc, anchor, yOfs, getter, setter, needsReload, liveSet)
  local rowH = desc and 52 or 34
  local con  = CreateFrame("Frame", nil, parent)
  con:SetSize(CW() - 20, rowH)
  con:SetPoint("TOPLEFT", anchor or parent, "TOPLEFT", 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 9 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", 10, desc and 9 or 0)
  lbl:SetPoint("RIGHT", con, "RIGHT", needsReload and -28 or -4, 0)
  lbl:SetJustifyH("LEFT")
  lbl:SetTextColor(unpack(C.text))
  lbl:SetText(label)

  -- Reload-Hinweis-Icon
  if needsReload then
    local reloadHint = FS(con, "GameFontHighlightSmall")
    reloadHint:SetPoint("RIGHT", con, "RIGHT", -4, desc and 9 or 0)
    reloadHint:SetTextColor(C.warn[1], C.warn[2], C.warn[3], 0.75)
    local wh = (rCompassCastbarGuiTheme and rCompassCastbarGuiTheme.RGBToWoWColorHex)
      and rCompassCastbarGuiTheme.RGBToWoWColorHex(C.warn[1], C.warn[2], C.warn[3]) or "ffaa33"
    reloadHint:SetText("|cff" .. wh .. "R|r")
    reloadHint:SetJustifyH("RIGHT")
  end

  if desc then
    local d = FS(con, "GameFontHighlightSmall")
    d:SetPoint("TOPLEFT", lbl, "BOTTOMLEFT", 0, -3)
    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(C.border[1], C.border[2], C.border[3], 1)
      knob:ClearAllPoints()
      knob:SetPoint("LEFT", track, "LEFT", 3, 0)
      knob:SetBackdropColor(0.48, 0.40, 0.40, 1)
      knob:SetBackdropBorderColor(0.28, 0.22, 0.22, 1)
      lbl:SetTextColor(unpack(C.text))
    end
  end

  local function Refresh()
    local v = getter()
    SetVisual(v == true or v == 1)
  end
  Refresh()

  track:SetScript("OnEnter", function()
    local on = (getter() == true or getter() == 1)
    if on then
      track:SetBackdropBorderColor(C.primaryHi[1], C.primaryHi[2], C.primaryHi[3], 1)
    else
      track: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)
    end
  end)
  track:SetScript("OnLeave", function()
    local on = (getter() == true or getter() == 1)
    if on then
      track:SetBackdropBorderColor(C.primaryHi[1], C.primaryHi[2], C.primaryHi[3], 1)
    else
      track:SetBackdropBorderColor(C.border[1], C.border[2], C.border[3], 1)
    end
  end)
  track:SetScript("OnClick", function()
    local newVal = not (getter() == true or getter() == 1)
    SetVisual(newVal)
    setter(newVal)
    -- Immediate effect (live)
    if liveSet then liveSet(newVal) end
    -- Reload dialog if needed
    if needsReload then
      ShowReloadDialog(nil, nil)
    end
  end)

  con.Refresh = Refresh
  tinsert(allCheckboxes, con)
  return con
end

-- ── MODERN SLIDER ────────────────────────────────────────────────────────────
local function ModernSlider(parent, label, desc, minV, maxV, step, anchor, yOfs, getter, setter, fmt)
  local rowH = 58
  local con  = CreateFrame("Frame", nil, parent)
  con:SetSize(CW() - 20, rowH)
  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, 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 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 pct = 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*pct) 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(allSliders, con)
  return con
end

-- ── CUSTOM DROPDOWN ──────────────────────────────────────────────────────────
local activeDropdownPopup = nil
CloseActiveDropdown = function()
  if activeDropdownPopup then activeDropdownPopup:Hide(); activeDropdownPopup = nil end
end

local dropdownScrollGuard = CreateFrame("Frame")
dropdownScrollGuard:EnableMouseWheel(true)
dropdownScrollGuard:SetAllPoints(UIParent)
dropdownScrollGuard:SetFrameStrata("TOOLTIP")
dropdownScrollGuard:Hide()
dropdownScrollGuard:SetScript("OnMouseWheel", function()
  CloseActiveDropdown(); dropdownScrollGuard:Hide()
end)

local _origClose = CloseActiveDropdown
CloseActiveDropdown = function()
  _origClose(); dropdownScrollGuard:Hide()
end

local function ModernDropdown(parent, label, options, getter, setter, anchor, yOfs, width)
  local W      = width or 260
  local BTN_H  = 26
  local ITEM_H = 24
  local container = CreateFrame("Frame", nil, parent)
  container:SetPoint("TOPLEFT", anchor or parent, "TOPLEFT", 0, yOfs or 0)

  local lbl = FS(container, "GameFontHighlightSmall")
  lbl:SetPoint("TOPLEFT", 0, 0)
  lbl:SetTextColor(unpack(C.text)); lbl:SetText(label)

  local btn = CreateFrame("Button", nil, container, "BackdropTemplate")
  btn:SetSize(W, BTN_H)
  btn:SetBackdrop({bgFile="Interface\\Buttons\\WHITE8X8",
    edgeFile="Interface\\Tooltips\\UI-Tooltip-Border",
    tile=true, tileSize=8, edgeSize=12,
    insets={left=3,right=3,top=3,bottom=3}})
  btn:SetBackdropColor(0.06, 0.06, 0.08, 1)
  btn:SetBackdropBorderColor(0.35, 0.32, 0.42, 1)

  if label and label ~= "" then
    container:SetSize(W, 52); btn:SetPoint("TOPLEFT", 0, -20)
  else
    lbl:Hide(); container:SetSize(W, BTN_H + 4); btn:SetPoint("TOPLEFT", 0, -2)
  end

  local btnLbl = FS(btn, "GameFontHighlightSmall")
  btnLbl:SetPoint("LEFT", btn, "LEFT", 10, 0)
  btnLbl:SetPoint("RIGHT", btn, "RIGHT", -28, 0)
  btnLbl:SetJustifyH("LEFT"); btnLbl:SetTextColor(unpack(C.text))

  local arrow = Tex(btn, "OVERLAY"); arrow:SetSize(18, 18)
  arrow:SetPoint("RIGHT", btn, "RIGHT", -5, 0)
  arrow:SetTexture("Interface\\ChatFrame\\UI-ChatIcon-ScrollDown-Up")

  local rowRefreshers = {}

  local function UpdateDisplay()
    local val = getter()
    for _, opt in ipairs(options) do
      if opt.value == val then btnLbl:SetText(opt.text); return end
    end
    btnLbl:SetText(tostring(val or "\226\128\148"))
  end
  UpdateDisplay()

  local popup = CreateFrame("Frame", nil, UIParent, "BackdropTemplate")
  popup:SetFrameStrata("TOOLTIP")
  popup:SetWidth(W)
  popup:SetHeight(math.min(#options, 8) * ITEM_H + 4)
  popup:Hide()
  popup:SetBackdrop({bgFile="Interface\\Buttons\\WHITE8X8", edgeFile="Interface\\Buttons\\WHITE8X8",
    tile=false, edgeSize=1, insets={left=0,right=0,top=0,bottom=0}})
  popup:SetBackdropColor(0.07, 0.07, 0.07, 0.97)
  popup: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)

  for i, opt in ipairs(options) do
    local row = CreateFrame("Button", nil, popup, "BackdropTemplate")
    row:SetPoint("TOPLEFT",  popup, "TOPLEFT",  1, -(i-1)*ITEM_H - 1)
    row:SetPoint("TOPRIGHT", popup, "TOPRIGHT", -1, -(i-1)*ITEM_H - 1)
    row:SetHeight(ITEM_H)
    row:SetBackdrop({bgFile="Interface\\Buttons\\WHITE8X8", tile=false, edgeSize=0,
      insets={left=0,right=0,top=0,bottom=0}})
    row:SetBackdropColor(0,0,0,0)

    local pip = Tex(row, "ARTWORK"); pip:SetWidth(2); pip:SetHeight(ITEM_H-8)
    pip:SetPoint("LEFT", row, "LEFT", 4, 0)
    pip:SetColorTexture(C.primary[1], C.primary[2], C.primary[3], 1)

    local rowLbl = FS(row, "GameFontHighlightSmall")
    rowLbl:SetPoint("LEFT", row, "LEFT", 12, 0)
    rowLbl:SetTextColor(unpack(C.text)); rowLbl:SetText(opt.text)

    local function RefreshRow()
      if opt.value == getter() then
        rowLbl:SetTextColor(C.primaryHi[1], C.primaryHi[2], C.primaryHi[3], 1); pip:SetAlpha(1)
      else
        rowLbl:SetTextColor(unpack(C.text)); pip:SetAlpha(0)
      end
    end
    RefreshRow(); rowRefreshers[#rowRefreshers+1] = RefreshRow

    row:SetScript("OnEnter", function(s) s:SetBackdropColor(0.16, 0.16, 0.16, 1) end)
    row:SetScript("OnLeave", function(s) s:SetBackdropColor(0,0,0,0) end)
    row:SetScript("OnClick", function()
      setter(opt.value); UpdateDisplay(); CloseActiveDropdown()
    end)
  end

  btn:SetScript("OnEnter", function(s) s:SetBackdropBorderColor(C.primary[1], C.primary[2], C.primary[3], 1) end)
  btn:SetScript("OnLeave", function(s)
    s:SetBackdropBorderColor(C.border[1], C.border[2], C.border[3], C.border[4] or 1)
  end)
  btn:SetScript("OnClick", function()
    if popup:IsShown() then CloseActiveDropdown(); return end
    CloseActiveDropdown()
    popup:ClearAllPoints()
    local bLeft   = btn:GetLeft()
    local bBottom = btn:GetBottom()
    local bTop    = btn:GetTop()
    if bBottom and bBottom > (#options * ITEM_H + 20) then
      popup:SetPoint("TOPLEFT", nil, "BOTTOMLEFT", bLeft, bBottom - 2)
    else
      popup:SetPoint("BOTTOMLEFT", nil, "BOTTOMLEFT", bLeft, (bTop or 0) + 2)
    end
    UpdateDisplay()
    for r = 1, #rowRefreshers do rowRefreshers[r]() end
    popup:Show()
    activeDropdownPopup = popup
    dropdownScrollGuard:Show()
  end)

  container.Refresh = function()
    UpdateDisplay()
    for r = 1, #rowRefreshers do rowRefreshers[r]() end
  end
  return container
end

-- ── GUI FADE ──────────────────────────────────────────────────────────────────
local CFG_IN  = 0.18
local CFG_OUT = 0.14
local guiFadeTicker

local function StopGuiFade()
  if guiFadeTicker then guiFadeTicker:Cancel(); guiFadeTicker = nil end
end

local function ConfigFadeIn(f)
  if not f then return end
  StopGuiFade()
  f:SetAlpha(0); f:Show()
  local t = 0
  guiFadeTicker = C_Timer.NewTicker(0.016, function()
    t = t + 0.016
    f:SetAlpha(math.min(t / CFG_IN, 1))
    if t >= CFG_IN then StopGuiFade() end
  end)
end

local function ConfigFadeOut(f)
  if not f then return end
  StopGuiFade()
  local t = 0
  local s = f:GetAlpha()
  guiFadeTicker = C_Timer.NewTicker(0.016, function()
    t = t + 0.016
    local p = math.min(t / CFG_OUT, 1)
    f:SetAlpha(s * (1 - p))
    if p >= 1 then
      StopGuiFade(); f:Hide(); f:SetAlpha(1)
    end
  end)
end

-- ── LIVE-Getter/Setter ────────────────────────────────────────────────────────
-- Liest/schreibt in rCompassCastbarCfg; falls nicht existierend → Addon-Defaults
local function GetDefs()
  return {
    useClassColor                = false,
    useEmpowerStageColoring      = true,
    showCurrentCastIcon          = true,
    castIconSize                 = 160,
    castIconOffsetX              = -170,
    castIconOffsetY              = 0,
    showAssistedHighlightIcon    = true,
    assistedHighlightIconSize    = 160,
    assistedHighlightIconScale   = 0.2,
    assistedHighlightIconOffsetX = 320,
    assistedHighlightIconOffsetY = 0,
    castbarFadeInDuration        = 0.12,
    castbarFadeOutDuration       = 0.12,
    overallScale                 = 0.80,
    playerEnabled                = true,
    playerScale                  = 0.16,
    playerFollowCursor           = true,
    playerBgEnabled              = true,
    playerSparkEnabled           = true,
    gcdEnabled                   = false,
    gcdScale                     = 0.12,
    gcdBgEnabled                 = false,
    gcdSparkEnabled              = true,
    playerRingColor              = {1, 0.8, 0, 1},
    playerBgColor                = {0.4, 0.3, 0, 1},
    gcdRingColor                 = {0.5, 0.5, 0.5, 1},
    gcdBgColor                   = {0.5, 0.4, 0, 1},
    guiScale                     = 1.0,
  }
end

local function G(key)
  local cfg = _G.rCompassCastbarCfg
  if cfg and cfg[key] ~= nil then return cfg[key] end
  return GetDefs()[key]
end

local function S(key, val)
  if not _G.rCompassCastbarCfg then _G.rCompassCastbarCfg = {} end
  _G.rCompassCastbarCfg[key] = val
end

local function shallowCopy4(t)
  return {t[1], t[2], t[3], t[4] ~= nil and t[4] or 1}
end

local function RefreshAllGuiControls()
  for _, cb in ipairs(allCheckboxes) do
    if cb.Refresh then cb:Refresh() end
  end
  for _, sld in ipairs(allSliders) do
    if sld.Refresh then sld:Refresh() end
  end
  if colorSwatchesRegistry then
    for key, sw in pairs(colorSwatchesRegistry) do
      local c = G(key)
      if c and sw.SetColor then sw:SetColor(c[1], c[2], c[3]) end
    end
  end
  if configFrame and configFrame.SyncScale then
    configFrame:SyncScale()
  end
end

-- Reset only current tab keys to GetDefs() defaults
local function ResetCurrentTab(tabIdx)
  local D = GetDefs()
  if tabIdx == 1 then
    S("overallScale", D.overallScale)
    S("useClassColor", D.useClassColor)
    S("useEmpowerStageColoring", D.useEmpowerStageColoring)
    S("castbarFadeInDuration", D.castbarFadeInDuration)
    S("castbarFadeOutDuration", D.castbarFadeOutDuration)
  elseif tabIdx == 2 then
    S("playerEnabled", D.playerEnabled)
    S("playerFollowCursor", D.playerFollowCursor)
    S("playerScale", D.playerScale)
    S("playerBgEnabled", D.playerBgEnabled)
    S("playerSparkEnabled", D.playerSparkEnabled)
    local pf = rCompassCastbarFrames and rCompassCastbarFrames.playerFrame
    if pf then pf:SetScale(D.playerScale) end
  elseif tabIdx == 3 then
    S("gcdEnabled", D.gcdEnabled)
    S("gcdScale", D.gcdScale)
    S("gcdBgEnabled", D.gcdBgEnabled)
    S("gcdSparkEnabled", D.gcdSparkEnabled)
    local gf = rCompassCastbarFrames and rCompassCastbarFrames.gcdFrame
    if gf then gf:SetScale(D.gcdScale) end
  elseif tabIdx == 4 then
    S("showCurrentCastIcon", D.showCurrentCastIcon)
    S("castIconSize", D.castIconSize)
    S("castIconOffsetX", D.castIconOffsetX)
    S("castIconOffsetY", D.castIconOffsetY)
    S("showAssistedHighlightIcon", D.showAssistedHighlightIcon)
    S("assistedHighlightIconSize", D.assistedHighlightIconSize)
    S("assistedHighlightIconScale", D.assistedHighlightIconScale)
    S("assistedHighlightIconOffsetX", D.assistedHighlightIconOffsetX)
    S("assistedHighlightIconOffsetY", D.assistedHighlightIconOffsetY)
    if rCompassCastbar_SetShowCastIcon then rCompassCastbar_SetShowCastIcon(D.showCurrentCastIcon) end
    if rCompassCastbar_SetShowAssistedIcon then rCompassCastbar_SetShowAssistedIcon(D.showAssistedHighlightIcon) end
    local pf = rCompassCastbarFrames and rCompassCastbarFrames.playerFrame
    if rCompassCastbarFrames and rCompassCastbarFrames.SyncPlayerFrameFromSavedCfg and pf then
      rCompassCastbarFrames.SyncPlayerFrameFromSavedCfg(pf)
    end
    local af = rCompassCastbarFrames and rCompassCastbarFrames.assistedHighlight
    if rCompassCastbarFrames and rCompassCastbarFrames.SyncAssistedHighlightFromSavedCfg and af then
      rCompassCastbarFrames.SyncAssistedHighlightFromSavedCfg(af)
    end
    if rCompassCastbarFrames and rCompassCastbarFrames.UpdateAssistedHighlightIcon then
      rCompassCastbarFrames.UpdateAssistedHighlightIcon()
    end
  elseif tabIdx == 5 then
    S("playerRingColor", shallowCopy4(D.playerRingColor))
    S("playerBgColor", shallowCopy4(D.playerBgColor))
    S("gcdRingColor", shallowCopy4(D.gcdRingColor))
    S("gcdBgColor", shallowCopy4(D.gcdBgColor))
    local pr, pb, gr, gb = D.playerRingColor, D.playerBgColor, D.gcdRingColor, D.gcdBgColor
    if rCompassCastbar_SetRingColor then
      rCompassCastbar_SetRingColor("player", pr[1], pr[2], pr[3], pr[4] or 1)
      rCompassCastbar_SetRingColor("gcd", gr[1], gr[2], gr[3], gr[4] or 1)
    end
    if rCompassCastbar_SetBgColor then
      rCompassCastbar_SetBgColor("player", pb[1], pb[2], pb[3], pb[4] or 1)
      rCompassCastbar_SetBgColor("gcd", gb[1], gb[2], gb[3], gb[4] or 1)
    end
  end
  RefreshAllGuiControls()
end

-- ── SCROLL HELPER ─────────────────────────────────────────────────────────────
local function MakeScroll(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+4), 4)

  local sc = CreateFrame("Frame", nil, sf)
  sc:SetWidth(CW() - 30)
  sf:SetScrollChild(sc)
  EnablePaneSections(sc)

  local track = CreateFrame("Frame", nil, container)
  track:SetSize(TRACK_W, 400)
  track:SetPoint("TOPRIGHT", container, "TOPRIGHT", -2, -40)
  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 function SyncTrackHeight()
    local h = container:GetHeight() - 44
    if h > 0 then track:SetHeight(h) end
  end
  container:HookScript("OnSizeChanged", SyncTrackHeight)
  C_Timer.After(0, SyncTrackHeight)

  local function ScrollThumbBase()
    local pr, pg, pb = C.primary[1], C.primary[2], C.primary[3]
    return math.min(1, pr*0.5+0.10), math.min(1, pg*0.5+0.10), math.min(1, pb*0.5+0.10)
  end
  local function ScrollThumbBorder()
    local pr, pg, pb = C.primary[1], C.primary[2], C.primary[3]
    return math.min(1, pr*0.62+0.06), math.min(1, pg*0.62+0.06), math.min(1, pb*0.62+0.06)
  end

  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}})
  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(20, trackH * ratio)
    thumb:SetHeight(thumbH)
    local maxScroll = contentH - viewH
    local cur    = sf:GetVerticalScroll()
    local pct    = cur / maxScroll
    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)
    local cur = self:GetVerticalScroll()
    self:SetVerticalScroll(math.max(0, math.min(maxScroll, cur - d * 30)))
    UpdateThumb()
  end)

  local dragStartY, dragStartScroll
  thumb:RegisterForClicks("LeftButtonUp")
  thumb:EnableMouse(true)
  thumb:SetScript("OnMouseDown", function(self, btn)
    if btn ~= "LeftButton" then return end
    local _, my = GetCursorPosition()
    local scale = UIParent:GetEffectiveScale()
    dragStartY      = my / scale
    dragStartScroll = sf:GetVerticalScroll()
    self:SetScript("OnUpdate", function()
      local _, cy = GetCursorPosition()
      cy = cy / scale
      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
      local scrollDelta = (delta / maxY) * maxScroll
      sf:SetVerticalScroll(math.max(0, math.min(maxScroll, dragStartScroll + scrollDelta)))
      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, btn)
    if btn ~= "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)
  local function OnScrollFrameSized()
    local w = sf:GetWidth()
    if w and w > 40 then sc:SetWidth(math.max(100, w - 8)) end
    UpdateThumb()
  end
  sf:SetScript("OnSizeChanged", OnScrollFrameSized)

  container.sf = sf
  container.sc = sc
  container.UpdateThumb = UpdateThumb
  sc._updateThumb = UpdateThumb
  return container, sf, sc
end

-- ── BUILD MAIN FRAME ─────────────────────────────────────────────────────────
local activeTab = 0

local function BuildConfigFrame()
  if configFrame then return configFrame end

  if rCompassCastbarGuiTheme and rCompassCastbarGuiTheme.ApplyPalette then
    rCompassCastbarGuiTheme.ApplyPalette(C)
  end

  _G.rCompassCastbarCfg = _G.rCompassCastbarCfg or {}
  local _cfg = _G.rCompassCastbarCfg
  FRAME_W = math.max(480, math.min(1100, math.floor((_cfg.guiFrameW or FRAME_W_DEFAULT) + 0.5)))
  FRAME_H = math.max(380, math.min(900, math.floor((_cfg.guiFrameH or FRAME_H_DEFAULT) + 0.5)))

  local frame = CreateFrame("Frame", "rCompassCastbarConfigFrame", 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()
  tinsert(UISpecialFrames, "rCompassCastbarConfigFrame")
  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.52 + 0.06),
    math.min(1, C.primary[2] * 0.52 + 0.06),
    math.min(1, C.primary[3] * 0.52 + 0.06), 1)

  frame:SetScript("OnShow", function()
    for _, cb in ipairs(allCheckboxes) do if cb.Refresh then cb:Refresh() end end
  end)

  -- ── TITLE BAR (drag anchor) ──────────────────────────────────────────────
  local titleBg = FlatFrame(frame,
    C.primary[1]*0.22, C.primary[2]*0.18, C.primary[3]*0.18, 0,
    C.primary[1]*0.50, C.primary[2]*0.38, C.primary[3]*0.36, 0)
  titleBg:SetPoint("TOPLEFT", frame, "TOPLEFT", 0, 0)
  titleBg:SetPoint("TOPRIGHT", frame, "TOPRIGHT", 0, 0)
  titleBg:SetHeight(34)

  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)

  -- rOrbs-style: no floating addon title/logo in the main content shell.

  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, frame)
  closeBtn:SetSize(28, 28)
  closeBtn:SetPoint("TOPRIGHT", frame, "TOPRIGHT", -12, -10)
  closeBtn:SetFrameLevel(frame:GetFrameLevel() + 25)
  local closeBg = Tex(closeBtn, "BACKGROUND")
  closeBg:SetAllPoints()
  closeBg:SetColorTexture(0, 0, 0, 0)
  local closeLbl = closeBtn:CreateFontString(nil, "OVERLAY")
  closeLbl:SetFont("Fonts\\FRIZQT__.TTF", 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() ConfigFadeOut(frame) end)

  -- ── SIDEBAR ───────────────────────────────────────────────────────────────
  local sidebar = FlatFrame(frame, C.sidebar[1], C.sidebar[2], C.sidebar[3], C.sidebar[4])
  sidebar:SetPoint("TOPLEFT",    frame, "TOPLEFT",   2,  -2)
  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)

  -- ── BOTTOM BAR ────────────────────────────────────────────────────────────
  local bot = FlatFrame(frame,
    (C.botFill and C.botFill[1]) or 0.07, (C.botFill and C.botFill[2]) or 0.07, (C.botFill and C.botFill[3]) or 0.07, (C.botFill and C.botFill[4]) or 1,
    (C.botEdge and C.botEdge[1]) or 0.18, (C.botEdge and C.botEdge[2]) or 0.18, (C.botEdge and C.botEdge[3]) or 0.18, (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)

  frame.SyncScale = nil

  -- Preview button in bottom bar
  StyledButton(bot, L["Preview"], 90, 26, function()
    if rCompassCastbar_Preview then rCompassCastbar_Preview() end
  end):SetPoint("RIGHT", bot, "RIGHT", -110, 0)

  local function HideSearchDrop()
    if bot.searchDrop then bot.searchDrop:Hide() end
  end

  local searchShell = FlatFrame(bot, 0.045, 0.045, 0.05, 0.98, C.primary[1] * 0.33, C.primary[2] * 0.33, C.primary[3] * 0.33, 1)
  searchShell:SetSize(270, 24)
  searchShell:SetPoint("LEFT", bot, "LEFT", 10, 0)
  local searchIcon = Tex(searchShell, "ARTWORK")
  searchIcon:SetSize(14, 14)
  searchIcon:SetPoint("LEFT", searchShell, "LEFT", 7, 0)
  searchIcon:SetTexture("Interface\\Common\\UI-Searchbox-Icon")
  searchIcon:SetVertexColor(C.primary[1], C.primary[2], C.primary[3], 0.85)
  local botSearchBox = CreateFrame("EditBox", nil, searchShell)
  botSearchBox:SetAutoFocus(false)
  botSearchBox:SetFont("Fonts\\FRIZQT__.TTF", 11, "")
  botSearchBox:SetTextColor(0.90, 0.90, 0.90, 1)
  botSearchBox:SetPoint("LEFT", searchIcon, "RIGHT", 5, 0)
  botSearchBox:SetPoint("RIGHT", searchShell, "RIGHT", -22, 0)
  botSearchBox:SetHeight(22)
  botSearchBox:SetScript("OnEscapePressed", function(self) self:ClearFocus(); HideSearchDrop() end)
  local searchPlaceholder = FS(searchShell, "GameFontDisableSmall")
  searchPlaceholder:SetPoint("LEFT", botSearchBox, "LEFT", 0, 0)
  searchPlaceholder:SetText("Search settings...")
  local searchClear = CreateFrame("Button", nil, searchShell)
  searchClear:SetSize(14, 14)
  searchClear:SetPoint("RIGHT", searchShell, "RIGHT", -5, 0)
  local clearText = FS(searchClear, "GameFontNormalSmall")
  clearText:SetPoint("CENTER", 0, 0)
  clearText:SetText("x")
  clearText:SetTextColor(C.textDim[1], C.textDim[2], C.textDim[3], 1)
  searchClear:SetScript("OnClick", function()
    botSearchBox:SetText("")
    botSearchBox:ClearFocus()
    searchPlaceholder:Show()
    HideSearchDrop()
  end)
  local searchDrop = FlatFrame(bot, 0.04, 0.04, 0.05, 0.98, C.primary[1] * 0.36, C.primary[2] * 0.36, C.primary[3] * 0.36, 1)
  searchDrop:SetPoint("TOPLEFT", searchShell, "BOTTOMLEFT", 0, -4)
  searchDrop:SetSize(300, 10)
  searchDrop:SetFrameStrata("DIALOG")
  searchDrop:SetFrameLevel(frame:GetFrameLevel() + 100)
  searchDrop:Hide()
  bot.searchDrop = searchDrop

  -- ── TABS ──────────────────────────────────────────────────────────────────
  local TAB_DEFS = {
    { id="general", label=L["General"], icon="Interface\\Icons\\INV_Misc_Note_06" },
    { id="player",  label=L["Player"],  icon="Interface\\Icons\\Ability_Hunter_SteadyShot" },
    { id="gcd",     label=L["GCD"],     icon="Interface\\Icons\\Spell_Nature_Cyclone" },
    { id="icon",    label=L["Icon"],    icon=GetSpellIconTextureForTab(GUI_TAB_ICON_SPELL_ID) },
    { id="colors",  label=L["Colors"],  icon="Interface\\Icons\\Inv_misc_gem_pearl_05" },
  }

  local TAB_H   = 40
  local TAB_GAP = 2
  local tabs      = {}
  local tabPanels = {}

  local contentHeader = FS(contentBg, "GameFontNormalLarge")
  contentHeader:SetPoint("TOPLEFT", contentBg, "TOPLEFT", PAD, -14)
  contentHeader:SetTextColor(C.primary[1], C.primary[2], C.primary[3], 0.7)

  local chLine = Tex(contentBg, "BACKGROUND"); chLine:SetHeight(1)
  chLine:SetPoint("TOPLEFT",  contentBg, "TOPLEFT",  PAD, -34)
  chLine:SetPoint("TOPRIGHT", contentBg, "TOPRIGHT", -PAD, -34)
  chLine:SetColorTexture(C.primary[1], C.primary[2], C.primary[3], 0.20)

  local function SelectTab(idx)
    CloseActiveDropdown()
    activeTab = 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.18+0.06), math.min(1, C.primary[2]*0.18+0.06), math.min(1, C.primary[3]*0.18+0.06), 1)
        tab.icon:SetVertexColor(C.primary[1], C.primary[2], C.primary[3], 1)
        tab.lbl:SetTextColor(1, 1, 1, 1)
      else
        tab.accent:Hide()
        tab.bgTex:SetColorTexture(C.sidebar[1], C.sidebar[2], C.sidebar[3], 1)
        tab.icon:SetVertexColor(
          math.min(1, C.primaryDim[1]+0.28), math.min(1, C.primaryDim[2]+0.28), math.min(1, C.primaryDim[3]+0.28), 1)
        tab.lbl:SetTextColor(1, 1, 1, 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.primary[1]*0.12+0.04, C.primary[2]*0.12+0.04, C.primary[3]*0.12+0.04, 1)

    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(
      math.min(1, C.primaryDim[1]+0.28), math.min(1, C.primaryDim[2]+0.28), math.min(1, C.primaryDim[3]+0.28), 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(1, 1, 1, 1)
    tab.lbl:SetText(td.label)

    local ii = i
    tab:SetScript("OnEnter", function(s)
      if activeTab ~= ii then
        s.bgTex:SetColorTexture(
          math.min(1, C.primary[1]*0.14+0.05), math.min(1, C.primary[2]*0.14+0.05), math.min(1, C.primary[3]*0.14+0.05), 1)
        s.lbl:SetTextColor(1, 1, 1, 1)
        s.icon:SetVertexColor(C.primary[1], C.primary[2], C.primary[3], 1)
      end
    end)
    tab:SetScript("OnLeave", function(s)
      if activeTab ~= ii then
        s.bgTex:SetColorTexture(C.sidebar[1], C.sidebar[2], C.sidebar[3], 1)
        s.lbl:SetTextColor(1, 1, 1, 1)
        s.icon:SetVertexColor(
          math.min(1, C.primaryDim[1]+0.28), math.min(1, C.primaryDim[2]+0.28), math.min(1, C.primaryDim[3]+0.28), 1)
      end
    end)
    tab:SetScript("OnClick", function() SelectTab(ii) end)
    tabs[i] = tab
  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 (rCompassCastbarGuiTheme and rCompassCastbarGuiTheme.ApplyCustomClassIconToTexture
        and rCompassCastbarGuiTheme.ApplyCustomClassIconToTexture(ciTex)) then
      classIconHolder:Hide()
    else
      classIconHolder:EnableMouse(true)
      classIconHolder:SetScript("OnEnter", function()
        GameTooltip:SetOwner(classIconHolder, "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

    local ver = AddOnVersionFromToc("rCompassCastbar")
    local sbVer
    if ver then
      sbVer = sidebar:CreateFontString(nil, "OVERLAY")
      sbVer:SetFont("Fonts\\FRIZQT__.TTF", 11, "")
      sbVer:SetPoint("BOTTOM", classIconHolder, "TOP", 0, 10)
      sbVer:SetTextColor(C.textDim[1], C.textDim[2], C.textDim[3], 0.95)
      sbVer:SetText("v" .. ver)
    end

    local sbTitle = sidebar:CreateFontString(nil, "OVERLAY")
    sbTitle:SetFont("Fonts\\FRIZQT__.TTF", 18, "OUTLINE")
    if sbVer then
      sbTitle:SetPoint("BOTTOM", sbVer, "TOP", 0, 2)
    else
      sbTitle:SetPoint("BOTTOM", classIconHolder, "TOP", 0, 10)
    end
    sbTitle:SetText("rCompassCastbar")
    sbTitle:SetTextColor(1, 1, 1, 1)

    local sbFooterDiv = sidebar:CreateTexture(nil, "ARTWORK")
    sbFooterDiv:SetHeight(1)
    sbFooterDiv:SetPoint("LEFT", sidebar, "LEFT", 8, 0)
    sbFooterDiv:SetPoint("RIGHT", sidebar, "RIGHT", -8, 0)
    sbFooterDiv:SetPoint("BOTTOM", sbTitle, "TOP", 0, 8)
    sbFooterDiv:SetColorTexture(C.primary[1] * 0.55, C.primary[2] * 0.55, C.primary[3] * 0.55, 0.30)
  end

  -- ═══════════════════════════════════════════════════════════════════════════
  -- TAB 1 – GENERAL
  -- ═══════════════════════════════════════════════════════════════════════════
  local panelG, sfG, sG = MakeScroll(contentBg)
  do
    local y = -PAD

    local logoHolder = CreateFrame("Frame", nil, sG)
    logoHolder:SetSize(CW() - 30, 254)
    logoHolder:SetPoint("TOPLEFT", sG, "TOPLEFT", 0, y)
    local logoImg = Tex(logoHolder, "ARTWORK")
    logoImg:SetTexture(MEDIA_LOGO_BIG)
    logoImg:SetSize(381, 254)
    logoImg:SetPoint("TOP", logoHolder, "TOP", 0, 0)
    y = y - 262

    SectionHeader(sG, L["General Settings"], sG, 0, y); y = y - 36

    ModernSlider(sG, L["Overall Scale"], L["Overall Scale Desc"],
      0.3, 2.0, 0.05, sG, y,
      function() return G("overallScale") end,
      function(v) S("overallScale", v) end,
      function(v) return string.format("%.0f%%", v*100) end)
    y = y - 68

    HRule(sG, sG, y); y = y - 22

    SectionHeader(sG, L["ClassColorEmpowerSection"], sG, 0, y); y = y - 36

    -- needsReload=true, no liveSet → reload dialog only
    ModernCheckbox(sG, L["Use Class Color"], L["Use Class Color Desc"],
      sG, y,
      function() return G("useClassColor") end,
      function(v) S("useClassColor", v) end,
      true)  -- needsReload
    y = y - 52

    ModernCheckbox(sG, L["Use Empower Coloring"], L["Use Empower Coloring Desc"],
      sG, y,
      function() return G("useEmpowerStageColoring") end,
      function(v) S("useEmpowerStageColoring", v) end,
      true)  -- needsReload
    y = y - 52

    HRule(sG, sG, y); y = y - 22

    SectionHeader(sG, L["Fade Section"], sG, 0, y); y = y - 36

    ModernSlider(sG, L["Fade In Duration"], L["Fade In Duration Desc"],
      0.0, 1.0, 0.01, sG, y,
      function() return G("castbarFadeInDuration") end,
      function(v) S("castbarFadeInDuration", v) end,
      function(v) return string.format("%.2fs", v) end)
    y = y - 68

    ModernSlider(sG, L["Fade Out Duration"], L["Fade Out Duration Desc"],
      0.0, 1.0, 0.01, sG, y,
      function() return G("castbarFadeOutDuration") end,
      function(v) S("castbarFadeOutDuration", v) end,
      function(v) return string.format("%.2fs", v) end)
    y = y - 68

    -- Reload-Hinweis-Box
    HRule(sG, sG, y); y = y - 16
    local reloadNote = FlatFrame(sG, C.warnBg[1], C.warnBg[2], C.warnBg[3], C.warnBg[4],
      C.warnBorder[1], C.warnBorder[2], C.warnBorder[3], 1)
    reloadNote:SetSize(CW() - 30, 34)
    reloadNote:SetPoint("TOPLEFT", sG, "TOPLEFT", 0, y)
    local reloadIco = reloadNote:CreateTexture(nil, "ARTWORK")
    reloadIco:SetSize(16, 16)
    reloadIco:SetPoint("LEFT", reloadNote, "LEFT", 8, 0)
    reloadIco:SetTexture("Interface\\DialogFrame\\UI-Dialog-Icon-AlertNew")
    local reloadNoteLbl = FS(reloadNote, "GameFontHighlightSmall")
    reloadNoteLbl:SetPoint("LEFT", reloadIco, "RIGHT", 6, 0)
    reloadNoteLbl:SetPoint("RIGHT", reloadNote, "RIGHT", -8, 0)
    reloadNoteLbl:SetTextColor(C.warn[1], C.warn[2], C.warn[3], 1)
    reloadNoteLbl:SetJustifyH("LEFT")
    local wh = (rCompassCastbarGuiTheme and rCompassCastbarGuiTheme.RGBToWoWColorHex)
      and rCompassCastbarGuiTheme.RGBToWoWColorHex(C.warn[1], C.warn[2], C.warn[3]) or "ffaa33"
    reloadNoteLbl:SetText(string.gsub(L["Reload Note"], "R", "|cff" .. wh .. "R|r", 1))
    y = y - 44

    y = y - 12
    HRule(sG, sG, y); y = y - 16
    StyledButton(sG, L["Reset Tab"], 170, 26, function() ResetCurrentTab(1) end)
      :SetPoint("TOPLEFT", sG, "TOPLEFT", 0, y)
    y = y - 34

    sG:SetHeight(math.abs(y) + 40)
  end
  tabPanels[1] = panelG

  -- ═══════════════════════════════════════════════════════════════════════════
  -- TAB 2 – PLAYER RING
  -- ═══════════════════════════════════════════════════════════════════════════
  local panelP, sfP, sP = MakeScroll(contentBg); panelP:Hide()
  do
    local y = -PAD

    SectionHeader(sP, L["Player Settings"], sP, 0, y); y = y - 36

    -- needsReload=true (Frames werden beim Init erstellt)
    ModernCheckbox(sP, L["Player Enabled"], L["Player Enabled Desc"],
      sP, y,
      function() return G("playerEnabled") end,
      function(v) S("playerEnabled", v) end,
      true)
    y = y - 52

    ModernCheckbox(sP, L["Follow Cursor"], L["Follow Cursor Desc"],
      sP, y,
      function() return G("playerFollowCursor") end,
      function(v) S("playerFollowCursor", v) end,
      true)
    y = y - 52

    ModernSlider(sP, L["Player Scale"], L["Player Scale Desc"],
      0.05, 0.60, 0.01, sP, y,
      function() return G("playerScale") end,
      function(v)
        S("playerScale", v)
        -- Live: Skalierung sofort anwenden
        local pf = rCompassCastbarFrames and rCompassCastbarFrames.playerFrame
        if pf then pf:SetScale(v) end
      end,
      function(v) return string.format("%.2f", v) end)
    y = y - 68

    HRule(sP, sP, y); y = y - 22

    ModernCheckbox(sP, L["Player BG Enabled"], L["Player BG Enabled Desc"],
      sP, y,
      function() return G("playerBgEnabled") end,
      function(v) S("playerBgEnabled", v) end,
      true)
    y = y - 52

    ModernCheckbox(sP, L["Player Spark Enabled"], L["Player Spark Enabled Desc"],
      sP, y,
      function() return G("playerSparkEnabled") end,
      function(v) S("playerSparkEnabled", v) end,
      true)
    y = y - 52

    y = y - 12
    HRule(sP, sP, y); y = y - 16
    StyledButton(sP, L["Reset Tab"], 170, 26, function() ResetCurrentTab(2) end)
      :SetPoint("TOPLEFT", sP, "TOPLEFT", 0, y)
    y = y - 34

    sP:SetHeight(math.abs(y) + 40)
  end
  tabPanels[2] = panelP

  -- ═══════════════════════════════════════════════════════════════════════════
  -- TAB 3 – GCD RING
  -- ═══════════════════════════════════════════════════════════════════════════
  local panelGCD, sfGCD, sGCD = MakeScroll(contentBg); panelGCD:Hide()
  do
    local y = -PAD

    SectionHeader(sGCD, L["GCD Settings"], sGCD, 0, y); y = y - 36

    ModernCheckbox(sGCD, L["GCD Enabled"], L["GCD Enabled Desc"],
      sGCD, y,
      function() return G("gcdEnabled") end,
      function(v) S("gcdEnabled", v) end,
      true)
    y = y - 52

    ModernSlider(sGCD, L["GCD Scale"], L["GCD Scale Desc"],
      0.05, 0.40, 0.01, sGCD, y,
      function() return G("gcdScale") end,
      function(v)
        S("gcdScale", v)
        local gf = rCompassCastbarFrames and rCompassCastbarFrames.gcdFrame
        if gf then gf:SetScale(v) end
      end,
      function(v) return string.format("%.2f", v) end)
    y = y - 68

    HRule(sGCD, sGCD, y); y = y - 22

    ModernCheckbox(sGCD, L["GCD BG Enabled"], L["GCD BG Enabled Desc"],
      sGCD, y,
      function() return G("gcdBgEnabled") end,
      function(v) S("gcdBgEnabled", v) end,
      true)
    y = y - 52

    ModernCheckbox(sGCD, L["GCD Spark Enabled"], L["GCD Spark Enabled Desc"],
      sGCD, y,
      function() return G("gcdSparkEnabled") end,
      function(v) S("gcdSparkEnabled", v) end,
      true)
    y = y - 52

    y = y - 12
    HRule(sGCD, sGCD, y); y = y - 16
    StyledButton(sGCD, L["Reset Tab"], 170, 26, function() ResetCurrentTab(3) end)
      :SetPoint("TOPLEFT", sGCD, "TOPLEFT", 0, y)
    y = y - 34

    sGCD:SetHeight(math.abs(y) + 40)
  end
  tabPanels[3] = panelGCD

  -- ═══════════════════════════════════════════════════════════════════════════
  -- TAB 4 – ICON
  -- ═══════════════════════════════════════════════════════════════════════════
  local panelI, sfI, sI = MakeScroll(contentBg); panelI:Hide()
  do
    local y = -PAD

    SectionHeader(sI, L["Icon Settings"], sI, 0, y); y = y - 36

    -- LIVE: rCompassCastbar_SetShowCastIcon drives frames directly
    ModernCheckbox(sI, L["Show Cast Icon"], L["Show Cast Icon Desc"],
      sI, y,
      function() return G("showCurrentCastIcon") end,
      function(v) S("showCurrentCastIcon", v) end,
      false,  -- no needsReload
      function(v)
        if rCompassCastbar_SetShowCastIcon then rCompassCastbar_SetShowCastIcon(v) end
      end)
    y = y - 52

    ModernSlider(sI, L["Cast Icon Size"], L["Cast Icon Size Desc"],
      40, 320, 4, sI, y,
      function() return G("castIconSize") end,
      function(v) S("castIconSize", v) end,
      function(v) return math.floor(v).."px" end)
    y = y - 68

    ModernSlider(sI, L["Cast Icon Offset X"], L["Cast Icon Offset X Desc"],
      -400, 400, 2, sI, y,
      function() return G("castIconOffsetX") end,
      function(v) S("castIconOffsetX", v) end,
      function(v) return tostring(math.floor(v)).."px" end)
    y = y - 68

    ModernSlider(sI, L["Cast Icon Offset Y"], L["Cast Icon Offset Y Desc"],
      -400, 400, 2, sI, y,
      function() return G("castIconOffsetY") end,
      function(v) S("castIconOffsetY", v) end,
      function(v) return tostring(math.floor(v)).."px" end)
    y = y - 68

    HRule(sI, sI, y); y = y - 22

    SectionHeader(sI, L["Show Assisted Icon"], sI, 0, y); y = y - 36

    -- LIVE: rCompassCastbar_SetShowAssistedIcon drives frame directly
    ModernCheckbox(sI, L["Show Assisted Icon"], L["Show Assisted Icon Desc"],
      sI, y,
      function() return G("showAssistedHighlightIcon") end,
      function(v) S("showAssistedHighlightIcon", v) end,
      false,  -- no needsReload
      function(v)
        if rCompassCastbar_SetShowAssistedIcon then rCompassCastbar_SetShowAssistedIcon(v) end
      end)
    y = y - 52

    ModernSlider(sI, L["Assisted Icon Size"], L["Assisted Icon Size Desc"],
      40, 320, 4, sI, y,
      function() return G("assistedHighlightIconSize") end,
      function(v) S("assistedHighlightIconSize", v) end,
      function(v) return math.floor(v).."px" end)
    y = y - 68

    ModernSlider(sI, L["Assisted Icon Scale"], L["Assisted Icon Scale Desc"],
      0.05, 1.0, 0.01, sI, y,
      function() return G("assistedHighlightIconScale") end,
      function(v)
        S("assistedHighlightIconScale", v)
        -- Live: Scale auf dem Frame anwenden
        local af = rCompassCastbarFrames and rCompassCastbarFrames.assistedHighlight
        if af then af:SetScale(v) end
      end,
      function(v) return string.format("%.2f", v) end)
    y = y - 68

    ModernSlider(sI, L["Assisted Offset X"], L["Assisted Offset X Desc"],
      -600, 600, 2, sI, y,
      function() return G("assistedHighlightIconOffsetX") end,
      function(v)
        S("assistedHighlightIconOffsetX", v)
        local af = rCompassCastbarFrames and rCompassCastbarFrames.assistedHighlight
        if af and af.cursorAnchor then
          af:ClearAllPoints()
          af:SetPoint("CENTER", af.cursorAnchor, "CENTER", v, G("assistedHighlightIconOffsetY"))
        end
      end,
      function(v) return tostring(math.floor(v)).."px" end)
    y = y - 68

    ModernSlider(sI, L["Assisted Offset Y"], L["Assisted Offset Y Desc"],
      -600, 600, 2, sI, y,
      function() return G("assistedHighlightIconOffsetY") end,
      function(v)
        S("assistedHighlightIconOffsetY", v)
        local af = rCompassCastbarFrames and rCompassCastbarFrames.assistedHighlight
        if af and af.cursorAnchor then
          af:ClearAllPoints()
          af:SetPoint("CENTER", af.cursorAnchor, "CENTER", G("assistedHighlightIconOffsetX"), v)
        end
      end,
      function(v) return tostring(math.floor(v)).."px" end)
    y = y - 68

    y = y - 12
    HRule(sI, sI, y); y = y - 16
    StyledButton(sI, L["Reset Tab"], 170, 26, function() ResetCurrentTab(4) end)
      :SetPoint("TOPLEFT", sI, "TOPLEFT", 0, y)
    y = y - 34

    sI:SetHeight(math.abs(y) + 40)
  end
  tabPanels[4] = panelI

  -- ═══════════════════════════════════════════════════════════════════════════
  -- TAB 5 – COLORS
  -- ═══════════════════════════════════════════════════════════════════════════
  local panelCol, sfCol, sCol = MakeScroll(contentBg); panelCol:Hide()
  do
    local y = -PAD

    SectionHeader(sCol, L["Colors"], sCol, 0, y); y = y - 36

    local colorDesc = FS(sCol, "GameFontHighlightSmall")
    colorDesc:SetPoint("TOPLEFT", sCol, "TOPLEFT", 0, y)
    colorDesc:SetTextColor(unpack(C.textDim))
    colorDesc:SetText(L["Color Desc"])
    y = y - 22

    local swSize = 22
    local swatches = {}

    local colorDefs = {
      { key="playerRingColor", label=L["Player Ring Color"], def={1,0.8,0,1},       liveUnit="player", liveType="ring" },
      { key="playerBgColor",   label=L["Player BG Color"],   def={0.4,0.3,0,1},    liveUnit="player", liveType="bg"   },
      { key="gcdRingColor",    label=L["GCD Ring Color"],    def={0.5,0.5,0.5,1}, liveUnit="gcd",    liveType="ring" },
      { key="gcdBgColor",      label=L["GCD BG Color"],      def={0.5,0.4,0,1},    liveUnit="gcd",    liveType="bg"   },
    }

    for _, def in ipairs(colorDefs) do
      local row = CreateFrame("Frame", nil, sCol)
      row:SetSize(CW() - 30, swSize + 8)
      row:SetPoint("TOPLEFT", sCol, "TOPLEFT", 0, y)

      local col = G(def.key) or def.def
      local sw = MakeColorSwatch(row, col[1], col[2], col[3], swSize, swSize)
      sw:SetPoint("LEFT", row, "LEFT", 0, 0)
      swatches[def.key] = sw

      local rl = FS(row)
      rl:SetPoint("LEFT", sw, "RIGHT", 8, 0)
      rl:SetTextColor(unpack(C.text))
      rl:SetText(def.label)

      local resetBtn = StyledButton(row, L["Reset"], 70, 20, function()
        local d = def.def
        S(def.key, {d[1], d[2], d[3], d[4] or 1})
        sw:SetColor(d[1], d[2], d[3])
        -- Live-Reset
        if def.liveType == "ring" and rCompassCastbar_SetRingColor then
          rCompassCastbar_SetRingColor(def.liveUnit, d[1], d[2], d[3], d[4] or 1)
        elseif def.liveType == "bg" and rCompassCastbar_SetBgColor then
          rCompassCastbar_SetBgColor(def.liveUnit, d[1], d[2], d[3], d[4] or 1)
        end
      end)
      resetBtn:SetPoint("RIGHT", row, "RIGHT", 0, 0)

      local dkey = def.key
      local ddef = def.def
      local dliveUnit = def.liveUnit
      local dliveType = def.liveType
      sw:SetScript("OnClick", function(self)
        RCCB_OpenColorPicker(self.r, self.g, self.b, function(nr, ng, nb)
          local cur = G(dkey) or ddef
          S(dkey, {nr, ng, nb, cur[4] or 1})
          self:SetColor(nr, ng, nb)
          -- Live-Farbe
          if dliveType == "ring" and rCompassCastbar_SetRingColor then
            rCompassCastbar_SetRingColor(dliveUnit, nr, ng, nb, cur[4] or 1)
          elseif dliveType == "bg" and rCompassCastbar_SetBgColor then
            rCompassCastbar_SetBgColor(dliveUnit, nr, ng, nb, cur[4] or 1)
          end
        end)
      end)

      y = y - (swSize + 10)
    end

    colorSwatchesRegistry = swatches

    -- Swatches bei Show refreshen
    sCol:HookScript("OnShow", function()
      C_Timer.After(0, function()
        for key, sw in pairs(swatches) do
          local c = G(key)
          if c then sw:SetColor(c[1], c[2], c[3]) end
        end
      end)
    end)

    y = y - 12
    HRule(sCol, sCol, y); y = y - 16
    StyledButton(sCol, L["Reset Tab"], 170, 26, function() ResetCurrentTab(5) end)
      :SetPoint("TOPLEFT", sCol, "TOPLEFT", 0, y)
    y = y - 34

    sCol:SetHeight(math.abs(y) + 40)
  end
  tabPanels[5] = panelCol

  local SEARCH_ENTRIES = {
    { label = L["General Settings"], tab = 1, section = L["General Settings"] },
    { label = L["Overall Scale"], tab = 1, section = L["General Settings"] },
    { label = L["Use Class Color"], tab = 1, section = L["ClassColorEmpowerSection"] },
    { label = L["Use Empower Coloring"], tab = 1, section = L["ClassColorEmpowerSection"] },
    { label = L["Fade In Duration"], tab = 1, section = L["Fade Section"] },
    { label = L["Fade Out Duration"], tab = 1, section = L["Fade Section"] },
    { label = L["Player Settings"], tab = 2, section = L["Player Settings"] },
    { label = L["Player Enabled"], tab = 2, section = L["Player Settings"] },
    { label = L["Follow Cursor"], tab = 2, section = L["Player Settings"] },
    { label = L["Player Scale"], tab = 2, section = L["Player Settings"] },
    { label = L["Player BG Enabled"], tab = 2, section = L["Player Settings"] },
    { label = L["Player Spark Enabled"], tab = 2, section = L["Player Settings"] },
    { label = L["GCD Settings"], tab = 3, section = L["GCD Settings"] },
    { label = L["GCD Enabled"], tab = 3, section = L["GCD Settings"] },
    { label = L["GCD Scale"], tab = 3, section = L["GCD Settings"] },
    { label = L["GCD BG Enabled"], tab = 3, section = L["GCD Settings"] },
    { label = L["GCD Spark Enabled"], tab = 3, section = L["GCD Settings"] },
    { label = L["Icon Settings"], tab = 4, section = L["Icon Settings"] },
    { label = L["Show Cast Icon"], tab = 4, section = L["Icon Settings"] },
    { label = L["Cast Icon Size"], tab = 4, section = L["Icon Settings"] },
    { label = L["Cast Icon Offset X"], tab = 4, section = L["Icon Settings"] },
    { label = L["Cast Icon Offset Y"], tab = 4, section = L["Icon Settings"] },
    { label = L["Show Assisted Icon"], tab = 4, section = L["Show Assisted Icon"] },
    { label = L["Assisted Icon Size"], tab = 4, section = L["Show Assisted Icon"] },
    { label = L["Assisted Icon Scale"], tab = 4, section = L["Show Assisted Icon"] },
    { label = L["Assisted Offset X"], tab = 4, section = L["Show Assisted Icon"] },
    { label = L["Assisted Offset Y"], tab = 4, section = L["Show Assisted Icon"] },
    { label = L["Colors"], tab = 5, section = L["Colors"] },
    { label = L["Player Ring Color"], tab = 5, section = L["Colors"] },
    { label = L["Player BG Color"], tab = 5, section = L["Colors"] },
    { label = L["GCD Ring Color"], tab = 5, section = L["Colors"] },
    { label = L["GCD BG Color"], tab = 5, section = L["Colors"] },
  }

  local function ExpandSectionForEntry(entry)
    local panel = tabPanels[entry.tab]
    if not panel or not panel.sc or not panel.sf then return end
    local pane = panel.sc
    if pane._sections and entry.section then
      _Section_FinalizePane(pane)
      for _, sec in ipairs(pane._sections) do
        if sec.label and sec.label:GetText() == entry.section then
          _Section_SetCollapsed(pane, sec, false, true)
        else
          _Section_SetCollapsed(pane, sec, true, true)
        end
      end
    end
    panel.sf:SetVerticalScroll(0)
    if panel.UpdateThumb then panel.UpdateThumb() end
  end

  local function NavigateToResult(entry)
    if not entry then return end
    SelectTab(entry.tab)
    C_Timer.After(0, function()
      ExpandSectionForEntry(entry)
      HideSearchDrop()
    end)
  end

  local function RunSearch(q)
    local query = strlower(strtrim(q or ""))
    if query == "" then HideSearchDrop(); return end
    for _, child in ipairs({ searchDrop:GetChildren() }) do child:Hide() end
    local results = {}
    for _, e in ipairs(SEARCH_ENTRIES) do
      local label = e.label or ""
      if label ~= "" and strfind(strlower(label), query, 1, true) then
        tinsert(results, e)
      end
    end
    if #results == 0 then HideSearchDrop(); return end
    local rowH, maxRows = 24, 8
    local shown = math.min(#results, maxRows)
    searchDrop:SetHeight(shown * rowH + 8)
    searchDrop:Show()
    for i = 1, shown do
      local e = results[i]
      local row = searchDrop.rows and searchDrop.rows[i]
      if not row then
        searchDrop.rows = searchDrop.rows or {}
        row = CreateFrame("Button", nil, searchDrop)
        row:SetHeight(rowH)
        row:SetPoint("LEFT", searchDrop, "LEFT", 4, 0)
        row:SetPoint("RIGHT", searchDrop, "RIGHT", -4, 0)
        row.label = FS(row, "GameFontHighlightSmall")
        row.label:SetPoint("LEFT", row, "LEFT", 6, 0)
        row.label:SetPoint("RIGHT", row, "RIGHT", -6, 0)
        row.label:SetJustifyH("LEFT")
        row:SetScript("OnEnter", function(self) self.label:SetTextColor(C.primaryHi[1], C.primaryHi[2], C.primaryHi[3], 1) end)
        row:SetScript("OnLeave", function(self) self.label:SetTextColor(0.9, 0.9, 0.9, 1) end)
        searchDrop.rows[i] = row
      end
      row:SetPoint("TOPLEFT", searchDrop, "TOPLEFT", 4, -4 - (i - 1) * rowH)
      row.label:SetText((e.label or "") .. "   |cff888888in " .. (TAB_DEFS[e.tab] and TAB_DEFS[e.tab].label or "") .. "|r")
      row:SetScript("OnClick", function()
        botSearchBox:SetText(e.label or "")
        NavigateToResult(e)
      end)
      row:Show()
    end
  end

  botSearchBox:SetScript("OnTextChanged", function(self)
    local txt = self:GetText() or ""
    if txt == "" then searchPlaceholder:Show(); HideSearchDrop() else searchPlaceholder:Hide(); RunSearch(txt) end
  end)
  botSearchBox:SetScript("OnEnterPressed", function(self)
    local txt = strtrim(self:GetText() or "")
    if txt == "" then HideSearchDrop(); self:ClearFocus(); return end
    for _, e in ipairs(SEARCH_ENTRIES) do
      if strfind(strlower(e.label or ""), strlower(txt), 1, true) then
        NavigateToResult(e)
        self:ClearFocus()
        return
      end
    end
    HideSearchDrop()
    self:ClearFocus()
  end)
  botSearchBox:SetScript("OnEditFocusLost", function()
    C_Timer.After(0.12, function() if not MouseIsOver(searchDrop) then HideSearchDrop() end end)
  end)

  -- ── FINISH ───────────────────────────────────────────────────────────────
  function frame:FadeIn()  ConfigFadeIn(self)  end
  function frame:FadeOut() ConfigFadeOut(self) end

  SelectTab(1)

  if RR_ConfigResizeGrip_Attach then
    RR_ConfigResizeGrip_Attach({
      frame = frame,
      titleBg = titleBg,
      dbTable = _G.rCompassCastbarCfg,
      texturePath = "Interface\\AddOns\\rCompassCastbar\\media\\resize.tga",
      gripName = "rCompassCastbarConfigResizeGrip",
      defW = FRAME_W_DEFAULT,
      defH = FRAME_H_DEFAULT,
      minW = 480,
      maxW = 1100,
      minH = 380,
      maxH = 900,
      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"],
      gripParent = frame,
      gripPoint = "BOTTOMRIGHT",
      gripAnchor = frame,
      gripRelativePoint = "BOTTOMRIGHT",
      gripX = -2,
      gripY = 2,
      onSaved = function()
        FRAME_W = _G.rCompassCastbarCfg.guiFrameW or FRAME_W
        FRAME_H = _G.rCompassCastbarCfg.guiFrameH or FRAME_H
      end,
      onLiveResize = function(w, h)
        FRAME_W, FRAME_H = w, h
        local cw = CW() - 20
        for _, c in ipairs(allCheckboxes) 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(allSliders) do
          if c and c.SetSize then c:SetSize(cw, c:GetHeight()); if c.Refresh then c:Refresh() end end
        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
  return frame
end

-- ── PUBLIC API ────────────────────────────────────────────────────────────────
function rCompassCastbar_ToggleConfig()
  local f = BuildConfigFrame()
  if f:IsShown() then ConfigFadeOut(f) else ConfigFadeIn(f) end
end

-- ── BLIZZARD OPTIONS ───────────────────
local function RegisterBlizzardPanel()
  if rCompassCastbarGuiTheme and rCompassCastbarGuiTheme.ApplyPalette then
    rCompassCastbarGuiTheme.ApplyPalette(C)
  end

  local panel = CreateFrame("Frame", "rCompassCastbarInterfacePanel")
  panel.name = "rCompassCastbar"

  local logo = panel:CreateTexture(nil, "ARTWORK")
  logo:SetTexture(MEDIA_LOGO_BIG)
  logo:SetSize(381, 254)
  logo:SetPoint("TOP", panel, "TOP", 0, -28)

  local optsHex = (rCompassCastbarGuiTheme and rCompassCastbarGuiTheme.RGBToWoWColorHex)
    and rCompassCastbarGuiTheme.RGBToWoWColorHex(C.primary[1], C.primary[2], C.primary[3])
    or "f68800"

  local titleLbl = panel:CreateFontString(nil, "ARTWORK", "GameFontNormalHuge")
  titleLbl:SetPoint("TOP", logo, "BOTTOM", 0, -10)
  titleLbl:SetTextColor(1, 1, 1, 1)
  titleLbl:SetText("|cff" .. optsHex .. "r|rCompassCastbar")
  AttachGuiTitleVersion(panel, titleLbl, "rCompassCastbar", C.textDim[1], C.textDim[2], C.textDim[3])

  local descLbl = panel:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
  descLbl:SetPoint("TOP", titleLbl, "BOTTOM", 0, -10)
  descLbl:SetWidth(440)
  descLbl:SetJustifyH("CENTER")
  descLbl:SetTextColor(unpack(C.textDim))
  descLbl:SetText(L["addon_desc"] or "")

  local openBtn = StyledButton(panel, L["RCCB Settings"], 240, 40, function()
    C_Timer.After(0, function()
      if SettingsPanel and SettingsPanel:IsShown() then HideUIPanel(SettingsPanel) end
      rCompassCastbar_ToggleConfig()
    end)
  end)
  openBtn:SetPoint("TOP", descLbl, "BOTTOM", 0, -20)

  local accentL = panel:CreateTexture(nil, "ARTWORK")
  accentL:SetHeight(1)
  accentL:SetWidth(240)
  accentL:SetPoint("TOP", openBtn, "BOTTOM", 0, -6)
  accentL:SetGradient("HORIZONTAL",
    CreateColor(0, 0, 0, 0),
    CreateColor(C.primary[1], C.primary[2], C.primary[3], 0.6))
  local accentR = panel:CreateTexture(nil, "ARTWORK")
  accentR:SetHeight(1)
  accentR:SetWidth(240)
  accentR:SetPoint("TOP", openBtn, "BOTTOM", 0, -6)
  accentR:SetGradient("HORIZONTAL",
    CreateColor(C.primary[1], C.primary[2], C.primary[3], 0.6),
    CreateColor(0, 0, 0, 0))

  local cat = Settings.RegisterCanvasLayoutCategory(panel, panel.name)
  Settings.RegisterAddOnCategory(cat)
end

-- ── ADDON COMPARTMENT ────────────────────────────────────────────────────────
function AddonCompartmentFunc(addonName, mouseButton)
  if addonName == "rCompassCastbar" then
    rCompassCastbar_ToggleConfig()
  end
end

-- ── BOOTSTRAP ────────────────────────────────────────────────────────────────
local boot = CreateFrame("Frame")
boot:RegisterEvent("PLAYER_LOGIN")
boot:SetScript("OnEvent", function(self)
  self:UnregisterEvent("PLAYER_LOGIN")
  BuildConfigFrame()
  RegisterBlizzardPanel()
end)
