-- 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 BOTTOM_RELOAD_NOTE_H = 34
local BOTTOM_RELOAD_NOTE_PAD = 4
local BOTTOM_RELOAD_RESERVE = BOTTOM_RELOAD_NOTE_H + BOTTOM_RELOAD_NOTE_PAD * 2
local CloseActiveDropdown = function() end
local MaybeRefreshGuiPreviews = 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 activeTab = 0

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 AttachBottomReloadNote(container, noteText)
  if not container or not container.sf or container._bottomReloadNote then return end
  local sf = container.sf
  local topPad = container._scrollTopPad or 40
  local trackRight = container._trackRight or 12
  container._bottomReloadReserve = BOTTOM_RELOAD_RESERVE

  sf:ClearAllPoints()
  sf:SetPoint("TOPLEFT", container, "TOPLEFT", 2, -topPad)
  sf:SetPoint("BOTTOMRIGHT", container, "BOTTOMRIGHT", -trackRight, BOTTOM_RELOAD_RESERVE)

  local note = FlatFrame(container, C.warnBg[1], C.warnBg[2], C.warnBg[3], C.warnBg[4],
    C.warnBorder[1], C.warnBorder[2], C.warnBorder[3], 1)
  note:SetHeight(BOTTOM_RELOAD_NOTE_H)
  note:SetPoint("BOTTOMLEFT", container, "BOTTOMLEFT", 0, BOTTOM_RELOAD_NOTE_PAD)
  note:SetPoint("BOTTOMRIGHT", container, "BOTTOMRIGHT", -trackRight, BOTTOM_RELOAD_NOTE_PAD)
  note:SetFrameLevel((container:GetFrameLevel() or 1) + 5)

  local reloadIco = note:CreateTexture(nil, "ARTWORK")
  reloadIco:SetSize(16, 16)
  reloadIco:SetPoint("LEFT", note, "LEFT", 8, 0)
  reloadIco:SetTexture("Interface\\DialogFrame\\UI-Dialog-Icon-AlertNew")

  local reloadLbl = FS(note, "GameFontHighlightSmall")
  reloadLbl:SetPoint("LEFT", reloadIco, "RIGHT", 6, 0)
  reloadLbl:SetPoint("RIGHT", note, "RIGHT", -8, 0)
  reloadLbl:SetTextColor(C.warn[1], C.warn[2], C.warn[3], 1)
  reloadLbl:SetJustifyH("LEFT")
  reloadLbl:SetText(noteText or "")

  container._bottomReloadNote = note
  if container._syncTrackHeight then container._syncTrackHeight() end
  if container.UpdateThumb then container.UpdateThumb() end
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_CollapseOthers(pane, keepSec)
  if not pane._sections then return end
  for _, other in ipairs(pane._sections) do
    if other ~= keepSec and not other.collapsed then
      _Section_SetCollapsed(pane, other, true)
    end
  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)
      local willExpand = sec.collapsed
      if willExpand then
        _Section_CollapseOthers(parent, sec)
      end
      _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
    MaybeRefreshGuiPreviews()
    -- 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, needsReload)
  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", needsReload and -22 or 0, -1)
  valLbl:SetTextColor(C.primary[1], C.primary[2], C.primary[3], 1)

  if needsReload then
    local reloadHint = FS(con, "GameFontHighlightSmall")
    reloadHint:SetPoint("TOPRIGHT", 0, -1)
    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, -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 dragStartVal, dragDirty = getter(), false
  local function UpdateVal()
    local v = sl:GetValue()
    valLbl:SetText(fmt and fmt(v) or string.format("%.2f", v))
    UpdateFill(); setter(v)
    MaybeRefreshGuiPreviews()
    if needsReload and v ~= dragStartVal then dragDirty = true end
  end
  sl:SetScript("OnValueChanged", UpdateVal)
  if needsReload then
    sl:SetScript("OnMouseDown", function() dragStartVal = getter(); dragDirty = false end)
    sl:SetScript("OnMouseUp", function()
      if dragDirty then ShowReloadDialog(nil, nil) end
      dragStartVal = getter(); dragDirty = false
    end)
  end
  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()
      MaybeRefreshGuiPreviews()
    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

MaybeRefreshGuiPreviews = function()
  if configFrame and configFrame:IsShown() and activeTab >= 2 and activeTab <= 5 then
    if rCompassCastbar_RefreshAllGuiPreviews then rCompassCastbar_RefreshAllGuiPreviews() end
  end
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
  if rCompassCastbar_RefreshAllGuiPreviews then
    rCompassCastbar_RefreshAllGuiPreviews()
  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, topPad)
  topPad = topPad or 40
  local TRACK_W = 8
  local container = CreateFrame("Frame", nil, parent)
  container:SetAllPoints(parent)
  container._scrollTopPad = topPad
  container._trackRight = TRACK_W + 4
  container._bottomReloadReserve = 4

  local sf = CreateFrame("ScrollFrame", nil, container)
  sf:SetPoint("TOPLEFT",     container, "TOPLEFT",              2, -topPad)
  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, -topPad)
  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 bottom = container._bottomReloadReserve or 4
    local h = container:GetHeight() - topPad - bottom
    if h > 0 then track:SetHeight(h) end
  end
  container._syncTrackHeight = SyncTrackHeight
  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 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
    if frame._showGuiPreviewForTab and activeTab >= 2 and activeTab <= 5 then
      frame._showGuiPreviewForTab(activeTab)
    end
  end)
  frame:SetScript("OnHide", function()
    if rCompassCastbar_HideGuiPreviews then rCompassCastbar_HideGuiPreviews() 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 PREVIEW_BAR_H = 108
  local PREVIEW_INNER_GAP = 6
  local PREVIEW_CAPTION_H = 24
  local PREVIEW_GAP = 8
  local PREVIEW_STACK_H = PREVIEW_BAR_H + PREVIEW_INNER_GAP + PREVIEW_CAPTION_H
  local contentScrollTopPad = 38 + PREVIEW_STACK_H + PREVIEW_GAP
  local previewArea

  previewArea = CreateFrame("Frame", nil, contentBg)
  previewArea:SetPoint("TOPLEFT", contentBg, "TOPLEFT", PAD, -38)
  previewArea:SetPoint("TOPRIGHT", contentBg, "TOPRIGHT", -PAD, -38)
  previewArea:SetHeight(PREVIEW_STACK_H)
  previewArea:Hide()

  local previewHolder = CreateFrame("Frame", nil, previewArea)
  previewHolder:SetHeight(PREVIEW_BAR_H)
  previewHolder:SetPoint("TOPLEFT", previewArea, "TOPLEFT", 0, 0)
  previewHolder:SetPoint("TOPRIGHT", previewArea, "TOPRIGHT", 0, 0)
  previewHolder:SetClipsChildren(false)

  local pvCaption = FS(previewArea, "GameFontNormalSmall")
  pvCaption:SetPoint("TOP", previewHolder, "BOTTOM", 0, -PREVIEW_INNER_GAP)
  pvCaption:SetPoint("LEFT", previewArea, "LEFT", 0, 0)
  pvCaption:SetPoint("RIGHT", previewArea, "RIGHT", 0, 0)
  pvCaption:SetJustifyH("CENTER")
  pvCaption:SetText(L["Gui Preview Panel"] or "Live preview")
  pvCaption:SetTextColor(C.textDim[1], C.textDim[2], C.textDim[3], 1)

  local function ShowGuiPreviewForTab(idx)
    local fr = rCompassCastbarFrames
    if not previewArea or not previewHolder or not fr then return end
    local showPreview = (idx >= 2 and idx <= 5)
    previewArea:SetShown(showPreview)
    if not showPreview then
      if fr.HideAllGuiPreviews then fr.HideAllGuiPreviews() end
      return
    end
    if idx == 3 then
      if fr.HideGuiPreviewFrame then fr.HideGuiPreviewFrame("player") end
      if fr.EnsureGuiPreviewInHolder then fr.EnsureGuiPreviewInHolder("gcd", previewHolder) end
      if fr.SetGuiPreviewAssistedVisible then fr.SetGuiPreviewAssistedVisible(false) end
    else
      if fr.HideGuiPreviewFrame then fr.HideGuiPreviewFrame("gcd") end
      if fr.EnsureGuiPreviewInHolder then fr.EnsureGuiPreviewInHolder("player", previewHolder) end
      if idx == 4 and fr.EnsureGuiPreviewAssistedInHolder then
        fr.EnsureGuiPreviewAssistedInHolder(previewHolder)
      elseif fr.SetGuiPreviewAssistedVisible then
        fr.SetGuiPreviewAssistedVisible(false)
      end
    end
    if fr.RefreshAllGuiPreviews then fr.RefreshAllGuiPreviews() end
  end
  frame._showGuiPreviewForTab = ShowGuiPreviewForTab

  previewHolder:SetScript("OnShow", function()
    if activeTab >= 2 and activeTab <= 5 then
      ShowGuiPreviewForTab(activeTab)
    end
  end)
  previewHolder:SetScript("OnSizeChanged", function()
    if previewHolder:IsShown() and rCompassCastbar_RefreshAllGuiPreviews then
      rCompassCastbar_RefreshAllGuiPreviews()
    end
  end)

  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)
    ShowGuiPreviewForTab(idx)
  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

    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
  do
    local wh = (rCompassCastbarGuiTheme and rCompassCastbarGuiTheme.RGBToWoWColorHex)
      and rCompassCastbarGuiTheme.RGBToWoWColorHex(C.warn[1], C.warn[2], C.warn[3]) or "ffaa33"
    AttachBottomReloadNote(panelG, string.gsub(L["Reload Note"], "R", "|cff" .. wh .. "R|r", 1))
  end
  tabPanels[1] = panelG

  -- ═══════════════════════════════════════════════════════════════════════════
  -- TAB 2 – PLAYER RING
  -- ═══════════════════════════════════════════════════════════════════════════
  local panelP, sfP, sP = MakeScroll(contentBg, contentScrollTopPad); 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, contentScrollTopPad); 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, contentScrollTopPad); 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"],
      -500, 400, 2, sI, y,
      function() return G("castIconOffsetX") end,
      function(v) S("castIconOffsetX", v) end,
      function(v) return tostring(math.floor(v)).."px" end,
      true)
    y = y - 68

    ModernSlider(sI, L["Cast Icon Offset Y"], L["Cast Icon Offset Y Desc"],
      -500, 400, 2, sI, y,
      function() return G("castIconOffsetY") end,
      function(v) S("castIconOffsetY", v) end,
      function(v) return tostring(math.floor(v)).."px" end,
      true)
    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
  do
    local iconWh = (rCompassCastbarGuiTheme and rCompassCastbarGuiTheme.RGBToWoWColorHex)
      and rCompassCastbarGuiTheme.RGBToWoWColorHex(C.warn[1], C.warn[2], C.warn[3]) or "ffaa33"
    AttachBottomReloadNote(panelI, string.gsub(L["Cast Icon Offset Reload Note"], "R", "|cff" .. iconWh .. "R|r", 1))
  end
  tabPanels[4] = panelI

  -- ═══════════════════════════════════════════════════════════════════════════
  -- TAB 5 – COLORS
  -- ═══════════════════════════════════════════════════════════════════════════
  local panelCol, sfCol, sCol = MakeScroll(contentBg, contentScrollTopPad); 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
        MaybeRefreshGuiPreviews()
      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
          MaybeRefreshGuiPreviews()
        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 = C_AddOns.GetAddOnMetadata("rCompassCastbar", "Title") or "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])

  -- Author sub-line
  local subLbl = panel:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
  subLbl:SetPoint("TOP", titleLbl, "BOTTOM", 0, -4)
  subLbl:SetTextColor(unpack(C.textDim))
  subLbl:SetText(L["author_line"])

  local descLbl = panel:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
  descLbl:SetPoint("TOP", subLbl, "BOTTOM", 0, -8)
  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)
