local addon, ns = ...

-- ============================================================
--  rOrbs – Settings GUI  (v4 – target tab, slider fix, font scale)
-- ============================================================

local L   = ns.L
local db  = nil
local LSM = LibStub("LibSharedMedia-3.0")

local IS_MAINLINE     = WOW_PROJECT_ID == WOW_PROJECT_MAINLINE
local IS_NEW_SETTINGS = IS_MAINLINE and Settings and Settings.RegisterCanvasLayoutCategory

-- ============================================================
--  COLOR SCHEME  (rThreat-style red palette)
-- ============================================================
local C = {
    -- structure
    bg         = { 0.055, 0.055, 0.062, 0.98 },
    bgDeep     = { 0.040, 0.040, 0.046, 1.00 },
    sidebar    = { 0.045, 0.045, 0.052, 1.00 },
    content    = { 0.068, 0.068, 0.075, 0.97 },
    header     = { 0.10,  0.028, 0.028, 1.00 },
    border     = { 0.20,  0.20,  0.22,  1.00 },
    separator  = { 0.22,  0.22,  0.26,  0.55 },
    -- accent (red)
    accent     = { 0.90,  0.18,  0.18,  1.00 },
    accentHi   = { 1.00,  0.42,  0.42,  1.00 },
    accentDim  = { 0.55,  0.08,  0.08,  1.00 },
    -- section header color (#ff7800 orange)
    sectionHdr = { 1.00,  0.47,  0.00,  1.00 },
    -- tabs
    tabActive  = { 0.22,  0.04,  0.04,  1.00 },
    tabIdle    = { 0.045, 0.045, 0.052, 1.00 },
    tabHover   = { 0.09,  0.03,  0.03,  1.00 },
    -- text
    text       = { 0.90,  0.90,  0.92,  1.00 },
    textDim    = { 0.48,  0.48,  0.52,  1.00 },
    textBright = { 1.00,  1.00,  1.00,  1.00 },
    yellow     = { 1.00,  0.80,  0.20,  1.00 },
    green      = { 0.35,  0.82,  0.35,  1.00 },
    red        = { 0.90,  0.18,  0.18,  1.00 },
    -- slider
    sliderTrack = { 0.16, 0.16,  0.18,  1.00 },
    sliderFill  = { 0.90, 0.18,  0.18,  0.85 },
    sliderThumb = { 0.90, 0.18,  0.18,  1.00 },
    section    = { 0.90,  0.18,  0.18,  1.00 },
}

local PANEL_W  = 1100
local PANEL_H  = 800
local SUBNAV_W = 155   -- middle sub-navigation column
local TITLE_H  = 50
local BOT_H    = 40
local TAB_H    = 40
local PAD      = 18
local SLIDER_W = 240

-- ============================================================
--  BACKGROUND BUILDER
-- ============================================================
local function AddBackground(frame)
    local bg = frame:CreateTexture(nil, "BACKGROUND", nil, -8)
    bg:SetAllPoints()
    bg:SetColorTexture(C.bg[1], C.bg[2], C.bg[3], C.bg[4])
    frame._bgTex = bg

    local vig = frame:CreateTexture(nil, "BACKGROUND", nil, -6)
    vig:SetAllPoints()
    vig:SetColorTexture(0, 0, 0, 0.22)

    local function Edge(pt, rpt, w, h)
        local e = frame:CreateTexture(nil, "BORDER", nil, -7)
        e:SetColorTexture(C.border[1], C.border[2], C.border[3], 0.85)
        e:SetSize(w, h)
        e:SetPoint(pt, frame, rpt)
    end
    Edge("TOPLEFT",    "TOPLEFT",    PANEL_W, 1)
    Edge("BOTTOMLEFT", "BOTTOMLEFT", PANEL_W, 1)
    Edge("TOPLEFT",    "TOPLEFT",    1, PANEL_H)
    Edge("TOPRIGHT",   "TOPRIGHT",   1, PANEL_H)

    local stripe = frame:CreateTexture(nil, "ARTWORK", nil, 1)
    stripe:SetPoint("TOPLEFT")
    stripe:SetPoint("TOPRIGHT")
    stripe:SetHeight(3)
    stripe:SetColorTexture(C.accent[1], C.accent[2], C.accent[3], 0.9)
end

-- ============================================================
--  WIDGET HELPERS
-- ============================================================

local function Tip(owner, text)
    GameTooltip:SetOwner(owner, "ANCHOR_RIGHT")
    GameTooltip:SetText(text, C.text[1], C.text[2], C.text[3], 1)
    GameTooltip:Show()
end

local function MakeSep(parent, yOffset)
    local line = parent:CreateTexture(nil, "BACKGROUND")
    line:SetHeight(1)
    line:SetPoint("TOPLEFT",  parent, "TOPLEFT",  PAD, yOffset)
    line:SetPoint("TOPRIGHT", parent, "TOPRIGHT", -PAD, yOffset)
    line:SetColorTexture(C.separator[1], C.separator[2], C.separator[3], C.separator[4])
    return line
end

-- Counter for unique StaticPopup dialog IDs per section reset
local _sectionResetID = 0

local function MakeSectionHeader(parent, text, x, y, noTopLine, resetFn)
    -- Subtle full-width separator line above the header (visual section divider)
    if not noTopLine then
        local sep = parent:CreateTexture(nil, "BACKGROUND")
        sep:SetHeight(1)
        sep:SetPoint("TOPLEFT",  parent, "TOPLEFT",  PAD, y + 14)
        sep:SetPoint("TOPRIGHT", parent, "TOPRIGHT", -PAD, y + 14)
        sep:SetColorTexture(C.separator[1], C.separator[2], C.separator[3], 0.45)
    end

    -- Optional reset icon: a small Button covering ONLY the 14×14 icon area.
    -- Keeping the hit-rect tiny ensures no overlap with sliders or checkboxes below.
    local ICON_SIZE = 14
    local labelX    = x
    if resetFn then
        local hdrBtn = CreateFrame("Button", nil, parent)
        hdrBtn:SetSize(ICON_SIZE, ICON_SIZE)          -- exact icon footprint, nothing more
        hdrBtn:SetPoint("TOPLEFT", parent, "TOPLEFT", x, y - 1)

        local iconTex = hdrBtn:CreateTexture(nil, "OVERLAY")
        iconTex:SetAllPoints(hdrBtn)
        iconTex:SetTexture("Interface\\AddOns\\rOrbs\\media\\reset_section_icon.tga")
        iconTex:SetVertexColor(C.sectionHdr[1], C.sectionHdr[2], C.sectionHdr[3], 0.85)

        hdrBtn:SetScript("OnEnter", function(self)
            iconTex:SetVertexColor(1, 1, 1, 1)
            GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
            GameTooltip:SetText(L["RESET_SECTION_WARN_BTN"] or "Reset", 1, 0.8, 0.2, 1)
            GameTooltip:Show()
        end)
        hdrBtn:SetScript("OnLeave", function(self)
            iconTex:SetVertexColor(C.sectionHdr[1], C.sectionHdr[2], C.sectionHdr[3], 0.85)
            GameTooltip:Hide()
        end)
        hdrBtn:SetScript("OnClick", function()
            _sectionResetID = _sectionResetID + 1
            local dialogKey = "RORBS_SECTION_RESET_" .. _sectionResetID
            StaticPopupDialogs[dialogKey] = {
                text    = L["RESET_SECTION_WARN"] or "Reset this section to defaults?",
                button1 = L["RESET_SECTION_WARN_BTN"] or "Reset",
                button2 = CANCEL,
                OnAccept = function() resetFn() end,
                timeout = 0, whileDead = true, hideOnEscape = true,
            }
            StaticPopup_Show(dialogKey)
        end)

        labelX = x + ICON_SIZE + 4
    end

    local lbl = parent:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    lbl:SetPoint("TOPLEFT", parent, "TOPLEFT", labelX, y)
    lbl:SetTextColor(C.sectionHdr[1], C.sectionHdr[2], C.sectionHdr[3], 1)
    lbl:SetText(text)

    -- Gradient line from label to right edge
    local line = parent:CreateTexture(nil, "BACKGROUND")
    line:SetHeight(1)
    line:SetPoint("TOPLEFT",  lbl,    "BOTTOMLEFT",  0, -3)
    line:SetPoint("TOPRIGHT", parent, "TOPRIGHT",   -PAD, y)
    line:SetGradient("HORIZONTAL",
        CreateColor(C.sectionHdr[1], C.sectionHdr[2], C.sectionHdr[3], 0.7),
        CreateColor(0, 0, 0, 0))
    return lbl
end

local function MakeInfoText(parent, text, x, y, w)
    local lbl = parent:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
    lbl:SetPoint("TOPLEFT", parent, "TOPLEFT", x, y)
    lbl:SetWidth(w or (PANEL_W - CONTENT_X - PAD - SB_W - 30))
    lbl:SetJustifyH("LEFT")
    lbl:SetNonSpaceWrap(false)
    lbl:SetText(text)
    lbl:SetTextColor(C.textDim[1] * 1.3, C.textDim[2] * 1.3, C.textDim[3] * 1.3)
    return lbl
end

-- ---- rThreat-style Checkbox (plain Frame, identical to rThreat's ModernCheckbox) -------
-- desc (3rd arg = tooltip string) is shown as a small static text below the label,
-- exactly like rThreat. No GameTooltip popup. Border NEVER changes color when checked;
-- only the red chk-texture is shown/hidden. Border only goes red on hover.
local function MakeCheckbox(parent, labelText, desc, x, y)
    local con = CreateFrame("Frame", nil, parent)
    con:SetSize(16, 16)
    con:SetPoint("TOPLEFT", parent, "TOPLEFT", x, y)
    con:EnableMouse(true)

    -- Dark box background
    local boxBg = con:CreateTexture(nil, "BACKGROUND")
    boxBg:SetAllPoints()
    boxBg:SetColorTexture(0.07, 0.07, 0.08, 1)

    -- Border – stays gray always (only turns red on hover, like rThreat)
    local boxBdr = con:CreateTexture(nil, "BORDER")
    boxBdr:SetPoint("TOPLEFT",     con, "TOPLEFT",     1, -1)
    boxBdr:SetPoint("BOTTOMRIGHT", con, "BOTTOMRIGHT", -1, 1)
    boxBdr:SetColorTexture(C.border[1], C.border[2], C.border[3], 1)

    -- Red checkmark (hidden by default, shown when checked)
    local chk = con:CreateTexture(nil, "OVERLAY")
    chk:SetSize(11, 11)
    chk:SetPoint("CENTER")
    chk:SetTexture("Interface\\Buttons\\UI-CheckBox-Check")
    chk:SetVertexColor(C.accent[1], C.accent[2], C.accent[3], 1)
    chk:Hide()

    -- Main label (right of box)
    local lbl = parent:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    lbl:SetPoint("LEFT", con, "RIGHT", 9, 0)
    lbl:SetText(labelText)
    lbl:SetTextColor(C.text[1], C.text[2], C.text[3])
    con.label = lbl

    -- Description text below label (like rThreat's desc)
    if desc and desc ~= "" then
        local d = parent:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
        d:SetPoint("TOPLEFT", lbl, "BOTTOMLEFT", 0, -2)
        d:SetPoint("RIGHT", parent, "RIGHT", -PAD, 0)
        d:SetJustifyH("LEFT")
        d:SetTextColor(C.textDim[1], C.textDim[2], C.textDim[3])
        d:SetText(desc)
        con.descLabel = d
    end

    -- Internal state
    local _checked = false
    local _onClick = nil

    -- SyncVisual: ONLY show/hide chk. Border stays gray (rThreat behavior).
    local function SyncVisual(on)
        _checked = on
        if on then chk:Show() else chk:Hide() end
    end

    -- Public API
    con.GetChecked = function(self) return _checked end
    con.SetChecked = function(self, val)
        SyncVisual(val and val ~= false)
    end
    con.SetScript = function(self, event, fn)
        if event == "OnClick" then
            _onClick = fn
        else
            getmetatable(self).__index.SetScript(self, event, fn)
        end
    end

    -- Hover: border → red (only on hover, NOT on checked state)
    con:SetScript("OnEnter", function(self)
        boxBdr:SetColorTexture(C.accent[1], C.accent[2], C.accent[3], 0.8)
        lbl:SetTextColor(C.accentHi[1], C.accentHi[2], C.accentHi[3], 1)
    end)
    con:SetScript("OnLeave", function()
        boxBdr:SetColorTexture(C.border[1], C.border[2], C.border[3], 1)
        lbl:SetTextColor(C.text[1], C.text[2], C.text[3])
    end)
    con:SetScript("OnMouseDown", function(self, btn)
        if btn == "LeftButton" then
            SyncVisual(not _checked)
            if _onClick then _onClick(con) end
        end
    end)

    return con
end

-- ---- Modern Slider (rThreat-style: full-width track) --------
-- Returns: slider, label
local function MakeSlider(parent, labelText, tipText, minV, maxV, step, x, y, width)
    -- Label row
    local lbl = parent:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
    lbl:SetPoint("TOPLEFT", parent, "TOPLEFT", x, y)
    lbl:SetText(labelText)
    lbl:SetTextColor(C.text[1], C.text[2], C.text[3])
    lbl:SetJustifyH("LEFT")

    -- Value label pinned to top-right
    local valLabel = parent:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    valLabel:SetPoint("TOPRIGHT", parent, "TOPRIGHT", -PAD, y - 1)
    valLabel:SetTextColor(C.accent[1], C.accent[2], C.accent[3], 1)

    -- Track texture spanning full width like rThreat (6px inset each side)
    local TRACK_INSET = 6
    local track = parent:CreateTexture(nil, "BACKGROUND")
    track:SetHeight(3)
    track:SetPoint("TOPLEFT",  parent, "TOPLEFT",  x + TRACK_INSET,  y - 36)
    track:SetPoint("TOPRIGHT", parent, "TOPRIGHT", -(PAD - TRACK_INSET), y - 36)
    track:SetColorTexture(0.16, 0.16, 0.18, 1)

    local fill = parent:CreateTexture(nil, "ARTWORK")
    fill:SetHeight(3)
    fill:SetPoint("LEFT", track, "LEFT", 0, 0)
    fill:SetColorTexture(C.accent[1], C.accent[2], C.accent[3], 0.85)

    -- Invisible Slider frame anchored to the track
    local s = CreateFrame("Slider", nil, parent)
    s:SetHeight(18)
    s:SetPoint("LEFT",  track, "LEFT",  0, 0)
    s:SetPoint("RIGHT", track, "RIGHT", 0, 0)
    s:SetMinMaxValues(minV, maxV)
    s:SetValueStep(step)
    s:SetObeyStepOnDrag(true)
    s:SetOrientation("HORIZONTAL")

    local thumb = parent:CreateTexture(nil, "OVERLAY")
    thumb:SetSize(10, 14)
    thumb:SetTexture("Interface\\Buttons\\WHITE8X8")
    thumb:SetVertexColor(C.accent[1], C.accent[2], C.accent[3], 1)
    s:SetThumbTexture(thumb)

    s.valLabel  = valLabel
    s.fillTex  = fill
    s._trackTex = track

    local function UpdateFill()
        local lo, hi = s:GetMinMaxValues()
        local pct = math.max(0, (s:GetValue() - lo) / math.max(hi - lo, 0.001))
        local w = track:GetWidth()
        if w and w > 0 then fill:SetWidth(w * pct) end
    end

    local function UpdateVal()
        local v = s:GetValue()
        local fmt = (step < 1) and "%.2f" or "%d"
        valLabel:SetText(string.format(fmt, v))
        UpdateFill()
    end

    s._updateVisuals = function(slider, val) UpdateVal() end
    s._refresh       = function(val) s:SetValue(val); UpdateVal() end

    s:SetScript("OnValueChanged", function(self, val) UpdateVal() end)
    s:SetScript("OnSizeChanged",  function() UpdateFill() end)
    s:SetScript("OnEnter", function(self)
        thumb:SetVertexColor(C.accentHi[1], C.accentHi[2], C.accentHi[3], 1)
        if tipText then Tip(self, tipText) end
    end)
    s:SetScript("OnLeave", function(self)
        thumb:SetVertexColor(C.accent[1], C.accent[2], C.accent[3], 1)
        GameTooltip:Hide()
    end)
    s:SetValue(minV)
    UpdateVal()
    return s, lbl
end

-- Helper: Set slider OnValueChanged WITH visual update
local function SetSliderHandler(slider, handler)
    slider:SetScript("OnValueChanged", function(self, val)
        if self._updateVisuals then self._updateVisuals(self, val) end
        handler(self, val)
    end)
end

-- ============================================================
--  CUSTOM DROPDOWN  (replaces UIDropDownMenuTemplate)
-- ============================================================
-- Shim state: one "current" dd during Initialize calls
local _ddCurrentInit = nil
local _ddItems       = {}   -- dd → list of {text, value, func, disabled}
local _ddValue       = {}   -- dd → current selected value
local _ddOpenFrame   = nil  -- only one popup open at a time

-- Shim functions that replace the global UIDropDownMenu_* API locally.
-- The existing call-sites are unchanged; these locals shadow the globals.
local function UIDropDownMenu_CreateInfo()
    return {}
end

local function UIDropDownMenu_AddButton(info, level)
    if _ddCurrentInit then
        tinsert(_ddItems[_ddCurrentInit], {
            text     = info.text or "",
            value    = info.value,
            func     = info.func,
            disabled = info.disabled,
        })
    end
end

local function UIDropDownMenu_SetText(dd, text)
    if dd and dd._labelText then dd._labelText:SetText(text or "") end
end

local function UIDropDownMenu_SetSelectedValue(dd, val)
    if dd then _ddValue[dd] = val end
end

local function UIDropDownMenu_GetSelectedValue(dd)
    return dd and _ddValue[dd]
end

-- Close any open popup
local function CloseDropdownPopup()
    if _ddOpenFrame then
        _ddOpenFrame:Hide()
        _ddOpenFrame = nil
    end
end

-- Build/show the popup list for a dropdown
local function ShowDropdownPopup(dd)
    CloseDropdownPopup()

    local items  = _ddItems[dd] or {}
    if #items == 0 then return end

    local ddW    = dd._width or 220
    local itemH  = 22
    local visMax = 15   -- max items before scrolling
    local needsScroll = #items > visMax
    local scrollBarW = needsScroll and 10 or 0
    local popH   = math.min(#items, visMax) * itemH + 4

    local pop = CreateFrame("Frame", nil, UIParent)
    pop:SetFrameStrata("TOOLTIP")
    pop:SetFrameLevel(200)
    pop:SetSize(ddW, popH)
    pop:SetPoint("TOPLEFT", dd._btn, "BOTTOMLEFT", 0, -2)
    pop:SetClipsChildren(true)

    -- Background
    local bg = pop:CreateTexture(nil, "BACKGROUND")
    bg:SetAllPoints()
    bg:SetColorTexture(C.bgDeep[1], C.bgDeep[2], C.bgDeep[3], 0.98)
    -- Border
    local function PopEdge(pt, rpt, w, h)
        local e = pop:CreateTexture(nil, "BORDER")
        e:SetColorTexture(C.border[1], C.border[2], C.border[3], 0.90)
        e:SetSize(w, h)
        e:SetPoint(pt, pop, rpt, 0, 0)
    end
    PopEdge("TOPLEFT",     "TOPLEFT",     1, popH)
    PopEdge("TOPRIGHT",    "TOPRIGHT",    1, popH)
    PopEdge("TOPLEFT",     "TOPLEFT",     ddW, 1)
    PopEdge("BOTTOMLEFT",  "BOTTOMLEFT",  ddW, 1)

    -- Scroll frame for many items
    local sf   = CreateFrame("ScrollFrame", nil, pop)
    sf:SetPoint("TOPLEFT",     pop, "TOPLEFT",     1, -2)
    sf:SetPoint("BOTTOMRIGHT", pop, "BOTTOMRIGHT", -(1 + scrollBarW), 2)
    local content = CreateFrame("Frame", nil, sf)
    content:SetSize(ddW - 2 - scrollBarW, #items * itemH)
    sf:SetScrollChild(content)
    sf:SetVerticalScroll(0)
    sf:EnableMouseWheel(true)
    pop:EnableMouseWheel(true)

    local maxScroll = math.max(0, #items * itemH - popH + 4)

    -- ── Draggable scrollbar (only when list exceeds visMax) ────────────────
    local thumbBtn = nil
    local thumbH   = 0
    local TRACK_PAD_TOP    = 2
    local TRACK_PAD_BOTTOM = 2
    local trackH = popH - TRACK_PAD_TOP - TRACK_PAD_BOTTOM

    local function GetScrollPct()
        if maxScroll <= 0 then return 0 end
        return sf:GetVerticalScroll() / maxScroll
    end

    local function SetScrollFromPct(pct)
        local newScroll = math.max(0, math.min(maxScroll, pct * maxScroll))
        sf:SetVerticalScroll(newScroll)
    end

    local function UpdateThumbPos()
        if not thumbBtn then return end
        local pct = GetScrollPct()
        local travelRange = trackH - thumbH
        local yOff = -(TRACK_PAD_TOP + pct * travelRange)
        thumbBtn:ClearAllPoints()
        thumbBtn:SetPoint("TOPRIGHT", pop, "TOPRIGHT", -1, yOff)
    end

    if needsScroll then
        -- Track background
        local sbTrack = pop:CreateTexture(nil, "BACKGROUND", nil, 5)
        sbTrack:SetWidth(scrollBarW)
        sbTrack:SetPoint("TOPRIGHT",    pop, "TOPRIGHT",    0, -TRACK_PAD_TOP)
        sbTrack:SetPoint("BOTTOMRIGHT", pop, "BOTTOMRIGHT", 0, TRACK_PAD_BOTTOM)
        sbTrack:SetColorTexture(C.bgDeep[1]*2.5, C.bgDeep[2]*2.5, C.bgDeep[3]*2.5, 1)

        -- Draggable thumb button
        thumbH = math.max(16, math.floor((popH / (#items * itemH)) * trackH))
        thumbBtn = CreateFrame("Button", nil, pop)
        thumbBtn:SetFrameStrata("TOOLTIP")
        thumbBtn:SetFrameLevel(210)
        thumbBtn:SetSize(scrollBarW - 2, thumbH)
        thumbBtn:SetPoint("TOPRIGHT", pop, "TOPRIGHT", -1, -TRACK_PAD_TOP)

        local thumbTex = thumbBtn:CreateTexture(nil, "ARTWORK")
        thumbTex:SetAllPoints()
        thumbTex:SetColorTexture(C.accent[1], C.accent[2], C.accent[3], 0.85)
        thumbBtn.tex = thumbTex

        -- Click on track (above/below thumb) scrolls by one page
        local sbClickFrame = CreateFrame("Button", nil, pop)
        sbClickFrame:SetFrameStrata("TOOLTIP")
        sbClickFrame:SetFrameLevel(209)
        sbClickFrame:SetWidth(scrollBarW)
        sbClickFrame:SetPoint("TOPRIGHT",    pop, "TOPRIGHT",    0, -TRACK_PAD_TOP)
        sbClickFrame:SetPoint("BOTTOMRIGHT", pop, "BOTTOMRIGHT", 0, TRACK_PAD_BOTTOM)
        sbClickFrame:SetScript("OnClick", function(self, btn)
            if btn ~= "LeftButton" then return end
            local _, mouseY = GetCursorPosition()
            local uiScale   = UIParent:GetEffectiveScale()
            local _, popY   = pop:GetCenter()
            local _, thumbY = thumbBtn:GetCenter()
            local scaledMouseY = mouseY / uiScale
            local scaledThumbY = thumbY  -- already in screen coords
            -- thumbBtn center relative to screen
            local tCX, tCY = thumbBtn:GetCenter()
            if scaledMouseY > tCY then
                -- click above thumb → scroll up
                local newScroll = math.max(0, sf:GetVerticalScroll() - (popH - itemH))
                sf:SetVerticalScroll(newScroll)
            else
                -- click below thumb → scroll down
                local newScroll = math.min(maxScroll, sf:GetVerticalScroll() + (popH - itemH))
                sf:SetVerticalScroll(newScroll)
            end
            UpdateThumbPos()
        end)

        -- Drag thumb to scroll
        local _dragStartY   = nil
        local _dragStartScr = nil
        thumbBtn:SetScript("OnMouseDown", function(self, btn)
            if btn ~= "LeftButton" then return end
            local _, mouseY = GetCursorPosition()
            _dragStartY   = mouseY / UIParent:GetEffectiveScale()
            _dragStartScr = sf:GetVerticalScroll()
            self.tex:SetColorTexture(C.accentHi[1], C.accentHi[2], C.accentHi[3], 1)
        end)
        thumbBtn:SetScript("OnMouseUp", function(self)
            _dragStartY   = nil
            _dragStartScr = nil
            self.tex:SetColorTexture(C.accent[1], C.accent[2], C.accent[3], 0.85)
        end)
        thumbBtn:SetScript("OnUpdate", function(self)
            if not _dragStartY then return end
            local _, mouseY = GetCursorPosition()
            local curY = mouseY / UIParent:GetEffectiveScale()
            local delta = _dragStartY - curY   -- positive = dragged down
            local travelRange = trackH - thumbH
            if travelRange <= 0 then return end
            local scrollDelta = (delta / travelRange) * maxScroll
            local newScroll = math.max(0, math.min(maxScroll, _dragStartScr + scrollDelta))
            sf:SetVerticalScroll(newScroll)
            UpdateThumbPos()
        end)
        thumbBtn:SetScript("OnEnter", function(self)
            self.tex:SetColorTexture(C.accentHi[1], C.accentHi[2], C.accentHi[3], 1)
        end)
        thumbBtn:SetScript("OnLeave", function(self)
            if not _dragStartY then
                self.tex:SetColorTexture(C.accent[1], C.accent[2], C.accent[3], 0.85)
            end
        end)
    end

    local function OnDropdownScroll(_, delta)
        local newScroll = math.max(0, math.min(maxScroll, sf:GetVerticalScroll() - delta * itemH * 2))
        sf:SetVerticalScroll(newScroll)
        UpdateThumbPos()
    end
    sf:SetScript("OnMouseWheel", OnDropdownScroll)
    pop:SetScript("OnMouseWheel", OnDropdownScroll)

    -- Find index of selected item for auto-scroll
    local selectedIdx = nil
    for idx, item in ipairs(items) do
        if _ddValue[dd] ~= nil and _ddValue[dd] == item.value then
            selectedIdx = idx
        end
    end

    for idx, item in ipairs(items) do
        local row = CreateFrame("Button", nil, content)
        row:SetSize(ddW - 2, itemH)
        row:SetPoint("TOPLEFT", content, "TOPLEFT", 0, -(idx - 1) * itemH)

        local rowBg = row:CreateTexture(nil, "BACKGROUND")
        rowBg:SetAllPoints()
        rowBg:SetColorTexture(0, 0, 0, 0)

        local rowTxt = row:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
        rowTxt:SetPoint("LEFT", row, "LEFT", 8, 0)
        rowTxt:SetPoint("RIGHT", row, "RIGHT", -4, 0)
        rowTxt:SetJustifyH("LEFT")
        rowTxt:SetText(item.text)
        if item.disabled then
            rowTxt:SetTextColor(C.textDim[1], C.textDim[2], C.textDim[3])
        else
            rowTxt:SetTextColor(C.text[1], C.text[2], C.text[3])
        end

        -- Highlight current selection
        if _ddValue[dd] ~= nil and _ddValue[dd] == item.value then
            rowBg:SetColorTexture(C.accent[1], C.accent[2], C.accent[3], 0.20)
            rowTxt:SetTextColor(C.yellow[1], C.yellow[2], C.yellow[3])
        end

        if not item.disabled then
            row:SetScript("OnEnter", function()
                rowBg:SetColorTexture(C.accentDim[1], C.accentDim[2], C.accentDim[3], 0.35)
                rowTxt:SetTextColor(C.yellow[1], C.yellow[2], C.yellow[3])
            end)
            row:SetScript("OnLeave", function()
                if _ddValue[dd] ~= nil and _ddValue[dd] == item.value then
                    rowBg:SetColorTexture(C.accent[1], C.accent[2], C.accent[3], 0.20)
                    rowTxt:SetTextColor(C.yellow[1], C.yellow[2], C.yellow[3])
                else
                    rowBg:SetColorTexture(0, 0, 0, 0)
                    rowTxt:SetTextColor(C.text[1], C.text[2], C.text[3])
                end
            end)
            row:SetScript("OnClick", function()
                if item.func then item.func() end
                CloseDropdownPopup()
            end)
        end
    end

    -- Auto-scroll to selected item (center it in the visible area if possible)
    if needsScroll and selectedIdx then
        local selTopY   = (selectedIdx - 1) * itemH   -- distance from content top
        local visHeight = popH - 4
        -- Center the selected row in the visible area
        local targetScroll = selTopY - math.floor((visHeight - itemH) / 2)
        local initScroll = math.max(0, math.min(maxScroll, targetScroll))
        sf:SetVerticalScroll(initScroll)
        UpdateThumbPos()
    end

    -- Close when clicking outside
    pop:SetScript("OnHide", function() if _ddOpenFrame == pop then _ddOpenFrame = nil end end)
    local catch = CreateFrame("Frame", nil, UIParent)
    catch:SetAllPoints(UIParent)
    catch:SetFrameStrata("DIALOG")
    catch:SetFrameLevel(199)
    catch:EnableMouse(true)
    catch:EnableMouseWheel(false)
    catch:SetScript("OnMouseDown", function()
        CloseDropdownPopup()
        catch:Hide()
        catch:SetParent(nil)
    end)
    catch:Show()
    pop:SetScript("OnHide", function()
        if _ddOpenFrame == pop then _ddOpenFrame = nil end
        catch:Hide()
        catch:SetParent(nil)
    end)

    pop:Show()
    _ddOpenFrame = pop
end

local function UIDropDownMenu_Initialize(dd, fn)
    _ddItems[dd] = {}
    _ddCurrentInit = dd
    fn(dd, 1)
    _ddCurrentInit = nil
    -- Rebuild label if we have a selected value already
end

-- ---- Dropdown widget ----------------------------------------
local function MakeDropdown(parent, labelText, tipText, x, y, width)
    width = width or 220

    -- Label above
    local lbl = parent:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
    lbl:SetPoint("TOPLEFT", parent, "TOPLEFT", x, y)
    lbl:SetText(labelText)
    lbl:SetTextColor(C.text[1], C.text[2], C.text[3])

    local H = 22   -- button height

    -- Main button
    local btn = CreateFrame("Button", nil, parent)
    btn:SetSize(width, H)
    btn:SetPoint("TOPLEFT", parent, "TOPLEFT", x, y - 18)

    -- Button background
    local btnBg = btn:CreateTexture(nil, "BACKGROUND")
    btnBg:SetAllPoints()
    btnBg:SetColorTexture(C.bgDeep[1], C.bgDeep[2], C.bgDeep[3], 0.95)

    -- Border edges
    local function BtnEdge(pt, rpt, w, h)
        local e = btn:CreateTexture(nil, "BORDER")
        e:SetColorTexture(C.border[1], C.border[2], C.border[3], 0.85)
        e:SetSize(w, h)
        e:SetPoint(pt, btn, rpt, 0, 0)
    end
    BtnEdge("TOPLEFT",     "TOPLEFT",     1, H)
    BtnEdge("TOPRIGHT",    "TOPRIGHT",    1, H)
    BtnEdge("TOPLEFT",     "TOPLEFT",     width, 1)
    BtnEdge("BOTTOMLEFT",  "BOTTOMLEFT",  width, 1)

    -- Selected label text
    local selTxt = btn:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    selTxt:SetPoint("LEFT",  btn, "LEFT",  8, 0)
    selTxt:SetPoint("RIGHT", btn, "RIGHT", -28, 0)
    selTxt:SetJustifyH("LEFT")
    selTxt:SetTextColor(C.text[1], C.text[2], C.text[3])
    selTxt:SetText("")

    -- Icon indicator (replaces text arrow) - right side box with skull icon
    local iconBox = btn:CreateTexture(nil, "BACKGROUND", nil, 1)
    iconBox:SetSize(H + 2, H + 2)
    iconBox:SetPoint("RIGHT", btn, "RIGHT", 1, 0)
    iconBox:SetColorTexture(C.bgDeep[1]*0.5, C.bgDeep[2]*0.5, C.bgDeep[3]*0.5, 1)

    local iconBoxBorder = btn:CreateTexture(nil, "BORDER", nil, 2)
    iconBoxBorder:SetSize(H + 2, H + 2)
    iconBoxBorder:SetPoint("RIGHT", btn, "RIGHT", 1, 0)
    iconBoxBorder:SetColorTexture(C.border[1], C.border[2], C.border[3], 0.90)

    local iconBoxInner = btn:CreateTexture(nil, "ARTWORK", nil, 3)
    iconBoxInner:SetSize(H, H)
    iconBoxInner:SetPoint("RIGHT", btn, "RIGHT", 0, 0)
    iconBoxInner:SetColorTexture(C.tabActive[1], C.tabActive[2], C.tabActive[3], 1)

    local iconTex = btn:CreateTexture(nil, "OVERLAY", nil, 4)
    iconTex:SetSize(H - 2, H - 2)
    iconTex:SetPoint("RIGHT", btn, "RIGHT", -1, 0)
    iconTex:SetTexture("Interface\\AddOns\\rOrbs\\media\\dropdown_icon.png")
    iconTex:SetAlpha(0.85)
    local arrow = iconTex  -- keep 'arrow' reference for hover scripts below

    -- The "dd" object is the btn itself; attach helpers
    -- IMPORTANT: dd must be defined BEFORE the SetScript closures reference it
    local dd = btn
    dd._btn       = btn
    dd._labelText = selTxt
    dd._width     = width
    _ddItems[dd]  = {}
    _ddValue[dd]  = nil

    -- Hover
    btn:SetScript("OnEnter", function()
        btnBg:SetColorTexture(C.tabHover[1], C.tabHover[2], C.tabHover[3], 0.95)
        arrow:SetAlpha(1.0)
        iconBoxInner:SetColorTexture(C.accentDim[1], C.accentDim[2], C.accentDim[3], 1)
    end)
    btn:SetScript("OnLeave", function()
        btnBg:SetColorTexture(C.bgDeep[1], C.bgDeep[2], C.bgDeep[3], 0.95)
        arrow:SetAlpha(0.85)
        iconBoxInner:SetColorTexture(C.tabActive[1], C.tabActive[2], C.tabActive[3], 1)
    end)

    btn:SetScript("OnClick", function()
        if _ddOpenFrame then
            CloseDropdownPopup()
        end
        ShowDropdownPopup(dd)
    end)

    if tipText then
        btn:SetScript("OnEnter", function(s)
            btnBg:SetColorTexture(C.tabHover[1], C.tabHover[2], C.tabHover[3], 0.95)
            arrow:SetAlpha(1.0)
            iconBoxInner:SetColorTexture(C.accentDim[1], C.accentDim[2], C.accentDim[3], 1)
            Tip(s, tipText)
        end)
        btn:SetScript("OnLeave", function()
            btnBg:SetColorTexture(C.bgDeep[1], C.bgDeep[2], C.bgDeep[3], 0.95)
            arrow:SetAlpha(0.85)
            iconBoxInner:SetColorTexture(C.tabActive[1], C.tabActive[2], C.tabActive[3], 1)
            GameTooltip:Hide()
        end)
        -- Restore OnClick after tipText block overwrote OnEnter/OnLeave
        btn:SetScript("OnClick", function()
            if _ddOpenFrame then
                CloseDropdownPopup()
            end
            ShowDropdownPopup(dd)
        end)
    end

    return dd, lbl
end

-- ---- Color Swatch --------------------------------------------
local function MakeColorSwatch(parent, x, y, r, g, b, labelText, onClick)
    local btn = CreateFrame("Button", nil, parent)
    btn:SetSize(22, 22)
    btn:SetPoint("TOPLEFT", parent, "TOPLEFT", x, y)

    local border = btn:CreateTexture(nil, "BACKGROUND", nil, -1)
    border:SetAllPoints()
    border:SetColorTexture(C.border[1], C.border[2], C.border[3], 1)

    local fill = btn:CreateTexture(nil, "ARTWORK", nil, 0)
    fill:SetPoint("TOPLEFT",     btn, "TOPLEFT",     2, -2)
    fill:SetPoint("BOTTOMRIGHT", btn, "BOTTOMRIGHT", -2, 2)
    fill:SetColorTexture(r or 1, g or 1, b or 1, 1)
    btn.fill = fill

    if labelText then
        local lbl = btn:GetParent():CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
        lbl:SetPoint("LEFT", btn, "RIGHT", 6, 0)
        lbl:SetText(labelText)
        lbl:SetTextColor(C.textDim[1], C.textDim[2], C.textDim[3])
    end

    btn:SetScript("OnClick", onClick)
    btn:SetScript("OnEnter", function(s)
        Tip(s, L["ORB_COLOR_TIP"])
    end)
    btn:SetScript("OnLeave", function() GameTooltip:Hide() end)

    btn.SetColor = function(self, nr, ng, nb)
        r, g, b = nr, ng, nb
        fill:SetColorTexture(nr, ng, nb, 1)
    end
    -- GetColor returns the colour currently shown in the swatch.
    -- The onClick for colSwatch uses this so the picker always opens
    -- with the same colour the swatch displays (set by refreshTab).
    btn.GetColor = function(self)
        return r, g, b
    end
    return btn
end

-- ---- Styled button -------------------------------------------
local BTN_PAD_X = 20
local function MakeButton(parent, text, x, y, w, h, r, g, b)
    r, g, b = r or C.accentDim[1], g or C.accentDim[2], b or C.accentDim[3]
    local btn = CreateFrame("Button", nil, parent)
    local BH = h or 26
    btn:SetSize(w or 100, BH)
    btn:SetPoint("TOPLEFT", parent, "TOPLEFT", x, y)

    local bgTex = btn:CreateTexture(nil, "BACKGROUND")
    bgTex:SetAllPoints()
    bgTex:SetColorTexture(r * 0.6, g * 0.6, b * 0.6, 1)

    local borderTex = btn:CreateTexture(nil, "BORDER")
    borderTex:SetAllPoints()
    borderTex:SetColorTexture(C.border[1], C.border[2], C.border[3], 0.8)

    local innerTex = btn:CreateTexture(nil, "ARTWORK")
    innerTex:SetPoint("TOPLEFT",     btn, "TOPLEFT",     1, -1)
    innerTex:SetPoint("BOTTOMRIGHT", btn, "BOTTOMRIGHT", -1, 1)
    innerTex:SetColorTexture(r, g, b, 1)
    btn.innerTex = innerTex

    local fs = btn:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    fs:SetAllPoints()
    fs:SetJustifyH("CENTER")
    fs:SetText(text)
    fs:SetTextColor(C.text[1], C.text[2], C.text[3])
    btn.fs = fs

    -- Auto-Breite anhand Textlänge (wie CombatStatus/nMainBar)
    if not w then
        C_Timer.After(0, function()
            local tw = fs:GetStringWidth()
            btn:SetSize(math.max(60, tw + BTN_PAD_X * 2), BH)
        end)
    end

    btn:SetScript("OnEnter", function(self)
        self.innerTex:SetColorTexture(math.min(1,r*1.4), math.min(1,g*1.5), math.min(1,b*1.5), 1)
    end)
    btn:SetScript("OnLeave", function(self)
        self.innerTex:SetColorTexture(r, g, b, 1)
    end)
    btn:SetScript("OnMouseDown", function(self) fs:SetPoint("CENTER", 1, -1) end)
    btn:SetScript("OnMouseUp",   function(self) fs:SetPoint("CENTER", 0, 0) end)
    return btn
end

-- ---- Icon Preview Toggle Button ----------------------------
-- Creates a small green/red toggle that forces-shows an icon for positioning.
-- getIcon: function() -> frame  (called at click-time, not at creation-time)
-- onStop:  function(frame)       (optional: custom logic when preview ends)
local function MakeIconPreviewButton(parent, x, y, getIcon, onStop, onStart)
    local btn = MakeButton(parent, L["CUSTOM_TMPL_PREVIEW"], x, y, 90, 22, 0.08, 0.18, 0.08)
    btn._prevActive = false
    btn:SetScript("OnClick", function(self)
        self._prevActive = not self._prevActive
        local f = getIcon and getIcon()
        if self._prevActive then
            self.fs:SetText(L["CUSTOM_TMPL_STOP"])
            self.innerTex:SetColorTexture(0.40, 0.05, 0.03, 1)
            if f then
                f:Show()
                if onStart then
                    onStart(f)
                end
            end
        else
            self.fs:SetText(L["CUSTOM_TMPL_PREVIEW"])
            self.innerTex:SetColorTexture(0.08, 0.18, 0.08, 1)
            if f then
                if onStop then onStop(f) else f:Hide() end
            end
        end
    end)
    return btn
end

-- ============================================================
--  MINIMAP RELOAD DIALOG  (nMainBar-Stil, rOrbs-Farben)
-- ============================================================
local _minimapReloadDialog = nil
local _isFirstLoad = true
C_Timer.After(2, function() _isFirstLoad = false end)

local function ShowMinimapReloadWarning(onCancel)
    if _isFirstLoad then return end
    if not _minimapReloadDialog then
        local dlg = CreateFrame("Frame", "rOrbsMinimapReloadDlg", UIParent, "BackdropTemplate")
        dlg:SetSize(420, 160)
        dlg:SetPoint("CENTER")
        dlg:SetFrameStrata("FULLSCREEN_DIALOG")
        dlg:SetFrameLevel(200)
        dlg:SetMovable(true)
        dlg:EnableMouse(true)
        dlg:RegisterForDrag("LeftButton")
        dlg:SetScript("OnDragStart", dlg.StartMoving)
        dlg:SetScript("OnDragStop",  dlg.StopMovingOrSizing)
        dlg:SetBackdrop({bgFile="Interface\\Buttons\\WHITE8X8", edgeFile="Interface\\Buttons\\WHITE8X8",
            tile=false, edgeSize=2, insets={left=0,right=0,top=0,bottom=0}})
        dlg:SetBackdropColor(C.bg[1], C.bg[2], C.bg[3], C.bg[4])
        dlg:SetBackdropBorderColor(C.accent[1]*0.6, 0.10, 0.10, 1)

        -- Titelleiste
        local tBg = CreateFrame("Frame", nil, dlg, "BackdropTemplate")
        tBg:SetPoint("TOPLEFT",  dlg, "TOPLEFT",  2, -2)
        tBg:SetPoint("TOPRIGHT", dlg, "TOPRIGHT", -2, -2)
        tBg:SetHeight(36)
        tBg:SetBackdrop({bgFile="Interface\\Buttons\\WHITE8X8", edgeFile="Interface\\Buttons\\WHITE8X8",
            tile=false, edgeSize=1, insets={left=0,right=0,top=0,bottom=0}})
        tBg:SetBackdropColor(C.accent[1]*0.19, 0.06, 0.06, 1)
        tBg:SetBackdropBorderColor(C.accent[1]*0.45, 0.05, 0.05, 1)
        -- Linker roter Akzentstreifen
        local tAccent = tBg:CreateTexture(nil, "ARTWORK")
        tAccent:SetWidth(3)
        tAccent:SetPoint("TOPLEFT",    tBg, "TOPLEFT",    0, 0)
        tAccent:SetPoint("BOTTOMLEFT", tBg, "BOTTOMLEFT", 0, 0)
        tAccent:SetColorTexture(C.accent[1], C.accent[2], C.accent[3], 1)
        -- Titeltext
        local tLbl = tBg:CreateFontString(nil, "OVERLAY", "GameFontNormal")
        tLbl:SetTextColor(1, 1, 1, 1)
        -- Logo in Titelleiste
        local dlgLogo = tBg:CreateTexture(nil, "OVERLAY")
        dlgLogo:SetTexture("Interface\\AddOns\\rOrbs\\media\\d2_skull")
        dlgLogo:SetSize(22, 22)
        dlgLogo:SetPoint("LEFT", tBg, "LEFT", 10, 0)
        tLbl:SetPoint("LEFT", dlgLogo, "RIGHT", 6, 0)
        tLbl:SetText(L["MINIMAP_RELOAD_TITLE"])
        -- Beschreibungstext
        local desc = dlg:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
        desc:SetPoint("TOPLEFT",  tBg,  "BOTTOMLEFT",  14, -12)
        desc:SetPoint("TOPRIGHT", dlg,  "TOPRIGHT",    -14, -50)
        desc:SetJustifyH("LEFT")
        desc:SetTextColor(C.text[1], C.text[2], C.text[3])
        desc:SetText(L["MINIMAP_RELOAD_DESC"])
        -- Reload-Button
        local reloadBtn = CreateFrame("Button", nil, dlg, "BackdropTemplate")
        reloadBtn:SetSize(130, 26)
        reloadBtn:SetPoint("BOTTOMRIGHT", dlg, "BOTTOM", -6, 10)
        reloadBtn:SetBackdrop({bgFile="Interface\\Buttons\\WHITE8X8", edgeFile="Interface\\Buttons\\WHITE8X8",
            tile=false, edgeSize=1, insets={left=0,right=0,top=0,bottom=0}})
        reloadBtn:SetBackdropColor(C.accent[1]*0.18, 0.06, 0.06, 1)
        reloadBtn:SetBackdropBorderColor(C.accent[1]*0.70, 0.08, 0.08, 1)
        local rLbl = reloadBtn:CreateFontString(nil, "OVERLAY", "GameFontNormal")
        rLbl:SetPoint("CENTER")
        rLbl:SetText(L["MINIMAP_RELOAD_BTN"])
        rLbl:SetTextColor(C.accent[1], C.accent[2], C.accent[3], 1)
        reloadBtn:SetScript("OnEnter", function(s)
            s:SetBackdropColor(C.accent[1]*0.38, 0.06, 0.06, 1)
            s:SetBackdropBorderColor(C.accentHi[1], 0.15, 0.15, 1)
            rLbl:SetTextColor(1, 1, 1, 1)
        end)
        reloadBtn:SetScript("OnLeave", function(s)
            s:SetBackdropColor(C.accent[1]*0.18, 0.06, 0.06, 1)
            s:SetBackdropBorderColor(C.accent[1]*0.70, 0.08, 0.08, 1)
            rLbl:SetTextColor(C.accent[1], C.accent[2], C.accent[3], 1)
        end)
        reloadBtn:SetScript("OnClick", function() dlg:Hide(); ReloadUI() end)
        -- Abbrechen-Button
        local cancelBtn = CreateFrame("Button", nil, dlg, "BackdropTemplate")
        cancelBtn:SetSize(100, 26)
        cancelBtn:SetPoint("BOTTOMLEFT", dlg, "BOTTOM", 6, 10)
        cancelBtn:SetBackdrop({bgFile="Interface\\Buttons\\WHITE8X8", edgeFile="Interface\\Buttons\\WHITE8X8",
            tile=false, edgeSize=1, insets={left=0,right=0,top=0,bottom=0}})
        cancelBtn:SetBackdropColor(0.08, 0.08, 0.09, 1)
        cancelBtn:SetBackdropBorderColor(C.border[1], C.border[2], C.border[3], 1)
        local cLbl = cancelBtn:CreateFontString(nil, "OVERLAY", "GameFontNormal")
        cLbl:SetPoint("CENTER")
        cLbl:SetText(L["MINIMAP_RELOAD_CANCEL"] or CANCEL)
        cLbl:SetTextColor(C.text[1], C.text[2], C.text[3])
        cancelBtn:SetScript("OnEnter", function(s)
            s:SetBackdropColor(0.12, 0.12, 0.14, 1)
            s:SetBackdropBorderColor(C.accentHi[1], 0.15, 0.15, 1)
            cLbl:SetTextColor(1, 1, 1, 1)
        end)
        cancelBtn:SetScript("OnLeave", function(s)
            s:SetBackdropColor(0.08, 0.08, 0.09, 1)
            s:SetBackdropBorderColor(C.border[1], C.border[2], C.border[3], 1)
            cLbl:SetTextColor(C.text[1], C.text[2], C.text[3])
        end)
        cancelBtn:SetScript("OnClick", function()
            dlg:Hide()
            if dlg._onCancel then dlg._onCancel() end
        end)
        dlg:SetScript("OnKeyDown", function(self, k)
            if k == "ESCAPE" then
                self:Hide()
                if self._onCancel then self._onCancel() end
                self:SetPropagateKeyboardInput(false)
            else
                self:SetPropagateKeyboardInput(true)
            end
        end)
        dlg:EnableKeyboard(true)
        _minimapReloadDialog = dlg
    end
    _minimapReloadDialog._onCancel = onCancel
    _minimapReloadDialog:Show()
end

-- ============================================================
--  MAIN PANEL
-- ============================================================

local Panel         = nil
local contentFrames = {}
local activeTab     = "general"

local function CreateMainPanel()
    if Panel then return end

    Panel = CreateFrame("Frame", "rOrbsSettingsPanel", UIParent, "BackdropTemplate")
    Panel:SetSize(PANEL_W, PANEL_H)
    Panel:SetPoint("CENTER")
    Panel:SetFrameStrata("DIALOG")
    Panel:SetFrameLevel(100)
    Panel:SetMovable(true)
    Panel:EnableMouse(true)
    Panel:RegisterForDrag("LeftButton")
    Panel:SetScript("OnDragStart", function(self) self:StartMoving() end)
    Panel:SetScript("OnDragStop",  function(self) self:StopMovingOrSizing() end)
    Panel:SetScript("OnKeyDown", function(self, key)
        if key == "ESCAPE" then
            if _ddOpenFrame then
                CloseDropdownPopup()   -- dropdown offen → nur Dropdown schließen, GUI bleibt
            else
                ns.GUI:Close()         -- kein Dropdown → GUI schließen
            end
            self:SetPropagateKeyboardInput(false)
        else
            self:SetPropagateKeyboardInput(true)
        end
    end)
    Panel:EnableKeyboard(false)  -- only active while shown
    Panel:HookScript("OnShow", function(self) self:EnableKeyboard(true) end)
    Panel:HookScript("OnHide", function(self) self:EnableKeyboard(false) end)
    Panel:SetBackdrop({bgFile="Interface\\Buttons\\WHITE8X8", edgeFile="Interface\\Buttons\\WHITE8X8",
        tile=false, edgeSize=2, insets={left=0,right=0,top=0,bottom=0}})
    Panel:SetBackdropColor(C.bg[1], C.bg[2], C.bg[3], C.bg[4])
    Panel:SetBackdropBorderColor(C.accent[1]*0.6, C.accent[2]*0.12, C.accent[3]*0.12, 1)
    Panel:SetAlpha(0)
    Panel:Hide()
    tinsert(UISpecialFrames, "rOrbsSettingsPanel")

    -- ── TITLE BAR ─────────────────────────────────────────────────────────────
    local titleBg = CreateFrame("Frame", nil, Panel, "BackdropTemplate")
    titleBg:SetPoint("TOPLEFT",  Panel, "TOPLEFT",   2, -2)
    titleBg:SetPoint("TOPRIGHT", Panel, "TOPRIGHT",  -2, -2)
    titleBg:SetHeight(TITLE_H)
    titleBg:SetBackdrop({bgFile="Interface\\Buttons\\WHITE8X8", edgeFile="Interface\\Buttons\\WHITE8X8",
        tile=false, edgeSize=1, insets={left=0,right=0,top=0,bottom=0}})
    titleBg:SetBackdropColor(C.accent[1]*0.19, C.accent[2]*0.06, C.accent[3]*0.06, 1)
    titleBg:SetBackdropBorderColor(C.accent[1]*0.45, 0.05, 0.05, 1)

    -- Left red accent stripe
    local ta = titleBg:CreateTexture(nil, "ARTWORK")
    ta:SetWidth(3)
    ta:SetPoint("TOPLEFT",    titleBg, "TOPLEFT",    0, 0)
    ta:SetPoint("BOTTOMLEFT", titleBg, "BOTTOMLEFT", 0, 0)
    ta:SetColorTexture(C.accent[1], C.accent[2], C.accent[3], 1)

    -- Logo
    local logoTex = titleBg:CreateTexture(nil, "OVERLAY")
    logoTex:SetTexture("Interface\\AddOns\\rOrbs\\media\\d2_skull")
    logoTex:SetSize(32, 32)
    logoTex:SetPoint("LEFT", titleBg, "LEFT", 14, 0)

    -- Title text (right of logo)
    local title = titleBg:CreateFontString(nil, "OVERLAY", "GameFontNormalHuge")
    title:SetPoint("LEFT", logoTex, "RIGHT", 10, 0)
    title:SetTextColor(1, 1, 1, 1)
    title:SetText(L["SETTINGS_TITLE"])

    -- Bottom gradient accent line
    local tgl = titleBg:CreateTexture(nil, "ARTWORK")
    tgl:SetHeight(2)
    tgl:SetPoint("BOTTOMLEFT",  titleBg, "BOTTOMLEFT",  0, 0)
    tgl:SetPoint("BOTTOMRIGHT", titleBg, "BOTTOMRIGHT", 0, 0)
    tgl:SetGradient("HORIZONTAL",
        CreateColor(C.accent[1], C.accent[2], C.accent[3], 1),
        CreateColor(C.accent[1]*0.2, 0, 0, 0))

    -- Close button (CombatStatus-style: red bg, x text, 4-line border)
    local closeBtn = CreateFrame("Button", nil, Panel)
    closeBtn:SetSize(28, 28)
    closeBtn:SetPoint("TOPRIGHT", Panel, "TOPRIGHT", -4, -4)
    closeBtn:SetFrameLevel(titleBg:GetFrameLevel() + 10)

    local closeBg = closeBtn:CreateTexture(nil, "BACKGROUND")
    closeBg:SetAllPoints()
    closeBg:SetColorTexture(0.45, 0.08, 0.08, 0.9)

    do
        local function _cbl(p1, p2, isW)
            local t = closeBtn:CreateTexture(nil, "BORDER")
            t:SetColorTexture(0.65, 0.15, 0.15, 1)
            t:SetPoint(p1, closeBtn, p1, 0, 0)
            t:SetPoint(p2, closeBtn, p2, 0, 0)
            if isW then t:SetWidth(1) else t:SetHeight(1) end
        end
        _cbl("TOPLEFT",    "TOPRIGHT",    false)
        _cbl("BOTTOMLEFT", "BOTTOMRIGHT", false)
        _cbl("TOPLEFT",    "BOTTOMLEFT",  true)
        _cbl("TOPRIGHT",   "BOTTOMRIGHT", true)
    end

    local closeLbl = closeBtn:CreateFontString(nil, "OVERLAY")
    closeLbl:SetFont("Fonts\\FRIZQT__.TTF", 14, "OUTLINE")
    closeLbl:SetPoint("CENTER", closeBtn, "CENTER", 0, 1)
    closeLbl:SetTextColor(1, 1, 1, 1)
    closeLbl:SetText("x")

    closeBtn:SetScript("OnEnter", function() closeBg:SetColorTexture(0.90, 0.22, 0.22, 1) end)
    closeBtn:SetScript("OnLeave", function() closeBg:SetColorTexture(0.45, 0.08, 0.08, 0.9) end)
    closeBtn:SetScript("OnClick", function() ns.GUI:Close() end)

    -- ── SIDEBAR ───────────────────────────────────────────────────────────────
    local SIDEBAR_W = 180
    local HEADER_H  = TITLE_H

    local sidebar = CreateFrame("Frame", nil, Panel, "BackdropTemplate")
    sidebar:SetPoint("TOPLEFT",    titleBg, "BOTTOMLEFT",   2,  0)
    sidebar:SetPoint("BOTTOMLEFT", Panel,   "BOTTOMLEFT",   2,  BOT_H+2)
    sidebar:SetWidth(SIDEBAR_W)
    sidebar:SetBackdrop({bgFile="Interface\\Buttons\\WHITE8X8"})
    sidebar:SetBackdropColor(C.sidebar[1], C.sidebar[2], C.sidebar[3], C.sidebar[4])

    -- Right border line of sidebar
    local sbl = sidebar:CreateTexture(nil, "BORDER")
    sbl:SetWidth(1)
    sbl:SetPoint("TOPRIGHT",    sidebar, "TOPRIGHT",    0,  0)
    sbl:SetPoint("BOTTOMRIGHT", sidebar, "BOTTOMRIGHT", 0,  0)
    sbl:SetColorTexture(C.accent[1]*0.45, 0.04, 0.04, 1)

    -- ── SUB-NAVIGATION COLUMN ─────────────────────────────────────────────────
    local subNavFrame = CreateFrame("Frame", "rOrbsSubNavFrame", Panel, "BackdropTemplate")
    subNavFrame:SetWidth(SUBNAV_W)
    subNavFrame:SetPoint("TOPLEFT",    sidebar, "TOPRIGHT",   1,  0)
    subNavFrame:SetPoint("BOTTOMLEFT", Panel,   "BOTTOMLEFT", 2 + 180 + 1, BOT_H+2)
    subNavFrame:SetBackdrop({bgFile="Interface\\Buttons\\WHITE8X8"})
    subNavFrame:SetBackdropColor(C.bgDeep[1]+0.01, C.bgDeep[2]+0.01, C.bgDeep[3]+0.012, 1)
    subNavFrame:Hide()
    Panel.subNavFrame = subNavFrame

    -- Right border line of sub-nav
    local snbl = subNavFrame:CreateTexture(nil, "BORDER")
    snbl:SetWidth(1)
    snbl:SetPoint("TOPRIGHT",    subNavFrame, "TOPRIGHT",    0, 0)
    snbl:SetPoint("BOTTOMRIGHT", subNavFrame, "BOTTOMRIGHT", 0, 0)
    snbl:SetColorTexture(C.accent[1]*0.30, 0.03, 0.03, 1)

    Panel.subNavButtons = {}   -- key → {btn, …}
    Panel.activeSubTab  = nil

    -- ── CONTENT AREA ──────────────────────────────────────────────────────────
    local contentBg = CreateFrame("Frame", nil, Panel, "BackdropTemplate")
    -- Initial anchor: right of sidebar. SetTab repositions it when sub-nav is shown.
    contentBg:SetPoint("TOPLEFT",     sidebar, "TOPRIGHT",    1,  0)
    contentBg:SetPoint("BOTTOMRIGHT", Panel,   "BOTTOMRIGHT", -2, BOT_H+2)
    Panel.contentBg = contentBg
    Panel.sidebar   = sidebar
    contentBg:SetBackdrop({bgFile="Interface\\Buttons\\WHITE8X8", edgeFile="Interface\\Buttons\\WHITE8X8",
        tile=false, edgeSize=1, insets={left=0,right=0,top=0,bottom=0}})
    contentBg:SetBackdropColor(C.content[1], C.content[2], C.content[3], C.content[4])
    contentBg:SetBackdropBorderColor(C.border[1], C.border[2], C.border[3], 0.5)

    -- Content header (shows active tab name)
    local contentHeader = contentBg:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
    contentHeader:SetPoint("TOPLEFT", contentBg, "TOPLEFT", PAD, -12)
    contentHeader:SetTextColor(C.accent[1], C.accent[2], C.accent[3], 0.7)
    contentHeader:SetText("")
    Panel.contentHeader = contentHeader

    local chLine = contentBg:CreateTexture(nil, "BACKGROUND")
    chLine:SetHeight(1)
    chLine:SetPoint("TOPLEFT",  contentBg, "TOPLEFT",  PAD,  -32)
    chLine:SetPoint("TOPRIGHT", contentBg, "TOPRIGHT", -PAD, -32)
    chLine:SetColorTexture(C.accent[1], C.accent[2], C.accent[3], 0.20)

    -- ── BOTTOM BAR ────────────────────────────────────────────────────────────
    local bot = CreateFrame("Frame", nil, Panel, "BackdropTemplate")
    bot:SetPoint("BOTTOMLEFT",  Panel, "BOTTOMLEFT",   2, 2)
    bot:SetPoint("BOTTOMRIGHT", Panel, "BOTTOMRIGHT", -2, 2)
    bot:SetHeight(BOT_H)
    bot:SetBackdrop({bgFile="Interface\\Buttons\\WHITE8X8", edgeFile="Interface\\Buttons\\WHITE8X8",
        tile=false, edgeSize=1, insets={left=0,right=0,top=0,bottom=0}})
    bot:SetBackdropColor(0.04, 0.04, 0.046, 1)
    bot:SetBackdropBorderColor(C.border[1], C.border[2], C.border[3], 0.5)

    -- Top accent line on bottom bar
    local botLine = bot:CreateTexture(nil, "ARTWORK")
    botLine:SetHeight(1)
    botLine:SetPoint("TOPLEFT",  bot, "TOPLEFT",  0, 0)
    botLine:SetPoint("TOPRIGHT", bot, "TOPRIGHT", 0, 0)
    botLine:SetColorTexture(C.accent[1], C.accent[2], C.accent[3], 0.35)

    -- ── GUI Scale stepper (rThreat-style: ◄ 1.00 ►) ─────────────────────────
    do
        local SCALE_STEP = 0.05
        local SCALE_MIN  = 0.5
        local SCALE_MAX  = 2.0

        local scaleLbl = bot:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
        scaleLbl:SetPoint("LEFT", bot, "LEFT", 10, 0)
        scaleLbl:SetTextColor(unpack(C.textDim))
        scaleLbl:SetText(L["GUI_SCALE"])

        local function GetScale()  return (rOrbsDB and rOrbsDB.guiScale) or 1.0 end
        local function ApplyScale(v)
            v = math.max(SCALE_MIN, math.min(SCALE_MAX, v))
            v = math.floor(v / SCALE_STEP + 0.5) * SCALE_STEP
            v = math.floor(v * 100 + 0.5) / 100
            if rOrbsDB then rOrbsDB.guiScale = v end
            Panel:SetScale(v)
        end

        local valBox = CreateFrame("EditBox", nil, bot, "BackdropTemplate")
        valBox:SetSize(46, 22)
        valBox:SetBackdrop({bgFile="Interface\\Buttons\\WHITE8x8", edgeFile="Interface\\Buttons\\WHITE8x8", edgeSize=1,
            insets={left=0,right=0,top=0,bottom=0}})
        valBox:SetBackdropColor(C.accent[1]*0.06, 0.01, 0.01, 0.9)
        valBox:SetBackdropBorderColor(C.accent[1]*0.4, 0.05, 0.05, 1)
        valBox:SetFont("Fonts\\FRIZQT__.TTF", 11, "")
        valBox:SetTextColor(1, 0.9, 0.8, 1)
        valBox:SetJustifyH("CENTER")
        valBox:SetMaxLetters(5)
        valBox:SetAutoFocus(false)
        valBox:EnableMouseWheel(true)

        local function SyncBox()
            valBox:SetText(string.format("%.2f", GetScale()))
            Panel:SetScale(GetScale())
        end

        local function MakeArrowBtn(label, dir)
            local b = CreateFrame("Button", nil, bot, "BackdropTemplate")
            b:SetSize(20, 22)
            b:SetBackdrop({bgFile="Interface\\Buttons\\WHITE8x8", edgeFile="Interface\\Buttons\\WHITE8x8", edgeSize=1,
                insets={left=1,right=1,top=1,bottom=1}})
            b:SetBackdropColor(C.accent[1]*0.10, 0.02, 0.02, 1)
            b:SetBackdropBorderColor(C.accent[1]*0.5, 0.05, 0.05, 1)
            local t = b:CreateFontString(nil, "OVERLAY", "GameFontNormal")
            t:SetAllPoints(); t:SetText(label)
            t:SetTextColor(C.accentHi[1], C.accentHi[2], C.accentHi[3], 1)
            b:SetScript("OnEnter", function(s)
                s:SetBackdropColor(C.accent[1]*0.18, 0.04, 0.04, 1)
                s:SetBackdropBorderColor(C.accent[1], 0.10, 0.10, 1)
            end)
            b:SetScript("OnLeave", function(s)
                s:SetBackdropColor(C.accent[1]*0.10, 0.02, 0.02, 1)
                s:SetBackdropBorderColor(C.accent[1]*0.5, 0.05, 0.05, 1)
                s.held = false
            end)
            local ticker
            b:SetScript("OnClick", function() ApplyScale(GetScale() + dir*SCALE_STEP); SyncBox() end)
            b:SetScript("OnMouseDown", function()
                b.held = true
                C_Timer.After(0.4, function()
                    if b.held then
                        ticker = C_Timer.NewTicker(0.08, function()
                            if b.held then ApplyScale(GetScale() + dir*SCALE_STEP); SyncBox()
                            else ticker:Cancel(); ticker = nil end
                        end)
                    end
                end)
            end)
            b:SetScript("OnMouseUp", function() b.held=false; if ticker then ticker:Cancel(); ticker=nil end end)
            return b
        end

        local btnMinus = MakeArrowBtn("<", -1)
        btnMinus:SetPoint("LEFT", scaleLbl, "RIGHT", 8, 0)
        valBox:SetPoint("LEFT", btnMinus, "RIGHT", 2, 0)
        local btnPlus = MakeArrowBtn(">", 1)
        btnPlus:SetPoint("LEFT", valBox, "RIGHT", 2, 0)

        valBox:SetScript("OnMouseWheel", function(_, d) ApplyScale(GetScale() + d*SCALE_STEP); SyncBox() end)
        local function CommitTyped()
            local v = tonumber(valBox:GetText())
            if v then ApplyScale(v) end
            SyncBox(); valBox:ClearFocus()
        end
        valBox:SetScript("OnEnterPressed",  CommitTyped)
        valBox:SetScript("OnTabPressed",    CommitTyped)
        valBox:SetScript("OnEditFocusLost", CommitTyped)
        valBox:SetScript("OnEscapePressed", function() SyncBox(); valBox:ClearFocus() end)
        Panel:HookScript("OnShow", SyncBox)
        Panel.SyncScale = SyncBox
        SyncBox()
    end

    -- Close button (bottom bar)
    local function StyledBtn(parent, text, w, onClick)
        local btn = CreateFrame("Button", nil, parent, "BackdropTemplate")
        btn:SetSize(w or 100, 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(C.accent[1]*0.18, C.accent[2]*0.06, C.accent[3]*0.06, 1)
        btn:SetBackdropBorderColor(C.accent[1]*0.70, 0.08, 0.08, 1)
        local lbl = btn:CreateFontString(nil, "OVERLAY", "GameFontNormal")
        lbl:SetPoint("CENTER"); lbl:SetText(text)
        lbl:SetTextColor(C.accent[1], C.accent[2], C.accent[3], 1)
        -- Auto-Breite wenn w nicht explizit angegeben
        if not w then
            C_Timer.After(0, function()
                local tw = lbl:GetStringWidth()
                btn:SetSize(math.max(60, tw + BTN_PAD_X * 2), 26)
            end)
        end
        btn:SetScript("OnEnter", function(s)
            s:SetBackdropColor(C.accent[1]*0.38, 0.06, 0.06, 1)
            s:SetBackdropBorderColor(C.accentHi[1], 0.15, 0.15, 1)
            lbl:SetTextColor(1, 1, 1, 1)
        end)
        btn:SetScript("OnLeave", function(s)
            s:SetBackdropColor(C.accent[1]*0.18, C.accent[2]*0.06, C.accent[3]*0.06, 1)
            s:SetBackdropBorderColor(C.accent[1]*0.70, 0.08, 0.08, 1)
            lbl:SetTextColor(C.accent[1], C.accent[2], C.accent[3], 1)
        end)
        btn:SetScript("OnClick", onClick)
        return btn, lbl
    end

    StyledBtn(bot, L["SETTINGS_CLOSE"], nil, function() ns.GUI:Close() end)
        :SetPoint("RIGHT", bot, "RIGHT", -12, 0)

    -- ── TAB DEFINITIONS (with icons) ─────────────────────────────────────────
    local tabData = {
        { key = "general",  label = L["TAB_GENERAL"],              icon = "Interface\\Icons\\INV_Misc_Note_06" },
        { key = "health",   label = L["TAB_HEALTH_ORB"],           icon = "Interface\\Icons\\spell_shadow_lifedrain" },
        { key = "power",    label = L["TAB_POWER_ORB"],            icon = "Interface\\Icons\\spell_shadow_manaburn" },
        { key = "target",   label = L["TAB_TARGET_ORB"],           icon = "Interface\\Icons\\Ability_Spy" },
        { key = "tot",      label = L["TAB_TOT_ORB"], icon = "Interface\\Icons\\Ability_Rogue_Shadowstep" },
        { key = "pet",      label = L["TAB_PET_ORB"], icon = "Interface\\Icons\\Ability_Hunter_BeastTraining" },
        { key = "party",   label = L["TAB_PARTY"], icon = "Interface\\Icons\\Ability_Warrior_Challange" },
        { key = "models",   label = L["TAB_MODELS"],   icon = "Interface\\Icons\\INV_Misc_Toy_08" },
        { key = "profiles", label = L["TAB_PROFILES"], icon = "Interface\\Icons\\Achievement_Character_Human_Female" },
    }
    Panel.tabs = {}

    -- Sidebar background (already created above as `sidebar`)
    -- Nav buttons
    local TAB_GAP = 2
    for i, td in ipairs(tabData) 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))

        -- Background texture
        local bbg = tab:CreateTexture(nil, "BACKGROUND"); bbg:SetAllPoints()
        bbg:SetColorTexture(C.tabIdle[1], C.tabIdle[2], C.tabIdle[3], 1)
        tab.bg = bbg

        -- Left accent bar (3px red) - hidden when inactive
        local bbar = tab:CreateTexture(nil, "OVERLAY")
        bbar:SetWidth(3)
        bbar:SetPoint("TOPLEFT",    tab, "TOPLEFT",    0, 0)
        bbar:SetPoint("BOTTOMLEFT", tab, "BOTTOMLEFT", 0, 0)
        bbar:SetColorTexture(C.accent[1], C.accent[2], C.accent[3], 0)
        tab.underline = bbar

        -- Separator line at bottom
        local sep = tab:CreateTexture(nil, "BORDER"); sep:SetHeight(1)
        sep:SetPoint("BOTTOMLEFT",  tab, "BOTTOMLEFT",  0, 0)
        sep:SetPoint("BOTTOMRIGHT", tab, "BOTTOMRIGHT", 0, 0)
        sep:SetColorTexture(0.12, 0.12, 0.14, 1)

        -- Icon
        local icon = tab:CreateTexture(nil, "OVERLAY"); icon:SetSize(18, 18)
        icon:SetPoint("LEFT", tab, "LEFT", 14, 0)
        icon:SetTexture(td.icon)
        icon:SetVertexColor(0.50, 0.50, 0.50, 1)
        tab.icon = icon

        -- Label
        local bfs = tab:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
        bfs:SetPoint("LEFT",  tab.icon, "RIGHT",  8, 0)
        bfs:SetPoint("RIGHT", tab,      "RIGHT", -6, 0)
        bfs:SetJustifyH("LEFT")
        bfs:SetText(td.label)
        bfs:SetTextColor(0.75, 0.73, 0.72, 1)
        tab.fs = bfs

        local ii = i
        tab:SetScript("OnClick", function() ns.GUI:SetTab(td.key) end)
        tab:SetScript("OnEnter", function(s)
            if activeTab ~= td.key then
                s.bg:SetColorTexture(C.tabHover[1], C.tabHover[2], C.tabHover[3], 1)
                s.fs:SetTextColor(C.accentHi[1], C.accentHi[2], C.accentHi[3], 1)
                s.icon:SetVertexColor(C.accent[1], C.accent[2], C.accent[3], 1)
            end
        end)
        tab:SetScript("OnLeave", function(s)
            if activeTab ~= td.key then
                s.bg:SetColorTexture(C.tabIdle[1], C.tabIdle[2], C.tabIdle[3], 1)
                s.fs:SetTextColor(0.75, 0.73, 0.72, 1)
                s.icon:SetVertexColor(0.50, 0.50, 0.50, 1)
            end
        end)
        tab.key    = td.key
        tab.label  = td.label
        Panel.tabs[td.key] = tab
    end

    -- ── SCROLL PANE BUILDER ───────────────────────────────────────────────────
    local contentTop = 0
    local CONTENT_X  = SIDEBAR_W + 6
    local SB_W = 6   -- CombatStatus-Stil: schmälere Scrollbar


    -- ============================================================
    --  SUB-NAVIGATION BUILDER
    -- ============================================================
    local activeSubTab = nil

    -- subNavData: tabs that have sub-sections
    -- Each entry: { key, labelKey }
    local subNavData = {
        health = {
            { key = "orb",        label = L["SUBNAV_ORB"]        },
            { key = "classpower", label = L["SUBNAV_CLASSPOWER"]  },
            { key = "ring",       label = L["SUBNAV_RING"]        },
            { key = "icons",      label = L["SUBNAV_ICONS"]       },
            { key = "figure",     label = L["SUBNAV_FIGURE"]      },
            { key = "font",       label = L["SUBNAV_FONT"]        },
            { key = "reset",      label = L["SUBNAV_RESET"]       },
        },
        power = {
            { key = "orb",    label = L["SUBNAV_ORB"]    },
            { key = "ring",   label = L["SUBNAV_RING"]   },
            { key = "figure", label = L["SUBNAV_FIGURE"] },
            { key = "font",   label = L["SUBNAV_FONT"]   },
            { key = "reset",  label = L["SUBNAV_RESET"]  },
        },
        target = {
            { key = "orb",      label = L["SUBNAV_ORB"]       },
            { key = "ring",     label = L["SUBNAV_RING"]      },
            { key = "icons",    label = L["SUBNAV_ICONS"]     },
            { key = "castring", label = L["SUBNAV_CASTRING"]  },
            { key = "font",     label = L["SUBNAV_FONT"]      },
            { key = "reset",    label = L["SUBNAV_RESET"]     },
        },
        tot = {
            { key = "orb",   label = L["SUBNAV_ORB"]   },
            { key = "ring",  label = L["SUBNAV_RING"]  },
            { key = "font",  label = L["SUBNAV_FONT"]  },
            { key = "reset", label = L["SUBNAV_RESET"] },
        },
        pet = {
            { key = "orb",      label = L["SUBNAV_ORB"]      },
            { key = "ring",     label = L["SUBNAV_RING"]     },
            { key = "castring", label = L["SUBNAV_CASTRING"] },
            { key = "font",     label = L["SUBNAV_FONT"]     },
            { key = "reset",    label = L["SUBNAV_RESET"]    },
        },
        party = {
            { key = "layout", label = L["SUBNAV_PARTY_LAYOUT"] },
            { key = "font",   label = L["SUBNAV_FONT"]         },
            { key = "icons",  label = L["SUBNAV_ICONS"]        },
            { key = "reset",  label = L["SUBNAV_RESET"]        },
        },
    }

    local SUBNAV_BTN_H = 36
    local SUBNAV_GAP   = 1

    local function SetSubTab(tabKey, subKey)
        activeSubTab = tabKey.."."..subKey
        -- Show/hide sub-panes
        for k, sf in pairs(contentFrames) do
            if k:sub(1, #tabKey+1) == tabKey.."." then
                if k == activeSubTab then
                    sf:Show()
                    local child = sf._sf and sf._sf:GetScrollChild()
                    if child and child._updateThumb then
                        local fn = child._updateThumb
                        sf:SetScript("OnUpdate", function(self)
                            self:SetScript("OnUpdate", nil)
                            fn()
                        end)
                    end
                else
                    sf:Hide()
                end
            end
        end
        -- Update sub-nav button styles
        if Panel.subNavButtons and Panel.subNavButtons[tabKey] then
            for _, btnData in ipairs(Panel.subNavButtons[tabKey]) do
                -- Reset-Button behält immer seinen roten Dauerstil
                if btnData.key == "reset" then
                    -- Stil wird in BuildSubNav gesetzt und nie überschrieben
                else
                    local isActive = (tabKey.."."..btnData.key == activeSubTab)
                    btnData.bg:SetColorTexture(
                        isActive and C.accent[1]*0.22 or C.bgDeep[1]+0.005,
                        isActive and 0.04 or C.bgDeep[2]+0.005,
                        isActive and 0.04 or C.bgDeep[3]+0.006,
                        1)
                    btnData.fs:SetTextColor(
                        isActive and 1     or 0.72,
                        isActive and 0.95  or 0.70,
                        isActive and 0.93  or 0.70,
                        1)
                    if btnData.bar then
                        btnData.bar:SetColorTexture(
                            C.accent[1], C.accent[2], C.accent[3],
                            isActive and 1 or 0)
                    end
                end
            end
        end
        -- Update content header to show tab + sub-tab
        if Panel.contentHeader then
            local tabLabel = ""
            for _, td in ipairs({
                {key="health",label=L["TAB_HEALTH_ORB"]},
                {key="power", label=L["TAB_POWER_ORB"]},
                {key="target",label=L["TAB_TARGET_ORB"]},
                {key="tot",   label=L["TAB_TOT_ORB"]},
                {key="pet",   label=L["TAB_PET_ORB"]},
            }) do
                if td.key == tabKey then tabLabel = td.label; break end
            end
            local subLabel = ""
            if subNavData[tabKey] then
                for _, sd in ipairs(subNavData[tabKey]) do
                    if sd.key == subKey then subLabel = sd.label; break end
                end
            end
            Panel.contentHeader:SetText(subLabel ~= "" and (tabLabel.." — "..subLabel) or tabLabel)
        end
    end
    Panel.SetSubTab = SetSubTab

    local function BuildSubNav(tabKey)
        local data = subNavData[tabKey]
        if not data then return end

        -- Clear old buttons for this tab
        if Panel.subNavButtons[tabKey] then
            for _, bd in ipairs(Panel.subNavButtons[tabKey]) do
                bd.btn:Hide()
            end
        end
        Panel.subNavButtons[tabKey] = {}

        for idx, sd in ipairs(data) do
            local btn = CreateFrame("Button", nil, subNavFrame)
            btn:SetHeight(SUBNAV_BTN_H)
            btn:SetPoint("TOPLEFT",  subNavFrame, "TOPLEFT",   0, -(idx-1)*(SUBNAV_BTN_H+SUBNAV_GAP))
            btn:SetPoint("TOPRIGHT", subNavFrame, "TOPRIGHT", -1, -(idx-1)*(SUBNAV_BTN_H+SUBNAV_GAP))

            local bg = btn:CreateTexture(nil, "BACKGROUND")
            bg:SetAllPoints()
            bg:SetColorTexture(C.bgDeep[1]+0.005, C.bgDeep[2]+0.005, C.bgDeep[3]+0.006, 1)

            -- Left accent bar (3px)
            local bar = btn:CreateTexture(nil, "OVERLAY")
            bar:SetWidth(3)
            bar:SetPoint("TOPLEFT",    btn, "TOPLEFT",    0, 0)
            bar:SetPoint("BOTTOMLEFT", btn, "BOTTOMLEFT", 0, 0)
            bar:SetColorTexture(C.accent[1], C.accent[2], C.accent[3], 0)

            -- Separator below
            local sep = btn:CreateTexture(nil, "BORDER")
            sep:SetHeight(1)
            sep:SetPoint("BOTTOMLEFT",  btn, "BOTTOMLEFT",  0, 0)
            sep:SetPoint("BOTTOMRIGHT", btn, "BOTTOMRIGHT", 0, 0)
            sep:SetColorTexture(0.12, 0.12, 0.14, 1)

            local fs = btn:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
            fs:SetPoint("LEFT",  btn, "LEFT",  10, 0)
            fs:SetPoint("RIGHT", btn, "RIGHT", -4, 0)
            fs:SetJustifyH("LEFT")
            fs:SetText(sd.label)
            fs:SetTextColor(0.72, 0.70, 0.70, 1)

            local capturedKey = sd.key

            if capturedKey == "reset" then
                -- Reset-Button: kein Pane, direkter Dialog-Aufruf.
                -- Dauerhaft in der Hover-Farbe leuchtend (roter Akzent).
                bg:SetColorTexture(C.accent[1]*0.30, 0.04, 0.04, 1)
                bar:SetColorTexture(C.accent[1], C.accent[2], C.accent[3], 0.70)
                fs:SetTextColor(C.accent[1], C.accent[2]*2, C.accent[3]*2, 1)
                -- _resetAction wird nach BuildOrbTab per Wire-Up gesetzt
                local btnData = { btn=btn, bg=bg, bar=bar, fs=fs, key=capturedKey, _resetAction=nil }
                btn:SetScript("OnClick", function()
                    if btnData._resetAction then btnData._resetAction() end
                end)
                btn:SetScript("OnEnter", function()
                    bg:SetColorTexture(C.accent[1]*0.50, 0.06, 0.06, 1)
                    fs:SetTextColor(1, 1, 1, 1)
                end)
                btn:SetScript("OnLeave", function()
                    bg:SetColorTexture(C.accent[1]*0.30, 0.04, 0.04, 1)
                    fs:SetTextColor(C.accent[1], C.accent[2]*2, C.accent[3]*2, 1)
                end)
                btn:Hide()
                tinsert(Panel.subNavButtons[tabKey], btnData)
            else
                btn:SetScript("OnClick",  function() SetSubTab(tabKey, capturedKey) end)
                btn:SetScript("OnEnter",  function()
                    if (tabKey.."."..capturedKey) ~= activeSubTab then
                        bg:SetColorTexture(C.tabHover[1], C.tabHover[2], C.tabHover[3], 1)
                        fs:SetTextColor(C.accentHi[1], C.accentHi[2], C.accentHi[3], 1)
                    end
                end)
                btn:SetScript("OnLeave", function()
                    if (tabKey.."."..capturedKey) ~= activeSubTab then
                        bg:SetColorTexture(C.bgDeep[1]+0.005, C.bgDeep[2]+0.005, C.bgDeep[3]+0.006, 1)
                        fs:SetTextColor(0.72, 0.70, 0.70, 1)
                    end
                end)
                btn:Hide()  -- hidden by default; SetTab shows the right tab's buttons
                tinsert(Panel.subNavButtons[tabKey], { btn=btn, bg=bg, bar=bar, fs=fs, key=capturedKey })
            end
        end
    end
    Panel.BuildSubNav = BuildSubNav

    local function BuildScrollPane(key)
        local container = CreateFrame("Frame", nil, contentBg)
        container:SetPoint("TOPLEFT",     contentBg, "TOPLEFT",     2,     -38)
        container:SetPoint("BOTTOMRIGHT", contentBg, "BOTTOMRIGHT", -2,      4)
        container:Hide()

        -- Scrollbar-Track (CombatStatus-Stil: dunkles Grau)
        local sbTrack = CreateFrame("Frame", nil, container)
        sbTrack:SetWidth(SB_W)
        sbTrack:SetPoint("TOPRIGHT",    container, "TOPRIGHT",    -2, -2)
        sbTrack:SetPoint("BOTTOMRIGHT", container, "BOTTOMRIGHT", -2,  2)
        local sbTrackBg = sbTrack:CreateTexture(nil, "BACKGROUND")
        sbTrackBg:SetAllPoints()
        sbTrackBg:SetColorTexture(0.10, 0.10, 0.12, 0.85)   -- CombatStatus Track-Farbe

        -- Scrollbar-Thumb (CombatStatus-Stil: Accent-Farbe, solid)
        local sbThumb = CreateFrame("Button", nil, container)
        sbThumb:SetWidth(SB_W)
        sbThumb:SetPoint("TOPRIGHT", container, "TOPRIGHT", -2, -2)
        local sbThumbBg = sbThumb:CreateTexture(nil, "BACKGROUND")
        sbThumbBg:SetAllPoints()
        sbThumbBg:SetColorTexture(C.accent[1], C.accent[2], C.accent[3], 1.0)
        sbThumb:SetScript("OnEnter", function()
            sbThumbBg:SetColorTexture(C.accentHi[1], C.accentHi[2], C.accentHi[3], 1)
        end)
        sbThumb:SetScript("OnLeave", function()
            sbThumbBg:SetColorTexture(C.accent[1], C.accent[2], C.accent[3], 1.0)
        end)

        local sf = CreateFrame("ScrollFrame", nil, container)
        sf:SetPoint("TOPLEFT",     container, "TOPLEFT",     0,           0)
        sf:SetPoint("BOTTOMRIGHT", container, "BOTTOMRIGHT", -(SB_W + 4), 0)
        sf:EnableMouseWheel(true)

        local child = CreateFrame("Frame", nil, sf)
        -- Width is set dynamically when the container resizes
        child:SetWidth(PANEL_W - CONTENT_X - PAD - SB_W - 14)
        child:SetHeight(3200)
        sf:SetScrollChild(child)
        sf.child = child
        -- Dynamically adjust child width to scrollframe width
        sf:SetScript("OnSizeChanged", function(self)
            local w = self:GetWidth()
            if w and w > 60 then
                child:SetWidth(w - PAD)
            end
        end)

        local function GetMaxScroll()
            local containerH = container:GetHeight() - 4
            local contentH   = child._contentH or child:GetHeight()
            return math.max(0, contentH - containerH)
        end

        local function UpdateThumb()
            local containerH = container:GetHeight() - 4
            local contentH   = child._contentH or child:GetHeight()
            local trackH     = sbTrack:GetHeight() or containerH
            if containerH <= 0 or contentH <= 0 or trackH <= 0 then return end
            local scrollRange = math.max(0, contentH - containerH)
            -- CombatStatus: Thumb und Track ausblenden wenn kein Scroll nötig
            if scrollRange <= 0 then
                sbThumb:Hide()
                sbTrack:Hide()
                return
            end
            sbTrack:Show()
            sbThumb:Show()
            local thumbH = math.max(16, trackH * (containerH / contentH))
            sbThumb:SetHeight(thumbH)
            local cur  = sf:GetVerticalScroll()
            local pct  = scrollRange > 0 and (cur / scrollRange) or 0
            local travel = trackH - thumbH
            sbThumb:ClearAllPoints()
            sbThumb:SetPoint("TOP", sbTrack, "TOP", 0, -(pct * travel))
        end

        local function ScrollTo(newScroll)
            local maxS = GetMaxScroll()
            sf:SetVerticalScroll(math.max(0, math.min(maxS, newScroll)))
            UpdateThumb()
        end

        -- CombatStatus: delta * 24 Scroll-Schritt
        sf:SetScript("OnMouseWheel", function(self, delta) ScrollTo(sf:GetVerticalScroll() - delta * 24) end)
        sf:SetScript("OnVerticalScroll", function(self, offset) UpdateThumb() end)
        sbTrack:EnableMouseWheel(true)
        sbTrack:SetScript("OnMouseWheel", function(self, delta) ScrollTo(sf:GetVerticalScroll() - delta * 24) end)

        local _dragging, _dragStartY, _dragStartScroll = false, 0, 0
        sbThumb:SetScript("OnMouseDown", function(self, btn)
            if btn ~= "LeftButton" then return end
            _dragging = true
            local _, y = GetCursorPosition()
            _dragStartY = y / UIParent:GetEffectiveScale()
            _dragStartScroll = sf:GetVerticalScroll()
            self:SetScript("OnUpdate", function()
                if not _dragging then self:SetScript("OnUpdate", nil); return end
                local _, cy = GetCursorPosition()
                cy = cy / UIParent:GetEffectiveScale()
                local dy = _dragStartY - cy
                local trackH     = sbTrack:GetHeight() or 1
                local thumbH     = sbThumb:GetHeight()
                local travel     = math.max(1, trackH - thumbH)
                ScrollTo(_dragStartScroll + (dy / travel) * GetMaxScroll())
            end)
        end)
        sbThumb:SetScript("OnMouseUp", function(self) _dragging=false; self:SetScript("OnUpdate",nil) end)

        sf:SetScript("OnShow",               function() UpdateThumb() end)
        container:SetScript("OnShow",        function() UpdateThumb() end)
        container:SetScript("OnSizeChanged", function() UpdateThumb() end)
        child:SetScript("OnSizeChanged",     function() UpdateThumb() end)
        child._updateThumb = UpdateThumb

        contentFrames[key] = container
        container._sf      = sf
        return child
    end

    -- BuildSubPane: like BuildScrollPane but for compound keys like "health.orb"
    local function BuildSubPane(compoundKey)
        return BuildScrollPane(compoundKey)
    end



    -- ============================================================
    --  TAB: GENERAL
    -- ============================================================
    local genPane = BuildScrollPane("general")
    local yg = -PAD

    -- Zentriertes rOrbs-Logo oben im General-Tab
    local genLogo = genPane:CreateTexture(nil, "ARTWORK")
    genLogo:SetTexture("Interface\\AddOns\\rOrbs\\media\\logo_big")
    genLogo:SetSize(500, 237)
    genLogo:SetPoint("TOP", genPane, "TOP", 0, yg)
    yg = yg - 247

    MakeSectionHeader(genPane, L["TAB_GENERAL"], PAD, yg, true)
    yg = yg - 44

    -- Minimap checkbox
    local cbMinimap = MakeCheckbox(genPane, L["GEN_MINIMAP"], L["GEN_MINIMAP_TIP"], PAD, yg)
    cbMinimap:SetScript("OnClick", function(self)
        local val = self:GetChecked()
        ns.GUI:SetMinimapShown(val)
        ns.GUI:ChatMsg(val and L["CHAT_MINIMAP_SHOW"] or L["CHAT_MINIMAP_HIDE"])
        -- Reload-Hinweis anzeigen (nMainBar-Stil)
        ShowMinimapReloadWarning(function()
            -- Abbrechen: Änderung rückgängig machen
            ns.GUI:SetMinimapShown(not val)
            self:SetChecked(not val)
        end)
    end)
    Panel.cbMinimap = cbMinimap
    yg = yg - 50

    -- Info text about frame positioning
    MakeSectionHeader(genPane, L["FRAME_POSITIONING"], PAD, yg)
    yg = yg - 44

    MakeInfoText(genPane,
        L["FRAME_POSITIONING_TIP"],
        PAD, yg, PANEL_W - CONTENT_X - PAD - SB_W - 30)
    yg = yg - 62



    -- Credits / Danksagung
    MakeSectionHeader(genPane, L["CREDITS_HEADER"], PAD, yg)
    yg = yg - 32

    local credLine1 = genPane:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
    credLine1:SetPoint("TOPLEFT", genPane, "TOPLEFT", PAD, yg)
    credLine1:SetWidth(PANEL_W - CONTENT_X - PAD - SB_W - 30)
    credLine1:SetJustifyH("LEFT")
    credLine1:SetText(L["CREDITS_LINE1"])
    yg = yg - 24

    local credLine2 = genPane:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    credLine2:SetPoint("TOPLEFT", genPane, "TOPLEFT", PAD, yg)
    credLine2:SetWidth(PANEL_W - CONTENT_X - PAD - SB_W - 30)
    credLine2:SetJustifyH("LEFT")
    credLine2:SetTextColor(0.75, 0.75, 0.75)
    credLine2:SetText(L["CREDITS_LINE2"])
    yg = yg - 38

    MakeSep(genPane, yg) ; yg = yg - 18

    local resetBtn = MakeButton(genPane, L["SETTINGS_RESET"], PAD, yg, 200, 28, 0.40, 0.05, 0.03)
    resetBtn:SetScript("OnClick", function()
        StaticPopupDialogs["RORBS_RESET_CONFIRM"] = {
            text          = L["SETTINGS_RESET_CONFIRM"],
            button1       = OKAY,
            button2       = CANCEL,
            OnAccept      = function()
                rOrbs_DB_CHAR = nil
                rOrbs_DB_GLOB = nil
                rOrbsDB       = nil
                ns.GUI:ChatMsg(L["CHAT_RESET_DONE"])
                ReloadUI()
            end,
            timeout = 0, whileDead = true, hideOnEscape = true,
        }
        StaticPopup_Show("RORBS_RESET_CONFIRM")
    end)
    yg = yg - 42

    genPane._contentH = math.abs(yg) + PAD + 40
    if genPane._updateThumb then genPane._updateThumb() end

    -- ============================================================
    --  ORB TAB BUILDER  (health, power, target)
    -- ============================================================
    local function BuildOrbTab(key, orbType)
        -- For health/power/target: build sub-panes; for others: single pane
        local isMulti = (orbType == "HEALTH" or orbType == "POWER" or orbType == "TARGET_HEALTH" or orbType == "TOT_HEALTH" or orbType == "PET_HEALTH")
        local subSuffix = orbType == "HEALTH" and "health" or (orbType == "TARGET_HEALTH" and "target" or (orbType == "TOT_HEALTH" and "tot" or (orbType == "PET_HEALTH" and "pet" or "power")))

        -- Create all needed panes upfront
        local paneOrb       = BuildSubPane(key..".orb")
        local paneRing      = (orbType == "HEALTH" or orbType == "POWER" or orbType == "TARGET_HEALTH" or orbType == "TOT_HEALTH" or orbType == "PET_HEALTH") and BuildSubPane(key..".ring") or nil
        local paneClassPow  = (orbType == "HEALTH") and BuildSubPane(key..".classpower") or nil
        local paneIcons     = (orbType == "HEALTH" or orbType == "TARGET_HEALTH") and BuildSubPane(key..".icons") or nil
        local paneFigure    = (orbType == "HEALTH" or orbType == "POWER") and BuildSubPane(key..".figure") or nil
        local paneCastRing  = (orbType == "TARGET_HEALTH" or orbType == "PET_HEALTH") and BuildSubPane(key..".castring") or nil
        local paneFont      = isMulti and BuildSubPane(key..".font") or nil
        local paneReset     = isMulti and BuildSubPane(key..".reset") or nil
        -- For non-multi orbs, all content goes into .orb
        -- We alias "pane" to track the current active sub-pane
        local pane = paneOrb
        local yo   = -PAD
        Panel[key] = {}

        -- SwitchPane: save yo for the current pane, switch to newPane, restore its yo
        local function SwitchPane(newPane)
            if pane then
                pane._savedYo = yo   -- remember where this pane left off
                pane._contentH = math.abs(yo) + PAD + 40
                if pane._updateThumb then pane._updateThumb() end
            end
            pane = newPane
            -- Restore saved yo (in case we revisit a pane, e.g. paneOrb gets a second batch)
            yo = (newPane and newPane._savedYo) or -PAD
        end

        -- After construction wire sub-nav if multi
        local function FinishOrbTab()
            if pane then
                pane._contentH = math.abs(yo) + PAD + 40
                if pane._updateThumb then pane._updateThumb() end
            end
            if isMulti then
                Panel.BuildSubNav(key)
            end
        end

        -- Determine which config section and label to use
        local cfgKey = orbType  -- HEALTH, POWER, TARGET_HEALTH, or TOT_HEALTH
        local tabLabel
        if     orbType == "HEALTH"        then tabLabel = L["TAB_HEALTH_ORB"]
        elseif orbType == "POWER"         then tabLabel = L["TAB_POWER_ORB"]
        elseif orbType == "TARGET_HEALTH" then tabLabel = L["TAB_TARGET_ORB"]
        elseif orbType == "TOT_HEALTH"    then tabLabel = L["TAB_TOT_ORB"]
        elseif orbType == "PET_HEALTH"    then tabLabel = L["TAB_PET_ORB"]
        end

        -- Ensure db.char[cfgKey] exists (backward compat for new orb types)
        local function ensureCharCfg()
            local db2 = ns.db
            if db2 and not db2.char[cfgKey] then
                local defaults = db2:GetOrbDefaults()
                if defaults and defaults[cfgKey] then
                    db2.char[cfgKey] = defaults[cfgKey]
                    if rOrbs_DB_CHAR then rOrbs_DB_CHAR[cfgKey] = db2.char[cfgKey] end
                end
            end
        end
        ensureCharCfg()

        MakeSectionHeader(pane, tabLabel, PAD, yo, true, function()
            local db2 = ns.db
            if db2 and db2.char[cfgKey] then
                local defs = db2:GetOrbDefaults()
                if defs and defs[cfgKey] and defs[cfgKey].filling then
                    db2.char[cfgKey].filling.texture = defs[cfgKey].filling.texture
                    db2.char[cfgKey].filling.color   = { r=defs[cfgKey].filling.color.r, g=defs[cfgKey].filling.color.g, b=defs[cfgKey].filling.color.b }
                    db2.char[cfgKey].classColor      = defs[cfgKey].classColor
                    db2.char[cfgKey].autoColor       = defs[cfgKey].autoColor
                    rOrbs_DB_CHAR = db2.char
                    ns.GUI:ApplyTexture(cfgKey, db2.char[cfgKey].filling.texture)
                    local c = db2.char[cfgKey].filling.color
                    ns.GUI:ApplyColor(cfgKey, c.r, c.g, c.b)
                end
            end
            ns.GUI:ChatMsg("|cffffff00" .. tabLabel .. "|r reset.")
            ns.GUI:RefreshValues()
        end)
        yo = yo - 44

        -- ENABLE ORB CHECKBOX (Target and ToT only) ----------------------------
        if cfgKey == "TARGET_HEALTH" or cfgKey == "TOT_HEALTH" or cfgKey == "PET_HEALTH" then
            local enableKey = (cfgKey == "TARGET_HEALTH") and "targetOrbEnabled" or (cfgKey == "TOT_HEALTH") and "totOrbEnabled" or "petOrbEnabled"
            local cbEnable = MakeCheckbox(pane, L["ORB_ENABLE"],
                L["ORB_ENABLE_TIP"],
                PAD, yo)
            cbEnable:SetChecked(rOrbsDB[enableKey] ~= false)
            cbEnable:SetScript("OnClick", function(self)
                local val = self:GetChecked()
                StaticPopupDialogs["RORBS_RELOAD_ORB_ENABLE_"..cfgKey] = {
                    text = val and L["ORB_RELOAD_ENABLE_ON"] or L["ORB_RELOAD_ENABLE_OFF"],
                    button1 = L["ORB_RELOAD_ENABLE_BTN"],
                    button2 = CANCEL,
                    OnAccept = function()
                        rOrbsDB[enableKey] = val
                        ReloadUI()
                    end,
                    OnCancel = function()
                        cbEnable:SetChecked(rOrbsDB[enableKey] ~= false)
                    end,
                    timeout = 0, whileDead = true, hideOnEscape = false,
                }
                StaticPopup_Show("RORBS_RELOAD_ORB_ENABLE_"..cfgKey)
            end)
            Panel[key].cbEnable = cbEnable
            yo = yo - 50
            MakeSep(pane, yo) ; yo = yo - 12
        end

        yo = yo - 8
        -- TEXTURE DROPDOWN ---------------------------------------------------
        local ddTex, _ = MakeDropdown(pane, L["ORB_TEXTURE"], L["ORB_TEXTURE_TIP"], PAD, yo, 220)
        Panel[key].ddTex = ddTex
        UIDropDownMenu_Initialize(ddTex, function(self, level)
            local db2 = ns.db
            if not db2 then return end
            local currentTex = db2.char[cfgKey] and db2.char[cfgKey].filling.texture or ""
            for _, entry in ipairs(db2.getListFillingTexture()) do
                local info = UIDropDownMenu_CreateInfo()
                info.text  = entry.key ; info.value = entry.value
                info.checked = (entry.value == currentTex)
                info.func = function()
                    UIDropDownMenu_SetSelectedValue(ddTex, entry.value)
                    UIDropDownMenu_SetText(ddTex, entry.key)
                    ensureCharCfg()
                    if db2.char[cfgKey] and db2.char[cfgKey].filling then
                        db2.char[cfgKey].filling.texture = entry.value
                    end
                    rOrbs_DB_CHAR = db2.char
                    ns.GUI:ApplyTexture(cfgKey, entry.value)
                    ns.GUI:ChatMsg(string.format(L["CHAT_TEXTURE_CHANGED"], entry.key))
                end
                UIDropDownMenu_AddButton(info, level)
            end
        end)
        -- Set initial text (prevents WoW showing "Custom" as default)
        do
            local db2 = ns.db
            if db2 and db2.char[cfgKey] then
                local currentTex = db2.char[cfgKey].filling.texture or ""
                local dispName = "?"
                for _, entry in ipairs(db2.getListFillingTexture()) do
                    if entry.value == currentTex then dispName = entry.key; break end
                end
                UIDropDownMenu_SetText(ddTex, dispName)
                UIDropDownMenu_SetSelectedValue(ddTex, currentTex)
            end
        end
        yo = yo - 56

        -- FILL COLOR ---------------------------------------------------------
        local colLabel = pane:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
        colLabel:SetPoint("TOPLEFT", pane, "TOPLEFT", PAD, yo)
        colLabel:SetText(L["ORB_COLOR"])
        colLabel:SetTextColor(C.text[1], C.text[2], C.text[3])
        yo = yo - 22

        local initCol = { r=1, g=1, b=1 }
        if ns.db and ns.db.char and ns.db.char[cfgKey] then
            initCol = ns.db.char[cfgKey].filling.color
        end

        local colSwatch  -- pre-declare so closure can reference it
        colSwatch = MakeColorSwatch(pane, PAD, yo,
            initCol.r, initCol.g, initCol.b, L["ORB_COLOR"], function()
            local db2 = ns.db
            ensureCharCfg()
            if not db2 or not db2.char[cfgKey] then return end
            -- Read colour from the swatch itself: refreshTab keeps the swatch
            -- in sync with the DB, so this is always the current displayed value.
            -- (Reading directly from db2.char[cfgKey].filling.color can return a
            -- stale or default value if the DB reference drifted since the last
            -- refreshTab call, causing the picker to open with the wrong colour.)
            local cr, cg, cb = colSwatch:GetColor()
            ns.ColorPickerHelper:Open({
                r = cr, g = cg, b = cb,
                onChange = function(r, g, b)
                    db2.char[cfgKey].filling.color = {r=r, g=g, b=b}
                    colSwatch:SetColor(r, g, b)
                    rOrbs_DB_CHAR = db2.char
                    ns.GUI:ApplyColor(cfgKey, r, g, b)
                end,
                onCancel = function()
                    local c = db2.char[cfgKey].filling.color
                    ns.GUI:ApplyColor(cfgKey, c.r, c.g, c.b)
                end,
            })
        end)
        Panel[key].colSwatch = colSwatch

        -- RESET COLOR BUTTON
        local resetColorBtn = MakeButton(pane, "Reset Color", PAD + 140, yo, 100, 22,
            0.20, 0.06, 0.04)
        resetColorBtn:SetScript("OnClick", function()
            local db2 = ns.db
            if not db2 or not db2.char[cfgKey] then return end
            local defaults = db2:GetOrbDefaults()
            if not defaults or not defaults[cfgKey] then return end
            local defCol = defaults[cfgKey].filling.color
            db2.char[cfgKey].filling.color = { r=defCol.r, g=defCol.g, b=defCol.b }
            rOrbs_DB_CHAR = db2.char
            colSwatch:SetColor(defCol.r, defCol.g, defCol.b)
            ns.GUI:ApplyColor(cfgKey, defCol.r, defCol.g, defCol.b)
            ns.GUI:ChatMsg("|cffffff00Orb color|r reset to default.")
        end)
        yo = yo - 44

        -- AUTO / CLASS COLOR (with reload popup) -----------------------------
        -- Only show this option for HEALTH and POWER orbs.
        -- TARGET_HEALTH, TOT_HEALTH and PET_HEALTH do not support class color via oUF.
        if orbType == "HEALTH" or orbType == "POWER" then
            local autoLabel = (orbType == "POWER" and L["ORB_COLOR_AUTO"] or L["ORB_COLOR_CLASS"])
            local autoTip   = (orbType == "POWER" and L["ORB_COLOR_AUTO_TIP"] or L["ORB_COLOR_CLASS_TIP"])
            local cbAuto = MakeCheckbox(pane, autoLabel, autoTip, PAD, yo)
            cbAuto:SetScript("OnClick", function(self)
                local val = self:GetChecked()
                ensureCharCfg()
                if not ns.db or not ns.db.char[cfgKey] then return end
                -- Apply the setting and immediately re-color the orb via
                -- Blizzard-native event handlers (no reload required).
                ns.GUI:ApplyColorAuto(cfgKey, val)
                local msg
                if cfgKey == "HEALTH" then
                    msg = "|cffffff00Health orb class color|r " .. (val and "|cff00ff00enabled|r." or "|cffff4444disabled|r.")
                else
                    msg = "|cffffff00Power orb auto color|r " .. (val and "|cff00ff00enabled|r." or "|cffff4444disabled|r.")
                end
                ns.GUI:ChatMsg(msg)
            end)
            Panel[key].cbAuto = cbAuto
            yo = yo - 56
        end

        -- Switch to ring sub-pane (health/power/target)
        if isMulti and paneRing then SwitchPane(paneRing) end

        -- DECORATIVE RING (Health, Power, Target and ToT orbs) -------------------------
        if cfgKey == "HEALTH" or cfgKey == "POWER" or cfgKey == "TARGET_HEALTH" or cfgKey == "TOT_HEALTH" or cfgKey == "PET_HEALTH" then
            local ringDBKey2    = (cfgKey == "HEALTH") and "healthRingShow"   or (cfgKey == "TARGET_HEALTH") and "targetRingShow"   or (cfgKey == "TOT_HEALTH") and "totRingShow"   or (cfgKey == "PET_HEALTH") and "petRingShow"   or "powerRingShow"
            local ringRotKey2   = (cfgKey == "HEALTH") and "healthRingRotate" or (cfgKey == "TARGET_HEALTH") and "targetRingRotate" or (cfgKey == "TOT_HEALTH") and "totRingRotate" or (cfgKey == "PET_HEALTH") and "petRingRotate" or "powerRingRotate"
            local ringSpeedKey2 = (cfgKey == "HEALTH") and "healthRingSpeed"  or (cfgKey == "TARGET_HEALTH") and "targetRingSpeed"  or (cfgKey == "TOT_HEALTH") and "totRingSpeed"  or (cfgKey == "PET_HEALTH") and "petRingSpeed"  or "powerRingSpeed"
            local ringDirKey2   = (cfgKey == "HEALTH") and "healthRingDir"    or (cfgKey == "TARGET_HEALTH") and "targetRingDir"    or (cfgKey == "TOT_HEALTH") and "totRingDir"    or (cfgKey == "PET_HEALTH") and "petRingDir"    or "powerRingDir"
            MakeSectionHeader(pane, L["SUBNAV_RING"], PAD, yo, true, function()
                -- ringDBKey2/ringRotKey2/ringSpeedKey2/ringDirKey2 are declared above
                -- and hold the same values as ringDBKey/ringRotateKey etc.
                rOrbsDB[ringDBKey2]    = nil
                rOrbsDB[ringRotKey2]   = nil
                rOrbsDB[ringSpeedKey2] = nil
                rOrbsDB[ringDirKey2]   = nil
                local orbGlb = (cfgKey == "HEALTH") and "rOrbsHealthOrb" or (cfgKey == "TARGET_HEALTH") and "rOrbsTargetHealthOrb" or (cfgKey == "TOT_HEALTH") and "rOrbsToTHealthOrb" or (cfgKey == "PET_HEALTH") and "rOrbsPetHealthOrb" or "rOrbsPowerOrb"
                local orb = _G[orbGlb]
                if orb and orb.ring then orb.ring:Show() end
                ns.GUI:ChatMsg("|cffffff00" .. L["SUBNAV_RING"] .. "|r reset.")
                ns.GUI:RefreshValues()
            end) ; yo = yo - 44
            local ringDBKey  = (cfgKey == "HEALTH") and "healthRingShow" or (cfgKey == "TARGET_HEALTH") and "targetRingShow" or (cfgKey == "TOT_HEALTH") and "totRingShow" or (cfgKey == "PET_HEALTH") and "petRingShow" or "powerRingShow"
            local ringOrbGlb = (cfgKey == "HEALTH") and "rOrbsHealthOrb"  or (cfgKey == "TARGET_HEALTH") and "rOrbsTargetHealthOrb" or (cfgKey == "TOT_HEALTH") and "rOrbsToTHealthOrb" or (cfgKey == "PET_HEALTH") and "rOrbsPetHealthOrb" or "rOrbsPowerOrb"
            local ringOrbLabel = (cfgKey == "HEALTH") and "health" or (cfgKey == "TARGET_HEALTH") and "target" or (cfgKey == "TOT_HEALTH") and "tot" or (cfgKey == "PET_HEALTH") and "pet" or "power"
            local cbRing = MakeCheckbox(pane,
                "Show decorative ring",
                "Show or hide the decorative stone ring/wreath around the "..ringOrbLabel.." orb.",
                PAD, yo)
            cbRing:SetChecked(rOrbsDB[ringDBKey] ~= false)
            cbRing:SetScript("OnClick", function(self)
                local val = self:GetChecked()
                rOrbsDB[ringDBKey] = val
                local orb = _G[ringOrbGlb]
                if orb and orb.ring then
                    if val then orb.ring:Show() else orb.ring:Hide() end
                end
                ns.GUI:ChatMsg("|cffffff00Decorative ring ("..ringOrbLabel..")|r "..(val and "|cff00ff00shown|r." or "|cffff4444hidden|r."))
            end)
            Panel[key].cbRing = cbRing ; yo = yo - 50

            -- RING ROTATION CONTROLS
            local ringRotateKey = (cfgKey == "HEALTH") and "healthRingRotate" or (cfgKey == "TARGET_HEALTH") and "targetRingRotate" or (cfgKey == "TOT_HEALTH") and "totRingRotate" or (cfgKey == "PET_HEALTH") and "petRingRotate" or "powerRingRotate"
            local ringSpeedKey  = (cfgKey == "HEALTH") and "healthRingSpeed"  or (cfgKey == "TARGET_HEALTH") and "targetRingSpeed"  or (cfgKey == "TOT_HEALTH") and "totRingSpeed"  or (cfgKey == "PET_HEALTH") and "petRingSpeed"  or "powerRingSpeed"
            local ringDirKey    = (cfgKey == "HEALTH") and "healthRingDir"    or (cfgKey == "TARGET_HEALTH") and "targetRingDir"    or (cfgKey == "TOT_HEALTH") and "totRingDir"    or (cfgKey == "PET_HEALTH") and "petRingDir"    or "powerRingDir"

            local cbRingRotate = MakeCheckbox(pane,
                "Rotate decorative ring",
                "Enable a slow spinning rotation animation on the decorative " .. ringOrbLabel .. " ring.",
                PAD + 20, yo)
            cbRingRotate:SetChecked(rOrbsDB[ringRotateKey] == true)
            cbRingRotate:SetScript("OnClick", function(self)
                local val = self:GetChecked()
                rOrbsDB[ringRotateKey] = val
                local orb = _G[ringOrbGlb]
                if orb and orb.ring then
                    local ring = orb.ring
                    if val then
                        if not ring.ag then
                            ring.ag = ring:CreateAnimationGroup()
                            ring.ag.anim = ring.ag:CreateAnimation("Rotation")
                            local spd = rOrbsDB[ringSpeedKey] or 30
                            local dir = rOrbsDB[ringDirKey] or "CW"
                            ring.ag.anim:SetDegrees(dir == "CCW" and 360 or -360)
                            ring.ag.anim:SetDuration(spd)
                            ring.ag:SetLooping("REPEAT")
                        end
                        ring.ag:Play()
                    else
                        if ring.ag then ring.ag:Stop() end
                    end
                end
                ns.GUI:ChatMsg("|cffffff00Ring rotation (" .. ringOrbLabel .. ")|r " .. (val and "|cff00ff00enabled|r." or "|cffff4444disabled|r."))
            end)
            Panel[key].cbRingRotate = cbRingRotate ; yo = yo - 50

            local sRingSpeed, _ = MakeSlider(pane,
                "Rotation speed – seconds per revolution",
                "How many seconds for one full spin. Lower = faster.",
                5, 120, 1, PAD + 20, yo, SLIDER_W)
            SetSliderHandler(sRingSpeed, function(self, val)
                rOrbsDB[ringSpeedKey] = val
                local orb = _G[ringOrbGlb]
                if orb and orb.ring and orb.ring.ag then
                    local playing = orb.ring.ag:IsPlaying()
                    orb.ring.ag:Stop()
                    orb.ring.ag.anim:SetDuration(val)
                    if playing then orb.ring.ag:Play() end
                end
            end)
            sRingSpeed:SetValue(rOrbsDB[ringSpeedKey] or 30)
            Panel[key].sRingSpeed = sRingSpeed ; yo = yo - 52

            local ddRingDir, _ = MakeDropdown(pane,
                "Rotation direction",
                "Choose clockwise or counter-clockwise rotation direction.",
                PAD + 20, yo, 200)
            Panel[key].ddRingDir = ddRingDir
            UIDropDownMenu_Initialize(ddRingDir, function(self, level)
                local currentDir = rOrbsDB[ringDirKey] or "CW"
                local dirs = {
                    { text = L["RING_DIR_CW"],  value = "CW"  },
                    { text = L["RING_DIR_CCW"], value = "CCW" },
                }
                for _, entry in ipairs(dirs) do
                    local info = UIDropDownMenu_CreateInfo()
                    info.text    = entry.text
                    info.value   = entry.value
                    info.checked = (entry.value == currentDir)
                    info.func = function()
                        UIDropDownMenu_SetSelectedValue(ddRingDir, entry.value)
                        UIDropDownMenu_SetText(ddRingDir, entry.text)
                        rOrbsDB[ringDirKey] = entry.value
                        local orb = _G[ringOrbGlb]
                        if orb and orb.ring and orb.ring.ag then
                            local playing = orb.ring.ag:IsPlaying()
                            orb.ring.ag:Stop()
                            orb.ring.ag.anim:SetDegrees(entry.value == "CCW" and 360 or -360)
                            if playing then orb.ring.ag:Play() end
                        end
                        ns.GUI:ChatMsg("|cffffff00Ring direction (" .. ringOrbLabel .. ")|r \u2192 " .. entry.text)
                    end
                    UIDropDownMenu_AddButton(info, level)
                end
            end)
            do
                local dir = rOrbsDB[ringDirKey] or "CW"
                local dirLabels = { CW = L["RING_DIR_CW"], CCW = L["RING_DIR_CCW"] }
                UIDropDownMenu_SetText(ddRingDir, dirLabels[dir] or L["RING_DIR_CW"])
                UIDropDownMenu_SetSelectedValue(ddRingDir, dir)
            end
            yo = yo - 56

            if cfgKey == "HEALTH" then
                Panel[key].cbAngel = nil

                -- Switch to classpower sub-pane
                if paneClassPow then SwitchPane(paneClassPow) end

                -- CLASS POWER / COMBO POINTS ------------------------------------
                MakeSectionHeader(pane, L["SECTION_CLASS_POWER"], PAD, yo, true, function()
                    rOrbsDB.comboPointStyle  = nil
                    rOrbsDB.runePointsShow   = nil
                    if ns.RunePoints then ns.RunePoints.ForceRefresh() end
                    ns.GUI:ChatMsg("|cffffff00" .. L["SECTION_CLASS_POWER"] .. "|r reset.")
                    ns.GUI:RefreshValues()
                end) ; yo = yo - 44

                local styleLabels = {
                    runes    = "Rune Icons (new)",
                    combobar = "Combo Bar (legacy)",
                    hidden   = "Hidden",
                }
                local ddStyle, _ = MakeDropdown(pane,
                    "Class Power Style",
                    "Choose how combo / class points are shown.\n\n"..
                    "|cffffff00Rune Icons|r: Arc of rune icons (all classes).\n\n"..
                    "|cffffff00Combo Bar|r: Roth_UI-style horizontal gem bar (requires Roth_UI installed).\n\n"..
                    "|cffffff00Hidden|r: Disable both systems.",
                    PAD, yo, 230)
                Panel[key].ddStyle = ddStyle
                local function ApplyComboStyle(val)
                    rOrbsDB.comboPointStyle = val
                    rOrbsDB.runePointsShow  = (val == "runes")
                    if ns.RunePoints then ns.RunePoints.ForceRefresh() end
                    if ns.ComboBar   then ns.ComboBar.ForceRefresh()   end
                    ns.GUI:ChatMsg("|cffffff00Class Power Style|r: "..(styleLabels[val] or val))
                end
                UIDropDownMenu_Initialize(ddStyle, function(self, level)
                    local current = rOrbsDB.comboPointStyle or "runes"
                    local entries = {
                        { value = "runes",    text = "Rune Icons (new)"  },
                        { value = "combobar", text = "Combo Bar (legacy)" },
                        { value = "hidden",   text = "Hidden"             },
                    }
                    for _, e in ipairs(entries) do
                        local info = UIDropDownMenu_CreateInfo()
                        info.text    = e.text
                        info.value   = e.value
                        info.checked = (e.value == current)
                        info.func    = function()
                            UIDropDownMenu_SetSelectedValue(ddStyle, e.value)
                            UIDropDownMenu_SetText(ddStyle, e.text)
                            ApplyComboStyle(e.value)
                        end
                        UIDropDownMenu_AddButton(info, level)
                    end
                end)
                do
                    local cur = rOrbsDB.comboPointStyle or "runes"
                    UIDropDownMenu_SetText(ddStyle, styleLabels[cur] or "Rune Icons (new)")
                    UIDropDownMenu_SetSelectedValue(ddStyle, cur)
                end
                yo = yo - 56

                -- Rune Icon sub-options ----------------------------------------
                MakeSep(pane, yo) ; yo = yo - 14
                MakeSectionHeader(pane, L["SECTION_RUNE_ICONS"], PAD, yo, false, function()
                    rOrbsDB.runePointsBGShow   = nil
                    rOrbsDB.runePointsPosition = nil
                    rOrbsDB.runePointsSize     = nil
                    rOrbsDB.runePointsAlpha    = nil
                    rOrbsDB.runePointsOffX     = nil
                    rOrbsDB.runePointsOffY     = nil
                    rOrbsDB.runePointsHorizX   = nil
                    rOrbsDB.runePointsHorizY   = nil
                    rOrbsDB.runeHorizOffX      = nil
                    rOrbsDB.runeHorizOffY      = nil
                    rOrbsDB.runeHorizSize      = nil
                    rOrbsDB.runeHorizSpacing   = nil
                    -- Update arc slider widgets to their defaults
                    if Panel[key].sArcSize  then Panel[key].sArcSize:SetValue(22)  end
                    if Panel[key].sArcOffX  then Panel[key].sArcOffX:SetValue(0)   end
                    if Panel[key].sArcOffY  then Panel[key].sArcOffY:SetValue(0)   end
                    -- Update horiz slider widgets to their defaults
                    if Panel[key].sRuneHorizOffX     then Panel[key].sRuneHorizOffX:SetValue(0)    end
                    if Panel[key].sRuneHorizOffY     then Panel[key].sRuneHorizOffY:SetValue(-200) end
                    if Panel[key].sRuneHorizSize     then Panel[key].sRuneHorizSize:SetValue(28)   end
                    if Panel[key].sRuneHorizSpacing  then Panel[key].sRuneHorizSpacing:SetValue(6) end
                    -- Reset position dropdown display to default
                    if Panel[key].ddRunePos then
                        UIDropDownMenu_SetText(Panel[key].ddRunePos, "Inside \226\128\147 Top")
                        UIDropDownMenu_SetSelectedValue(Panel[key].ddRunePos, "inside_top")
                    end
                    -- Reset bg checkbox
                    if Panel[key].cbRuneBG then Panel[key].cbRuneBG:SetChecked(true) end
                    if ns.RunePoints then ns.RunePoints.ForceRefresh() end
                    ns.GUI:ChatMsg("|cffffff00" .. L["SECTION_RUNE_ICONS"] .. "|r reset.")
                    ns.GUI:RefreshValues()
                end) ; yo = yo - 44

                -- Rune Icons: Preview Button ----------------------------------
                do
                    local btnRune = MakeButton(pane, L["RUNE_PREVIEW"], PAD + 20, yo, 130, 22, 0.06, 0.16, 0.06)
                    btnRune._prevActive = false
                    btnRune:SetScript("OnClick", function(self)
                        self._prevActive = not self._prevActive
                        if self._prevActive then
                            self.fs:SetText(L["CUSTOM_TMPL_STOP"])
                            self.innerTex:SetColorTexture(0.40, 0.05, 0.03, 1)
                            if ns.RunePoints then ns.RunePoints.ShowAll() end
                        else
                            self.fs:SetText(L["RUNE_PREVIEW"])
                            self.innerTex:SetColorTexture(0.06, 0.16, 0.06, 1)
                            if ns.RunePoints then
                                ns.RunePoints.StopPreview()
                                ns.RunePoints.ForceRefresh()
                            end
                        end
                    end)
                    btnRune:SetScript("OnHide", function(self)
                        if self._prevActive then
                            self._prevActive = false
                            self.fs:SetText(L["RUNE_PREVIEW"])
                            self.innerTex:SetColorTexture(0.06, 0.16, 0.06, 1)
                            if ns.RunePoints then
                                ns.RunePoints.StopPreview()
                                ns.RunePoints.ForceRefresh()
                            end
                        end
                    end)
                    Panel[key].btnRunePreview = btnRune
                end
                yo = yo - 34

                local cbRuneBG = MakeCheckbox(pane,
                    "Show Background Texture (cc_bg)",
                    "Show or hide the cc_bg.tga texture behind each rune / class point icon.",
                    PAD + 20, yo)
                cbRuneBG:SetChecked(rOrbsDB.runePointsBGShow ~= false)
                cbRuneBG:SetScript("OnClick", function(self)
                    local val = self:GetChecked()
                    rOrbsDB.runePointsBGShow = val
                    if ns.RunePoints then ns.RunePoints.UpdateBGVisibility() end
                    ns.GUI:ChatMsg("|cffffff00Rune BG Texture|r "..(val and "|cff00ff00shown|r." or "|cffff4444hidden|r."))
                end)
                Panel[key].cbRuneBG = cbRuneBG ; yo = yo - 44

                -- RUNE POSITION DROPDOWN
                local posLabels = {
                    inside_top     = "Inside – Top",
                    outside_top    = "Outside – Top",
                    inside_bottom  = "Inside – Bottom",
                    outside_bottom = "Outside – Bottom",
                    horizontal     = L["RUNE_POS_HORIZONTAL"],
                }
                local ddRunePos, _ = MakeDropdown(pane,
                    "Rune Icon Position",
                    "Place rune / class point icons inside or outside the orb, on the top or bottom arc.\n\n|cffffff00"..L["RUNE_POS_HORIZONTAL"]..":|r Free-floating horizontal row (SHIFT+Drag to move).",
                    PAD + 20, yo, 200)
                Panel[key].ddRunePos = ddRunePos

                -- Horizontal sub-options (shown/hidden based on mode)
                -- Declared here so the dropdown callback can toggle them
                local horizWidgets = {}
                local horizSliders  = {}   -- sliders whose sub-elements also need toggling

                -- Arc sub-options: visible for all non-horizontal positions
                local arcWidgets = {}
                local arcSliders  = {}

                local function SliderSetVisible(s, visible)
                    if visible then s:Show() else s:Hide() end
                    if s.valLabel  then if visible then s.valLabel:Show()  else s.valLabel:Hide()  end end
                    if s.fillTex   then if visible then s.fillTex:Show()   else s.fillTex:Hide()   end end
                    if s._trackTex then if visible then s._trackTex:Show() else s._trackTex:Hide() end end
                    local thumb = s.GetThumbTexture and s:GetThumbTexture()
                    if thumb then if visible then thumb:Show() else thumb:Hide() end end
                end

                local function SetArcSubOptionsVisible(visible)
                    for _, w in ipairs(arcWidgets) do
                        if visible then w:Show() else w:Hide() end
                    end
                    for _, s in ipairs(arcSliders) do
                        SliderSetVisible(s, visible)
                    end
                end

                local function SetHorizSubOptionsVisible(visible)
                    for _, w in ipairs(horizWidgets) do
                        if visible then w:Show() else w:Hide() end
                    end
                    -- Slider track/fill/valLabel/thumb are children of pane (not the slider
                    -- frame), so they don't hide automatically with the slider.  Toggle them
                    -- explicitly so the pane is fully clear when horizontal mode is off.
                    for _, s in ipairs(horizSliders) do
                        if s.valLabel then
                            if visible then s.valLabel:Show() else s.valLabel:Hide() end
                        end
                        if s.fillTex then
                            if visible then s.fillTex:Show() else s.fillTex:Hide() end
                        end
                        if s._trackTex then
                            if visible then s._trackTex:Show() else s._trackTex:Hide() end
                        end
                        local thumb = s.GetThumbTexture and s:GetThumbTexture()
                        if thumb then
                            if visible then thumb:Show() else thumb:Hide() end
                        end
                    end
                end

                UIDropDownMenu_Initialize(ddRunePos, function(self, level)
                    local current = rOrbsDB.runePointsPosition or "inside_top"
                    local entries = {
                        { value = "inside_top",     text = "Inside \226\128\147 Top"     },
                        { value = "outside_top",    text = "Outside \226\128\147 Top"    },
                        { value = "inside_bottom",  text = "Inside \226\128\147 Bottom"  },
                        { value = "outside_bottom", text = "Outside \226\128\147 Bottom" },
                        { value = "horizontal",     text = L["RUNE_POS_HORIZONTAL"]      },
                    }
                    for _, e in ipairs(entries) do
                        local info = UIDropDownMenu_CreateInfo()
                        info.text    = e.text
                        info.value   = e.value
                        info.checked = (e.value == current)
                        info.func    = function()
                            UIDropDownMenu_SetSelectedValue(ddRunePos, e.value)
                            UIDropDownMenu_SetText(ddRunePos, e.text)
                            rOrbsDB.runePointsPosition = e.value
                            if ns.RunePoints then ns.RunePoints.ForceRefresh() end
                            local nowHoriz = (e.value == "horizontal")
                            SetHorizSubOptionsVisible(nowHoriz)
                            SetArcSubOptionsVisible(not nowHoriz)
                            -- Recalculate scroll pane height (arc=3 sliders, horiz=header+4 sliders)
                            if pane and pane._contentH then
                                local arcH   = 3 * 46
                                local horizH = 26 + 4 * 46
                                local diff   = nowHoriz and (horizH - arcH) or (arcH - horizH)
                                pane._contentH = pane._contentH + diff
                                if pane._updateThumb then pane._updateThumb() end
                            end
                            ns.GUI:ChatMsg("|cffffff00Rune Position|r: "..e.text)
                        end
                        UIDropDownMenu_AddButton(info, level)
                    end
                end)
                do
                    local cur = rOrbsDB.runePointsPosition or "inside_top"
                    UIDropDownMenu_SetText(ddRunePos, posLabels[cur] or "Inside \226\128\147 Top")
                    UIDropDownMenu_SetSelectedValue(ddRunePos, cur)
                end
                yo = yo - 56

                -- yoBeforeArc declared outside both do-blocks so the horiz block
                -- can reference it to start at the same vertical position.
                local yoBeforeArc = yo

                -- Arc sub-options (shown for all non-horizontal positions) ------
                do
                    -- (yoBeforeArc declared above for shared scope)
                    local sArcSize, lArcSize = MakeSlider(pane, "Icon Size",
                        "Size (pixels) of each rune/class point icon in arc mode.",
                        12, 48, 1, PAD + 20, yo, SLIDER_W)
                    SetSliderHandler(sArcSize, function(self, val)
                        rOrbsDB.runePointsSize = val
                        if ns.RunePoints then ns.RunePoints.ForceRefresh() end
                    end)
                    sArcSize:SetValue((rOrbsDB and rOrbsDB.runePointsSize) or 22)
                    tinsert(arcWidgets, sArcSize) ; tinsert(arcWidgets, lArcSize)
                    tinsert(arcSliders, sArcSize)
                    Panel[key].sArcSize = sArcSize ; yo = yo - 46

                    local sArcOffX, lArcOffX = MakeSlider(pane, "Arc Offset X",
                        "Horizontal shift of the entire icon arc (pixels).",
                        -80, 80, 1, PAD + 20, yo, SLIDER_W)
                    SetSliderHandler(sArcOffX, function(self, val)
                        rOrbsDB.runePointsOffX = val
                        if ns.RunePoints then ns.RunePoints.ForceRefresh() end
                    end)
                    sArcOffX:SetValue((rOrbsDB and rOrbsDB.runePointsOffX) or 0)
                    tinsert(arcWidgets, sArcOffX) ; tinsert(arcWidgets, lArcOffX)
                    tinsert(arcSliders, sArcOffX)
                    Panel[key].sArcOffX = sArcOffX ; yo = yo - 46

                    local sArcOffY, lArcOffY = MakeSlider(pane, "Arc Offset Y",
                        "Vertical shift of the entire icon arc (pixels).",
                        -80, 80, 1, PAD + 20, yo, SLIDER_W)
                    SetSliderHandler(sArcOffY, function(self, val)
                        rOrbsDB.runePointsOffY = val
                        if ns.RunePoints then ns.RunePoints.ForceRefresh() end
                    end)
                    sArcOffY:SetValue((rOrbsDB and rOrbsDB.runePointsOffY) or 0)
                    tinsert(arcWidgets, sArcOffY) ; tinsert(arcWidgets, lArcOffY)
                    tinsert(arcSliders, sArcOffY)
                    Panel[key].sArcOffY = sArcOffY ; yo = yo - 46

                    -- Initial visibility; if horizontal, restore yo so arc
                    -- widgets don't leave a whitespace gap.
                    local initIsHoriz = ((rOrbsDB and rOrbsDB.runePointsPosition) or "inside_top") == "horizontal"
                    SetArcSubOptionsVisible(not initIsHoriz)
                    if initIsHoriz then yo = yoBeforeArc end
                end

                -- Horizontal sub-options (Position X/Y, Icon Size, Spacing) ----
                -- Both arc and horiz groups share the same start position (yoBeforeArc)
                -- so they overlap vertically - only one group is visible at a time.
                do
                    local yoBeforeHoriz = yoBeforeArc   -- start at same yo as arc group
                    local hdrHoriz = MakeSectionHeader(pane, L["RUNE_HORIZ_SECTION"], PAD + 20, yo, true)
                    tinsert(horizWidgets, hdrHoriz)
                    yo = yo - 26

                    local sHorizOffX, lHorizOffX = MakeSlider(pane, L["RUNE_HORIZ_OFFX"], L["RUNE_HORIZ_OFFX_TIP"],
                        -800, 800, 1, PAD + 20, yo, SLIDER_W)
                    SetSliderHandler(sHorizOffX, function(self, val)
                        rOrbsDB.runeHorizOffX = val
                        if ns.RunePoints then ns.RunePoints.ApplyRuneHorizLayout() end
                    end)
                    sHorizOffX:SetValue((rOrbsDB and rOrbsDB.runeHorizOffX) or 0)
                    tinsert(horizWidgets, sHorizOffX) ; tinsert(horizWidgets, lHorizOffX)
                    tinsert(horizSliders,  sHorizOffX)
                    Panel[key].sRuneHorizOffX = sHorizOffX ; yo = yo - 46

                    local sHorizOffY, lHorizOffY = MakeSlider(pane, L["RUNE_HORIZ_OFFY"], L["RUNE_HORIZ_OFFY_TIP"],
                        -600, 600, 1, PAD + 20, yo, SLIDER_W)
                    SetSliderHandler(sHorizOffY, function(self, val)
                        rOrbsDB.runeHorizOffY = val
                        if ns.RunePoints then ns.RunePoints.ApplyRuneHorizLayout() end
                    end)
                    sHorizOffY:SetValue((rOrbsDB and rOrbsDB.runeHorizOffY) or -200)
                    tinsert(horizWidgets, sHorizOffY) ; tinsert(horizWidgets, lHorizOffY)
                    tinsert(horizSliders,  sHorizOffY)
                    Panel[key].sRuneHorizOffY = sHorizOffY ; yo = yo - 46

                    local sHorizSize, lHorizSize = MakeSlider(pane, L["RUNE_HORIZ_SIZE"], L["RUNE_HORIZ_SIZE_TIP"],
                        12, 64, 1, PAD + 20, yo, SLIDER_W)
                    SetSliderHandler(sHorizSize, function(self, val)
                        rOrbsDB.runeHorizSize = val
                        if ns.RunePoints then ns.RunePoints.ForceRefresh() end
                    end)
                    sHorizSize:SetValue((rOrbsDB and rOrbsDB.runeHorizSize) or 28)
                    tinsert(horizWidgets, sHorizSize) ; tinsert(horizWidgets, lHorizSize)
                    tinsert(horizSliders,  sHorizSize)
                    Panel[key].sRuneHorizSize = sHorizSize ; yo = yo - 46

                    local sHorizGap, lHorizGap = MakeSlider(pane, L["RUNE_HORIZ_SPACING"], L["RUNE_HORIZ_SPACING_TIP"],
                        0, 30, 1, PAD + 20, yo, SLIDER_W)
                    SetSliderHandler(sHorizGap, function(self, val)
                        rOrbsDB.runeHorizSpacing = val
                        if ns.RunePoints then ns.RunePoints.ForceRefresh() end
                    end)
                    sHorizGap:SetValue((rOrbsDB and rOrbsDB.runeHorizSpacing) or 6)
                    tinsert(horizWidgets, sHorizGap) ; tinsert(horizWidgets, lHorizGap)
                    tinsert(horizSliders,  sHorizGap)
                    Panel[key].sRuneHorizSpacing = sHorizGap ; yo = yo - 46

                    -- Apply initial visibility; if not horizontal, restore yo so
                    -- the invisible horiz widgets don't leave a whitespace gap.
                    local isHoriz = ((rOrbsDB and rOrbsDB.runePointsPosition) or "inside_top") == "horizontal"
                    SetHorizSubOptionsVisible(isHoriz)
                    if not isHoriz then yo = yoBeforeHoriz end
                    -- (arc visibility already set in the arc block above)
                end

                yo = yo - 20
                -- Combo Bar sub-options ----------------------------------------
                MakeSep(pane, yo) ; yo = yo - 14
                MakeSectionHeader(pane, L["SECTION_COMBO_BAR"], PAD, yo, false, function()
                    rOrbsDB.comboBarColor  = nil
                    rOrbsDB.comboBarScale  = nil
                    rOrbsDB.comboBarOffX   = nil
                    rOrbsDB.comboBarOffY   = nil
                    -- Reset slider widgets to their defaults
                    if Panel[key].sComboScale then Panel[key].sComboScale:SetValue(70)   end
                    if Panel[key].sComboOffX  then Panel[key].sComboOffX:SetValue(0)     end
                    if Panel[key].sComboOffY  then Panel[key].sComboOffY:SetValue(-130)  end
                    -- Reset color swatch to default orange
                    if Panel[key].swComboColor then Panel[key].swComboColor:SetColor(0.90, 0.50, 0.10) end
                    if ns.ComboBar then ns.ComboBar.ApplyLayout() end
                    ns.GUI:ChatMsg("|cffffff00" .. L["SECTION_COMBO_BAR"] .. "|r reset.")
                    ns.GUI:RefreshValues()
                end) ; yo = yo - 44

                -- Combo Bar: Preview Button -----------------------------------
                do
                    local btnCombo = MakeButton(pane, L["COMBO_PREVIEW"], PAD + 20, yo, 130, 22, 0.06, 0.16, 0.06)
                    btnCombo._prevActive = false
                    btnCombo:SetScript("OnClick", function(self)
                        self._prevActive = not self._prevActive
                        if self._prevActive then
                            self.fs:SetText(L["CUSTOM_TMPL_STOP"])
                            self.innerTex:SetColorTexture(0.40, 0.05, 0.03, 1)
                            if ns.ComboBar then ns.ComboBar.ShowPreview() end
                        else
                            self.fs:SetText(L["COMBO_PREVIEW"])
                            self.innerTex:SetColorTexture(0.06, 0.16, 0.06, 1)
                            if ns.ComboBar then ns.ComboBar.HidePreview() end
                        end
                    end)
                    btnCombo:SetScript("OnHide", function(self)
                        if self._prevActive then
                            self._prevActive = false
                            self.fs:SetText(L["COMBO_PREVIEW"])
                            self.innerTex:SetColorTexture(0.06, 0.16, 0.06, 1)
                            if ns.ComboBar then ns.ComboBar.HidePreview() end
                        end
                    end)
                    Panel[key].btnComboPreview = btnCombo
                end
                yo = yo - 34

                do
                    local lbl = pane:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
                    lbl:SetPoint("TOPLEFT", pane, "TOPLEFT", PAD + 20, yo)
                    lbl:SetText("Bar Color")
                    lbl:SetTextColor(C.text[1], C.text[2], C.text[3])
                    local cc = rOrbsDB.comboBarColor or { r = 0.90, g = 0.50, b = 0.10 }
                    local swCombo
                    swCombo = MakeColorSwatch(pane, PAD + 100, yo - 2,
                        cc.r, cc.g, cc.b, nil, function()
                        local c = rOrbsDB.comboBarColor or { r = 0.90, g = 0.50, b = 0.10 }
                        ns.ColorPickerHelper:Open({
                            r = c.r, g = c.g, b = c.b,
                            onChange = function(r, g, b)
                                rOrbsDB.comboBarColor = { r = r, g = g, b = b }
                                swCombo:SetColor(r, g, b)
                                if ns.ComboBar then ns.ComboBar.UpdateColor() end
                            end,
                            onCancel = function()
                                if ns.ComboBar then ns.ComboBar.UpdateColor() end
                            end,
                        })
                    end)
                    Panel[key].swComboColor = swCombo
                end
                yo = yo - 36

                local sComboScale, _ = MakeSlider(pane, "Bar Scale (%)",
                    "Scale of the combo bar (30 = tiny, 100 = native 64-px tile size).",
                    30, 150, 1, PAD + 20, yo, SLIDER_W)
                SetSliderHandler(sComboScale, function(self, val)
                    rOrbsDB.comboBarScale = val / 100
                    if ns.ComboBar then ns.ComboBar.ApplyLayout() end
                end)
                sComboScale:SetValue(math.floor(((rOrbsDB and rOrbsDB.comboBarScale) or 0.70) * 100 + 0.5))
                Panel[key].sComboScale = sComboScale ; yo = yo - 46

                local sComboOffX, _ = MakeSlider(pane, "Bar Offset X",
                    "Horizontal offset from the screen centre (pixels).",
                    -400, 400, 1, PAD + 20, yo, SLIDER_W)
                SetSliderHandler(sComboOffX, function(self, val)
                    rOrbsDB.comboBarOffX = val
                    if ns.ComboBar then ns.ComboBar.ApplyLayout() end
                end)
                sComboOffX:SetValue((rOrbsDB and rOrbsDB.comboBarOffX) or 0)
                Panel[key].sComboOffX = sComboOffX ; yo = yo - 46

                local sComboOffY, _ = MakeSlider(pane, "Bar Offset Y",
                    "Vertical offset from the screen centre (negative = below).",
                    -400, 400, 1, PAD + 20, yo, SLIDER_W)
                SetSliderHandler(sComboOffY, function(self, val)
                    rOrbsDB.comboBarOffY = val
                    if ns.ComboBar then ns.ComboBar.ApplyLayout() end
                end)
                sComboOffY:SetValue((rOrbsDB and rOrbsDB.comboBarOffY) or -130)
                Panel[key].sComboOffY = sComboOffY ; yo = yo - 46

                -- Switch to icons sub-pane
                if paneIcons then SwitchPane(paneIcons) end

                -- PLAYER ICONS ---------------------------------------------
                MakeSectionHeader(pane, L["SECTION_PLAYER_ICONS"], PAD, yo, true, function()
                    rOrbsDB.playerCombatIconShow  = nil ; rOrbsDB.playerCombatIconSize  = nil
                    rOrbsDB.playerCombatIconOffX  = nil ; rOrbsDB.playerCombatIconOffY  = nil
                    rOrbsDB.playerRestingIconShow = nil ; rOrbsDB.playerRestingIconSize = nil
                    rOrbsDB.playerRestingIconOffX = nil ; rOrbsDB.playerRestingIconOffY = nil
                    rOrbsDB.playerRestingIconColor = nil
                    rOrbsDB.playerLeaderIconShow  = nil ; rOrbsDB.playerLeaderIconSize  = nil
                    rOrbsDB.playerLeaderIconOffX  = nil ; rOrbsDB.playerLeaderIconOffY  = nil
                    if ns.UpdateCombatIconLayout  then ns.UpdateCombatIconLayout()  end
                    if ns.UpdateRestingIconLayout then ns.UpdateRestingIconLayout() end
                    if ns.UpdateLeaderIconLayout  then ns.UpdateLeaderIconLayout()  end
                    ns.GUI:ChatMsg("|cffffff00" .. L["SECTION_PLAYER_ICONS"] .. "|r reset.")
                    ns.GUI:RefreshValues()
                end) ; yo = yo - 44

                -- Combat Icon ------------------------------------------------
                MakeSep(pane, yo) ; yo = yo - 14
                MakeSectionHeader(pane, L["SECTION_COMBAT_ICON"], PAD + 20, yo, true, function()
                    rOrbsDB.playerCombatIconShow = nil ; rOrbsDB.playerCombatIconSize = nil
                    rOrbsDB.playerCombatIconOffX = nil ; rOrbsDB.playerCombatIconOffY = nil
                    if ns.UpdateCombatIconLayout then ns.UpdateCombatIconLayout() end
                    ns.GUI:ChatMsg("|cffffff00" .. L["SECTION_COMBAT_ICON"] .. "|r reset.")
                    ns.GUI:RefreshValues()
                end) ; yo = yo - 26

                MakeIconPreviewButton(pane, PAD + 20, yo,
                    function() return ns.PlayerCombatIcon end,
                    function(f)
                        if not UnitAffectingCombat("player") then f:Hide() end
                    end)
                yo = yo - 38

                local cbPlayerCombat = MakeCheckbox(pane,
                    "Show Combat Icon (crossed swords)",
                    "Show or hide the crossed swords icon above the health orb when you are in combat.",
                    PAD, yo)
                cbPlayerCombat:SetChecked(rOrbsDB.playerCombatIconShow ~= false)
                cbPlayerCombat:SetScript("OnClick", function(self)
                    local val = self:GetChecked()
                    rOrbsDB.playerCombatIconShow = val
                    local f = ns.PlayerCombatIcon
                    if f then
                        if val and UnitAffectingCombat("player") then
                            f:Show()
                        else
                            f:Hide()
                        end
                    end
                    ns.GUI:ChatMsg("|cffffff00Player combat icon|r "..(val and "|cff00ff00shown|r." or "|cffff4444hidden|r."))
                end)
                Panel[key].cbPlayerCombatIcon = cbPlayerCombat ; yo = yo - 50

                -- Size / position sliders for Player Combat Icon
                local sPCombatSize, _ = MakeSlider(pane, "Combat Icon – Size",
                    "Width and height of the combat (crossed swords) icon in pixels.",
                    12, 64, 1, PAD + 20, yo, SLIDER_W)
                SetSliderHandler(sPCombatSize, function(self, val)
                    rOrbsDB.playerCombatIconSize = val
                    if ns.UpdateCombatIconLayout then ns.UpdateCombatIconLayout() end
                end)
                sPCombatSize:SetValue(rOrbsDB.playerCombatIconSize or 28)
                Panel[key].sPCombatSize = sPCombatSize ; yo = yo - 46

                local sPCombatOffX, _ = MakeSlider(pane, "Combat Icon – Offset X",
                    "Horizontal offset of the combat icon (added to its default centre-above-orb position).",
                    -200, 200, 1, PAD + 20, yo, SLIDER_W)
                SetSliderHandler(sPCombatOffX, function(self, val)
                    rOrbsDB.playerCombatIconOffX = val
                    if ns.UpdateCombatIconLayout then ns.UpdateCombatIconLayout() end
                end)
                sPCombatOffX:SetValue(rOrbsDB.playerCombatIconOffX or 0)
                Panel[key].sPCombatOffX = sPCombatOffX ; yo = yo - 46

                local sPCombatOffY, _ = MakeSlider(pane, "Combat Icon – Offset Y",
                    "Vertical offset of the combat icon (added to its default position above the orb).",
                    -200, 200, 1, PAD + 20, yo, SLIDER_W)
                SetSliderHandler(sPCombatOffY, function(self, val)
                    rOrbsDB.playerCombatIconOffY = val
                    if ns.UpdateCombatIconLayout then ns.UpdateCombatIconLayout() end
                end)
                sPCombatOffY:SetValue(rOrbsDB.playerCombatIconOffY or 0)
                Panel[key].sPCombatOffY = sPCombatOffY ; yo = yo - 52

                yo = yo - 20
                -- Resting Icon ----------------------------------------------
                MakeSep(pane, yo) ; yo = yo - 14
                MakeSectionHeader(pane, L["SECTION_RESTING_ICON"], PAD + 20, yo, true, function()
                    rOrbsDB.playerRestingIconShow  = nil ; rOrbsDB.playerRestingIconSize  = nil
                    rOrbsDB.playerRestingIconOffX  = nil ; rOrbsDB.playerRestingIconOffY  = nil
                    rOrbsDB.playerRestingIconColor = nil
                    if ns.UpdateRestingIconLayout then ns.UpdateRestingIconLayout() end
                    ns.GUI:ChatMsg("|cffffff00" .. L["SECTION_RESTING_ICON"] .. "|r reset.")
                    ns.GUI:RefreshValues()
                end) ; yo = yo - 26

                MakeIconPreviewButton(pane, PAD + 20, yo,
                    function() return ns.PlayerRestingIcon end,
                    function(f)
                        if not IsResting() then f:Hide() end
                    end,
                    function(f)
                        -- PlayerRestLoop is a Frame whose *regions* carry
                        -- AnimationGroups. The frame itself has no Play().
                        -- Walk the entire subtree and start every animation.
                        local function kickAnim(obj)
                            if obj and obj.GetAnimationGroups then
                                for _, ag in ipairs({ obj:GetAnimationGroups() }) do
                                    if not ag:IsPlaying() then ag:Play() end
                                end
                            end
                        end
                        kickAnim(f)
                        if f.GetRegions then
                            for _, r in ipairs({ f:GetRegions() }) do
                                kickAnim(r)
                            end
                        end
                        if f.GetChildren then
                            for _, child in ipairs({ f:GetChildren() }) do
                                kickAnim(child)
                                if child.GetRegions then
                                    for _, r in ipairs({ child:GetRegions() }) do
                                        kickAnim(r)
                                    end
                                end
                            end
                        end
                    end)
                yo = yo - 38

                local cbPlayerResting = MakeCheckbox(pane,
                    "Show Resting Icon (animated zzz)",
                    "Show or hide the animated zzz icon above the health orb when you are resting in an inn (uses Blizzard's PlayerRestLoop).",
                    PAD, yo)
                cbPlayerResting:SetChecked(rOrbsDB.playerRestingIconShow ~= false)
                cbPlayerResting:SetScript("OnClick", function(self)
                    local val = self:GetChecked()
                    rOrbsDB.playerRestingIconShow = val
                    -- ns.PlayerRestingIcon is the non-secure wrapper frame.
                    -- Re-evaluate resting state so we don't show zzz when not resting.
                    if ns.PlayerRestingIcon then
                        if not val then
                            ns.PlayerRestingIcon:Hide()
                        elseif IsResting() then
                            ns.PlayerRestingIcon:Show()
                        else
                            ns.PlayerRestingIcon:Hide()
                        end
                    end
                    ns.GUI:ChatMsg("|cffffff00Player resting icon|r "..(val and "|cff00ff00shown|r." or "|cffff4444hidden|r."))
                end)
                Panel[key].cbPlayerRestingIcon = cbPlayerResting ; yo = yo - 50

                -- Size / position sliders for Player Resting Icon
                local sPRestSize, _ = MakeSlider(pane, "Resting Icon – Size",
                    "Size of the animated resting (zzz) icon in pixels (uses scale for the Blizzard animation).",
                    12, 64, 1, PAD + 20, yo, SLIDER_W)
                SetSliderHandler(sPRestSize, function(self, val)
                    rOrbsDB.playerRestingIconSize = val
                    if ns.UpdateRestingIconLayout then ns.UpdateRestingIconLayout() end
                end)
                sPRestSize:SetValue(rOrbsDB.playerRestingIconSize or 28)
                Panel[key].sPRestSize = sPRestSize ; yo = yo - 46

                local sPRestOffX, _ = MakeSlider(pane, "Resting Icon – Offset X",
                    "Horizontal offset of the resting icon (added to its default -30 px left position).",
                    -200, 200, 1, PAD + 20, yo, SLIDER_W)
                SetSliderHandler(sPRestOffX, function(self, val)
                    rOrbsDB.playerRestingIconOffX = val
                    if ns.UpdateRestingIconLayout then ns.UpdateRestingIconLayout() end
                end)
                sPRestOffX:SetValue(rOrbsDB.playerRestingIconOffX or 0)
                Panel[key].sPRestOffX = sPRestOffX ; yo = yo - 46

                local sPRestOffY, _ = MakeSlider(pane, "Resting Icon – Offset Y",
                    "Vertical offset of the resting icon (added to its default position above the orb).",
                    -200, 200, 1, PAD + 20, yo, SLIDER_W)
                SetSliderHandler(sPRestOffY, function(self, val)
                    rOrbsDB.playerRestingIconOffY = val
                    if ns.UpdateRestingIconLayout then ns.UpdateRestingIconLayout() end
                end)
                sPRestOffY:SetValue(rOrbsDB.playerRestingIconOffY or 0)
                Panel[key].sPRestOffY = sPRestOffY ; yo = yo - 46

                -- Color picker for Player Resting Icon
                local restCol = rOrbsDB.playerRestingIconColor or { r = 1, g = 1, b = 1 }
                local restColorSwatch
                restColorSwatch = MakeColorSwatch(pane, PAD, yo,
                    restCol.r, restCol.g, restCol.b, "Resting Icon – Color", function()
                    local col = rOrbsDB.playerRestingIconColor or { r = 1, g = 1, b = 1 }
                    ns.ColorPickerHelper:Open({
                        r = col.r, g = col.g, b = col.b,
                        onChange = function(r, g, b)
                            rOrbsDB.playerRestingIconColor = { r = r, g = g, b = b }
                            restColorSwatch:SetColor(r, g, b)
                            if ns.ApplyRestingIconColor then ns.ApplyRestingIconColor(r, g, b) end
                        end,
                        onCancel = function()
                            local c = rOrbsDB.playerRestingIconColor or { r = 1, g = 1, b = 1 }
                            if ns.ApplyRestingIconColor then ns.ApplyRestingIconColor(c.r, c.g, c.b) end
                        end,
                    })
                end)
                Panel[key].restColorSwatch = restColorSwatch ; yo = yo - 34

                -- Reset resting icon color to white (original)
                local resetRestColor = MakeButton(pane, "Reset Color", PAD, yo, 100, 22,
                    0.20, 0.06, 0.04)
                resetRestColor:SetScript("OnClick", function()
                    rOrbsDB.playerRestingIconColor = { r = 1, g = 1, b = 1 }
                    restColorSwatch:SetColor(1, 1, 1)
                    if ns.ApplyRestingIconColor then ns.ApplyRestingIconColor(1, 1, 1) end
                    ns.GUI:ChatMsg("|cffffff00Resting icon color|r reset to |cffffffffwhite|r.")
                end)
                Panel[key].resetRestColor = resetRestColor ; yo = yo - 32

                yo = yo - 20
                -- Leader Icon -----------------------------------------------
                MakeSep(pane, yo) ; yo = yo - 14
                MakeSectionHeader(pane, L["SECTION_LEADER_ICON"], PAD + 20, yo, true, function()
                    rOrbsDB.playerLeaderIconShow = nil ; rOrbsDB.playerLeaderIconSize = nil
                    rOrbsDB.playerLeaderIconOffX = nil ; rOrbsDB.playerLeaderIconOffY = nil
                    if ns.UpdateLeaderIconLayout then ns.UpdateLeaderIconLayout() end
                    ns.GUI:ChatMsg("|cffffff00" .. L["SECTION_LEADER_ICON"] .. "|r reset.")
                    ns.GUI:RefreshValues()
                end) ; yo = yo - 26

                MakeIconPreviewButton(pane, PAD + 20, yo,
                    function() return ns.PlayerLeaderIcon end,
                    function(f)
                        if not (UnitIsGroupLeader("player") and IsInGroup()) then f:Hide() end
                    end)
                yo = yo - 38

                local cbPlayerLeader = MakeCheckbox(pane,
                    "Show Leader Icon (crown)",
                    "Show or hide the leader icon (crown) above the health orb when you are the party/raid leader.",
                    PAD, yo)
                cbPlayerLeader:SetChecked(rOrbsDB.playerLeaderIconShow ~= false)
                cbPlayerLeader:SetScript("OnClick", function(self)
                    local val = self:GetChecked()
                    rOrbsDB.playerLeaderIconShow = val
                    local f = ns.PlayerLeaderIcon
                    if f then
                        if val and UnitIsGroupLeader("player") and IsInGroup() then
                            f:Show()
                        else
                            f:Hide()
                        end
                    end
                    ns.GUI:ChatMsg("|cffffff00Player leader icon|r "..(val and "|cff00ff00shown|r." or "|cffff4444hidden|r."))
                end)
                Panel[key].cbPlayerLeaderIcon = cbPlayerLeader ; yo = yo - 50

                -- Size / position sliders for Player Leader Icon
                local sPLeaderSize, _ = MakeSlider(pane, "Leader Icon \226\128\147 Size",
                    "Width and height of the leader (crown) icon in pixels.",
                    8, 64, 1, PAD + 20, yo, SLIDER_W)
                SetSliderHandler(sPLeaderSize, function(self, val)
                    rOrbsDB.playerLeaderIconSize = val
                    if ns.UpdateLeaderIconLayout then ns.UpdateLeaderIconLayout() end
                end)
                sPLeaderSize:SetValue(rOrbsDB.playerLeaderIconSize or 22)
                Panel[key].sPLeaderSize = sPLeaderSize ; yo = yo - 46

                local sPLeaderOffX, _ = MakeSlider(pane, "Leader Icon \226\128\147 Offset X",
                    "Horizontal offset of the leader icon from its default position.",
                    -200, 200, 1, PAD + 20, yo, SLIDER_W)
                SetSliderHandler(sPLeaderOffX, function(self, val)
                    rOrbsDB.playerLeaderIconOffX = val
                    if ns.UpdateLeaderIconLayout then ns.UpdateLeaderIconLayout() end
                end)
                sPLeaderOffX:SetValue(rOrbsDB.playerLeaderIconOffX or 0)
                Panel[key].sPLeaderOffX = sPLeaderOffX ; yo = yo - 46

                local sPLeaderOffY, _ = MakeSlider(pane, "Leader Icon \226\128\147 Offset Y",
                    "Vertical offset of the leader icon from its default position.",
                    -200, 200, 1, PAD + 20, yo, SLIDER_W)
                SetSliderHandler(sPLeaderOffY, function(self, val)
                    rOrbsDB.playerLeaderIconOffY = val
                    if ns.UpdateLeaderIconLayout then ns.UpdateLeaderIconLayout() end
                end)
                sPLeaderOffY:SetValue(rOrbsDB.playerLeaderIconOffY or 0)
                Panel[key].sPLeaderOffY = sPLeaderOffY ; yo = yo - 52

                yo = yo - 20
                -- Switch to figure sub-pane (health)
                if paneFigure then SwitchPane(paneFigure) end

                -- FIGURE ----------------------------------------------------
                MakeSectionHeader(pane, L["SECTION_FIGURE"], PAD, yo, true, function()
                    rOrbsDB.healthFigureMode = nil
                    rOrbsDB.figureScale      = nil ; rOrbsDB.angelScale = nil
                    rOrbsDB.figureOffX       = nil ; rOrbsDB.angelOffX  = nil
                    rOrbsDB.figureOffY       = nil ; rOrbsDB.angelOffY  = nil
                    if ns.ApplyFigureMode then ns.ApplyFigureMode() end
                    ns.GUI:ChatMsg("|cffffff00" .. L["SECTION_FIGURE"] .. "|r reset.")
                    ns.GUI:RefreshValues()
                end) ; yo = yo - 44

                -- ── Dropdown: figure mode ──────────────────────────────────
                local figureModeLabels = {
                    angel  = "Angel",
                    diablo = "Diablo",
                    skull  = "Skull",
                    demon  = "Demon",
                    none   = "Hidden",
                }
                local ddFigureMode, _ = MakeDropdown(pane,
                    "Figure",
                    nil,  -- no tooltip
                    PAD, yo, 180)
                Panel[key].ddFigureMode = ddFigureMode
                local function ApplyFigureMode(val)
                    rOrbsDB.healthFigureMode = val
                    if ns.ApplyFigureMode then ns.ApplyFigureMode() end
                    ns.GUI:ChatMsg("|cffffff00Figure|r: "..(figureModeLabels[val] or val))
                end
                UIDropDownMenu_Initialize(ddFigureMode, function(self, level)
                    local current = rOrbsDB.healthFigureMode or "angel"
                    local entries = {
                        { value = "angel",  text = "Angel"  },
                        { value = "diablo", text = "Diablo" },
                        { value = "skull",  text = "Skull"  },
                        { value = "demon",  text = "Demon"  },
                        { value = "none",   text = "Hidden" },
                    }
                    for _, e in ipairs(entries) do
                        local info = UIDropDownMenu_CreateInfo()
                        info.text    = e.text
                        info.value   = e.value
                        info.checked = (e.value == current)
                        info.func    = function()
                            UIDropDownMenu_SetSelectedValue(ddFigureMode, e.value)
                            UIDropDownMenu_SetText(ddFigureMode, e.text)
                            ApplyFigureMode(e.value)
                        end
                        UIDropDownMenu_AddButton(info, level)
                    end
                end)
                do
                    local cur = rOrbsDB.healthFigureMode or "angel"
                    UIDropDownMenu_SetText(ddFigureMode, figureModeLabels[cur] or "Angel")
                    UIDropDownMenu_SetSelectedValue(ddFigureMode, cur)
                end
                yo = yo - 56

                local sFigureScale, _ = MakeSlider(pane, "Scale", "Scale of the figure relative to the orb size.", 0.3, 3.0, 0.05, PAD, yo, SLIDER_W)
                SetSliderHandler(sFigureScale, function(self, val)
                    rOrbsDB.figureScale = val
                    if ns.UpdateFigurePositionLayout then ns.UpdateFigurePositionLayout() end
                end)
                sFigureScale:SetValue(rOrbsDB.figureScale or rOrbsDB.angelScale or 1.0)
                Panel[key].sFigureScale = sFigureScale ; yo = yo - 52

                local sFigureOffX, _ = MakeSlider(pane, "Offset X", "Horizontal fine-tuning. Negative = further left, positive = overlap more.", -200, 200, 1, PAD, yo, SLIDER_W)
                SetSliderHandler(sFigureOffX, function(self, val)
                    rOrbsDB.figureOffX = val
                    if ns.UpdateFigurePositionLayout then ns.UpdateFigurePositionLayout() end
                end)
                sFigureOffX:SetValue(rOrbsDB.figureOffX or rOrbsDB.angelOffX or 0)
                Panel[key].sFigureOffX = sFigureOffX ; yo = yo - 52

                local sFigureOffY, _ = MakeSlider(pane, "Offset Y", "Vertical fine-tuning. Negative = lower, positive = higher.", -200, 200, 1, PAD, yo, SLIDER_W)
                SetSliderHandler(sFigureOffY, function(self, val)
                    rOrbsDB.figureOffY = val
                    if ns.UpdateFigurePositionLayout then ns.UpdateFigurePositionLayout() end
                end)
                sFigureOffY:SetValue(rOrbsDB.figureOffY or rOrbsDB.angelOffY or 0)
                Panel[key].sFigureOffY = sFigureOffY ; yo = yo - 52

                -- Legacy aliases so old RefreshValues refs still compile
                Panel[key].sAngelScale = Panel[key].sFigureScale
                Panel[key].sAngelOffX  = Panel[key].sFigureOffX
                Panel[key].sAngelOffY  = Panel[key].sFigureOffY

            elseif cfgKey == "POWER" then
                Panel[key].cbDemon = nil

                -- Switch to figure sub-pane (power)
                if paneFigure then SwitchPane(paneFigure) end

                -- FIGURE ----------------------------------------------------
                MakeSectionHeader(pane, L["SECTION_FIGURE"], PAD, yo, true, function()
                    rOrbsDB.powerFigureMode = nil
                    rOrbsDB.demonScale      = nil
                    rOrbsDB.demonOffX       = nil
                    rOrbsDB.demonOffY       = nil
                    if ns.ApplyPowerFigureMode then ns.ApplyPowerFigureMode()
                    elseif ns.UpdateFigureLayout and ns.DemonFrame and ns.PowerOrb then
                        ns.UpdateFigureLayout(ns.DemonFrame, ns.PowerOrb, "RIGHT", 1.0, 0, 0)
                    end
                    ns.GUI:ChatMsg("|cffffff00" .. L["SECTION_FIGURE"] .. "|r reset.")
                    ns.GUI:RefreshValues()
                end) ; yo = yo - 44

                -- ── Dropdown: power figure mode ──────────────────────────────
                local powerFigureLabels = { angel = "Angel", demon = "Demon", skull = "Skull", diablo = "Diablo", none = "Hidden" }
                local ddPowerFigure, _ = MakeDropdown(pane,
                    "Figure",
                    nil,  -- no tooltip
                    PAD, yo, 180)
                Panel[key].ddPowerFigure = ddPowerFigure
                local function ApplyPowerFigureModeLocal(val)
                    rOrbsDB.powerFigureMode = val
                    if ns.ApplyPowerFigureMode then ns.ApplyPowerFigureMode() end
                    ns.GUI:ChatMsg("|cffffff00Figure (power)|r: "..(powerFigureLabels[val] or val))
                end
                UIDropDownMenu_Initialize(ddPowerFigure, function(self, level)
                    local current = rOrbsDB.powerFigureMode or "angel"
                    local entries = {
                        { value = "angel",  text = "Angel"  },
                        { value = "demon",  text = "Demon"  },
                        { value = "skull",  text = "Skull"  },
                        { value = "diablo", text = "Diablo" },
                        { value = "none",   text = "Hidden" },
                    }
                    for _, e in ipairs(entries) do
                        local info = UIDropDownMenu_CreateInfo()
                        info.text    = e.text
                        info.value   = e.value
                        info.checked = (e.value == current)
                        info.func    = function()
                            UIDropDownMenu_SetSelectedValue(ddPowerFigure, e.value)
                            UIDropDownMenu_SetText(ddPowerFigure, e.text)
                            ApplyPowerFigureModeLocal(e.value)
                        end
                        UIDropDownMenu_AddButton(info, level)
                    end
                end)
                do
                    local cur = rOrbsDB.powerFigureMode or "angel"
                    UIDropDownMenu_SetText(ddPowerFigure, powerFigureLabels[cur] or "Angel")
                    UIDropDownMenu_SetSelectedValue(ddPowerFigure, cur)
                end
                yo = yo - 56

                local sDemonScale, _ = MakeSlider(pane, "Scale", "Scale of the figure relative to the orb size.", 0.3, 3.0, 0.05, PAD, yo, SLIDER_W)
                SetSliderHandler(sDemonScale, function(self, val)
                    rOrbsDB.demonScale = val
                    if ns.UpdatePowerFigurePositionLayout then ns.UpdatePowerFigurePositionLayout()
                    elseif ns.UpdateFigureLayout and ns.DemonFrame and ns.PowerOrb then
                        ns.UpdateFigureLayout(ns.DemonFrame, ns.PowerOrb, "RIGHT", rOrbsDB.demonScale, rOrbsDB.demonOffX, rOrbsDB.demonOffY)
                    end
                end)
                sDemonScale:SetValue(rOrbsDB.demonScale or 1.0)
                Panel[key].sDemonScale = sDemonScale ; yo = yo - 52

                local sDemonOffX, _ = MakeSlider(pane, "Offset X", "Horizontal fine-tuning. Negative = further right, positive = overlap more.", -200, 200, 1, PAD, yo, SLIDER_W)
                SetSliderHandler(sDemonOffX, function(self, val)
                    rOrbsDB.demonOffX = val
                    if ns.UpdatePowerFigurePositionLayout then ns.UpdatePowerFigurePositionLayout()
                    elseif ns.UpdateFigureLayout and ns.DemonFrame and ns.PowerOrb then
                        ns.UpdateFigureLayout(ns.DemonFrame, ns.PowerOrb, "RIGHT", rOrbsDB.demonScale, rOrbsDB.demonOffX, rOrbsDB.demonOffY)
                    end
                end)
                sDemonOffX:SetValue(rOrbsDB.demonOffX or 0)
                Panel[key].sDemonOffX = sDemonOffX ; yo = yo - 52

                local sDemonOffY, _ = MakeSlider(pane, "Offset Y", "Vertical fine-tuning. Negative = lower, positive = higher.", -200, 200, 1, PAD, yo, SLIDER_W)
                SetSliderHandler(sDemonOffY, function(self, val)
                    rOrbsDB.demonOffY = val
                    if ns.UpdatePowerFigurePositionLayout then ns.UpdatePowerFigurePositionLayout()
                    elseif ns.UpdateFigureLayout and ns.DemonFrame and ns.PowerOrb then
                        ns.UpdateFigureLayout(ns.DemonFrame, ns.PowerOrb, "RIGHT", rOrbsDB.demonScale, rOrbsDB.demonOffX, rOrbsDB.demonOffY)
                    end
                end)
                sDemonOffY:SetValue(rOrbsDB.demonOffY or 0)
                Panel[key].sDemonOffY = sDemonOffY ; yo = yo - 52
            end
        end

        -- Switch back to orb pane for effects / templates / text
        if isMulti and cfgKey ~= "TARGET_HEALTH" and cfgKey ~= "TOT_HEALTH" and cfgKey ~= "PET_HEALTH" then SwitchPane(paneOrb) end

        -- TARGET/TOT/PET CLASS COLOR + RING + PORTRAIT ------
        if cfgKey == "TARGET_HEALTH" or cfgKey == "TOT_HEALTH" or cfgKey == "PET_HEALTH" then
            local ccKey   = (cfgKey == "TARGET_HEALTH") and "targetClassColor" or (cfgKey == "TOT_HEALTH") and "totClassColor" or nil
            local ringKey = (cfgKey == "TARGET_HEALTH") and "targetRingShow"   or (cfgKey == "TOT_HEALTH") and "totRingShow" or "petRingShow"
            local orbName = (cfgKey == "TARGET_HEALTH") and "rOrbsTargetHealthOrb" or (cfgKey == "TOT_HEALTH") and "rOrbsToTHealthOrb" or "rOrbsPetHealthOrb"

            -- CLASS COLOR CHECKBOX (Target / ToT only, not Pet)
            if ccKey then
              local cbOrbClassColor = MakeCheckbox(pane,
                "Use Class Color (Orb)",
                "Color the orb fill by the target's class color when the target is a player. Non-players use the configured orb color.",
                PAD, yo)
              cbOrbClassColor:SetChecked(rOrbsDB[ccKey] == true)
              cbOrbClassColor:SetScript("OnClick", function(self)
                local val = self:GetChecked()
                rOrbsDB[ccKey] = val
                -- Force immediate postUpdate so the color changes live
                local frameKey = (cfgKey == "TARGET_HEALTH") and "rOrbsTargetHealthFrame"
                             or (cfgKey == "TOT_HEALTH")    and "rOrbsToTHealthFrame"
                local tf = frameKey and _G[frameKey]
                if tf and tf.UpdateAllElements then
                  tf:UpdateAllElements("ForceUpdate")
                end
                ns.GUI:ChatMsg("|cffffff00Orb class color|r " .. (val and "|cff00ff00enabled|r." or "|cffff4444disabled|r."))
              end)
              Panel[key].cbOrbClassColor = cbOrbClassColor
              yo = yo - 50
            end

            if cfgKey ~= "TARGET_HEALTH" and cfgKey ~= "PET_HEALTH" then  -- Ring already in paneRing for Target/Pet
            -- DECORATIVE RING
            local cbRing = MakeCheckbox(pane,
                "Show decorative ring",
                "Show or hide the decorative stone ring/wreath around the orb.",
                PAD, yo)
            cbRing:SetChecked(rOrbsDB[ringKey] ~= false)
            cbRing:SetScript("OnClick", function(self)
                local val = self:GetChecked()
                rOrbsDB[ringKey] = val
                local orb = _G[orbName]
                if orb and orb.ring then
                    if val then orb.ring:Show() else orb.ring:Hide() end
                end
                ns.GUI:ChatMsg("|cffffff00Decorative ring|r "..(val and "|cff00ff00shown|r." or "|cffff4444hidden|r."))
            end)
            Panel[key].cbRing = cbRing ; yo = yo - 50
            end

            -- ── CAST RING (Target-Health tab only) ──────────────────────────
            if cfgKey == "TARGET_HEALTH" then
                if paneCastRing then SwitchPane(paneCastRing) end
                MakeSectionHeader(pane, L["CASTRING_SECTION"], PAD, yo, true, function()
                    rOrbsDB.castRingEnabled      = nil ; rOrbsDB.castRingRingScale    = nil
                    rOrbsDB.castRingUseClassColor = nil
                    rOrbsDB.castRingCastR  = nil ; rOrbsDB.castRingCastG  = nil ; rOrbsDB.castRingCastB  = nil
                    rOrbsDB.castRingChanR  = nil ; rOrbsDB.castRingChanG  = nil ; rOrbsDB.castRingChanB  = nil
                    rOrbsDB.castRingBgMode = nil ; rOrbsDB.castRingBgAlpha = nil
                    rOrbsDB.castRingBgR    = nil ; rOrbsDB.castRingBgG    = nil ; rOrbsDB.castRingBgB    = nil
                    rOrbsDB.castRingBgClassColor = nil ; rOrbsDB.castRingFadeTime = nil
                    ns.GUI:ChatMsg("|cffffff00" .. L["CASTRING_SECTION"] .. "|r reset.")
                    ns.GUI:RefreshValues()
                end) ; yo = yo - 44

                -- Self-learning info text
                local infoW = PANEL_W - CONTENT_X - PAD - SB_W - 30
                local castRingInfoLbl = MakeInfoText(pane,
                    L["CASTRING_SELFLEARN_INFO"] or
                    "|cffffd700Note - Self-Learning Cast Timing:|r\nWoW 12.x restricts addons from reading cast durations for enemy targets. The Cast Ring uses GetTime() combined with a self-learning duration estimate that improves over time.",
                    PAD, yo, infoW)
                local infoH = castRingInfoLbl:GetStringHeight() + 6
                yo = yo - infoH - 8

                -- ── BLOCK 1: CHECKBOXEN ─────────────────────────────────────
                -- Enable / disable Cast Ring
                local cbCastRing = MakeCheckbox(pane,
                    L["CASTRING_ENABLE"],
                    L["CASTRING_ENABLE_TIP"],
                    PAD, yo)
                cbCastRing:SetChecked(rOrbsDB.castRingEnabled ~= false)
                cbCastRing:SetScript("OnClick", function(self)
                    local val = self:GetChecked()
                    self:SetChecked(not val)
                    local stateStr = val and L["CASTRING_RELOAD_ON"] or L["CASTRING_RELOAD_OFF"]
                    local popupText = string.format(L["CASTRING_RELOAD_TEXT"], stateStr)
                    StaticPopupDialogs["RORBS_RELOAD_CASTRING"] = {
                        text    = popupText,
                        button1 = L["ORB_RELOAD_ENABLE_BTN"],
                        button2 = CANCEL,
                        OnAccept = function()
                            rOrbsDB.castRingEnabled = val
                            ReloadUI()
                        end,
                        OnCancel = function()
                            cbCastRing:SetChecked(rOrbsDB.castRingEnabled ~= false)
                        end,
                        timeout = 0, whileDead = true, hideOnEscape = false,
                    }
                    StaticPopup_Show("RORBS_RELOAD_CASTRING")
                end)
                Panel[key].cbCastRing = cbCastRing ; yo = yo - 46

                -- Cast Color = Target Class Color
                local cbClassColor = MakeCheckbox(pane,
                    L["CASTRING_USE_CLASSCOLOR"],
                    L["CASTRING_USE_CLASSCOLOR_TIP"],
                    PAD, yo)
                cbClassColor:SetChecked(rOrbsDB.castRingUseClassColor == true)
                cbClassColor:SetScript("OnClick", function(self)
                    rOrbsDB.castRingUseClassColor = self:GetChecked()
                    if ns.CastRing then ns.CastRing.ApplyColors() end
                end)
                Panel[key].cbClassColor = cbClassColor ; yo = yo - 46

                -- Compass Rose anzeigen
                local cbCastBg = MakeCheckbox(pane,
                    L["CASTRING_SHOW_BG"],
                    L["CASTRING_SHOW_BG_TIP"],
                    PAD, yo)
                cbCastBg:SetChecked((rOrbsDB.castRingBgMode or "rose_bright") == "rose_bright")
                cbCastBg:SetScript("OnClick", function(self)
                    local val = self:GetChecked()
                    rOrbsDB.castRingBgMode = val and "rose_bright" or "ring_only"
                    if ns.CastRing then ns.CastRing.ApplyBackground() end
                end)
                Panel[key].cbCastBg = cbCastBg ; yo = yo - 46

                -- Compass Rose = Klassenfarbe
                local cbBgClassColor = MakeCheckbox(pane,
                    L["CASTRING_BG_CLASSCOLOR"],
                    L["CASTRING_BG_CLASSCOLOR_TIP"],
                    PAD, yo)
                cbBgClassColor:SetChecked(rOrbsDB.castRingBgClassColor == true)
                cbBgClassColor:SetScript("OnClick", function(self)
                    rOrbsDB.castRingBgClassColor = self:GetChecked()
                    if ns.CastRing then ns.CastRing.ApplyBgColor() end
                end)
                Panel[key].cbBgClassColor = cbBgClassColor ; yo = yo - 46

                -- ── TRENNER ─────────────────────────────────────────────────
                yo = yo - 14
                MakeSep(pane, yo) ; yo = yo - 20

                -- ── BLOCK 2: FARBEN ─────────────────────────────────────────
                -- Cast-Farbe (Fallback) – Label + Swatch darunter
                local castColLbl = pane:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
                castColLbl:SetPoint("TOPLEFT", pane, "TOPLEFT", PAD, yo)
                castColLbl:SetText(L["CASTRING_CAST_COLOR"])
                castColLbl:SetTextColor(C.text[1], C.text[2], C.text[3])
                yo = yo - 22
                local castSwatch
                castSwatch = MakeColorSwatch(pane, PAD, yo + 2,
                    rOrbsDB.castRingCastR or 1, rOrbsDB.castRingCastG or 0.15, rOrbsDB.castRingCastB or 0.05,
                    L["CASTRING_CAST_COLOR_TIP"],
                    function()
                        ns.ColorPickerHelper:Open({
                            r = rOrbsDB.castRingCastR or 1,
                            g = rOrbsDB.castRingCastG or 0.15,
                            b = rOrbsDB.castRingCastB or 0.05,
                            onChange = function(r, g, b)
                                rOrbsDB.castRingCastR = r
                                rOrbsDB.castRingCastG = g
                                rOrbsDB.castRingCastB = b
                                castSwatch:SetColor(r, g, b)
                                if ns.CastRing then ns.CastRing.ApplyColors() end
                            end,
                        })
                    end)
                Panel[key].castSwatch = castSwatch ; yo = yo - 34

                -- Channel-Farbe – Label + Swatch darunter
                yo = yo - 10
                local chanColLbl = pane:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
                chanColLbl:SetPoint("TOPLEFT", pane, "TOPLEFT", PAD, yo)
                chanColLbl:SetText(L["CASTRING_CHANNEL_COLOR"])
                chanColLbl:SetTextColor(C.text[1], C.text[2], C.text[3])
                yo = yo - 22
                local chanSwatch
                chanSwatch = MakeColorSwatch(pane, PAD, yo + 2,
                    rOrbsDB.castRingChanR or 0.3, rOrbsDB.castRingChanG or 0.6, rOrbsDB.castRingChanB or 1,
                    L["CASTRING_CHANNEL_COLOR_TIP"],
                    function()
                        ns.ColorPickerHelper:Open({
                            r = rOrbsDB.castRingChanR or 0.3,
                            g = rOrbsDB.castRingChanG or 0.6,
                            b = rOrbsDB.castRingChanB or 1,
                            onChange = function(r, g, b)
                                rOrbsDB.castRingChanR = r
                                rOrbsDB.castRingChanG = g
                                rOrbsDB.castRingChanB = b
                                chanSwatch:SetColor(r, g, b)
                                if ns.CastRing then ns.CastRing.ApplyColors() end
                            end,
                        })
                    end)
                Panel[key].chanSwatch = chanSwatch ; yo = yo - 34

                -- Compass Rose Farbe – Label + Swatch darunter
                yo = yo - 10
                local bgColLbl = pane:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
                bgColLbl:SetPoint("TOPLEFT", pane, "TOPLEFT", PAD, yo)
                bgColLbl:SetText(L["CASTRING_BG_COLOR"])
                bgColLbl:SetTextColor(C.text[1], C.text[2], C.text[3])
                yo = yo - 22
                local bgSwatch
                bgSwatch = MakeColorSwatch(pane, PAD, yo + 2,
                    rOrbsDB.castRingBgR or 1, rOrbsDB.castRingBgG or 1, rOrbsDB.castRingBgB or 1,
                    L["CASTRING_BG_COLOR_TIP"],
                    function()
                        ns.ColorPickerHelper:Open({
                            r = rOrbsDB.castRingBgR or 1,
                            g = rOrbsDB.castRingBgG or 1,
                            b = rOrbsDB.castRingBgB or 1,
                            onChange = function(r, g, b)
                                rOrbsDB.castRingBgR = r
                                rOrbsDB.castRingBgG = g
                                rOrbsDB.castRingBgB = b
                                bgSwatch:SetColor(r, g, b)
                                if ns.CastRing then ns.CastRing.ApplyBgColor() end
                            end,
                        })
                    end)
                Panel[key].bgSwatch = bgSwatch ; yo = yo - 34

                -- ── TRENNER ─────────────────────────────────────────────────
                yo = yo - 14
                MakeSep(pane, yo) ; yo = yo - 20

                -- ── BLOCK 3: SLIDER ─────────────────────────────────────────
                -- Ring-Staerke
                local sRingScale, _ = MakeSlider(pane, L["CASTRING_RING_SCALE"],
                    L["CASTRING_RING_SCALE_TIP"],
                    0.3, 2.0, 0.05, PAD, yo, SLIDER_W)
                sRingScale:SetValue(rOrbsDB.castRingRingScale or 1.0)
                SetSliderHandler(sRingScale, function(self, val)
                    rOrbsDB.castRingRingScale = val
                    if ns.CastRing then ns.CastRing.ApplyScale() end
                end)
                Panel[key].sRingScale = sRingScale ; yo = yo - 52

                -- Compass Rose Deckkraft
                local sBgAlpha, _ = MakeSlider(pane, L["CASTRING_BG_ALPHA"],
                    L["CASTRING_BG_ALPHA_TIP"],
                    0, 1, 0.05, PAD, yo, SLIDER_W)
                sBgAlpha:SetValue(rOrbsDB.castRingBgAlpha or 0.8)
                SetSliderHandler(sBgAlpha, function(self, val)
                    rOrbsDB.castRingBgAlpha = val
                    if ns.CastRing then ns.CastRing.ApplyBgAlpha(val) end
                end)
                Panel[key].sBgAlpha = sBgAlpha ; yo = yo - 52

                -- Einblend-Geschwindigkeit
                local sFadeTime, _ = MakeSlider(pane, L["CASTRING_FADE_SPEED"],
                    L["CASTRING_FADE_SPEED_TIP"],
                    0.1, 1.5, 0.05, PAD, yo, SLIDER_W)
                sFadeTime:SetValue(rOrbsDB.castRingFadeTime or 0.4)
                SetSliderHandler(sFadeTime, function(self, val)
                    rOrbsDB.castRingFadeTime = val
                end)
                Panel[key].sFadeTime = sFadeTime ; yo = yo - 52

            end
            -- ── PET CAST RING (Pet-Health tab only) ─────────────────────────
            if cfgKey == "PET_HEALTH" then
                if paneCastRing then SwitchPane(paneCastRing) end
                MakeSectionHeader(pane, L["CASTRING_SECTION"], PAD, yo, true, function()
                    rOrbsDB.petCastRingEnabled   = nil ; rOrbsDB.petCastRingRingScale = nil
                    rOrbsDB.petCastRingCastR  = nil ; rOrbsDB.petCastRingCastG  = nil ; rOrbsDB.petCastRingCastB  = nil
                    rOrbsDB.petCastRingChanR  = nil ; rOrbsDB.petCastRingChanG  = nil ; rOrbsDB.petCastRingChanB  = nil
                    rOrbsDB.petCastRingBgMode = nil ; rOrbsDB.petCastRingBgAlpha = nil
                    rOrbsDB.petCastRingBgR    = nil ; rOrbsDB.petCastRingBgG    = nil ; rOrbsDB.petCastRingBgB    = nil
                    rOrbsDB.petCastRingFadeTime = nil
                    ns.GUI:ChatMsg("|cffffff00" .. L["CASTRING_SECTION"] .. "|r reset.")
                    ns.GUI:RefreshValues()
                end) ; yo = yo - 44

                -- ── BLOCK 1: CHECKBOXEN ─────────────────────────────────────
                local cbPetCastRing = MakeCheckbox(pane,
                    L["CASTRING_ENABLE"],
                    L["CASTRING_ENABLE_TIP"],
                    PAD, yo)
                cbPetCastRing:SetChecked(rOrbsDB.petCastRingEnabled ~= false)
                cbPetCastRing:SetScript("OnClick", function(self)
                    local val = self:GetChecked()
                    self:SetChecked(not val)
                    local stateStr = val and L["CASTRING_RELOAD_ON"] or L["CASTRING_RELOAD_OFF"]
                    StaticPopupDialogs["RORBS_RELOAD_PET_CASTRING"] = {
                        text = string.format(L["CASTRING_RELOAD_TEXT"], stateStr),
                        button1 = L["ORB_RELOAD_ENABLE_BTN"],
                        button2 = CANCEL,
                        OnAccept = function()
                            rOrbsDB.petCastRingEnabled = val
                            ReloadUI()
                        end,
                        OnCancel = function()
                            cbPetCastRing:SetChecked(rOrbsDB.petCastRingEnabled ~= false)
                        end,
                        timeout = 0, whileDead = true, hideOnEscape = false,
                    }
                    StaticPopup_Show("RORBS_RELOAD_PET_CASTRING")
                end)
                Panel[key].cbPetCastRing = cbPetCastRing ; yo = yo - 46

                -- Compass Rose anzeigen
                local cbPetCastBg = MakeCheckbox(pane,
                    L["CASTRING_SHOW_BG"],
                    L["CASTRING_SHOW_BG_TIP"],
                    PAD, yo)
                cbPetCastBg:SetChecked((rOrbsDB.petCastRingBgMode or "rose_bright") == "rose_bright")
                cbPetCastBg:SetScript("OnClick", function(self)
                    local val = self:GetChecked()
                    rOrbsDB.petCastRingBgMode = val and "rose_bright" or "ring_only"
                    if ns.PetCastRing then ns.PetCastRing.ApplyBackground() end
                end)
                Panel[key].cbPetCastBg = cbPetCastBg ; yo = yo - 46

                -- ── TRENNER ─────────────────────────────────────────────────
                yo = yo - 14
                MakeSep(pane, yo) ; yo = yo - 20

                -- ── BLOCK 2: FARBEN ─────────────────────────────────────────
                -- Cast-Farbe – Label + Swatch darunter
                local petCastColLbl = pane:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
                petCastColLbl:SetPoint("TOPLEFT", pane, "TOPLEFT", PAD, yo)
                petCastColLbl:SetText(L["CASTRING_CAST_COLOR"])
                petCastColLbl:SetTextColor(C.text[1], C.text[2], C.text[3])
                yo = yo - 22
                local petCastSwatch
                petCastSwatch = MakeColorSwatch(pane, PAD, yo + 2,
                    rOrbsDB.petCastRingCastR or 0.2, rOrbsDB.petCastRingCastG or 0.85, rOrbsDB.petCastRingCastB or 0.2,
                    L["CASTRING_CAST_COLOR_TIP"],
                    function()
                        ns.ColorPickerHelper:Open({
                            r = rOrbsDB.petCastRingCastR or 0.2,
                            g = rOrbsDB.petCastRingCastG or 0.85,
                            b = rOrbsDB.petCastRingCastB or 0.2,
                            onChange = function(r, g, b)
                                rOrbsDB.petCastRingCastR = r
                                rOrbsDB.petCastRingCastG = g
                                rOrbsDB.petCastRingCastB = b
                                petCastSwatch:SetColor(r, g, b)
                                if ns.PetCastRing then ns.PetCastRing.ApplyColors() end
                            end,
                        })
                    end)
                Panel[key].petCastSwatch = petCastSwatch ; yo = yo - 34

                -- Channel-Farbe – Label + Swatch darunter
                yo = yo - 10
                local petChanColLbl = pane:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
                petChanColLbl:SetPoint("TOPLEFT", pane, "TOPLEFT", PAD, yo)
                petChanColLbl:SetText(L["CASTRING_CHANNEL_COLOR"])
                petChanColLbl:SetTextColor(C.text[1], C.text[2], C.text[3])
                yo = yo - 22
                local petChanSwatch
                petChanSwatch = MakeColorSwatch(pane, PAD, yo + 2,
                    rOrbsDB.petCastRingChanR or 0.5, rOrbsDB.petCastRingChanG or 1.0, rOrbsDB.petCastRingChanB or 0.3,
                    L["CASTRING_CHANNEL_COLOR_TIP"],
                    function()
                        ns.ColorPickerHelper:Open({
                            r = rOrbsDB.petCastRingChanR or 0.5,
                            g = rOrbsDB.petCastRingChanG or 1.0,
                            b = rOrbsDB.petCastRingChanB or 0.3,
                            onChange = function(r, g, b)
                                rOrbsDB.petCastRingChanR = r
                                rOrbsDB.petCastRingChanG = g
                                rOrbsDB.petCastRingChanB = b
                                petChanSwatch:SetColor(r, g, b)
                                if ns.PetCastRing then ns.PetCastRing.ApplyColors() end
                            end,
                        })
                    end)
                Panel[key].petChanSwatch = petChanSwatch ; yo = yo - 34

                -- Compass Rose Farbe – Label + Swatch darunter
                yo = yo - 10
                local petBgColLbl = pane:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
                petBgColLbl:SetPoint("TOPLEFT", pane, "TOPLEFT", PAD, yo)
                petBgColLbl:SetText(L["CASTRING_BG_COLOR"])
                petBgColLbl:SetTextColor(C.text[1], C.text[2], C.text[3])
                yo = yo - 22
                local petBgSwatch
                petBgSwatch = MakeColorSwatch(pane, PAD, yo + 2,
                    rOrbsDB.petCastRingBgR or 0.3, rOrbsDB.petCastRingBgG or 1.0, rOrbsDB.petCastRingBgB or 0.3,
                    L["CASTRING_BG_COLOR_TIP"],
                    function()
                        ns.ColorPickerHelper:Open({
                            r = rOrbsDB.petCastRingBgR or 0.3,
                            g = rOrbsDB.petCastRingBgG or 1.0,
                            b = rOrbsDB.petCastRingBgB or 0.3,
                            onChange = function(r, g, b)
                                rOrbsDB.petCastRingBgR = r
                                rOrbsDB.petCastRingBgG = g
                                rOrbsDB.petCastRingBgB = b
                                petBgSwatch:SetColor(r, g, b)
                                if ns.PetCastRing then ns.PetCastRing.ApplyBgColor() end
                            end,
                        })
                    end)
                Panel[key].petBgSwatch = petBgSwatch ; yo = yo - 34

                -- ── TRENNER ─────────────────────────────────────────────────
                yo = yo - 14
                MakeSep(pane, yo) ; yo = yo - 20

                -- ── BLOCK 3: SLIDER ─────────────────────────────────────────
                -- Ring-Staerke
                local sPetRingScale, _ = MakeSlider(pane,
                    L["CASTRING_RING_SCALE"],
                    L["CASTRING_RING_SCALE_TIP"],
                    0.3, 2.0, 0.05, PAD, yo, SLIDER_W)
                sPetRingScale:SetValue(rOrbsDB.petCastRingRingScale or 1.0)
                SetSliderHandler(sPetRingScale, function(self, val)
                    rOrbsDB.petCastRingRingScale = val
                    if ns.PetCastRing then ns.PetCastRing.ApplyScale() end
                end)
                Panel[key].sPetRingScale = sPetRingScale ; yo = yo - 52

                -- Compass Rose Deckkraft
                local sPetBgAlpha, _ = MakeSlider(pane,
                    L["CASTRING_BG_ALPHA"],
                    L["CASTRING_BG_ALPHA_TIP"],
                    0, 1, 0.05, PAD, yo, SLIDER_W)
                sPetBgAlpha:SetValue(rOrbsDB.petCastRingBgAlpha or 0.7)
                SetSliderHandler(sPetBgAlpha, function(self, val)
                    rOrbsDB.petCastRingBgAlpha = val
                    if ns.PetCastRing then ns.PetCastRing.ApplyBgAlpha(val) end
                end)
                Panel[key].sPetBgAlpha = sPetBgAlpha ; yo = yo - 52

                -- Einblend-Geschwindigkeit
                local sPetFadeTime, _ = MakeSlider(pane,
                    L["CASTRING_FADE_SPEED"],
                    L["CASTRING_FADE_SPEED_TIP"],
                    0.1, 1.5, 0.05, PAD, yo, SLIDER_W)
                sPetFadeTime:SetValue(rOrbsDB.petCastRingFadeTime or 0.4)
                SetSliderHandler(sPetFadeTime, function(self, val)
                    rOrbsDB.petCastRingFadeTime = val
                end)
                Panel[key].sPetFadeTime = sPetFadeTime ; yo = yo - 52

            end
            -- ── END CAST RING ───────────────────────────────────────────────
        end

        -- TARGET: Icons -> paneIcons
        if cfgKey == "TARGET_HEALTH" then
            if paneIcons then SwitchPane(paneIcons) end
            MakeSectionHeader(pane, L["SUBNAV_ICONS"], PAD, yo, true, function()
                rOrbsDB.targetCombatIconShow = nil ; rOrbsDB.targetCombatIconSize = nil
                rOrbsDB.targetCombatIconOffX = nil ; rOrbsDB.targetCombatIconOffY = nil
                rOrbsDB.targetLeaderIconShow = nil ; rOrbsDB.targetLeaderIconSize = nil
                rOrbsDB.targetLeaderIconOffX = nil ; rOrbsDB.targetLeaderIconOffY = nil
                if ns.UpdateTargetCombatIconLayout  then ns.UpdateTargetCombatIconLayout()  end
                if ns.UpdateTargetLeaderIconLayout   then ns.UpdateTargetLeaderIconLayout()   end
                ns.GUI:ChatMsg("|cffffff00" .. L["SUBNAV_ICONS"] .. "|r reset.")
                ns.GUI:RefreshValues()
            end) ; yo = yo - 44

            -- ── TARGET COMBAT ICON ─────────────────────────────────────────
            MakeSep(pane, yo) ; yo = yo - 14
            MakeSectionHeader(pane, L["SECTION_COMBAT_ICON"], PAD + 20, yo, true, function()
                rOrbsDB.targetCombatIconShow = nil ; rOrbsDB.targetCombatIconSize = nil
                rOrbsDB.targetCombatIconOffX = nil ; rOrbsDB.targetCombatIconOffY = nil
                if ns.UpdateTargetCombatIconLayout then ns.UpdateTargetCombatIconLayout() end
                ns.GUI:ChatMsg("|cffffff00" .. L["SECTION_COMBAT_ICON"] .. "|r reset.")
                ns.GUI:RefreshValues()
            end) ; yo = yo - 26

            MakeIconPreviewButton(pane, PAD + 20, yo,
                function() return ns.TargetCombatIcon end,
                function(f)
                    if not UnitAffectingCombat("target") then f:Hide() end
                end)
            yo = yo - 38

            local cbTargetCombat = MakeCheckbox(pane,
                L["TARGET_COMBAT_ICON_SHOW"],
                L["TARGET_COMBAT_ICON_SHOW_TIP"],
                PAD, yo)
            cbTargetCombat:SetChecked(rOrbsDB.targetCombatIconShow ~= false)
            cbTargetCombat:SetScript("OnClick", function(self)
                local val = self:GetChecked()
                rOrbsDB.targetCombatIconShow = val
                local f = ns.TargetCombatIcon
                if f then
                    if val and UnitAffectingCombat("target") then
                        f:Show()
                    else
                        f:Hide()
                    end
                end
                ns.GUI:ChatMsg("|cffffff00" .. L["SECTION_COMBAT_ICON"] .. "|r "..(val and "|cff00ff00shown|r." or "|cffff4444hidden|r."))
            end)
            Panel[key].cbTargetCombatIcon = cbTargetCombat ; yo = yo - 50

            local sTCombatSize, _ = MakeSlider(pane, L["COMBAT_ICON_SIZE"], L["COMBAT_ICON_SIZE_TIP"],
                12, 64, 1, PAD + 20, yo, SLIDER_W)
            SetSliderHandler(sTCombatSize, function(self, val)
                rOrbsDB.targetCombatIconSize = val
                if ns.UpdateTargetCombatIconLayout then ns.UpdateTargetCombatIconLayout() end
            end)
            sTCombatSize:SetValue(rOrbsDB.targetCombatIconSize or 28)
            Panel[key].sTCombatSize = sTCombatSize ; yo = yo - 46

            local sTCombatOffX, _ = MakeSlider(pane, L["COMBAT_ICON_OFFX"], L["COMBAT_ICON_OFFX_TIP"],
                -200, 200, 1, PAD + 20, yo, SLIDER_W)
            SetSliderHandler(sTCombatOffX, function(self, val)
                rOrbsDB.targetCombatIconOffX = val
                if ns.UpdateTargetCombatIconLayout then ns.UpdateTargetCombatIconLayout() end
            end)
            sTCombatOffX:SetValue(rOrbsDB.targetCombatIconOffX or 0)
            Panel[key].sTCombatOffX = sTCombatOffX ; yo = yo - 46

            local sTCombatOffY, _ = MakeSlider(pane, L["COMBAT_ICON_OFFY"], L["COMBAT_ICON_OFFY_TIP"],
                -200, 200, 1, PAD + 20, yo, SLIDER_W)
            SetSliderHandler(sTCombatOffY, function(self, val)
                rOrbsDB.targetCombatIconOffY = val
                if ns.UpdateTargetCombatIconLayout then ns.UpdateTargetCombatIconLayout() end
            end)
            sTCombatOffY:SetValue(rOrbsDB.targetCombatIconOffY or 0)
            Panel[key].sTCombatOffY = sTCombatOffY ; yo = yo - 52

            -- ── TARGET LEADER ICON ─────────────────────────────────────────
            yo = yo - 20
            MakeSep(pane, yo) ; yo = yo - 14
            MakeSectionHeader(pane, L["SECTION_LEADER_ICON"], PAD + 20, yo, true, function()
                rOrbsDB.targetLeaderIconShow = nil ; rOrbsDB.targetLeaderIconSize = nil
                rOrbsDB.targetLeaderIconOffX = nil ; rOrbsDB.targetLeaderIconOffY = nil
                if ns.UpdateTargetLeaderIconLayout then ns.UpdateTargetLeaderIconLayout() end
                ns.GUI:ChatMsg("|cffffff00" .. L["SECTION_LEADER_ICON"] .. "|r reset.")
                ns.GUI:RefreshValues()
            end) ; yo = yo - 26

            MakeIconPreviewButton(pane, PAD + 20, yo,
                function() return ns.TargetLeaderIcon end,
                function(f)
                    if not (UnitIsGroupLeader("target") and IsInGroup()) then f:Hide() end
                end)
            yo = yo - 38

            local cbTargetLeader = MakeCheckbox(pane,
                L["TARGET_LEADER_ICON_SHOW"],
                L["TARGET_LEADER_ICON_SHOW_TIP"],
                PAD, yo)
            cbTargetLeader:SetChecked(rOrbsDB.targetLeaderIconShow ~= false)
            cbTargetLeader:SetScript("OnClick", function(self)
                local val = self:GetChecked()
                rOrbsDB.targetLeaderIconShow = val
                local f = ns.TargetLeaderIcon
                if f then
                    if val and UnitIsGroupLeader("target") and IsInGroup() then
                        f:Show()
                    else
                        f:Hide()
                    end
                end
                ns.GUI:ChatMsg("|cffffff00" .. L["SECTION_LEADER_ICON"] .. "|r "..(val and "|cff00ff00shown|r." or "|cffff4444hidden|r."))
            end)
            Panel[key].cbTargetLeaderIcon = cbTargetLeader ; yo = yo - 50

            local sTLeaderSize, _ = MakeSlider(pane, L["PARTY_ICON_SIZE"], L["PARTY_ICON_SIZE_TIP"],
                8, 64, 1, PAD + 20, yo, SLIDER_W)
            SetSliderHandler(sTLeaderSize, function(self, val)
                rOrbsDB.targetLeaderIconSize = val
                if ns.UpdateTargetLeaderIconLayout then ns.UpdateTargetLeaderIconLayout() end
            end)
            sTLeaderSize:SetValue(rOrbsDB.targetLeaderIconSize or 22)
            Panel[key].sTLeaderSize = sTLeaderSize ; yo = yo - 46

            local sTLeaderOffX, _ = MakeSlider(pane, L["PARTY_ICON_OFFX"], L["PARTY_ICON_OFFX_TIP"],
                -200, 200, 1, PAD + 20, yo, SLIDER_W)
            SetSliderHandler(sTLeaderOffX, function(self, val)
                rOrbsDB.targetLeaderIconOffX = val
                if ns.UpdateTargetLeaderIconLayout then ns.UpdateTargetLeaderIconLayout() end
            end)
            sTLeaderOffX:SetValue(rOrbsDB.targetLeaderIconOffX or 0)
            Panel[key].sTLeaderOffX = sTLeaderOffX ; yo = yo - 46

            local sTLeaderOffY, _ = MakeSlider(pane, L["PARTY_ICON_OFFY"], L["PARTY_ICON_OFFY_TIP"],
                -200, 200, 1, PAD + 20, yo, SLIDER_W)
            SetSliderHandler(sTLeaderOffY, function(self, val)
                rOrbsDB.targetLeaderIconOffY = val
                if ns.UpdateTargetLeaderIconLayout then ns.UpdateTargetLeaderIconLayout() end
            end)
            sTLeaderOffY:SetValue(rOrbsDB.targetLeaderIconOffY or 0)
            Panel[key].sTLeaderOffY = sTLeaderOffY ; yo = yo - 46
        end

        -- TARGET/TOT: Switch back to paneOrb for Effects/Template/Font/Reset
        if cfgKey == "TARGET_HEALTH" then SwitchPane(paneOrb) end
        if cfgKey == "TOT_HEALTH" then SwitchPane(paneOrb) end
        if cfgKey == "PET_HEALTH" then SwitchPane(paneOrb) end

        yo = yo - 16
        -- EFFECTS -------------------------------------------------------------
        MakeSectionHeader(pane, "Effects", PAD, yo, false, function()
            local db2 = ns.db
            if db2 and db2.char[cfgKey] then
                local defs = db2:GetOrbDefaults()
                if defs and defs[cfgKey] then
                    local defGal  = defs[cfgKey].galaxies  and defs[cfgKey].galaxies.alpha  or 0.4
                    local defBub  = defs[cfgKey].bubbles   and defs[cfgKey].bubbles.alpha   or 1.0
                    local defHigh = defs[cfgKey].highlight and defs[cfgKey].highlight.alpha or 1.0
                    db2.char[cfgKey].galaxies.alpha  = defGal
                    db2.char[cfgKey].bubbles.alpha   = defBub
                    db2.char[cfgKey].highlight.alpha = defHigh
                    ns.GUI:ApplyLayerAlpha(cfgKey, "galaxies",  defGal)
                    ns.GUI:ApplyLayerAlpha(cfgKey, "bubbles",   defBub)
                    ns.GUI:ApplyLayerAlpha(cfgKey, "highlight", defHigh)
                    -- update sliders immediately
                    if Panel[key] and Panel[key].sGal  then Panel[key].sGal:SetValue(defGal)   end
                    if Panel[key] and Panel[key].sBub  then Panel[key].sBub:SetValue(defBub)   end
                    if Panel[key] and Panel[key].sHigh then Panel[key].sHigh:SetValue(defHigh) end
                end
            end
            rOrbsDB["glossTex_"..cfgKey] = nil
            -- re-apply gloss to default "new" texture
            if ns.GUI.ApplyGloss then ns.GUI:ApplyGloss(cfgKey, "new") end
            ns.GUI:ChatMsg("|cffffff00Effects|r reset.")
            ns.GUI:RefreshValues()
        end)
        yo = yo - 44

        -- GALAXIES
        local sGal, _ = MakeSlider(pane, L["ORB_GALAXIES_ALPHA"], L["ORB_GALAXIES_ALPHA_TIP"],
            0, 1, 0.05, PAD, yo, SLIDER_W)
        SetSliderHandler(sGal, function(self, val)
            ensureCharCfg()
            if ns.db and ns.db.char[cfgKey] then ns.db.char[cfgKey].galaxies.alpha = val end
            if rOrbs_DB_CHAR and rOrbs_DB_CHAR[cfgKey] then rOrbs_DB_CHAR[cfgKey].galaxies.alpha = val end
            ns.GUI:ApplyLayerAlpha(cfgKey, "galaxies", val)
        end)
        Panel[key].sGal = sGal ; yo = yo - 52

        -- BUBBLES
        local sBub, _ = MakeSlider(pane, L["ORB_BUBBLES_ALPHA"], L["ORB_BUBBLES_ALPHA_TIP"],
            0, 1, 0.05, PAD, yo, SLIDER_W)
        SetSliderHandler(sBub, function(self, val)
            ensureCharCfg()
            if ns.db and ns.db.char[cfgKey] then ns.db.char[cfgKey].bubbles.alpha = val end
            if rOrbs_DB_CHAR and rOrbs_DB_CHAR[cfgKey] then rOrbs_DB_CHAR[cfgKey].bubbles.alpha = val end
            ns.GUI:ApplyLayerAlpha(cfgKey, "bubbles", val)
        end)
        Panel[key].sBub = sBub ; yo = yo - 52


        -- GLOSS
        local sHigh, _ = MakeSlider(pane, L["ORB_HIGHLIGHT_ALPHA"], L["ORB_HIGHLIGHT_ALPHA_TIP"],
            0, 1, 0.05, PAD, yo, SLIDER_W)
        SetSliderHandler(sHigh, function(self, val)
            ensureCharCfg()
            if ns.db and ns.db.char[cfgKey] then ns.db.char[cfgKey].highlight.alpha = val end
            if rOrbs_DB_CHAR and rOrbs_DB_CHAR[cfgKey] then rOrbs_DB_CHAR[cfgKey].highlight.alpha = val end
            ns.GUI:ApplyLayerAlpha(cfgKey, "highlight", val)
        end)
        Panel[key].sHigh = sHigh ; yo = yo - 52

        -- GLOSS TEXTURE SELECTOR
        local glossLabel = pane:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
        glossLabel:SetPoint("TOPLEFT", pane, "TOPLEFT", PAD, yo)
        glossLabel:SetText("Gloss Texture")
        glossLabel:SetTextColor(C.text[1], C.text[2], C.text[3])
        yo = yo - 22

        local glossDBKey = "glossTex_"..cfgKey
        local currentGloss = rOrbsDB[glossDBKey] or "new"

        local cbGlossNew = MakeCheckbox(pane, "New Gloss",
            "Use the new gloss texture on this orb.", PAD, yo)
        cbGlossNew:SetChecked(currentGloss ~= "classic")

        local cbGlossClassic = MakeCheckbox(pane, "Classic Gloss",
            "Use the original classic gloss texture on this orb.", PAD + 160, yo)
        cbGlossClassic:SetChecked(currentGloss == "classic")

        -- Mutual exclusion + live apply
        local function applyGloss(choice)
            rOrbsDB[glossDBKey] = choice
            cbGlossNew:SetChecked(choice ~= "classic")
            cbGlossClassic:SetChecked(choice == "classic")
            local orb = ns.GUI and ns.GUI.GetOrb and ns.GUI:GetOrb(cfgKey)
                        or (cfgKey == "HEALTH" and ns.HealthOrb)
                        or (cfgKey == "POWER"  and ns.PowerOrb)
                        or (cfgKey == "TARGET_HEALTH" and ns.TargetHealthOrb)
                        or (cfgKey == "TOT_HEALTH" and ns.ToTHealthOrb)
                        or (cfgKey == "PET_HEALTH" and ns.PetHealthOrb)
            if orb and orb.highlight then
                local tex = (choice == "classic")
                    and "Interface\\AddOns\\rOrbs\\media\\orb_gloss"
                    or  "Interface\\AddOns\\rOrbs\\media\\orb_gloss_new"
                orb.highlight:SetTexture(tex)
            end
        end
        cbGlossNew:SetScript("OnClick", function(self)
            applyGloss("new")
            ns.GUI:ChatMsg("|cffffff00Gloss ("..cfgKey..")|r → |cff00ff00New|r")
        end)
        cbGlossClassic:SetScript("OnClick", function(self)
            applyGloss("classic")
            ns.GUI:ChatMsg("|cffffff00Gloss ("..cfgKey..")|r → |cffffff00Classic|r")
        end)
        Panel[key].cbGlossNew     = cbGlossNew
        Panel[key].cbGlossClassic = cbGlossClassic
        yo = yo - 50

        yo = yo - 20
        -- ORB TEMPLATE PRESET ------------------------------------------------
        MakeSectionHeader(pane, L["TEMPLATE_SECTION"], PAD, yo, false, function()
            rOrbsDB["orbTemplate_"..cfgKey] = nil
            rOrbsDB["tmplShow_"..cfgKey]    = nil
            rOrbsDB["tmplScale_"..cfgKey]   = nil
            rOrbsDB["tmplOffsetX_"..cfgKey] = nil
            rOrbsDB["tmplOffsetZ_"..cfgKey] = nil
            rOrbsDB["tmplOpacity_"..cfgKey] = nil
            -- model alpha aliases (Target/ToT/Pet store separately)
            if     cfgKey == "TARGET_HEALTH" then rOrbsDB.targetModelAlpha = nil
            elseif cfgKey == "TOT_HEALTH"    then rOrbsDB.totModelAlpha    = nil
            elseif cfgKey == "PET_HEALTH"    then rOrbsDB.petModelAlpha    = nil
            else
                local db2 = ns.db
                if db2 and db2.char[cfgKey] and db2.char[cfgKey].model then
                    local defs = db2:GetOrbDefaults()
                    if defs and defs[cfgKey] and defs[cfgKey].model then
                        db2.char[cfgKey].model.alpha = defs[cfgKey].model.alpha
                    end
                end
            end
            if ns.OrbTemplateEngine then ns.OrbTemplateEngine:Apply(cfgKey, "none", false) end
            ns.GUI:ChatMsg("|cffffff00" .. L["TEMPLATE_SECTION"] .. "|r reset.")
            ns.GUI:RefreshValues()
        end) ; yo = yo - 44

        -- Show 3D model (built-in animated model)
        local cbOrbModel = MakeCheckbox(pane,
            "Show 3D model",
            "Toggle the animated 3D model displayed inside this orb.",
            PAD, yo)
        cbOrbModel:SetScript("OnClick", function(self)
            local val = self:GetChecked()
            ensureCharCfg()
            if not ns.db or not ns.db.char[cfgKey] then return end
            ns.db.char[cfgKey].model.enable = val
            if rOrbs_DB_CHAR and rOrbs_DB_CHAR[cfgKey] then rOrbs_DB_CHAR[cfgKey].model.enable = val end
            ns.GUI:ApplyOrbModel(cfgKey, val)
            ns.GUI:ChatMsg("|cffffff00"..cfgKey.." model|r "..(val and "|cff00ff00shown|r." or "|cffff4444hidden|r."))
        end)
        Panel[key].cbOrbModel = cbOrbModel ; yo = yo - 50

        -- Model Opacity (applies to built-in 3D model)
        local sOrbModelAlpha, _ = MakeSlider(pane, L["TARGET_MODEL_ALPHA"],
            "Opacity of the 3D model inside this orb.",
            0, 1, 0.05, PAD, yo, SLIDER_W)
        SetSliderHandler(sOrbModelAlpha, function(self, val)
            -- Always save to tmplOpacity_ so RestoreAll (0.5/2/4s after reload) reads the right value
            rOrbsDB["tmplOpacity_"..cfgKey] = val
            if cfgKey == "TARGET_HEALTH" then
                rOrbsDB.targetModelAlpha = val
            elseif cfgKey == "TOT_HEALTH" then
                rOrbsDB.totModelAlpha = val
            elseif cfgKey == "PET_HEALTH" then
                rOrbsDB.petModelAlpha = val
            else
                if ns.db and ns.db.char[cfgKey] then ns.db.char[cfgKey].model.alpha = val end
                if rOrbs_DB_CHAR and rOrbs_DB_CHAR[cfgKey] then rOrbs_DB_CHAR[cfgKey].model.alpha = val end
            end
            ns.GUI:ApplyOrbModelAlpha(cfgKey, val)
        end)
        Panel[key].sOrbModelAlpha = sOrbModelAlpha ; yo = yo - 52

        -- Show / Hide 3D Model Preset
        local cbTemplateShow = MakeCheckbox(pane,
            "Show 3D Model Preset",
            "Show: activates the selected 3D model preset. Hide: removes the preset and restores the normal fill texture.",
            PAD, yo)
        cbTemplateShow:SetScript("OnClick", function(self)
            local val = self:GetChecked()
            rOrbsDB["tmplShow_"..cfgKey] = val
            local orb
            if cfgKey == "HEALTH" then orb = ns.HealthOrb
            elseif cfgKey == "POWER" then orb = ns.PowerOrb
            elseif cfgKey == "TARGET_HEALTH" then orb = ns.TargetHealthOrb
            elseif cfgKey == "TOT_HEALTH" then orb = ns.ToTHealthOrb
            elseif cfgKey == "PET_HEALTH" then orb = ns.PetHealthOrb
            end
            if val then
                local tmplKey = rOrbsDB["orbTemplate_"..cfgKey]
                if tmplKey and tmplKey ~= "none" and ns.OrbTemplateEngine then
                    ns.OrbTemplateEngine:Apply(cfgKey, tmplKey, false)
                end
            else
                if orb and ns.OrbTemplateEngine then
                    ns.OrbTemplateEngine:RemoveTemplate(orb)
                end
                if orb and ns.db and ns.db.char[cfgKey] then
                    local texPath = ns.db.char[cfgKey].filling and ns.db.char[cfgKey].filling.texture or ""
                    ns.GUI:ApplyTexture(cfgKey, texPath)
                end
            end
        end)
        Panel[key].cbTemplateShow = cbTemplateShow ; yo = yo - 50

        local ddTemplate, _ = MakeDropdown(pane,
            L["TEMPLATE_DROPDOWN"],
            L["TEMPLATE_DROPDOWN_TIP"],
            PAD, yo, 220)
        Panel[key].ddTemplate = ddTemplate
        UIDropDownMenu_Initialize(ddTemplate, function(self, level)
            local list = ns.orbTemplateList
            if not list then return end
            local currentKey = rOrbsDB and rOrbsDB["orbTemplate_"..cfgKey] or "none"
            for _, entry in ipairs(list) do
                local info = UIDropDownMenu_CreateInfo()
                info.text     = entry.label
                info.value    = entry.key
                info.checked  = (entry.key == currentKey)
                info.func = function()
                    UIDropDownMenu_SetSelectedValue(ddTemplate, entry.key)
                    UIDropDownMenu_SetText(ddTemplate, entry.label)
                    if ns.OrbTemplateEngine then
                        ns.OrbTemplateEngine:Apply(cfgKey, entry.key, false)
                    end
                    rOrbsDB["tmplShow_"..cfgKey] = true
                    if Panel[key].cbTemplateShow then Panel[key].cbTemplateShow:SetChecked(true) end
                    ns.GUI:ChatMsg("|cffffff00Template ("..cfgKey..")|r: "..entry.label)
                end
                UIDropDownMenu_AddButton(info, level)
            end
        end)
        do
            local currentKey = rOrbsDB and rOrbsDB["orbTemplate_"..cfgKey] or "none"
            local dispName = "None (Custom)"
            if ns.orbTemplateList then
                for _, entry in ipairs(ns.orbTemplateList) do
                    if entry.key == currentKey then dispName = entry.label; break end
                end
            end
            UIDropDownMenu_SetText(ddTemplate, dispName)
            UIDropDownMenu_SetSelectedValue(ddTemplate, currentKey)
        end
        yo = yo - 56

        -- TEMPLATE FINE-TUNING SLIDERS (directly under template dropdown) ------
        local sTmplScale, _ = MakeSlider(pane, "Scale", "Scale the 3D model. 1.0 = default template size.", 0.1, 3.0, 0.05, PAD, yo, SLIDER_W)
        Panel[key].sTmplScale = sTmplScale
        SetSliderHandler(sTmplScale, function(self, val)
            if rOrbsDB then rOrbsDB["tmplScale_"..cfgKey] = val end
            if ns.OrbTemplateEngine then ns.OrbTemplateEngine:RefreshModel(cfgKey) end
        end)
        sTmplScale:SetValue(1.0)
        yo = yo - 52

        local sTmplOffX, _ = MakeSlider(pane, "Offset X", "Verschiebt das 3D-Modell horizontal (links/rechts).", -1.0, 1.0, 0.01, PAD, yo, SLIDER_W)
        Panel[key].sTmplOffX = sTmplOffX
        SetSliderHandler(sTmplOffX, function(self, val)
            if rOrbsDB then rOrbsDB["tmplOffsetX_"..cfgKey] = val end
            if ns.OrbTemplateEngine then ns.OrbTemplateEngine:RefreshModel(cfgKey) end
        end)
        sTmplOffX:SetValue(0.0)
        yo = yo - 52

        local sTmplOffZ, _ = MakeSlider(pane, "Offset Z", "Verschiebt das 3D-Modell vertikal (oben/unten).", -1.0, 1.0, 0.01, PAD, yo, SLIDER_W)
        Panel[key].sTmplOffZ = sTmplOffZ
        SetSliderHandler(sTmplOffZ, function(self, val)
            if rOrbsDB then rOrbsDB["tmplOffsetZ_"..cfgKey] = val end
            if ns.OrbTemplateEngine then ns.OrbTemplateEngine:RefreshModel(cfgKey) end
        end)
        sTmplOffZ:SetValue(0.0)
        yo = yo - 52

        -- sTmplOpacity entfernt: identisch mit "Modell-Deckkraft" (sOrbModelAlpha) weiter oben.
        -- Die Deckkraft des 3D-Modells wird ausschließlich über diesen Slider gesteuert.

        -- ============================================================
        --  CUSTOM TEMPLATE CREATOR
        -- ============================================================
        yo = yo - 40
        MakeSectionHeader(pane, L["CUSTOM_TMPL_SECTION"], PAD, yo) ; yo = yo - 44

        MakeInfoText(pane,
            L["CUSTOM_TMPL_INFO"],
            PAD, yo, PANEL_W - CONTENT_X - PAD - SB_W - 30)
        yo = yo - 38

        -- DisplayInfo ID input
        local idLabel = pane:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
        idLabel:SetPoint("TOPLEFT", pane, "TOPLEFT", PAD, yo)
        idLabel:SetText(L["CUSTOM_TMPL_ID_LABEL"])
        idLabel:SetTextColor(C.text[1], C.text[2], C.text[3])
        yo = yo - 20

        local idBox = CreateFrame("EditBox", nil, pane, "InputBoxTemplate")
        idBox:SetPoint("TOPLEFT", pane, "TOPLEFT", PAD + 4, yo)
        idBox:SetSize(120, 22)
        idBox:SetAutoFocus(false)
        idBox:SetNumeric(true)
        idBox:SetMaxLetters(10)
        idBox:SetText("")
        idBox:SetScript("OnEscapePressed", function(self) self:ClearFocus() end)
        idBox:SetScript("OnEnterPressed", function(self) self:ClearFocus() end)
        Panel[key].idBox = idBox
        yo = yo - 30

        -- Preview CamScale slider
        local sPrevCam, _ = MakeSlider(pane, L["CUSTOM_TMPL_CAMSCALE"],
            L["CUSTOM_TMPL_CAMSCALE_TIP"],
            0.1, 12.0, 0.1, PAD, yo, SLIDER_W)
        sPrevCam:SetValue(1.0)
        Panel[key].sPrevCam = sPrevCam
        yo = yo - 46

        -- Preview Offset Z slider
        local sPrevPosZ, _ = MakeSlider(pane, L["CUSTOM_TMPL_PREVZ"],
            L["CUSTOM_TMPL_PREVZ_TIP"],
            -8.0, 8.0, 0.01, PAD, yo, SLIDER_W)
        sPrevPosZ:SetValue(0.0)
        Panel[key].sPrevPosZ = sPrevPosZ
        yo = yo - 46

        -- Preview Offset X slider
        local sPrevPosX, _ = MakeSlider(pane, L["CUSTOM_TMPL_PREVX"],
            L["CUSTOM_TMPL_PREVX_TIP"],
            -8.0, 8.0, 0.01, PAD, yo, SLIDER_W)
        sPrevPosX:SetValue(0.0)
        Panel[key].sPrevPosX = sPrevPosX

        yo = yo - 46

        -- Helper: run FULL preview (loads the model from scratch)
        local function RunPreview()
            local idText = idBox:GetText()
            local id = tonumber(idText)
            if not id or id <= 0 then return end
            local cam  = sPrevCam:GetValue()
            local posZ = sPrevPosZ:GetValue()
            local posX = sPrevPosX:GetValue()
            if ns.CustomTemplateCreator then
                ns.CustomTemplateCreator:StartPreview(cfgKey, id, cam, posZ)
                if posX ~= 0 then
                    ns.CustomTemplateCreator:LiveUpdate(cfgKey, nil, nil, posX)
                end
                if Panel[key].cbTemplateShow then Panel[key].cbTemplateShow:SetChecked(true) end
            end
        end

        -- Hook slider OnValueChanged to live-update the preview model
        SetSliderHandler(sPrevCam, function(self, val)
            if ns.CustomTemplateCreator and ns.CustomTemplateCreator._active[cfgKey] then
                ns.CustomTemplateCreator:LiveUpdate(cfgKey, val, nil)
            end
        end)
        SetSliderHandler(sPrevPosZ, function(self, val)
            if ns.CustomTemplateCreator and ns.CustomTemplateCreator._active[cfgKey] then
                ns.CustomTemplateCreator:LiveUpdate(cfgKey, nil, val, nil)
            end
        end)
        SetSliderHandler(sPrevPosX, function(self, val)
            if ns.CustomTemplateCreator and ns.CustomTemplateCreator._active[cfgKey] then
                ns.CustomTemplateCreator:LiveUpdate(cfgKey, nil, nil, val)
            end
        end)

        -- Preview + Stop buttons
        local previewBtn = MakeButton(pane, L["CUSTOM_TMPL_PREVIEW"], PAD, yo, 110, 26, 0.10, 0.20, 0.10)
        previewBtn:SetScript("OnClick", function()
            local idText = idBox:GetText()
            local id = tonumber(idText)
            if not id or id <= 0 then
                ns.GUI:ChatMsg("|cffff4444Invalid DisplayInfo ID.|r")
                return
            end
            RunPreview()
            ns.GUI:ChatMsg("|cffffff00Preview|r: DisplayInfo ID |cff00ff00" .. id .. "|r")
            idBox:ClearFocus()
        end)

        local stopBtn = MakeButton(pane, L["CUSTOM_TMPL_STOP"], PAD + 118, yo, 110, 26, 0.40, 0.05, 0.03)
        stopBtn:SetScript("OnClick", function()
            if ns.CustomTemplateCreator then
                ns.CustomTemplateCreator:StopPreview(cfgKey)
            end
            if Panel[key].cbTemplateShow then Panel[key].cbTemplateShow:SetChecked(false) end
            ns.GUI:ChatMsg("|cffffff00Preview stopped.|r")
        end)
        yo = yo - 34

        -- Reset button: centers the 3D model and resets sliders
        local resetBtn = MakeButton(pane,
            L["CUSTOM_TMPL_CENTER"],
            PAD, yo, 228, 26, 0.15, 0.15, 0.15)
        resetBtn:SetScript("OnClick", function()
            if ns.CustomTemplateCreator then
                ns.CustomTemplateCreator:ResetPreviewToCenter(cfgKey)
                -- Reset preview sliders
                sPrevCam:SetValue(1.0)
                sPrevPosZ:SetValue(0.0)
                if Panel[key].sPrevPosX then Panel[key].sPrevPosX:SetValue(0.0) end
                -- Also reset Template Adjustment sliders
                if Panel[key].sTmplScale   then Panel[key].sTmplScale:SetValue(1.0)  end
                if Panel[key].sTmplOffX    then Panel[key].sTmplOffX:SetValue(0.0)   end
                if Panel[key].sTmplOffZ    then Panel[key].sTmplOffZ:SetValue(0.0)   end
                -- sTmplOpacity removed (duplicate of sOrbModelAlpha)
                ns.GUI:ChatMsg("|cffffff00Model centered.|r")
            end
        end)
        yo = yo - 34

        -- Save Template button
        local saveBtn = MakeButton(pane, L["CUSTOM_TMPL_SAVE"], PAD, yo, 228, 26, 0.08, 0.15, 0.25)
        saveBtn:SetScript("OnClick", function()
            local idText = idBox:GetText()
            local id = tonumber(idText)
            if not id or id <= 0 then
                ns.GUI:ChatMsg("|cffff4444Enter a DisplayInfo ID first.|r")
                return
            end
            StaticPopupDialogs["RORBS_CUSTOM_TMPL_SAVE"] = {
                text         = L["CUSTOM_TMPL_NAME_PROMPT"],
                button1      = OKAY,
                button2      = CANCEL,
                hasEditBox   = true,
                maxLetters   = 40,
                OnAccept     = function(self)
                    local name = ""
                    if self and self.editBox and self.editBox.GetText then
                        name = self.editBox:GetText()
                    else
                        for i = 1, 4 do
                            local eb = _G["StaticPopup"..i.."EditBox"]
                            if eb and eb:IsShown() then name = eb:GetText(); break end
                        end
                    end
                    if not name or name == "" then return end
                    local cam = sPrevCam:GetValue()
                    local posZ = sPrevPosZ:GetValue()
                    local posX = sPrevPosX:GetValue()
                    local state = {
                        displayInfoID  = id,
                        camScale       = cam,
                        modelOpacity   = 1.0,
                        posAdjustZ     = posZ,
                        posAdjustX     = posX,
                        statusBarColor = {1, 1, 1, 1},
                        sparkColor     = {1, 1, 1, 1},
                    }
                    local ok, savedKey = ns.CustomTemplateCreator:Save(name, state)
                    if ok then
                        -- Stop preview, apply the real saved template
                        ns.CustomTemplateCreator._active[cfgKey] = false
                        ns.orbTemplates["_preview_"] = nil
                        if ns.OrbTemplateEngine then
                            ns.OrbTemplateEngine:Apply(cfgKey, savedKey, false)
                        end
                        rOrbsDB["tmplShow_"..cfgKey] = true
                        if Panel[key].cbTemplateShow then Panel[key].cbTemplateShow:SetChecked(true) end
                        -- Refresh custom dropdown
                        if Panel[key].ddCustomTmpl and Panel[key]._ddCustomTmplInit then
                            UIDropDownMenu_Initialize(Panel[key].ddCustomTmpl, Panel[key]._ddCustomTmplInit)
                            UIDropDownMenu_SetText(Panel[key].ddCustomTmpl, name)
                            UIDropDownMenu_SetSelectedValue(Panel[key].ddCustomTmpl, savedKey)
                        end
                        ns.GUI:ChatMsg(L["CUSTOM_TMPL_SAVED"] .. "|cff00ff00" .. name .. "|r")
                    end
                end,
                timeout = 0, whileDead = true, hideOnEscape = true,
            }
            StaticPopup_Show("RORBS_CUSTOM_TMPL_SAVE")
        end)
        yo = yo - 42

        -- My Templates dropdown
        local ddCustomTmpl, _ = MakeDropdown(pane,
            L["CUSTOM_TMPL_DROPDOWN"],
            L["CUSTOM_TMPL_DROPDOWN_TIP"],
            PAD, yo, 220)
        Panel[key].ddCustomTmpl = ddCustomTmpl

        local function initCustomDropdown(self, level)
            local list = ns.customTemplateList
            if not list or #list == 0 then
                local info = UIDropDownMenu_CreateInfo()
                info.text = "|cff888888-- " .. L["CUSTOM_TMPL_NONE"] .. " --"
                info.disabled = true
                UIDropDownMenu_AddButton(info, level)
                return
            end
            local currentKey = rOrbsDB and rOrbsDB["orbTemplate_"..cfgKey] or "none"
            for _, entry in ipairs(list) do
                local info = UIDropDownMenu_CreateInfo()
                info.text     = entry.label
                info.value    = entry.key
                info.checked  = (entry.key == currentKey)
                info.func = function()
                    UIDropDownMenu_SetSelectedValue(ddCustomTmpl, entry.key)
                    UIDropDownMenu_SetText(ddCustomTmpl, entry.label)
                    if entry.key == "custom-none" then
                        if ns.OrbTemplateEngine then
                            ns.OrbTemplateEngine:Apply(cfgKey, "none", false)
                        end
                        rOrbsDB["tmplShow_"..cfgKey] = false
                        if Panel[key].cbTemplateShow then Panel[key].cbTemplateShow:SetChecked(false) end
                    else
                        ns.orbTemplates["_preview_"] = nil
                        if ns.OrbTemplateEngine then
                            ns.OrbTemplateEngine:Apply(cfgKey, entry.key, false)
                        end
                        rOrbsDB["tmplShow_"..cfgKey] = true
                        if Panel[key].cbTemplateShow then Panel[key].cbTemplateShow:SetChecked(true) end
                        -- Clear preset dropdown display
                        UIDropDownMenu_SetText(ddTemplate, "—")
                        UIDropDownMenu_SetSelectedValue(ddTemplate, nil)
                    end
                    ns.GUI:ChatMsg("|cffffff00Custom Template ("..cfgKey..")|r: "..entry.label)
                end
                UIDropDownMenu_AddButton(info, level)
            end
        end
        Panel[key]._ddCustomTmplInit = initCustomDropdown
        UIDropDownMenu_Initialize(ddCustomTmpl, initCustomDropdown)
        do
            local currentKey = rOrbsDB and rOrbsDB["orbTemplate_"..cfgKey] or "none"
            local dispName = "None (Custom)"
            local isCustom = false
            if ns.customTemplateList then
                for _, entry in ipairs(ns.customTemplateList) do
                    if entry.key == currentKey then
                        dispName = entry.label
                        isCustom = (entry.key ~= "custom-none")
                        break
                    end
                end
            end
            UIDropDownMenu_SetText(ddCustomTmpl, isCustom and dispName or "None (Custom)")
            UIDropDownMenu_SetSelectedValue(ddCustomTmpl, isCustom and currentKey or "custom-none")
        end
        yo = yo - 48

        -- Info text: after saving a template use "Orb Template" section for adjustments
        MakeInfoText(pane,
            L["CUSTOM_TMPL_SAVE_TIP"],
            PAD, yo, PANEL_W - CONTENT_X - PAD - SB_W - 30)
        yo = yo - 32

        -- Delete button for custom templates
        local delCustomBtn = MakeButton(pane, L["CUSTOM_TMPL_DELETE"], PAD, yo, 110, 26, 0.40, 0.05, 0.03)
        delCustomBtn:SetScript("OnClick", function()
            local selKey = UIDropDownMenu_GetSelectedValue(ddCustomTmpl)
            if not selKey or selKey == "custom-none" then
                ns.GUI:ChatMsg("|cffff4444No custom template selected.|r")
                return
            end
            local selLabel = ""
            if ns.customTemplateList then
                for _, e in ipairs(ns.customTemplateList) do
                    if e.key == selKey then selLabel = e.label; break end
                end
            end
            StaticPopupDialogs["RORBS_CUSTOM_TMPL_DELETE"] = {
                text = string.format(L["CUSTOM_TMPL_DELETE_CONFIRM"], selLabel),
                button1 = OKAY,
                button2 = CANCEL,
                OnAccept = function()
                    local activeKey = rOrbsDB["orbTemplate_"..cfgKey]
                    if activeKey == selKey then
                        if ns.OrbTemplateEngine then
                            ns.OrbTemplateEngine:Apply(cfgKey, "none", false)
                        end
                        rOrbsDB["tmplShow_"..cfgKey] = false
                        if Panel[key].cbTemplateShow then Panel[key].cbTemplateShow:SetChecked(false) end
                    end
                    ns.CustomTemplateCreator:Delete(selKey)
                    UIDropDownMenu_Initialize(ddCustomTmpl, Panel[key]._ddCustomTmplInit)
                    UIDropDownMenu_SetText(ddCustomTmpl, "None (Custom)")
                    UIDropDownMenu_SetSelectedValue(ddCustomTmpl, "custom-none")
                    ns.GUI:ChatMsg(L["CUSTOM_TMPL_DELETED"] .. "|cffff4444" .. selLabel .. "|r")
                end,
                timeout = 0, whileDead = true, hideOnEscape = true,
            }
            StaticPopup_Show("RORBS_CUSTOM_TMPL_DELETE")
        end)
        yo = yo - 42

        -- Switch to font sub-pane (health/power only)
        if isMulti and paneFont then SwitchPane(paneFont) end

        -- TEXT SETTINGS -------------------------------------------------------
        MakeSectionHeader(pane, L["TEXT_SECTION"], PAD, yo, true, function()
            rOrbsDB["textShow_"..cfgKey]  = nil
            rOrbsDB["textHover_"..cfgKey] = nil
            rOrbsDB["textMode_"..cfgKey]  = nil
            rOrbsDB["fontSize_"..cfgKey]  = nil
            rOrbsDB["textAlpha_"..cfgKey] = nil
            rOrbsDB["textColor_"..cfgKey] = nil
            rOrbsDB["font_"..cfgKey]       = nil
            ns.GUI:ApplyTextColor(cfgKey, 1, 1, 1)
            ns.GUI:ChatMsg("|cffffff00" .. L["TEXT_SECTION"] .. "|r reset.")
            ns.GUI:RefreshValues()
        end) ; yo = yo - 44

        -- TEXT SHOW ----------------------------------------------------------
        local cbText = MakeCheckbox(pane, L["ORB_TEXT_SHOW"], L["ORB_TEXT_SHOW_TIP"], PAD, yo)
        cbText:SetScript("OnClick", function(self)
            local val = self:GetChecked()
            rOrbsDB["textShow_"..cfgKey] = val
            ns.GUI:ApplyTextShow(cfgKey, val)
            ns.GUI:ChatMsg(val and L["CHAT_TEXT_SHOWN"] or L["CHAT_TEXT_HIDDEN"])
        end)
        Panel[key].cbText = cbText
        yo = yo - 50

        -- SHOW TEXT ON HOVER -------------------------------------------------
        local cbHover = MakeCheckbox(pane, "Show on mouseover only",
            "Hide text values normally. Show them only when the mouse hovers over the orb.", PAD + 20, yo)
        cbHover:SetScript("OnClick", function(self)
            local val = self:GetChecked()
            rOrbsDB["textHover_"..cfgKey] = val
            local orbFrame = cfgKey == "HEALTH" and _G["rOrbsHealthOrb"]
                          or cfgKey == "POWER"  and _G["rOrbsPowerOrb"]
                          or cfgKey == "TARGET_HEALTH" and _G["rOrbsTargetHealthOrb"]
                          or cfgKey == "TOT_HEALTH" and _G["rOrbsToTHealthOrb"]
                          or cfgKey == "PET_HEALTH" and _G["rOrbsPetHealthOrb"]
            if orbFrame and orbFrame.values then
                if val then orbFrame.values:Hide()
                else orbFrame.values:Show() end
            end
            ns.GUI:ChatMsg("|cffffff00Text on hover|r "..(val and "|cff00ff00enabled|r." or "|cffff4444disabled|r."))
        end)
        Panel[key].cbHover = cbHover
        yo = yo - 50

        -- HIDE % SIGN CHECKBOX -----------------------------------------------
        local cbHidePct = MakeCheckbox(pane, L["ORB_HIDE_PERCENT_SIGN"],
            L["ORB_HIDE_PERCENT_SIGN_TIP"], PAD + 20, yo)
        cbHidePct:SetScript("OnClick", function(self)
            local val = self:GetChecked()
            rOrbsDB["hidePercentSign_"..cfgKey] = val
            StaticPopupDialogs["RORBS_RELOAD_FOR_HIDEPCT"] = {
                text    = L["TEXTMODE_RELOAD_TEXT"],
                button1 = L["ORB_RELOAD_ENABLE_BTN"],
                button2 = CANCEL,
                OnAccept = function() ReloadUI() end,
                OnCancel = function()
                    -- revert the checkbox visually since we won't reload
                    -- the value stays saved; on next reload it will apply
                end,
                timeout = 0, whileDead = true, hideOnEscape = false,
            }
            StaticPopup_Show("RORBS_RELOAD_FOR_HIDEPCT")
        end)
        Panel[key].cbHidePct = cbHidePct
        yo = yo - 50

        -- TEXT MODE DROPDOWN -------------------------------------------------
        local ddTextMode, _ = MakeDropdown(pane, "Text Display Mode",
            "Choose what information is displayed on the orb.", PAD, yo, 180)
        Panel[key].ddTextMode = ddTextMode
        UIDropDownMenu_Initialize(ddTextMode, function(self, level)
            local currentMode = rOrbsDB["textMode_"..cfgKey] or "BOTH"
            local modes = {
                { text = "Both (HP + %)",  value = "BOTH"    },
                { text = "HP / Value only", value = "VALUE"   },
                { text = "Percent only",   value = "PERCENT" },
            }
            for _, entry in ipairs(modes) do
                local info = UIDropDownMenu_CreateInfo()
                info.text    = entry.text
                info.value   = entry.value
                info.checked = (entry.value == currentMode)
                info.func    = function()
                    UIDropDownMenu_SetSelectedValue(ddTextMode, entry.value)
                    UIDropDownMenu_SetText(ddTextMode, entry.text)
                    StaticPopupDialogs["RORBS_RELOAD_FOR_TEXTMODE"] = {
                        text    = L["TEXTMODE_RELOAD_TEXT"],
                        button1 = L["ORB_RELOAD_ENABLE_BTN"],
                        button2 = CANCEL,
                        OnAccept = function()
                            rOrbsDB["textMode_"..cfgKey] = entry.value
                            ReloadUI()
                        end,
                        OnCancel = function()
                            local prev = rOrbsDB["textMode_"..cfgKey] or "BOTH"
                            local modeLabels = { BOTH = "Both (HP + %)", VALUE = "HP / Value only", PERCENT = "Percent only" }
                            UIDropDownMenu_SetText(ddTextMode, modeLabels[prev] or "Both (HP + %)")
                            UIDropDownMenu_SetSelectedValue(ddTextMode, prev)
                        end,
                        timeout = 0, whileDead = true, hideOnEscape = false,
                    }
                    StaticPopup_Show("RORBS_RELOAD_FOR_TEXTMODE")
                end
                UIDropDownMenu_AddButton(info, level)
            end
        end)
        do
            local mode = rOrbsDB["textMode_"..cfgKey] or "BOTH"
            local modeLabels = { BOTH = "Both (HP + %)", VALUE = "HP / Value only", PERCENT = "Percent only" }
            UIDropDownMenu_SetText(ddTextMode, modeLabels[mode] or "Both (HP + %)")
            UIDropDownMenu_SetSelectedValue(ddTextMode, mode)
        end
        yo = yo - 56

        -- FONT DROPDOWN ------------------------------------------------------
        local ddFont, _ = MakeDropdown(pane, L["ORB_FONT"], L["ORB_FONT_TIP"], PAD, yo, 220)
        Panel[key].ddFont = ddFont
        UIDropDownMenu_Initialize(ddFont, function(self, level)
            local fonts = LSM:List("font")
            if not fonts then return end
            local currentFont = rOrbsDB and rOrbsDB["font_"..cfgKey] or "Enchanted Land"
            for _, fname in ipairs(fonts) do
                local path = LSM:Fetch("font", fname)
                if path and path ~= "" then
                    local info = UIDropDownMenu_CreateInfo()
                    info.text = fname ; info.value = fname
                    info.checked = (fname == currentFont)
                    info.func = function()
                        UIDropDownMenu_SetText(ddFont, fname)
                        UIDropDownMenu_SetSelectedValue(ddFont, fname)
                        rOrbsDB["font_"..cfgKey] = fname
                        ns.GUI:ApplyFont(cfgKey, path)
                        ns.GUI:ChatMsg(string.format(L["CHAT_FONT_CHANGED"], fname))
                    end
                    UIDropDownMenu_AddButton(info, level)
                end
            end
        end)
        do
            local fname = rOrbsDB and rOrbsDB["font_"..cfgKey] or "Enchanted Land"
            UIDropDownMenu_SetText(ddFont, fname)
            UIDropDownMenu_SetSelectedValue(ddFont, fname)
        end
        yo = yo - 56

        local sFontSize, _ = MakeSlider(pane, L["ORB_FONT_SCALE"], L["ORB_FONT_SCALE_TIP"],
            8, 48, 1, PAD, yo, SLIDER_W)
        SetSliderHandler(sFontSize, function(self, val)
            rOrbsDB["fontSize_"..cfgKey] = val
            ns.GUI:ApplyFontSize(cfgKey, val)
        end)
        Panel[key].sFontSize = sFontSize
        yo = yo - 52

        local sAlpha, _ = MakeSlider(pane, L["ORB_TEXT_ALPHA"], L["ORB_TEXT_ALPHA_TIP"],
            0, 1, 0.05, PAD, yo, SLIDER_W)
        SetSliderHandler(sAlpha, function(self, val)
            rOrbsDB["textAlpha_"..cfgKey] = val
            ns.GUI:ApplyTextAlpha(cfgKey, val)
        end)
        Panel[key].sAlpha = sAlpha
        yo = yo - 52

        yo = yo - 16
        -- TEXT COLOR ---------------------------------------------------------
        local tcLabel = pane:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
        tcLabel:SetPoint("TOPLEFT", pane, "TOPLEFT", PAD, yo)
        tcLabel:SetText("Text Color")
        tcLabel:SetTextColor(C.text[1], C.text[2], C.text[3])
        yo = yo - 22

        local initTC = { r=1, g=1, b=1 }
        if rOrbsDB and rOrbsDB["textColor_"..cfgKey] then
            initTC = rOrbsDB["textColor_"..cfgKey]
        end

        local tcSwatch
        tcSwatch = MakeColorSwatch(pane, PAD, yo,
            initTC.r, initTC.g, initTC.b, "Click to change text color",
            function()
                local cur = rOrbsDB["textColor_"..cfgKey] or {r=1,g=1,b=1}
                ns.ColorPickerHelper:Open({
                    r = cur.r, g = cur.g, b = cur.b,
                    onChange = function(r, g, b)
                        rOrbsDB["textColor_"..cfgKey] = {r=r, g=g, b=b}
                        tcSwatch:SetColor(r, g, b)
                        ns.GUI:ApplyTextColor(cfgKey, r, g, b)
                    end,
                    onCancel = function()
                        local c = rOrbsDB["textColor_"..cfgKey] or {r=1,g=1,b=1}
                        ns.GUI:ApplyTextColor(cfgKey, c.r, c.g, c.b)
                    end,
                })
            end)
        Panel[key].tcSwatch = tcSwatch

        yo = yo - 32

        local resetTcBtn = MakeButton(pane, "Reset Color", PAD, yo, 100, 22, 0.20, 0.06, 0.04)
        resetTcBtn:SetScript("OnClick", function()
            rOrbsDB["textColor_"..cfgKey] = {r=1, g=1, b=1}
            tcSwatch:SetColor(1, 1, 1)
            ns.GUI:ApplyTextColor(cfgKey, 1, 1, 1)
            ns.GUI:ChatMsg("|cffffff00Text color|r reset to default.")
        end)
        yo = yo - 36

        -- NAME SETTINGS (Target, ToT and Pet Orb tabs) --------------------
        if cfgKey == "TARGET_HEALTH" or cfgKey == "TOT_HEALTH" or cfgKey == "PET_HEALTH" then
            local nkShow  = (cfgKey == "TARGET_HEALTH") and "targetNameShow"       or (cfgKey == "TOT_HEALTH") and "totNameShow"     or "petNameShow"
            local nkFSize = (cfgKey == "TARGET_HEALTH") and "targetNameFontSize"   or (cfgKey == "TOT_HEALTH") and "totNameFontSize" or "petNameFontSize"
            local nkPos   = (cfgKey == "TARGET_HEALTH") and "targetNamePos"        or (cfgKey == "TOT_HEALTH") and "totNamePos"      or "petNamePos"
            local nkCC    = (cfgKey == "TARGET_HEALTH") and "targetNameClassColor" or (cfgKey == "TOT_HEALTH") and "totNameClassColor" or nil
            local nkOffY  = (cfgKey == "TARGET_HEALTH") and "targetNameOffY"       or (cfgKey == "TOT_HEALTH") and "totNameOffY"     or "petNameOffY"
            yo = yo - 20   -- extra gap before Target Name header

            MakeSectionHeader(pane, L["TARGET_NAME_SHOW"], PAD, yo, false, function()
                rOrbsDB[nkShow]  = nil ; rOrbsDB[nkFSize] = nil
                rOrbsDB[nkPos]   = nil ; rOrbsDB[nkOffY]  = nil
                if nkCC then rOrbsDB[nkCC] = nil end
                ns.GUI:ApplyNameShow(cfgKey, true)
                ns.GUI:ChatMsg("|cffffff00" .. L["TARGET_NAME_SHOW"] .. "|r reset.")
                ns.GUI:RefreshValues()
            end) ; yo = yo - 44

            local cbTargetName = MakeCheckbox(pane, L["TARGET_NAME_SHOW"], L["TARGET_NAME_SHOW_TIP"], PAD, yo)
            cbTargetName:SetScript("OnClick", function(self)
                local val = self:GetChecked()
                rOrbsDB[nkShow] = val
                ns.GUI:ApplyNameShow(cfgKey, val)
                ns.GUI:ChatMsg(val and "|cffffff00Name|r |cff00ff00shown|r." or "|cffffff00Name|r |cffff4444hidden|r.")
            end)
            cbTargetName:SetChecked(rOrbsDB[nkShow] ~= false)
            Panel[key].cbTargetName = cbTargetName
            yo = yo - 50

            -- POSITION DROPDOWN (above / below) --------------------------------
            local ddNamePos, _ = MakeDropdown(pane, L["TARGET_NAME_POS"],
                L["TARGET_NAME_POS_TIP"],
                PAD, yo, 180)
            Panel[key].ddNamePos = ddNamePos
            UIDropDownMenu_Initialize(ddNamePos, function(self, level)
                local currentPos = rOrbsDB[nkPos] or "TOP"
                local positions = {
                    { text = L["TARGET_NAME_POS_ABOVE"], value = "TOP"    },
                    { text = L["TARGET_NAME_POS_BELOW"], value = "BOTTOM" },
                }
                for _, entry in ipairs(positions) do
                    local info = UIDropDownMenu_CreateInfo()
                    info.text    = entry.text
                    info.value   = entry.value
                    info.checked = (entry.value == currentPos)
                    info.func    = function()
                        UIDropDownMenu_SetSelectedValue(ddNamePos, entry.value)
                        UIDropDownMenu_SetText(ddNamePos, entry.text)
                        rOrbsDB[nkPos] = entry.value
                        ns.GUI:ApplyNamePos(cfgKey, entry.value, rOrbsDB[nkOffY] or -8)
                    end
                    UIDropDownMenu_AddButton(info, level)
                end
            end)
            do
                local pos = rOrbsDB[nkPos] or "TOP"
                local posLabels = { TOP = L["TARGET_NAME_POS_ABOVE"], BOTTOM = L["TARGET_NAME_POS_BELOW"] }
                UIDropDownMenu_SetText(ddNamePos, posLabels[pos] or "Above orb")
                UIDropDownMenu_SetSelectedValue(ddNamePos, pos)
            end
            yo = yo - 56

            -- CLASS COLOR CHECKBOX (Target/ToT only, not Pet) ------------------
            if nkCC then
            local cbNameClass = MakeCheckbox(pane, L["TARGET_NAME_CLASSCOLOR"],
                L["TARGET_NAME_CLASSCOLOR_TIP"],
                PAD, yo)
            cbNameClass:SetScript("OnClick", function(self)
                local val = self:GetChecked()
                StaticPopupDialogs["RORBS_RELOAD_NAME_CLASSCOLOR"] = {
                    text = L["ORB_RELOAD_CLASSCOLOR"],
                    button1 = L["ORB_RELOAD_ENABLE_BTN"],
                    button2 = CANCEL,
                    OnAccept = function()
                        rOrbsDB[nkCC] = val
                        ReloadUI()
                    end,
                    OnCancel = function()
                        cbNameClass:SetChecked(rOrbsDB[nkCC] == true)
                    end,
                    timeout = 0, whileDead = true, hideOnEscape = false,
                }
                StaticPopup_Show("RORBS_RELOAD_NAME_CLASSCOLOR")
            end)
            cbNameClass:SetChecked(rOrbsDB[nkCC] == true)
            Panel[key].cbNameClass = cbNameClass
            yo = yo - 50
            end

            -- FONT SIZE SLIDER --------------------------------------------------
            local sNameSize, _ = MakeSlider(pane, L["TARGET_NAME_FONTSIZE"],
                L["TARGET_NAME_FONTSIZE_TIP"],
                10, 40, 1, PAD, yo, SLIDER_W)
            sNameSize:SetValue(rOrbsDB[nkFSize] or 22)
            SetSliderHandler(sNameSize, function(self, val)
                rOrbsDB[nkFSize] = val
                ns.GUI:ApplyNameFontSize(cfgKey, val)
            end)
            Panel[key].sNameSize = sNameSize
            yo = yo - 52

            -- Y-OFFSET SLIDER ---------------------------------------------------
            local sNameOffY, _ = MakeSlider(pane, L["TARGET_NAME_OFFY"],
                L["TARGET_NAME_OFFY_TIP"],
                -40, 40, 1, PAD, yo, SLIDER_W)
            sNameOffY:SetValue(rOrbsDB[nkOffY] or -8)
            SetSliderHandler(sNameOffY, function(self, val)
                rOrbsDB[nkOffY] = val
                ns.GUI:ApplyNamePos(cfgKey, rOrbsDB[nkPos] or "TOP", val)
            end)
            Panel[key].sNameOffY = sNameOffY
            yo = yo - 52
        end

        -- RESET PANE --------------------------------------------------------
        if isMulti and paneReset then SwitchPane(paneReset) end

        local resetTabLabel
        if cfgKey == "HEALTH" then resetTabLabel = L["RESET_HEALTH_ORB"]
        elseif cfgKey == "POWER" then resetTabLabel = L["RESET_POWER_ORB"]
        elseif cfgKey == "TOT_HEALTH" then resetTabLabel = L["RESET_TOT_ORB"]
        elseif cfgKey == "PET_HEALTH" then resetTabLabel = L["RESET_PET_ORB"]
        else resetTabLabel = L["RESET_TARGET_ORB"] end

        yo = yo - 16
        MakeSectionHeader(pane, L["RESET_SECTION"], PAD, yo, true) ; yo = yo - 44

        local resetTabBtn = MakeButton(pane, resetTabLabel, PAD, yo, 180, 28, 0.40, 0.05, 0.03)
        resetTabBtn:SetScript("OnClick", function()
            StaticPopupDialogs["RORBS_RESET_ORB_"..cfgKey] = {
                text = "Reset all "..resetTabLabel.." settings to defaults?",
                button1 = OKAY,
                button2 = CANCEL,
                OnAccept = function()
                    -- Restore defaults for this cfgKey from db
                    if ns.db then
                        local defaults = ns.db:GetOrbDefaults()
                        if defaults and defaults[cfgKey] then
                            -- Deep copy defaults into current db
                            local function deepcopy(orig)
                                local copy = {}
                                for k, v in pairs(orig) do
                                    if type(v) == "table" then copy[k] = deepcopy(v)
                                    else copy[k] = v end
                                end
                                return copy
                            end
                            ns.db.char[cfgKey] = deepcopy(defaults[cfgKey])
                            if rOrbs_DB_CHAR then rOrbs_DB_CHAR[cfgKey] = deepcopy(defaults[cfgKey]) end
                        end
                    end
                    -- Reset rOrbsDB keys for this cfgKey
                    rOrbsDB["textShow_"..cfgKey]  = nil
                    rOrbsDB["textAlpha_"..cfgKey] = nil
                    rOrbsDB["textColor_"..cfgKey] = nil
                    rOrbsDB["font_"..cfgKey]       = nil
                    rOrbsDB["fontSize_"..cfgKey]   = nil
                    rOrbsDB["textMode_"..cfgKey]   = nil
                    rOrbsDB["textHover_"..cfgKey]  = nil
                    rOrbsDB["orbTemplate_"..cfgKey] = nil
                    -- Reset name settings for target/tot
                    if cfgKey == "TARGET_HEALTH" then
                        rOrbsDB["targetNameShow"]       = nil
                        rOrbsDB["targetNameFontSize"]   = nil
                        rOrbsDB["targetNamePos"]        = nil
                        rOrbsDB["targetNameClassColor"] = nil
                        rOrbsDB["targetNameOffY"]       = nil
                        rOrbsDB["targetClassColor"]     = nil
                        rOrbsDB["targetRingShow"]       = nil
                    elseif cfgKey == "TOT_HEALTH" then
                        rOrbsDB["totNameShow"]       = nil
                        rOrbsDB["totNameFontSize"]   = nil
                        rOrbsDB["totNamePos"]        = nil
                        rOrbsDB["totNameClassColor"] = nil
                        rOrbsDB["totNameOffY"]       = nil
                        rOrbsDB["totClassColor"]     = nil
                        rOrbsDB["totRingShow"]       = nil
                        rOrbsDB["totRingRotate"]     = nil
                        rOrbsDB["totRingSpeed"]      = nil
                        rOrbsDB["totRingDir"]        = nil
                    elseif cfgKey == "PET_HEALTH" then
                        rOrbsDB["petNameShow"]     = nil
                        rOrbsDB["petNameFontSize"] = nil
                        rOrbsDB["petNamePos"]      = nil
                        rOrbsDB["petNameOffY"]     = nil
                        rOrbsDB["petRingShow"]     = nil
                        rOrbsDB["petRingRotate"]   = nil
                        rOrbsDB["petRingSpeed"]    = nil
                        rOrbsDB["petRingDir"]      = nil
                    end
                    rOrbsDB["tmplShow_"..cfgKey]    = nil
                    rOrbsDB["tmplScale_"..cfgKey]   = nil
                    rOrbsDB["tmplOffsetX_"..cfgKey] = nil
                    rOrbsDB["tmplOffsetZ_"..cfgKey] = nil
                    rOrbsDB["tmplOpacity_"..cfgKey] = nil
                    if ns.OrbTemplateEngine then
                        ns.OrbTemplateEngine:Apply(cfgKey, "none", false)
                    end
                    ns.GUI:ChatMsg("|cffffff00"..resetTabLabel.."|r reset to defaults.")
                    ns.GUI:RefreshValues()
                end,
                timeout = 0, whileDead = true, hideOnEscape = true,
            }
            StaticPopup_Show("RORBS_RESET_ORB_"..cfgKey)
        end)
        FinishOrbTab()  -- baut SubNav, erst danach Wire-Up möglich
        -- Wire-Up: _resetAction im Sub-Nav Reset-Button direkt mit dem Dialog verbinden
        if isMulti and Panel.subNavButtons[key] then
            for _, bd in ipairs(Panel.subNavButtons[key]) do
                if bd.key == "reset" then
                    bd._resetAction = function()
                        resetTabBtn:GetScript("OnClick")(resetTabBtn)
                    end
                    break
                end
            end
        end
    end

    BuildOrbTab("health", "HEALTH")
    BuildOrbTab("power",  "POWER")
    BuildOrbTab("target", "TARGET_HEALTH")
    BuildOrbTab("tot",    "TOT_HEALTH")
    BuildOrbTab("pet",    "PET_HEALTH")

    -- ============================================================
    --  TAB: PARTY FRAMES  (mit Sub-Tabs: Layout / Schrift / Icons / Reset)
    -- ============================================================
    do
        -- Sub-Panes anlegen
        local pLayout   = BuildSubPane("party.layout")
        local pFont     = BuildSubPane("party.font")
        local pIcons    = BuildSubPane("party.icons")
        local pReset    = BuildSubPane("party.reset")

        Panel.party = Panel.party or {}

        -- Hilfsfunktion: aktiven Pane wechseln + yo-Stand merken/wiederherstellen
        local curPane  = pLayout
        local paneYo   = {}  -- yo pro Pane
        local function SwitchPartyPane(newPane)
            if curPane then
                paneYo[curPane] = yo
                curPane._contentH = math.abs(yo) + PAD + 40
                if curPane._updateThumb then curPane._updateThumb() end
            end
            curPane = newPane
            yo = paneYo[newPane] or -PAD
        end

        -- ════════════════════════════════════════════════════════
        --  PANE: LAYOUT
        -- ════════════════════════════════════════════════════════
        local p = pLayout
        yo = -PAD

        MakeSectionHeader(p, L["TAB_PARTY"], PAD, yo, true, function()
            rOrbsDB.partyEnabled = nil
            if ns.RespawnParty then ns.RespawnParty() end
            ns.GUI:ChatMsg("|cffffff00" .. L["TAB_PARTY"] .. "|r reset.")
            ns.GUI:RefreshValues()
        end)
        yo = yo - 44

        local cbEnable = MakeCheckbox(p,
            L["PARTY_ENABLE"],
            L["PARTY_ENABLE_TIP"],
            PAD, yo)
        cbEnable:SetChecked(rOrbsDB.partyEnabled ~= false)
        cbEnable:SetScript("OnClick", function(self)
            local val = self:GetChecked()
            StaticPopupDialogs["RORBS_RELOAD_PARTY_ENABLE"] = {
                text = string.format(L["PARTY_RELOAD_TEXT"], val and L["PARTY_RELOAD_ON"] or L["PARTY_RELOAD_OFF"]),
                button1 = L["ORB_RELOAD_ENABLE_BTN"],
                button2 = CANCEL,
                OnAccept = function()
                    rOrbsDB.partyEnabled = val
                    ReloadUI()
                end,
                OnCancel = function()
                    cbEnable:SetChecked(rOrbsDB.partyEnabled ~= false)
                end,
                timeout = 0, whileDead = true, hideOnEscape = false,
            }
            StaticPopup_Show("RORBS_RELOAD_PARTY_ENABLE")
        end)
        Panel.party.cbEnable = cbEnable
        yo = yo - 50

        -- ── TRENNER ─────────────────────────────────────────────
        yo = yo - 10
        MakeSep(p, yo) ; yo = yo - 20

        -- ---- Layout H/V ----------------------------------------
        MakeSectionHeader(p, L["PARTY_LAYOUT"], PAD, yo, true, function()
            rOrbsDB.partyVertical = nil
            if ns.RespawnParty then ns.RespawnParty() end
            ns.GUI:ChatMsg("|cffffff00" .. L["PARTY_LAYOUT"] .. "|r reset.")
            ns.GUI:RefreshValues()
        end)
        yo = yo - 34
        local cbH = MakeCheckbox(p, L["PARTY_LAYOUT_H"], "", PAD,       yo)
        local cbV = MakeCheckbox(p, L["PARTY_LAYOUT_V"], "", PAD + 160, yo)
        yo = yo - 38

        local function SyncLayout()
            local isV = rOrbsDB.partyVertical == true
            cbH:SetChecked(not isV)
            cbV:SetChecked(isV)
        end
        SyncLayout()
        cbH:SetScript("OnClick", function()
            rOrbsDB.partyVertical = false
            if ns.cfg and ns.cfg.units and ns.cfg.units.party then
                ns.cfg.units.party.vertical = false
            end
            SyncLayout()
            if ns.RespawnParty then ns.RespawnParty() end
        end)
        cbV:SetScript("OnClick", function()
            rOrbsDB.partyVertical = true
            if ns.cfg and ns.cfg.units and ns.cfg.units.party then
                ns.cfg.units.party.vertical = true
            end
            SyncLayout()
            if ns.RespawnParty then ns.RespawnParty() end
        end)

        -- ── TRENNER ─────────────────────────────────────────────
        yo = yo - 10
        MakeSep(p, yo) ; yo = yo - 20

        -- ---- Visibility Options --------------------------------
        MakeSectionHeader(p, L["PARTY_VISIBILITY"], PAD, yo, true, function()
            rOrbsDB.partyHideInRaid  = nil
            rOrbsDB.partyShowPlayer  = nil
            rOrbsDB.partyHideArena   = nil
            if ns.RespawnParty then ns.RespawnParty() end
            ns.GUI:ChatMsg("|cffffff00" .. L["PARTY_VISIBILITY"] .. "|r reset.")
            ns.GUI:RefreshValues()
        end)
        yo = yo - 34

        local cbHideRaid = MakeCheckbox(p,
            L["PARTY_HIDE_RAID"],
            L["PARTY_HIDE_RAID_TIP"],
            PAD, yo)
        cbHideRaid:SetChecked(rOrbsDB.partyHideInRaid ~= false)
        cbHideRaid:SetScript("OnClick", function(self)
            local val = self:GetChecked()
            rOrbsDB.partyHideInRaid = val
            if ns.cfg and ns.cfg.units and ns.cfg.units.party then
                ns.cfg.units.party.attributes.visibility =
                    val and "[@raid6,exists] hide; show" or "show"
            end
            if ns.RespawnParty then ns.RespawnParty() end
        end)
        Panel.party.cbHideRaid = cbHideRaid
        yo = yo - 38

        local cbShowPlayer = MakeCheckbox(p,
            L["PARTY_SHOW_MYSELF"],
            L["PARTY_SHOW_MYSELF_TIP"],
            PAD, yo)
        cbShowPlayer:SetChecked(rOrbsDB.partyShowPlayer == true)
        cbShowPlayer:SetScript("OnClick", function(self)
            local val = self:GetChecked()
            rOrbsDB.partyShowPlayer = val
            if ns.cfg and ns.cfg.units and ns.cfg.units.party then
                ns.cfg.units.party.attributes.showPlayer = val
            end
            if ns.RespawnParty then ns.RespawnParty() end
        end)
        Panel.party.cbShowPlayer = cbShowPlayer
        yo = yo - 38

        -- ── TRENNER ─────────────────────────────────────────────
        yo = yo - 10
        MakeSep(p, yo) ; yo = yo - 20

        -- ---- Portrait Mode -------------------------------------
        MakeSectionHeader(p, L["PARTY_PORTRAIT"], PAD, yo, true, function()
            rOrbsDB.partyPortrait3D = nil
            if ns.RespawnParty then ns.RespawnParty() end
            ns.GUI:ChatMsg("|cffffff00" .. L["PARTY_PORTRAIT"] .. "|r reset.")
            ns.GUI:RefreshValues()
        end)
        yo = yo - 34

        local cb3D = MakeCheckbox(p, "3D", "", PAD,       yo)
        local cb2D = MakeCheckbox(p, "2D", "", PAD + 80,  yo)
        yo = yo - 38

        local function SyncPortraitMode()
            local is3D = (rOrbsDB.partyPortrait3D == true)
            cb3D:SetChecked(is3D)
            cb2D:SetChecked(not is3D)
        end
        SyncPortraitMode()
        cb3D:SetScript("OnClick", function()
            rOrbsDB.partyPortrait3D = true
            SyncPortraitMode()
            if ns.RespawnParty then ns.RespawnParty() end
        end)
        cb2D:SetScript("OnClick", function()
            rOrbsDB.partyPortrait3D = false
            SyncPortraitMode()
            if ns.RespawnParty then ns.RespawnParty() end
        end)

        -- ── TRENNER ─────────────────────────────────────────────
        yo = yo - 10
        MakeSep(p, yo) ; yo = yo - 20

        -- ---- Frame Gap -----------------------------------------
        MakeSectionHeader(p, L["PARTY_GAP"], PAD, yo, true, function()
            rOrbsDB.partyGapH = nil ; rOrbsDB.partyGapV = nil
            if ns.RespawnParty then ns.RespawnParty() end
            ns.GUI:ChatMsg("|cffffff00" .. L["PARTY_GAP"] .. "|r reset.")
            ns.GUI:RefreshValues()
        end)
        yo = yo - 34

        local sGapH, _ = MakeSlider(p,
            L["PARTY_GAP_H"], L["PARTY_GAP_H_TIP"],
            0, 120, 1, PAD, yo, 200)
        sGapH:SetValue((rOrbsDB and rOrbsDB.partyGapH) or 40)
        SetSliderHandler(sGapH, function(self, val)
            rOrbsDB.partyGapH = val
            if ns.RespawnParty then ns.RespawnParty() end
        end)
        yo = yo - 52

        local sGapV, _ = MakeSlider(p,
            L["PARTY_GAP_V"], L["PARTY_GAP_V_TIP"],
            0, 120, 1, PAD, yo, 200)
        sGapV:SetValue((rOrbsDB and rOrbsDB.partyGapV) or 40)
        SetSliderHandler(sGapV, function(self, val)
            rOrbsDB.partyGapV = val
            if ns.RespawnParty then ns.RespawnParty() end
        end)
        yo = yo - 52

        -- ── TRENNER ─────────────────────────────────────────────
        yo = yo - 10
        MakeSep(p, yo) ; yo = yo - 20

        -- ---- HP Bar --------------------------------------------
        MakeSectionHeader(p, L["PARTY_HP_BAR"], PAD, yo, true, function()
            rOrbsDB.partyHpClassColor  = nil
            rOrbsDB.partyHpTextMode    = nil
            rOrbsDB.partyHpTextFormat  = nil
            if ns.RespawnParty then ns.RespawnParty() end
            ns.GUI:ChatMsg("|cffffff00" .. L["PARTY_HP_BAR"] .. "|r reset.")
            ns.GUI:RefreshValues()
        end)
        yo = yo - 34

        local cbClassColor = MakeCheckbox(p,
            L["PARTY_HP_CLASS_COLOR"], L["PARTY_HP_CLASS_COLOR_TIP"],
            PAD, yo)
        cbClassColor:SetChecked(rOrbsDB.partyHpClassColor == true)
        cbClassColor:SetScript("OnClick", function(self)
            rOrbsDB.partyHpClassColor = self:GetChecked() or false
        end)
        yo = yo - 38

        local txtModeLabel = p:CreateFontString(nil, "OVERLAY")
        txtModeLabel:SetFont("Fonts\\FRIZQT__.TTF", 11, "OUTLINE")
        txtModeLabel:SetTextColor(0.88, 0.88, 0.88, 1)
        txtModeLabel:SetText(L["PARTY_HP_TEXT_LABEL"])
        txtModeLabel:SetPoint("TOPLEFT", p, "TOPLEFT", PAD, yo)
        txtModeLabel:SetSize(100, 20)
        yo = yo - 24

        local cbTxtNever  = MakeCheckbox(p, L["PARTY_HP_OFF"],       "", PAD,       yo)
        local cbTxtAlways = MakeCheckbox(p, L["PARTY_HP_ALWAYS"],    "", PAD + 80,  yo)
        local cbTxtHover  = MakeCheckbox(p, L["PARTY_HP_MOUSEOVER"], "", PAD + 175, yo)
        yo = yo - 34

        local function SyncHpTextMode()
            local m = (rOrbsDB and rOrbsDB.partyHpTextMode) or "never"
            cbTxtNever:SetChecked(m == "never")
            cbTxtAlways:SetChecked(m == "always")
            cbTxtHover:SetChecked(m == "mouseover")
        end
        SyncHpTextMode()

        local function ShowHpModeReloadDialog(newMode)
            local modeLabel = newMode == "never" and L["PARTY_HP_OFF"] or L["PARTY_HP_ALWAYS"]
            StaticPopupDialogs["RORBS_HPMODE_RELOAD"] = {
                text = string.format(L["PARTY_HPMODE_RELOAD_TEXT"], modeLabel),
                button1 = L["ORB_RELOAD_ENABLE_BTN"],
                button2 = CANCEL,
                OnAccept = function()
                    rOrbsDB.partyHpTextMode = newMode
                    ReloadUI()
                end,
                OnCancel = function()
                    SyncHpTextMode()
                end,
                timeout = 0, whileDead = true, hideOnEscape = false,
            }
            StaticPopup_Show("RORBS_HPMODE_RELOAD")
        end

        cbTxtNever:SetScript("OnClick",  function() ShowHpModeReloadDialog("never")  end)
        cbTxtAlways:SetScript("OnClick", function() ShowHpModeReloadDialog("always") end)
        cbTxtHover:SetScript("OnClick",  function()
            rOrbsDB.partyHpTextMode = "mouseover"
            SyncHpTextMode()
        end)

        yo = yo - 10
        local fmtLabel = p:CreateFontString(nil, "OVERLAY", "GameFontNormal")
        fmtLabel:SetText(L["PARTY_HP_FORMAT"])
        fmtLabel:SetPoint("TOPLEFT", p, "TOPLEFT", PAD, yo)
        fmtLabel:SetSize(100, 20)
        yo = yo - 24

        local cbFmtVal = MakeCheckbox(p, L["PARTY_HP_FORMAT_VAL"], "", PAD,      yo)
        local cbFmtPct = MakeCheckbox(p, "%",                       "", PAD + 80, yo)
        yo = yo - 34

        local function SyncFmtMode()
            local f = (rOrbsDB and rOrbsDB.partyHpTextFormat) or "value"
            cbFmtVal:SetChecked(f == "value")
            cbFmtPct:SetChecked(f == "percent")
        end
        SyncFmtMode()
        cbFmtVal:SetScript("OnClick", function()
            rOrbsDB.partyHpTextFormat = "value"
            SyncFmtMode()
        end)
        cbFmtPct:SetScript("OnClick", function()
            rOrbsDB.partyHpTextFormat = "percent"
            SyncFmtMode()
        end)

        -- contentH für Layout-Pane setzen
        pLayout._contentH = math.abs(yo) + PAD + 40
        if pLayout._updateThumb then pLayout._updateThumb() end

        -- ════════════════════════════════════════════════════════
        --  PANE: SCHRIFT
        -- ════════════════════════════════════════════════════════
        p = pFont
        yo = -PAD

        -- ---- Name Font Settings --------------------------------
        MakeSectionHeader(p, L["PARTY_NAME_SECTION"], PAD, yo, true, function()
            rOrbsDB.partyNameFontSize = nil ; rOrbsDB.partyNameFont = nil
            if ns.RespawnParty then ns.RespawnParty() end
            ns.GUI:ChatMsg("|cffffff00" .. L["PARTY_NAME_SECTION"] .. "|r reset.")
            ns.GUI:RefreshValues()
        end)
        yo = yo - 34

        local sNameSize, _ = MakeSlider(p,
            L["PARTY_NAME_FONT_SIZE"], L["PARTY_NAME_FONT_SIZE_TIP"],
            8, 32, 1, PAD, yo, 200)
        sNameSize:SetValue((rOrbsDB and rOrbsDB.partyNameFontSize) or 16)
        SetSliderHandler(sNameSize, function(self, val)
            rOrbsDB.partyNameFontSize = val
            if ns.RespawnParty then ns.RespawnParty() end
        end)
        yo = yo - 52

        local ddNameFont, _ = MakeDropdown(p,
            L["PARTY_NAME_FONT"], L["PARTY_NAME_FONT_TIP"],
            PAD, yo, 220)
        Panel.party.ddNameFont = ddNameFont
        UIDropDownMenu_Initialize(ddNameFont, function(self, level)
            local fonts = LSM:List("font")
            if not fonts then return end
            local currentFont = (rOrbsDB and rOrbsDB.partyNameFont) or "Enchanted Land"
            for _, fname in ipairs(fonts) do
                local fpath = LSM:Fetch("font", fname)
                if fpath and fpath ~= "" then
                    local info = UIDropDownMenu_CreateInfo()
                    info.text    = fname
                    info.value   = fname
                    info.checked = (fname == currentFont)
                    info.func    = function()
                        UIDropDownMenu_SetText(ddNameFont, fname)
                        UIDropDownMenu_SetSelectedValue(ddNameFont, fname)
                        rOrbsDB.partyNameFont = fname
                        if ns.RespawnParty then ns.RespawnParty() end
                    end
                    UIDropDownMenu_AddButton(info, level)
                end
            end
        end)
        do
            local fname = (rOrbsDB and rOrbsDB.partyNameFont) or "Enchanted Land"
            UIDropDownMenu_SetText(ddNameFont, fname)
            UIDropDownMenu_SetSelectedValue(ddNameFont, fname)
        end
        yo = yo - 56

        -- ── TRENNER ─────────────────────────────────────────────
        yo = yo - 10
        MakeSep(p, yo) ; yo = yo - 20

        -- ---- HP Text Font Size ---------------------------------
        MakeSectionHeader(p, L["PARTY_HP_TEXT_SECTION"], PAD, yo, true, function()
            rOrbsDB.partyHpFontSize   = nil
            rOrbsDB.partyHpTextMode   = nil
            rOrbsDB.partyHpTextFormat = nil
            if ns.RespawnParty then ns.RespawnParty() end
            ns.GUI:ChatMsg("|cffffff00" .. L["PARTY_HP_TEXT_SECTION"] .. "|r reset.")
            ns.GUI:RefreshValues()
        end)
        yo = yo - 34

        local sHpFontSize, _ = MakeSlider(p,
            L["PARTY_HP_FONT_SIZE"], L["PARTY_HP_FONT_SIZE_TIP"],
            6, 28, 1, PAD, yo, 200)
        sHpFontSize:SetValue((rOrbsDB and rOrbsDB.partyHpFontSize) or 9)
        SetSliderHandler(sHpFontSize, function(self, val)
            rOrbsDB.partyHpFontSize = val
            if ns.ApplyPartyHpFontSize then ns.ApplyPartyHpFontSize(val) end
        end)
        Panel.party.sHpFontSize = sHpFontSize
        yo = yo - 52

        -- ── TRENNER ─────────────────────────────────────────────
        yo = yo - 10
        MakeSep(p, yo) ; yo = yo - 20

        -- ---- Lower Part (HP/Power/Artwork) ---------------------
        MakeSectionHeader(p, L["PARTY_LOWER_PART"], PAD, yo, true, function()
            rOrbsDB.partyLowerMode  = nil
            rOrbsDB.partyLowerScale = nil
            if ns.RespawnParty then ns.RespawnParty() end
            ns.GUI:ChatMsg("|cffffff00" .. L["PARTY_LOWER_PART"] .. "|r reset.")
            ns.GUI:RefreshValues()
        end)
        yo = yo - 34

        local lpModeLabel = p:CreateFontString(nil, "OVERLAY", "GameFontNormal")
        lpModeLabel:SetText(L["PARTY_VISIBILITY_LABEL"])
        lpModeLabel:SetPoint("TOPLEFT", p, "TOPLEFT", PAD, yo)
        lpModeLabel:SetSize(120, 20)
        yo = yo - 24

        local cbLpShow      = MakeCheckbox(p, L["PARTY_LP_SHOW"],      "", PAD,       yo)
        local cbLpHide      = MakeCheckbox(p, L["PARTY_LP_HIDE"],      "", PAD + 80,  yo)
        local cbLpMouseover = MakeCheckbox(p, L["PARTY_LP_MOUSEOVER"], "", PAD + 175, yo)
        yo = yo - 34

        local function SyncLpMode()
            local m = (rOrbsDB and rOrbsDB.partyLowerMode) or "show"
            cbLpShow:SetChecked(m == "show")
            cbLpHide:SetChecked(m == "hide")
            cbLpMouseover:SetChecked(m == "mouseover")
        end
        SyncLpMode()

        local function SetLpMode(mode)
            rOrbsDB.partyLowerMode = mode
            SyncLpMode()
            if ns.ApplyPartyLowerMode then ns.ApplyPartyLowerMode(mode) end
        end
        cbLpShow:SetScript("OnClick",      function() SetLpMode("show")      end)
        cbLpHide:SetScript("OnClick",      function() SetLpMode("hide")      end)
        cbLpMouseover:SetScript("OnClick", function() SetLpMode("mouseover") end)
        Panel.party.cbLpShow      = cbLpShow
        Panel.party.cbLpHide      = cbLpHide
        Panel.party.cbLpMouseover = cbLpMouseover

        yo = yo - 10
        local sLowerScale, _ = MakeSlider(p,
            L["PARTY_LOWER_SCALE"], L["PARTY_LOWER_SCALE_TIP"],
            0.5, 2.0, 0.05, PAD, yo, 200)
        sLowerScale:SetValue((rOrbsDB and rOrbsDB.partyLowerScale) or 1.0)
        SetSliderHandler(sLowerScale, function(self, val)
            rOrbsDB.partyLowerScale = val
            if ns.ApplyPartyLowerScale then ns.ApplyPartyLowerScale(val) end
        end)
        Panel.party.sLowerScale = sLowerScale
        yo = yo - 52

        pFont._contentH = math.abs(yo) + PAD + 40
        if pFont._updateThumb then pFont._updateThumb() end

        -- ════════════════════════════════════════════════════════
        --  PANE: ICONS
        -- ════════════════════════════════════════════════════════
        p = pIcons
        yo = -PAD

        MakeSectionHeader(p, L["PARTY_ICONS_SECTION"], PAD, yo, true, function()
            rOrbsDB.partyRaidIconShow   = nil ; rOrbsDB.partyRaidIconSize   = nil
            rOrbsDB.partyRaidIconOffX   = nil ; rOrbsDB.partyRaidIconOffY   = nil
            rOrbsDB.partyRoleIconShow   = nil ; rOrbsDB.partyRoleIconSize   = nil
            rOrbsDB.partyRoleIconOffX   = nil ; rOrbsDB.partyRoleIconOffY   = nil
            rOrbsDB.partyLeaderIconShow  = nil ; rOrbsDB.partyLeaderIconSize  = nil
            rOrbsDB.partyLeaderIconOffX  = nil ; rOrbsDB.partyLeaderIconOffY  = nil
            if ns.RespawnParty then ns.RespawnParty() end
            ns.GUI:ChatMsg("|cffffff00" .. L["PARTY_ICONS_SECTION"] .. "|r reset.")
            ns.GUI:RefreshValues()
        end)
        yo = yo - 34

        local function MakeIconControls(showKey, sizeKey, offXKey, offYKey,
                                        label, defaultSize, defaultOffX, defaultOffY,
                                        wrapperKey)
            local function getShowVal()
                if rOrbsDB and rOrbsDB[showKey] == false then return false end
                return true
            end
            local cb = MakeCheckbox(p, label, L["PARTY_ICON_SHOW_TIP"], PAD, yo)
            cb:SetChecked(getShowVal())
            cb:SetScript("OnClick", function(self)
                local val = self:GetChecked()
                rOrbsDB[showKey] = val
                local function applyToFrame(f)
                    local wrapper = f[wrapperKey]
                    if not wrapper then return end
                    if val then wrapper:Show() else wrapper:Hide() end
                end
                if ns.partyHeader then
                    for _, child in ipairs({ ns.partyHeader:GetChildren() }) do
                        applyToFrame(child)
                    end
                end
                if ns.fakePartyFrames then
                    for _, f in ipairs(ns.fakePartyFrames) do applyToFrame(f) end
                end
            end)
            yo = yo - 30

            local sSize, _ = MakeSlider(p, L["PARTY_ICON_SIZE"], L["PARTY_ICON_SIZE_TIP"],
                6, 48, 1, PAD + 16, yo, 180)
            sSize:SetValue((rOrbsDB and rOrbsDB[sizeKey]) or defaultSize)
            SetSliderHandler(sSize, function(self, val)
                rOrbsDB[sizeKey] = val
                if ns.RespawnParty then ns.RespawnParty() end
            end)
            yo = yo - 46

            local sOffX, _ = MakeSlider(p, L["PARTY_ICON_OFFX"], L["PARTY_ICON_OFFX_TIP"],
                -40, 40, 1, PAD + 16, yo, 180)
            sOffX:SetValue((rOrbsDB and rOrbsDB[offXKey]) or defaultOffX)
            SetSliderHandler(sOffX, function(self, val)
                rOrbsDB[offXKey] = val
                if ns.RespawnParty then ns.RespawnParty() end
            end)
            yo = yo - 46

            local sOffY, _ = MakeSlider(p, L["PARTY_ICON_OFFY"], L["PARTY_ICON_OFFY_TIP"],
                -40, 40, 1, PAD + 16, yo, 180)
            sOffY:SetValue((rOrbsDB and rOrbsDB[offYKey]) or defaultOffY)
            SetSliderHandler(sOffY, function(self, val)
                rOrbsDB[offYKey] = val
                if ns.RespawnParty then ns.RespawnParty() end
            end)
            yo = yo - 46

            do
                local wKey = wrapperKey
                local sKey = showKey
                local prevBtn = MakeButton(p, L["CUSTOM_TMPL_PREVIEW"], PAD + 16, yo, 90, 22, 0.08, 0.18, 0.08)
                prevBtn._prevOn = false
                prevBtn:SetScript("OnClick", function(self)
                    self._prevOn = not self._prevOn
                    local function applyWrap(frame, show)
                        local w = frame[wKey]
                        if w then
                            if show then w:Show() else w:Hide() end
                        end
                    end
                    if self._prevOn then
                        self.fs:SetText(L["CUSTOM_TMPL_STOP"])
                        self.innerTex:SetColorTexture(0.40, 0.05, 0.03, 1)
                        if ns.partyHeader then
                            for _, child in ipairs({ ns.partyHeader:GetChildren() }) do
                                applyWrap(child, true)
                            end
                        end
                    else
                        self.fs:SetText(L["CUSTOM_TMPL_PREVIEW"])
                        self.innerTex:SetColorTexture(0.08, 0.18, 0.08, 1)
                        local showVal = not (rOrbsDB and rOrbsDB[sKey] == false)
                        if ns.partyHeader then
                            for _, child in ipairs({ ns.partyHeader:GetChildren() }) do
                                applyWrap(child, showVal)
                            end
                        end
                    end
                end)
            end
            yo = yo - 32

            return cb, sSize, sOffX, sOffY
        end

        -- ── TRENNER ─────────────────────────────────────────────
        yo = yo - 6
        MakeSep(p, yo) ; yo = yo - 20

        -- Raid Marker Icon
        local cbRaid, sRaidSize, sRaidOffX, sRaidOffY =
            MakeIconControls(
                "partyRaidIconShow", "partyRaidIconSize",
                "partyRaidIconOffX", "partyRaidIconOffY",
                L["PARTY_ICON_RAIDMARKER"],
                18, 4, 4, "_raidIconWrapper")
        Panel.party.cbRaidIconShow = cbRaid
        Panel.party.sRaidIconSize  = sRaidSize
        Panel.party.sRaidIconOffX  = sRaidOffX
        Panel.party.sRaidIconOffY  = sRaidOffY

        -- ── TRENNER ─────────────────────────────────────────────
        yo = yo - 10
        MakeSep(p, yo) ; yo = yo - 20

        -- Role Icon
        local cbRole, sRoleSize, sRoleOffX, sRoleOffY =
            MakeIconControls(
                "partyRoleIconShow", "partyRoleIconSize",
                "partyRoleIconOffX", "partyRoleIconOffY",
                L["PARTY_ICON_ROLE"],
                12, 2, -2, "_roleIconWrapper")
        Panel.party.cbRoleIconShow = cbRole
        Panel.party.sRoleIconSize  = sRoleSize
        Panel.party.sRoleIconOffX  = sRoleOffX
        Panel.party.sRoleIconOffY  = sRoleOffY

        -- ── TRENNER ─────────────────────────────────────────────
        yo = yo - 10
        MakeSep(p, yo) ; yo = yo - 20

        -- Leader Icon
        local cbLeader, sLeaderSize, sLeaderOffX, sLeaderOffY =
            MakeIconControls(
                "partyLeaderIconShow", "partyLeaderIconSize",
                "partyLeaderIconOffX", "partyLeaderIconOffY",
                L["PARTY_ICON_LEADER"],
                13, -2, -2, "_leaderIconWrapper")
        Panel.party.cbLeaderIconShow = cbLeader
        Panel.party.sLeaderIconSize  = sLeaderSize
        Panel.party.sLeaderIconOffX  = sLeaderOffX
        Panel.party.sLeaderIconOffY  = sLeaderOffY

        pIcons._contentH = math.abs(yo) + PAD + 40
        if pIcons._updateThumb then pIcons._updateThumb() end

        -- ════════════════════════════════════════════════════════
        --  PANE: RESET
        -- ════════════════════════════════════════════════════════
        p = pReset
        yo = -PAD

        yo = yo - 16
        MakeSectionHeader(p, L["RESET_SECTION"], PAD, yo, true) ; yo = yo - 44

        local partyResetBtn = MakeButton(p, L["RESET_HEALTH_ORB"]:gsub("Health", "Party") or "Reset Party Frames",
            PAD, yo, 180, 28, 0.40, 0.05, 0.03)
        partyResetBtn:SetScript("OnClick", function()
            StaticPopupDialogs["RORBS_RESET_PARTY"] = {
                text = "Reset all Party Frame settings to defaults?",
                button1 = OKAY,
                button2 = CANCEL,
                OnAccept = function()
                    rOrbsDB.partyEnabled       = nil
                    rOrbsDB.partyVertical       = nil
                    rOrbsDB.partyHideInRaid     = nil
                    rOrbsDB.partyShowPlayer     = nil
                    rOrbsDB.partyHideArena      = nil
                    rOrbsDB.partyPortrait3D     = nil
                    rOrbsDB.partyGapH           = nil
                    rOrbsDB.partyGapV           = nil
                    rOrbsDB.partyHpClassColor   = nil
                    rOrbsDB.partyHpTextMode     = nil
                    rOrbsDB.partyHpTextFormat   = nil
                    rOrbsDB.partyNameFontSize   = nil
                    rOrbsDB.partyNameFont       = nil
                    rOrbsDB.partyHpFontSize     = nil
                    rOrbsDB.partyLowerMode      = nil
                    rOrbsDB.partyLowerScale     = nil
                    rOrbsDB.partyRaidIconShow   = nil ; rOrbsDB.partyRaidIconSize   = nil
                    rOrbsDB.partyRaidIconOffX   = nil ; rOrbsDB.partyRaidIconOffY   = nil
                    rOrbsDB.partyRoleIconShow   = nil ; rOrbsDB.partyRoleIconSize   = nil
                    rOrbsDB.partyRoleIconOffX   = nil ; rOrbsDB.partyRoleIconOffY   = nil
                    rOrbsDB.partyLeaderIconShow  = nil ; rOrbsDB.partyLeaderIconSize  = nil
                    rOrbsDB.partyLeaderIconOffX  = nil ; rOrbsDB.partyLeaderIconOffY  = nil
                    if ns.RespawnParty then ns.RespawnParty() end
                    ns.GUI:ChatMsg("|cffffff00Party Frames|r reset to defaults.")
                    ns.GUI:RefreshValues()
                end,
                timeout = 0, whileDead = true, hideOnEscape = true,
            }
            StaticPopup_Show("RORBS_RESET_PARTY")
        end)
        yo = yo - 40

        pReset._contentH = math.abs(yo) + PAD + 40
        if pReset._updateThumb then pReset._updateThumb() end

        -- ── Sub-Nav aufbauen und Reset wire-up ──────────────────
        Panel.BuildSubNav("party")
        if Panel.subNavButtons["party"] then
            for _, bd in ipairs(Panel.subNavButtons["party"]) do
                if bd.key == "reset" then
                    bd._resetAction = function()
                        partyResetBtn:GetScript("OnClick")(partyResetBtn)
                    end
                    break
                end
            end
        end
    end

    -- ============================================================
    --  TAB: CREDITS / DANKSAGUNG
    -- ============================================================
    -- (Tab "commands" removed – slash commands are now listed in the General tab)

    -- ============================================================
    --  TAB: MODEL VIEWER  (integrated rIngameModelViewer)
    -- ============================================================
    -- This tab embeds a fully interactive WoW model browser that lets
    -- the user browse Display IDs and send them directly to the
    -- Custom Template Creator on any orb tab.
    -- ============================================================

    do
        local mvPane = BuildScrollPane("models")
        -- The model viewer renders directly inside Panel, not inside the
        -- scroll pane child, so we need a fixed-size container anchored to
        -- the scroll pane container frame.
        local mvContainer = contentFrames["models"]   -- the outer container frame

        -- ---- Palette shortcuts (reuse parent C table) ---------------
        local MV = {}   -- model viewer state

        MV.modelSize    = 80           -- px per model cell (smallest = most cells visible)
        MV.page         = 1
        MV.cols         = 0
        MV.rows         = 0
        MV.models       = {}           -- array of PlayerModel frames
        MV.initialized  = false
        MV.filterEmpty  = false    -- hide cells with no model
        MV.emptyCache   = {}       -- [displayId] = true if known empty
        MV.validCache   = {}       -- [displayId] = true if known valid

        -- Cached displayIndex list (sorted desc, same as rIMV)
        -- We reuse the embedded db from rOrbs or build a small inline list.
        -- The full db.lua list from rIngameModelViewer is embedded below.
        MV.displayList  = nil          -- populated lazily

        -- ---- Math helpers ------------------------------------------
        local pi, halfpi = math.pi, math.pi / 2

        local function MV_RoundNumber(n)
            return math.floor(n * 10) / 10
        end

        local function MV_SetOrientation(model, distance, yaw, pitch)
            if not model:HasCustomCamera() then return end
            model._mvDist, model._mvYaw, model._mvPitch = distance, yaw, pitch
            local x = distance * math.cos(yaw) * math.cos(pitch)
            local y = distance * math.sin(-yaw) * math.cos(pitch)
            local z = distance * math.sin(-pitch)
            model:SetCameraPosition(x, y, z)
        end

        local function MV_ResetModel(model)
            model._mvCamDist = 1
            model._mvZoom    = 0
            model._mvRotation = 0
            model:SetPortraitZoom(0)
            model:SetCamDistanceScale(1)
            model:SetPosition(0, 0, 0)
            model:SetRotation(0)
            model:RefreshCamera()
            model:SetCustomCamera(1)
            if model:HasCustomCamera() then
                local x, y, z = model:GetCameraPosition()
                if x ~= 0 then
                    MV_SetOrientation(model, math.sqrt(x*x + y*y + z*z),
                        -math.atan(y / x), -math.atan(z / x))
                end
            end
        end

        -- ---- Mouse handlers ----------------------------------------
        local function MV_CameraOnUpdate(model)
            if not model:HasCustomCamera() then return end
            local x, y = GetCursorPosition()
            local pitch = model._mvPitch + (y - model._mvCY) * pi / 256
            local limit = pitch > halfpi - 0.05 or pitch < -halfpi + 0.05
            if limit then
                local rot = math.abs(math.deg(((x - model._mvCX)/64 + model:GetFacing())) % 360)
                model:SetRotation(math.rad(rot))
                model._mvRotation = rot
            else
                MV_SetOrientation(model, model._mvDist,
                    model._mvYaw + (x - model._mvCX)*pi/256, pitch)
            end
            model._mvCX, model._mvCY = x, y
        end

        local function MV_RotationOnUpdate(model)
            local x, y = GetCursorPosition()
            local rot = math.abs(math.deg(((x - model._mvCX)/84 + model:GetFacing())) % 360)
            model:SetRotation(math.rad(rot))
            model._mvRotation = rot
            model._mvCX, model._mvCY = x, y
        end

        local function MV_PositionOnUpdate(model)
            local x, y = GetCursorPosition()
            local px, py, pz = model:GetPosition()
            if IsShiftKeyDown() then
                model:SetPosition(tonumber(string.format("%.2f", px + (y - model._mvCY)/100)), py, pz)
            else
                model:SetPosition(px,
                    tonumber(string.format("%.2f", py + (x - model._mvCX)/84)),
                    tonumber(string.format("%.2f", pz + (y - model._mvCY)/84)))
            end
            model._mvCX, model._mvCY = x, y
        end

        local function MV_OnMouseDown(model, button)
            if button == "LeftButton" then
                if IsShiftKeyDown() then
                    if model:HasCustomCamera() then
                        model._mvCX, model._mvCY = GetCursorPosition()
                        model:SetScript("OnUpdate", MV_CameraOnUpdate)
                    end
                else
                    -- send Display ID to a Custom Template Creator tab
                    local displayId = model._mvDisplayId
                    if displayId and displayId > 0 then
                        -- Try to set idBox on the currently active orb tab
                        local targetKeys = {"health","power","target","tot"}
                        for _, tk in ipairs(targetKeys) do
                            if Panel[tk] and Panel[tk].idBox then
                                Panel[tk].idBox:SetText(tostring(displayId))
                            end
                        end
                        -- Switch to health tab so user sees it
                        ns.GUI:SetTab("health")
                        ns.GUI:ChatMsg(string.format(L["MODELS_SENT_ID_MSG"], displayId))
                    end
                end
            elseif button == "RightButton" then
                if IsControlKeyDown() then
                    MV_ResetModel(model)
                else
                    model._mvCX, model._mvCY = GetCursorPosition()
                    model:SetScript("OnUpdate", MV_RotationOnUpdate)
                end
            elseif button == "MiddleButton" or button == "Button4" or button == "Button5" then
                model._mvCX, model._mvCY = GetCursorPosition()
                model:SetScript("OnUpdate", MV_PositionOnUpdate)
            end
        end

        local function MV_OnMouseUp(model)
            model:SetScript("OnUpdate", nil)
        end

        local function MV_OnWheel(model, delta)
            if model:HasCustomCamera() then
                model._mvDist = math.max(0.1, math.min(40, (model._mvDist or 5) - delta * 0.15))
                MV_SetOrientation(model, model._mvDist, model._mvYaw or 0, model._mvPitch or 0)
            else
                if IsShiftKeyDown() then
                    model._mvZoom = math.max(0, math.min(1, (model._mvZoom or 0) + delta*0.15))
                    model:SetPortraitZoom(model._mvZoom)
                else
                    model._mvCamDist = math.max(0.1, math.min(10, (model._mvCamDist or 1) - delta*0.15))
                    model:SetCamDistanceScale(model._mvCamDist)
                end
            end
        end

        local function MV_OnEnter(model)
            if not IsAltKeyDown() then return end
            local pz, px, py = model:GetPosition()
            GameTooltip:SetOwner(model, "ANCHOR_CURSOR")
            GameTooltip:AddLine("Model Info", 0, 1, 0.5)
            GameTooltip:AddLine(" ")
            GameTooltip:AddDoubleLine("DisplayID",     tostring(model._mvDisplayId or "?"),  1,1,1, 1,1,1)
            GameTooltip:AddDoubleLine("SetPosition",   string.format("(%.1f, %.1f, %.1f)", MV_RoundNumber(pz), MV_RoundNumber(px), MV_RoundNumber(py)), 1,1,1, 1,1,1)
            GameTooltip:AddDoubleLine("SetRotation",   tostring(model._mvRotation or 0),     1,1,1, 1,1,1)
            GameTooltip:AddDoubleLine("ModelFileID",   tostring(model._mvFileId   or "?"),   1,1,1, 1,1,1)
            GameTooltip:AddLine(" ")
            GameTooltip:AddLine("|cffffff00Left-click|r  → send ID to Template Creator", 0.8,0.8,0.8)
            GameTooltip:AddLine("|cffffff00Shift+drag|r  → orbit camera",              0.8,0.8,0.8)
            GameTooltip:AddLine("|cffffff00Right-drag|r  → rotate",                    0.8,0.8,0.8)
            GameTooltip:AddLine("|cffffff00Ctrl+Right|r  → reset",                     0.8,0.8,0.8)
            GameTooltip:AddLine("|cffffff00Wheel|r       → zoom",                      0.8,0.8,0.8)
            GameTooltip:Show()
        end

        local function MV_OnLeave()
            GameTooltip:Hide()
        end

        -- ---- Create a model cell ------------------------------------
        local function MV_CreateModel(parent, id)
            local cell = CreateFrame("Frame", nil, parent)
            cell:EnableMouse(true)
            cell:EnableMouseWheel(true)
            cell._mvId = id

            -- border
            local border = cell:CreateTexture(nil, "BACKGROUND", nil, -2)
            border:SetAllPoints()
            border:SetColorTexture(C.border[1], C.border[2], C.border[3], 0.5)

            -- background
            local bg = cell:CreateTexture(nil, "BACKGROUND", nil, -1)
            bg:SetPoint("TOPLEFT",  cell, "TOPLEFT",  1, -1)
            bg:SetPoint("BOTTOMRIGHT", cell, "BOTTOMRIGHT", -1, 1)
            bg:SetColorTexture(0.06, 0.04, 0.03, 1)
            cell._mvBg = bg

            -- hover glow
            local hover = cell:CreateTexture(nil, "ARTWORK", nil, 0)
            hover:SetAllPoints()
            hover:SetColorTexture(C.accent[1], C.accent[2], C.accent[3], 0)
            cell._mvHover = hover

            -- PlayerModel
            local model = CreateFrame("PlayerModel", nil, cell)
            model:SetPoint("TOPLEFT",  cell, "TOPLEFT",  1, -1)
            model:SetPoint("BOTTOMRIGHT", cell, "BOTTOMRIGHT", -1, 1)
            model:EnableMouse(true)
            model:EnableMouseWheel(true)
            model._mvParentCell = cell
            model._mvDisplayId  = 0
            model._mvFileId     = 0
            model._mvCamDist    = 1
            model._mvZoom       = 0
            model._mvRotation   = 0
            model:SetScript("OnMouseDown", MV_OnMouseDown)
            model:SetScript("OnMouseUp",   MV_OnMouseUp)
            model:SetScript("OnMouseWheel",MV_OnWheel)
            model:SetScript("OnEnter",     MV_OnEnter)
            model:SetScript("OnLeave",     MV_OnLeave)
            cell._mvModel = model

            -- Display ID label at bottom of cell
            local idLbl = cell:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
            idLbl:SetPoint("BOTTOM", cell, "BOTTOM", 0, 3)
            idLbl:SetTextColor(C.textDim[1]*1.5, C.textDim[2]*1.5, C.textDim[3]*1.5)
            idLbl:SetText("")
            cell._mvIdLbl = idLbl

            cell:SetScript("OnEnter", function(self)
                hover:SetColorTexture(C.accent[1], C.accent[2], C.accent[3], 0.15)
                border:SetColorTexture(C.accent[1], C.accent[2], C.accent[3], 0.9)
            end)
            cell:SetScript("OnLeave", function(self)
                hover:SetColorTexture(C.accent[1], C.accent[2], C.accent[3], 0)
                border:SetColorTexture(C.border[1], C.border[2], C.border[3], 0.5)
            end)

            return cell
        end

        -- ---- Update a model cell with a display ID ------------------
        local function MV_UpdateCell(cell, displayId)
            local model = cell._mvModel
            if not model then return end
            cell._mvDisplayId   = displayId
            model._mvDisplayId  = displayId
            -- Load model
            model:ClearModel()
            cell:Show()
            if displayId > 0 then
                model:SetDisplayInfo(displayId)
                -- Filter mode: check after short delay if model actually loaded
                if MV.filterEmpty then
                    model:SetScript("OnModelLoaded", function(self)
                        if self._mvDisplayId ~= displayId then return end
                        local fileId = self:GetModelFileID()
                        if fileId and fileId ~= 0 then
                            MV.validCache[displayId] = true
                            MV.emptyCache[displayId] = nil
                        else
                            MV.emptyCache[displayId] = true
                            MV.validCache[displayId] = nil
                            cell:Hide()
                        end
                        self:SetScript("OnModelLoaded", nil)
                    end)
                    -- Fallback timer: if OnModelLoaded never fires → empty
                    C_Timer.After(0.5, function()
                        if cell._mvDisplayId ~= displayId then return end
                        if MV.validCache[displayId] or MV.emptyCache[displayId] then return end
                        local fileId = model:GetModelFileID()
                        if not fileId or fileId == 0 then
                            MV.emptyCache[displayId] = true
                            cell:Hide()
                        else
                            MV.validCache[displayId] = true
                        end
                    end)
                else
                    model:SetScript("OnModelLoaded", nil)
                end
                local fileId = model:GetModelFileID()
                model._mvFileId = fileId or 0
            end
            MV_ResetModel(model)
            cell._mvIdLbl:SetText(tostring(displayId))
        end

        -- ---- Update all visible cells --------------------------------
        local function MV_UpdateGrid()
            if not mvContainer or not mvContainer:IsShown() then return end
            local containerW = mvContainer:GetWidth()  - 10  -- minus scrollbar
            local containerH = mvContainer:GetHeight() - 66  -- minus toolbar

            MV.cols = math.max(1, math.floor(containerW / MV.modelSize))
            MV.rows = math.max(1, math.floor(containerH / MV.modelSize))
            local count = MV.cols * MV.rows
            local startId = ((MV.page - 1) * count) + 1

            -- Update page editbox
            if MV.pageBox then MV.pageBox:SetText(tostring(MV.page)) end
            if MV.displayIdBox then MV.displayIdBox:SetText(tostring(startId)) end

            -- Position and populate cells
            for idx = 1, count do
                if not MV.models[idx] then
                    MV.models[idx] = MV_CreateModel(MV.gridFrame, idx)
                end
                local cell = MV.models[idx]
                local col = ((idx-1) % MV.cols)
                local row = math.floor((idx-1) / MV.cols)
                cell:SetSize(MV.modelSize, MV.modelSize)
                cell:ClearAllPoints()
                cell:SetPoint("TOPLEFT", MV.gridFrame, "TOPLEFT",
                    col * MV.modelSize, -row * MV.modelSize)
                local displayId = startId + (idx - 1)
                MV_UpdateCell(cell, displayId)
                cell:Show()
            end

            -- Hide unused cells
            for idx = count + 1, #MV.models do
                MV.models[idx]:Hide()
            end

            -- Resize grid frame
            MV.gridFrame:SetHeight(MV.rows * MV.modelSize + 4)
        end

        -- ---- Build Model Viewer UI inside the container -------------
        local function MV_Build()
            if MV.initialized then return end
            MV.initialized = true

            -- Toolbar at the bottom of the container
            local toolbar = CreateFrame("Frame", nil, mvContainer)
            toolbar:SetHeight(54)
            toolbar:SetPoint("BOTTOMLEFT",  mvContainer, "BOTTOMLEFT",  4, 4)
            toolbar:SetPoint("BOTTOMRIGHT", mvContainer, "BOTTOMRIGHT", -(SB_W+6), 4)
            local toolbarBg = toolbar:CreateTexture(nil, "BACKGROUND")
            toolbarBg:SetAllPoints()
            toolbarBg:SetColorTexture(C.bgDeep[1], C.bgDeep[2], C.bgDeep[3], 0.95)
            local toolbarLine = toolbar:CreateTexture(nil, "BORDER")
            toolbarLine:SetHeight(1)
            toolbarLine:SetPoint("TOPLEFT")
            toolbarLine:SetPoint("TOPRIGHT")
            toolbarLine:SetColorTexture(C.border[1], C.border[2], C.border[3], 0.6)

            -- Grid frame (fills container above toolbar)
            MV.gridFrame = CreateFrame("Frame", nil, mvContainer)
            MV.gridFrame:SetPoint("TOPLEFT",     mvContainer, "TOPLEFT",     4,  -4)
            MV.gridFrame:SetPoint("TOPRIGHT",    mvContainer, "TOPRIGHT",    -(SB_W+6), -4)
            MV.gridFrame:SetPoint("BOTTOM",      toolbar,     "TOP",         0,  2)

            -- Info text above toolbar (inside grid frame bottom area)
            local infoLbl = mvContainer:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
            infoLbl:SetPoint("BOTTOMLEFT",  toolbar, "TOPLEFT",  4, 2)
            infoLbl:SetPoint("BOTTOMRIGHT", toolbar, "TOPRIGHT", -4, 2)
            infoLbl:SetJustifyH("LEFT")
            infoLbl:SetTextColor(C.textDim[1]*1.2, C.textDim[2]*1.2, C.textDim[3]*1.2)
            infoLbl:SetText(L["MODELS_GRID_INFO"])

            -- Helper: make a styled toolbar button
            local function MkTBtn(text, w, tipText)
                local btn = MakeButton(toolbar, text, 0, 0, w, 26, 0.15, 0.05, 0.04)
                btn:SetPoint("TOP", toolbar, "TOP", 0, -4)  -- repositioned after
                if tipText then
                    btn:SetScript("OnEnter", function(s) Tip(s, tipText) end)
                    btn:SetScript("OnLeave", function() GameTooltip:Hide() end)
                end
                return btn
            end

            -- Prev button
            local btnPrev = MkTBtn(L["MODELS_PREV"], 64, L["MODELS_PREV_TIP"])
            btnPrev:ClearAllPoints()
            btnPrev:SetPoint("LEFT", toolbar, "LEFT", 8, 4)
            btnPrev:SetScript("OnClick", function()
                MV.page = math.max(1, MV.page - 1)
                MV_UpdateGrid()
            end)

            -- Page label + editbox
            local pageLbl = toolbar:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
            pageLbl:SetPoint("LEFT", btnPrev, "RIGHT", 6, 0)
            pageLbl:SetTextColor(C.textDim[1]*1.4, C.textDim[2]*1.4, C.textDim[3]*1.4)
            pageLbl:SetText(L["MODELS_PAGE"])

            local pageBox = CreateFrame("EditBox", nil, toolbar, "InputBoxTemplate")
            pageBox:SetSize(50, 22)
            pageBox:SetPoint("LEFT", pageLbl, "RIGHT", 4, 0)
            pageBox:SetAutoFocus(false)
            pageBox:SetNumeric(true)
            pageBox:SetMaxLetters(6)
            pageBox:SetText("1")
            pageBox:SetScript("OnEscapePressed", function(s) s:ClearFocus() end)
            pageBox:SetScript("OnEnterPressed", function(s)
                local v = tonumber(s:GetText())
                if v and v >= 1 then
                    MV.page = math.floor(v)
                    MV_UpdateGrid()
                end
                s:ClearFocus()
            end)
            MV.pageBox = pageBox

            -- Next button
            local btnNext = MkTBtn(L["MODELS_NEXT"], 64, L["MODELS_NEXT_TIP"])
            btnNext:ClearAllPoints()
            btnNext:SetPoint("LEFT", pageBox, "RIGHT", 6, 0)
            btnNext:SetScript("OnClick", function()
                MV.page = MV.page + 1
                MV_UpdateGrid()
            end)

            -- DisplayId jump box
            local dispIdLbl = toolbar:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
            dispIdLbl:SetPoint("LEFT", btnNext, "RIGHT", 14, 0)
            dispIdLbl:SetTextColor(C.textDim[1]*1.4, C.textDim[2]*1.4, C.textDim[3]*1.4)
            dispIdLbl:SetText(L["MODELS_DISPLAY_ID"])

            local dispIdBox = CreateFrame("EditBox", nil, toolbar, "InputBoxTemplate")
            dispIdBox:SetSize(70, 22)
            dispIdBox:SetPoint("LEFT", dispIdLbl, "RIGHT", 4, 0)
            dispIdBox:SetAutoFocus(false)
            dispIdBox:SetNumeric(true)
            dispIdBox:SetMaxLetters(8)
            dispIdBox:SetText("1")
            dispIdBox:SetScript("OnEscapePressed", function(s) s:ClearFocus() end)
            dispIdBox:SetScript("OnEnterPressed", function(s)
                local displayId = tonumber(s:GetText())
                if displayId and displayId >= 1 then
                    local count = math.max(1, MV.cols) * math.max(1, MV.rows)
                    MV.page = math.max(1, math.ceil(displayId / count))
                    MV_UpdateGrid()
                end
                s:ClearFocus()
            end)
            MV.displayIdBox = dispIdBox

            -- Size buttons
            local btnMinus = MkTBtn(L["MODELS_SIZE_MINUS"], 60, L["MODELS_SIZE_MINUS_TIP"])
            btnMinus:ClearAllPoints()
            btnMinus:SetPoint("LEFT", dispIdBox, "RIGHT", 14, 0)
            btnMinus:SetScript("OnClick", function()
                MV.modelSize = math.max(80, MV.modelSize - 20)
                MV_UpdateGrid()
            end)

            local btnPlus = MkTBtn(L["MODELS_SIZE_PLUS"], 60, L["MODELS_SIZE_PLUS_TIP"])
            btnPlus:ClearAllPoints()
            btnPlus:SetPoint("LEFT", btnMinus, "RIGHT", 4, 0)
            btnPlus:SetScript("OnClick", function()
                MV.modelSize = math.min(400, MV.modelSize + 20)
                MV_UpdateGrid()
            end)

            -- "Hide empty" checkbox
            local cbEmpty = CreateFrame("CheckButton", nil, toolbar, "UICheckButtonTemplate")
            cbEmpty:SetSize(24, 24)
            cbEmpty:SetPoint("LEFT", btnPlus, "RIGHT", 14, 0)
            cbEmpty:SetChecked(false)
            local cbEmptyLbl = toolbar:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
            cbEmptyLbl:SetPoint("LEFT", cbEmpty, "RIGHT", 2, 0)
            cbEmptyLbl:SetTextColor(C.textDim[1]*1.4, C.textDim[2]*1.4, C.textDim[3]*1.4)
            cbEmptyLbl:SetText(L["MODELS_HIDE_EMPTY"])
            cbEmpty:SetScript("OnClick", function(self)
                MV.filterEmpty = self:GetChecked()
                -- Clear caches when toggling so re-check happens fresh
                if MV.filterEmpty then
                    MV.emptyCache = {}
                    MV.validCache = {}
                end
                MV_UpdateGrid()
            end)

            -- Row 2 of toolbar: send ID target selector
            local sendLbl = toolbar:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
            sendLbl:SetPoint("BOTTOMLEFT", toolbar, "BOTTOMLEFT", 8, 6)
            sendLbl:SetTextColor(C.textDim[1]*1.4, C.textDim[2]*1.4, C.textDim[3]*1.4)
            sendLbl:SetText(L["MODELS_SEND_ROW2"])

            -- Initial render triggered when tab becomes visible
            mvContainer:HookScript("OnShow", function()
                C_Timer.After(0.05, function()
                    if MV.initialized and MV.gridFrame then
                        MV_UpdateGrid()
                    end
                end)
            end)
        end

        -- Build info header in the scroll pane child (static text above the model grid area)
        local ymv = -PAD
        MakeSectionHeader(mvPane, L["TAB_MODELS"], PAD, ymv) ; ymv = ymv - 30
        MakeInfoText(mvPane,
            L["MODELS_PANE_INFO"],
            PAD, ymv, PANEL_W - CONTENT_X - PAD - SB_W - 30)
        ymv = ymv - 50

        -- Note: the actual model grid is built lazily when the tab is first shown.
        -- We trigger the build by hooking the container OnShow via MV_Build().
        -- Register a deferred init so the container frame exists before we try to
        -- hook it.
        mvPane._contentH = 100
        if mvPane._updateThumb then mvPane._updateThumb() end

        -- Hook: when the models tab container is shown, lazily init the viewer
        local _mvBuildScheduled = false
        local origContainerShow = contentFrames["models"].SetScript
        -- Use a one-shot OnShow on the outer container
        contentFrames["models"]:HookScript("OnShow", function()
            if not _mvBuildScheduled then
                _mvBuildScheduled = true
                C_Timer.After(0.02, MV_Build)
            end
        end)
    end  -- do block for Model Viewer

    -- ============================================================
    --  TAB: PROFILE SETTINGS
    -- ============================================================
    do
        local profPane = BuildScrollPane("profiles")
        local yp = -PAD
        Panel.profiles = Panel.profiles or {}

        MakeSectionHeader(profPane, L["PROFILE_SECTION"], PAD, yp)
        yp = yp - 44

        MakeInfoText(profPane, L["PROFILE_SECTION_TIP"], PAD, yp, PANEL_W - CONTENT_X - PAD - SB_W - 30)
        yp = yp - 32

        -- Active profile label
        local profActiveLbl = profPane:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
        profActiveLbl:SetPoint("TOPLEFT", profPane, "TOPLEFT", PAD, yp)
        profActiveLbl:SetTextColor(C.text[1], C.text[2], C.text[3])
        -- Helper: get the active profile name for the current character
        -- rOrbsDB is per-character, so .activeProfile is always character-specific
        local function GetActiveProfileName()
            return rOrbsDB and rOrbsDB.activeProfile
        end

        local function UpdateActiveProfileLabel()
            local name = GetActiveProfileName() or L["PROFILE_NONE"]
            profActiveLbl:SetText(L["PROFILE_ACTIVE"] .. " |cffffff00" .. name .. "|r")
        end
        UpdateActiveProfileLabel()
        Panel.profActiveLbl = profActiveLbl
        yp = yp - 24

        -- Profile dropdown
        local ddProf, ddProfLbl = MakeDropdown(profPane, L["PROFILE_SELECT"], L["PROFILE_SELECT_TIP"], PAD, yp, 220)
        Panel.ddProf = ddProf

        local function RefreshProfileDropdown()
            UIDropDownMenu_Initialize(ddProf, function(self, level)
                local profDB = ns.db
                if not profDB then return end
                local list = profDB.getListProfile()
                if not list or #list == 0 then
                    local info = UIDropDownMenu_CreateInfo()
                    info.text = "|cff888888-- no profiles --"
                    info.disabled = true
                    info.notCheckable = true
                    UIDropDownMenu_AddButton(info, level)
                    return
                end
                local active = GetActiveProfileName()
                for _, entry in ipairs(list) do
                    local info = UIDropDownMenu_CreateInfo()
                    info.text    = entry.key
                    info.value   = entry.key
                    info.checked = (entry.key == active)
                    info.func    = function()
                        UIDropDownMenu_SetSelectedValue(ddProf, entry.key)
                        UIDropDownMenu_SetText(ddProf, entry.key)
                    end
                    UIDropDownMenu_AddButton(info, level)
                end
            end)
            -- Set text to active profile
            local active = GetActiveProfileName()
            if active then
                UIDropDownMenu_SetText(ddProf, active)
                UIDropDownMenu_SetSelectedValue(ddProf, active)
            else
                UIDropDownMenu_SetText(ddProf, L["PROFILE_NONE"])
            end
        end
        RefreshProfileDropdown()
        yp = yp - 50

        -- Helper: get selected profile name from dropdown
        local function GetSelectedProfile()
            return UIDropDownMenu_GetSelectedValue(ddProf)
        end

        -- Load / Apply button
        local profLoadBtn = MakeButton(profPane, L["PROFILE_SELECT"], PAD, yp, 100, 26, 0.10, 0.20, 0.10)
        profLoadBtn:SetScript("OnClick", function()
            local name = GetSelectedProfile()
            if not name then ns.GUI:ChatMsg("No profile selected."); return end
            StaticPopupDialogs["RORBS_PROFILE_LOAD_CONFIRM"] = {
                text     = string.format(L["PROFILE_LOAD_CONFIRM"], name),
                button1  = OKAY,
                button2  = CANCEL,
                OnAccept = function() ns.db.loadProfile(name) end,
                timeout = 0, whileDead = true, hideOnEscape = true,
            }
            StaticPopup_Show("RORBS_PROFILE_LOAD_CONFIRM")
        end)

        -- Delete button
        local profDelBtn = MakeButton(profPane, L["PROFILE_DELETE"], PAD + 108, yp, 100, 26, 0.40, 0.05, 0.03)
        profDelBtn:SetScript("OnClick", function()
            local name = GetSelectedProfile()
            if not name then ns.GUI:ChatMsg("No profile selected."); return end
            StaticPopupDialogs["RORBS_PROFILE_DELETE_CONFIRM"] = {
                text     = string.format(L["PROFILE_DELETE_CONFIRM"], name),
                button1  = OKAY,
                button2  = CANCEL,
                OnAccept = function()
                    ns.db.deleteProfile(name)
                    RefreshProfileDropdown()
                    UpdateActiveProfileLabel()
                end,
                timeout = 0, whileDead = true, hideOnEscape = true,
            }
            StaticPopup_Show("RORBS_PROFILE_DELETE_CONFIRM")
        end)

        -- Helper: safely get text from a StaticPopup editBox on WoW 12.x
        -- In 12.x the OnAccept self may not carry .editBox directly
        local function GetPopupEditBoxText(popupSelf)
            if popupSelf and popupSelf.editBox and popupSelf.editBox.GetText then
                return popupSelf.editBox:GetText()
            end
            for i = 1, 4 do
                local eb = _G["StaticPopup"..i.."EditBox"]
                if eb and eb:IsShown() then
                    return eb:GetText()
                end
            end
            return ""
        end

        -- Save (overwrite active profile) button
        local profSaveCurrentBtn = MakeButton(profPane, L["PROFILE_SAVE_CURRENT"], PAD + 216, yp, 100, 26, 0.05, 0.22, 0.12)
        profSaveCurrentBtn:SetScript("OnClick", function()
            local name = GetActiveProfileName()
            if not name then
                ns.GUI:ChatMsg(L["PROFILE_SAVE_CURRENT_NONE"])
                return
            end
            StaticPopupDialogs["RORBS_PROFILE_SAVE_CURRENT_CONFIRM"] = {
                text     = string.format(L["PROFILE_SAVE_CURRENT_CONFIRM"], name),
                button1  = OKAY,
                button2  = CANCEL,
                OnAccept = function()
                    ns.db.saveProfile(name)
                    RefreshProfileDropdown()
                    UpdateActiveProfileLabel()
                    ns.GUI:ChatMsg(string.format(L["CHAT_PROFILE_SAVED"], name))
                end,
                timeout = 0, whileDead = true, hideOnEscape = true,
            }
            StaticPopup_Show("RORBS_PROFILE_SAVE_CURRENT_CONFIRM")
        end)

        -- Save As button
        local profSaveBtn = MakeButton(profPane, L["PROFILE_SAVE"], PAD + 324, yp, 120, 26, 0.08, 0.15, 0.25)
        profSaveBtn:SetScript("OnClick", function()
            StaticPopupDialogs["RORBS_PROFILE_SAVE_NAME"] = {
                text         = L["PROFILE_NAME_PROMPT"],
                button1      = OKAY,
                button2      = CANCEL,
                hasEditBox   = true,
                maxLetters   = 40,
                OnAccept     = function(self)
                    local name = GetPopupEditBoxText(self)
                    if not name or name == "" then return end
                    local existingProfiles = rOrbs_DB_GLOB and rOrbs_DB_GLOB.PROFILES
                    if existingProfiles and existingProfiles[name] then
                        StaticPopupDialogs["RORBS_PROFILE_OVERWRITE_CONFIRM"] = {
                            text     = L["PROFILE_NAME_EXISTS"],
                            button1  = OKAY,
                            button2  = CANCEL,
                            OnAccept = function()
                                ns.db.saveProfile(name)
                                RefreshProfileDropdown()
                                UpdateActiveProfileLabel()
                            end,
                            timeout = 0, whileDead = true, hideOnEscape = true,
                        }
                        StaticPopup_Show("RORBS_PROFILE_OVERWRITE_CONFIRM")
                    else
                        ns.db.saveProfile(name)
                        RefreshProfileDropdown()
                        UpdateActiveProfileLabel()
                    end
                end,
                timeout = 0, whileDead = true, hideOnEscape = true,
            }
            StaticPopup_Show("RORBS_PROFILE_SAVE_NAME")
        end)
        yp = yp - 34

        -- ============================================================
        --  COPY FROM CHARACTER SECTION
        -- ============================================================
        yp = yp - 10
        MakeSectionHeader(profPane, "Copy Settings from Character", PAD, yp) ; yp = yp - 44

        MakeInfoText(profPane,
            "Copy all settings (including positions & scale) from another character on this account.",
            PAD, yp, PANEL_W - CONTENT_X - PAD - SB_W - 30)
        yp = yp - 32

        -- Dropdown: list all chars with saved data
        local ddChar, ddCharLbl = MakeDropdown(profPane, "Character", nil, PAD, yp, 260)
        Panel.ddChar = ddChar

        local selectedCharKey = nil

        local function RefreshCharDropdown()
            UIDropDownMenu_Initialize(ddChar, function(self, level)
                local list = ns.db.getCharList()
                if #list == 0 then
                    local info = UIDropDownMenu_CreateInfo()
                    info.text = "|cff888888-- no data --"
                    info.notCheckable = true
                    UIDropDownMenu_AddButton(info)
                    return
                end
                local currentKey = ns.db:GetCharKey()
                for _, key in ipairs(list) do
                    local info = UIDropDownMenu_CreateInfo()
                    -- Mark current character with dim color
                    if key == currentKey then
                        info.text = "|cff888888" .. key .. " (this character)|r"
                    else
                        info.text = key
                    end
                    local capturedKey = key
                    info.value = capturedKey
                    info.notCheckable = true
                    info.func = function()
                        selectedCharKey = capturedKey
                        UIDropDownMenu_SetText(ddChar, capturedKey)
                        UIDropDownMenu_SetSelectedValue(ddChar, capturedKey)
                    end
                    UIDropDownMenu_AddButton(info)
                end
            end)
            -- Set display text
            if selectedCharKey then
                UIDropDownMenu_SetText(ddChar, selectedCharKey)
            else
                UIDropDownMenu_SetText(ddChar, "-- select --")
            end
        end
        RefreshCharDropdown()
        yp = yp - 42

        -- Load from Character button
        local charLoadBtn = MakeButton(profPane, "Load from Character", PAD, yp, 180, 26, 0.10, 0.20, 0.10)
        charLoadBtn:SetScript("OnClick", function()
            if not selectedCharKey then ns.GUI:ChatMsg("No character selected."); return end
            if selectedCharKey == ns.db:GetCharKey() then
                ns.GUI:ChatMsg("That is the current character."); return
            end
            StaticPopupDialogs["RORBS_CHAR_LOAD_CONFIRM"] = {
                text     = string.format("Load all settings from |cffffff00%s|r?\nThis will overwrite your current settings.", selectedCharKey),
                button1  = OKAY,
                button2  = CANCEL,
                OnAccept = function() ns.db.loadFromChar(selectedCharKey) end,
                timeout = 0, whileDead = true, hideOnEscape = true,
            }
            StaticPopup_Show("RORBS_CHAR_LOAD_CONFIRM")
        end)

        -- Refresh char list button
        local charRefreshBtn = MakeButton(profPane, "Refresh List", PAD + 188, yp, 120, 26, 0.08, 0.12, 0.20)
        charRefreshBtn:SetScript("OnClick", function()
            -- Re-register current char snapshot before refreshing the list
            ns.db:RegisterCharSnapshot()
            RefreshCharDropdown()
            ns.GUI:ChatMsg("Character list refreshed.")
        end)
        yp = yp - 42

        profPane._contentH = math.abs(yp) + PAD + 40
        if profPane._updateThumb then profPane._updateThumb() end
    end  -- do block for Profiles


    return Panel
end

-- ============================================================
--  FADE
-- ============================================================

local function FadeIn(frame, dur)
    frame:SetAlpha(0) ; frame:Show()
    local e = 0
    frame:SetScript("OnUpdate", function(self, dt)
        e = e + dt
        local a = math.min(e / dur, 1)
        self:SetAlpha(a)
        if a >= 1 then self:SetScript("OnUpdate", nil) end
    end)
end

local function FadeOut(frame, dur, cb)
    local startA = frame:GetAlpha()
    local e = 0
    frame:SetScript("OnUpdate", function(self, dt)
        e = e + dt
        local pct = math.min(e / dur, 1)
        self:SetAlpha(startA * (1 - pct))
        if pct >= 1 then
            self:SetScript("OnUpdate", nil)
            self:Hide() ; self:SetAlpha(0)
            if cb then cb() end
        end
    end)
end

-- ============================================================
--  GUI CONTROLLER
-- ============================================================

ns.GUI = {}
local GUI = ns.GUI

function GUI:ChatMsg(msg)  print(L["CHAT_PREFIX"]..msg) end
function GUI:Debug(msg)    if ns.debug then print(L["DEBUG_PREFIX"]..tostring(msg)) end end

function GUI:Open()
    CreateMainPanel()
    GUI:RefreshValues()
    GUI:SetTab(activeTab)
    if not Panel:IsShown() then FadeIn(Panel, 0.20) end
end

function GUI:Close()
    if Panel and Panel:IsShown() then FadeOut(Panel, 0.18) end
end

function GUI:Toggle()
    if Panel and Panel:IsShown() then GUI:Close() else GUI:Open() end
end

function GUI:SetTab(key)
    activeTab = key

    -- Hide ALL panes (direct keys and sub-pane keys)
    for k, sf in pairs(contentFrames) do
        sf:Hide()
    end

    -- Determine if this tab uses sub-nav
    local hasSub = Panel and Panel.subNavButtons and Panel.subNavButtons[key] and #Panel.subNavButtons[key] > 0
    local subNav = Panel and Panel.subNavFrame
    local cbg    = Panel and Panel.contentBg
    local sbar   = Panel and Panel.sidebar

    -- Show only the current tab's sub-nav buttons, hide all others
    if Panel.subNavButtons then
        for tabK, btns in pairs(Panel.subNavButtons) do
            for _, bd in ipairs(btns) do
                if tabK == key and hasSub then bd.btn:Show() else bd.btn:Hide() end
            end
        end
    end

    if hasSub then
        -- Show sub-nav column and reanchor content area to its right
        if subNav and sbar then
            subNav:ClearAllPoints()
            subNav:SetPoint("TOPLEFT",    sbar,  "TOPRIGHT",    1,       0)
            subNav:SetPoint("BOTTOMLEFT", Panel, "BOTTOMLEFT",  2+180+1, BOT_H+2)
            subNav:Show()
        end
        if cbg and subNav then
            cbg:ClearAllPoints()
            cbg:SetPoint("TOPLEFT",     subNav, "TOPRIGHT",    1,  0)
            cbg:SetPoint("BOTTOMRIGHT", Panel,  "BOTTOMRIGHT", -2, BOT_H+2)
        end
        -- Activate first sub-tab
        local firstKey = Panel.subNavButtons[key][1].key
        Panel.SetSubTab(key, firstKey)
    else
        -- No sub-nav: hide sub-nav column and anchor content right of sidebar
        if subNav then subNav:Hide() end
        if cbg and sbar then
            cbg:ClearAllPoints()
            cbg:SetPoint("TOPLEFT",     sbar,  "TOPRIGHT",    1,  0)
            cbg:SetPoint("BOTTOMRIGHT", Panel, "BOTTOMRIGHT", -2, BOT_H+2)
        end
        -- Show this tab's pane — direct key or fallback to key..'.orb' for single-pane orb tabs
        local sf = contentFrames[key] or contentFrames[key..".orb"]
        if sf then
            sf:Show()
            local child = sf._sf and sf._sf:GetScrollChild()
            if child and child._updateThumb then
                local fn = child._updateThumb
                sf:SetScript("OnUpdate", function(self)
                    self:SetScript("OnUpdate", nil)
                    fn()
                end)
            end
        end
    end
    if not Panel then return end
    -- Update content header
    if Panel.contentHeader then
        for _, tab in pairs(Panel.tabs or {}) do
            if tab.key == key and tab.label then
                Panel.contentHeader:SetText(tab.label)
                break
            end
        end
    end
    for k, tab in pairs(Panel.tabs or {}) do
        if k == key then
            tab.bg:SetColorTexture(C.accent[1]*0.22, 0.04, 0.04, 1)
            tab.fs:SetTextColor(1, 0.95, 0.93, 1)
            tab.underline:SetColorTexture(C.accent[1], C.accent[2], C.accent[3], 1)
            if tab.icon then tab.icon:SetVertexColor(1, 0.85, 0.85, 1) end
        else
            tab.bg:SetColorTexture(C.tabIdle[1], C.tabIdle[2], C.tabIdle[3], 1)
            tab.fs:SetTextColor(0.75, 0.73, 0.72, 1)
            tab.underline:SetColorTexture(C.accent[1], C.accent[2], C.accent[3], 0)
            if tab.icon then tab.icon:SetVertexColor(0.50, 0.50, 0.50, 1) end
        end
    end
end

function GUI:RefreshValues()
    if not Panel or not rOrbsDB then return end
    db = ns.db

    if Panel.cbMinimap then Panel.cbMinimap:SetChecked(not (rOrbsDB.minimap and rOrbsDB.minimap.hide)) end


    -- Refresh all three orb tabs (wrapped in local function for Lua 5.1 early-exit)
    local function refreshTab(key, cfgKey)
        local w = Panel[key]
        if not w or not db then return end
        -- Ensure db.char[cfgKey] exists
        if db.char and not db.char[cfgKey] then
            local defaults = db:GetOrbDefaults()
            if defaults and defaults[cfgKey] then
                db.char[cfgKey] = defaults[cfgKey]
                if rOrbs_DB_CHAR then rOrbs_DB_CHAR[cfgKey] = db.char[cfgKey] end
            end
        end
        if not db.char or not db.char[cfgKey] then return end
        local c = db.char[cfgKey]

        -- Texture dropdown
        if w.ddTex then
            local texPath = c.filling.texture or ""
            local dispName = texPath:match("[^\\]+$") or "?"
            for _, entry in ipairs(db.getListFillingTexture()) do
                if entry.value == texPath then dispName = entry.key; break end
            end
            UIDropDownMenu_SetText(w.ddTex, dispName)
            UIDropDownMenu_SetSelectedValue(w.ddTex, texPath)
        end
        if w.colSwatch then
            local col = c.filling.color
            w.colSwatch:SetColor(col.r, col.g, col.b)
        end
        if w.cbAuto  then w.cbAuto:SetChecked(c.filling.colorAuto == true) end
        if w.cbOrbClassColor then
            local ccRefKey = (cfgKey == "TARGET_HEALTH") and "targetClassColor" or (cfgKey == "TOT_HEALTH") and "totClassColor"
            if ccRefKey then
                local _cb = w.cbOrbClassColor
                local _val = (rOrbsDB[ccRefKey] == true)
                C_Timer.After(0, function() if _cb then _cb:SetChecked(_val) end end)
            end
        end
        if w.cbText  then
            local val = rOrbsDB["textShow_"..cfgKey]
            w.cbText:SetChecked(val == nil or val == true)
        end
        if w.cbHover   then w.cbHover:SetChecked(rOrbsDB["textHover_"..cfgKey] == true) end
        if w.cbHidePct then w.cbHidePct:SetChecked(rOrbsDB["hidePercentSign_"..cfgKey] == true) end
        if w.ddTextMode then
            local mode = rOrbsDB["textMode_"..cfgKey] or "BOTH"
            local modeLabels = { BOTH = "Both (HP + %)", VALUE = "HP / Value only", PERCENT = "Percent only" }
            UIDropDownMenu_SetText(w.ddTextMode, modeLabels[mode] or "Both (HP + %)")
            UIDropDownMenu_SetSelectedValue(w.ddTextMode, mode)
        end
        if w.sFontSize then w.sFontSize:SetValue(rOrbsDB["fontSize_"..cfgKey] or 28) end
        if w.sAlpha  then w.sAlpha:SetValue(rOrbsDB["textAlpha_"..cfgKey] or c.value.alpha or 1) end
        if w.tcSwatch then
            local tc = rOrbsDB["textColor_"..cfgKey] or {r=1,g=1,b=1}
            w.tcSwatch:SetColor(tc.r, tc.g, tc.b)
        end
        if w.ddFont then
            local fname = rOrbsDB["font_"..cfgKey] or "Enchanted Land"
            UIDropDownMenu_SetText(w.ddFont, fname)
            UIDropDownMenu_SetSelectedValue(w.ddFont, fname)
        end
        -- Target/ToT/Pet name controls refresh
        if cfgKey == "TARGET_HEALTH" or cfgKey == "TOT_HEALTH" or cfgKey == "PET_HEALTH" then
            local rnkShow  = (cfgKey == "TARGET_HEALTH") and "targetNameShow"       or (cfgKey == "TOT_HEALTH") and "totNameShow"     or "petNameShow"
            local rnkFSize = (cfgKey == "TARGET_HEALTH") and "targetNameFontSize"   or (cfgKey == "TOT_HEALTH") and "totNameFontSize" or "petNameFontSize"
            local rnkCC    = (cfgKey == "TARGET_HEALTH") and "targetNameClassColor" or (cfgKey == "TOT_HEALTH") and "totNameClassColor" or nil
            local rnkOffY  = (cfgKey == "TARGET_HEALTH") and "targetNameOffY"       or (cfgKey == "TOT_HEALTH") and "totNameOffY"     or "petNameOffY"
            local rnkPos   = (cfgKey == "TARGET_HEALTH") and "targetNamePos"        or (cfgKey == "TOT_HEALTH") and "totNamePos"      or "petNamePos"
            if w.cbTargetName then w.cbTargetName:SetChecked(rOrbsDB[rnkShow] ~= false) end
            if w.sNameSize    then w.sNameSize:SetValue(rOrbsDB[rnkFSize] or 22) end
            if w.cbNameClass and rnkCC then w.cbNameClass:SetChecked(rOrbsDB[rnkCC] == true) end
            if w.sNameOffY    then w.sNameOffY:SetValue(rOrbsDB[rnkOffY] or -8) end
            if w.ddNamePos then
                local pos = rOrbsDB[rnkPos] or "TOP"
                local posLabels = { TOP = L["TARGET_NAME_POS_ABOVE"], BOTTOM = L["TARGET_NAME_POS_BELOW"] }
                UIDropDownMenu_SetText(w.ddNamePos, posLabels[pos] or L["TARGET_NAME_POS_ABOVE"])
                UIDropDownMenu_SetSelectedValue(w.ddNamePos, pos)
            end
            if w.cbEnable then
                local enableKey = (cfgKey == "TARGET_HEALTH") and "targetOrbEnabled" or (cfgKey == "TOT_HEALTH") and "totOrbEnabled" or "petOrbEnabled"
                w.cbEnable:SetChecked(rOrbsDB[enableKey] ~= false)
            end
        end
        if w.sGal    then w.sGal:SetValue(c.galaxies.alpha  or 0.4) end
        if w.sBub    then w.sBub:SetValue(c.bubbles.alpha   or 1.0) end
        if w.sHigh   then w.sHigh:SetValue(c.highlight.alpha or 1.0) end
        if w.cbOrbModel then w.cbOrbModel:SetChecked(c.model and c.model.enable ~= false) end
        if w.sOrbModelAlpha then
            local alphaVal = (cfgKey == "TARGET_HEALTH") and (rOrbsDB.targetModelAlpha or 1)
                             or (cfgKey == "TOT_HEALTH") and (rOrbsDB.totModelAlpha or 1)
                             or (cfgKey == "PET_HEALTH") and (rOrbsDB.petModelAlpha or 1)
                             or (c.model and c.model.alpha or 1)
            w.sOrbModelAlpha:SetValue(alphaVal)
        end
        if w.cbRing then
            local ringKey = (cfgKey == "TARGET_HEALTH") and "targetRingShow"
                         or (cfgKey == "TOT_HEALTH") and "totRingShow"
                         or (cfgKey == "PET_HEALTH") and "petRingShow"
            if ringKey then w.cbRing:SetChecked(rOrbsDB[ringKey] ~= false) end
        end
        if w.cbRingRotate then
            local rotKey = (cfgKey == "HEALTH") and "healthRingRotate" or (cfgKey == "TOT_HEALTH") and "totRingRotate" or (cfgKey == "PET_HEALTH") and "petRingRotate" or nil
            if rotKey then w.cbRingRotate:SetChecked(rOrbsDB[rotKey] == true) end
        end
        if w.sRingSpeed then
            local spdKey = (cfgKey == "HEALTH") and "healthRingSpeed" or (cfgKey == "TOT_HEALTH") and "totRingSpeed" or (cfgKey == "PET_HEALTH") and "petRingSpeed" or nil
            if spdKey then w.sRingSpeed:SetValue(rOrbsDB[spdKey] or 30) end
        end
        -- Cast Ring controls (Target tab only)
        if cfgKey == "TARGET_HEALTH" then
            if w.cbCastRing      then w.cbCastRing:SetChecked(rOrbsDB.castRingEnabled ~= false)      end
            if w.cbCastBg        then w.cbCastBg:SetChecked((rOrbsDB.castRingBgMode or "rose_bright") == "rose_bright") end
            if w.cbBgClassColor  then w.cbBgClassColor:SetChecked(rOrbsDB.castRingBgClassColor == true) end
            if w.cbClassColor    then w.cbClassColor:SetChecked(rOrbsDB.castRingUseClassColor == true) end
            if w.sBgAlpha        then w.sBgAlpha:SetValue(rOrbsDB.castRingBgAlpha or 0.8)            end
            if w.sFadeTime       then w.sFadeTime:SetValue(rOrbsDB.castRingFadeTime or 0.4)           end
            if w.sRingScale      then w.sRingScale:SetValue(rOrbsDB.castRingRingScale or 1.0)         end
            if w.bgSwatch        then w.bgSwatch:SetColor(
                rOrbsDB.castRingBgR or 1, rOrbsDB.castRingBgG or 1, rOrbsDB.castRingBgB or 1) end
            if w.castSwatch      then w.castSwatch:SetColor(
                rOrbsDB.castRingCastR or 1, rOrbsDB.castRingCastG or 0.15, rOrbsDB.castRingCastB or 0.05) end
            if w.chanSwatch      then w.chanSwatch:SetColor(
                rOrbsDB.castRingChanR or 0.3, rOrbsDB.castRingChanG or 0.6, rOrbsDB.castRingChanB or 1)  end
        end
        -- Pet Cast Ring controls (Pet tab only)
        if cfgKey == "PET_HEALTH" then
            if w.cbPetCastRing   then w.cbPetCastRing:SetChecked(rOrbsDB.petCastRingEnabled ~= false) end
            if w.cbPetCastBg     then w.cbPetCastBg:SetChecked((rOrbsDB.petCastRingBgMode or "rose_bright") == "rose_bright") end
            if w.sPetBgAlpha     then w.sPetBgAlpha:SetValue(rOrbsDB.petCastRingBgAlpha or 0.7)       end
            if w.sPetFadeTime    then w.sPetFadeTime:SetValue(rOrbsDB.petCastRingFadeTime or 0.4)      end
            if w.sPetRingScale   then w.sPetRingScale:SetValue(rOrbsDB.petCastRingRingScale or 1.0)    end
            if w.petBgSwatch     then w.petBgSwatch:SetColor(
                rOrbsDB.petCastRingBgR or 0.3, rOrbsDB.petCastRingBgG or 1.0, rOrbsDB.petCastRingBgB or 0.3) end
            if w.petCastSwatch   then w.petCastSwatch:SetColor(
                rOrbsDB.petCastRingCastR or 0.2, rOrbsDB.petCastRingCastG or 0.85, rOrbsDB.petCastRingCastB or 0.2) end
            if w.petChanSwatch   then w.petChanSwatch:SetColor(
                rOrbsDB.petCastRingChanR or 0.5, rOrbsDB.petCastRingChanG or 1.0, rOrbsDB.petCastRingChanB or 0.3) end
        end
        -- Health/Power ring checkboxes
        if Panel.health and Panel.health.cbRing  then Panel.health.cbRing:SetChecked(rOrbsDB.healthRingShow ~= false) end
        if Panel.power  and Panel.power.cbRing   then Panel.power.cbRing:SetChecked(rOrbsDB.powerRingShow   ~= false) end
        -- Ring rotation refresh
        if Panel.health and Panel.health.cbRingRotate then Panel.health.cbRingRotate:SetChecked(rOrbsDB.healthRingRotate == true) end
        if Panel.health and Panel.health.sRingSpeed   then Panel.health.sRingSpeed:SetValue(rOrbsDB.healthRingSpeed or 30) end
        if Panel.health and Panel.health.ddRingDir    then
            local _dir = rOrbsDB.healthRingDir or "CW"
            local _dl  = { CW = L["RING_DIR_CW"], CCW = L["RING_DIR_CCW"] }
            UIDropDownMenu_SetText(Panel.health.ddRingDir, _dl[_dir] or L["RING_DIR_CW"])
            UIDropDownMenu_SetSelectedValue(Panel.health.ddRingDir, _dir)
        end
        if Panel.power  and Panel.power.cbRingRotate  then Panel.power.cbRingRotate:SetChecked(rOrbsDB.powerRingRotate == true) end
        if Panel.power  and Panel.power.sRingSpeed    then Panel.power.sRingSpeed:SetValue(rOrbsDB.powerRingSpeed or 30) end
        if Panel.power  and Panel.power.ddRingDir     then
            local _dir = rOrbsDB.powerRingDir or "CW"
            local _dl  = { CW = L["RING_DIR_CW"], CCW = L["RING_DIR_CCW"] }
            UIDropDownMenu_SetText(Panel.power.ddRingDir, _dl[_dir] or L["RING_DIR_CW"])
            UIDropDownMenu_SetSelectedValue(Panel.power.ddRingDir, _dir)
        end
        -- Figure mode dropdown refresh
        if Panel.health and Panel.health.ddFigureMode then
            local _fml = { angel = "Angel", diablo = "Diablo", skull = "Skull", demon = "Demon", none = "Hidden" }
            local _fm  = rOrbsDB.healthFigureMode or "angel"
            UIDropDownMenu_SetText(Panel.health.ddFigureMode, _fml[_fm] or "Angel")
            UIDropDownMenu_SetSelectedValue(Panel.health.ddFigureMode, _fm)
            -- Show/hide Diablo sub-dropdown
            if Panel.health.ddDiabloTex  then Panel.health.ddDiabloTex:SetShown(_fm == "diablo") end
            if Panel.health.lblDiabloTex then Panel.health.lblDiabloTex:SetShown(_fm == "diablo") end
        end

        if Panel.power  and Panel.power.cbDemon   then Panel.power.cbDemon:SetChecked(rOrbsDB.powerDemonShow   ~= false) end
        if Panel.health and Panel.health.ddStyle then
            local _sl = { runes = "Rune Icons (new)", combobar = "Combo Bar (legacy)", hidden = "Hidden" }
            local _cur = rOrbsDB.comboPointStyle or "runes"
            UIDropDownMenu_SetText(Panel.health.ddStyle, _sl[_cur] or "Rune Icons (new)")
            UIDropDownMenu_SetSelectedValue(Panel.health.ddStyle, _cur)
        end
        if Panel.health and Panel.health.cbRuneBG       then Panel.health.cbRuneBG:SetChecked(rOrbsDB.runePointsBGShow ~= false) end
        if Panel.health and Panel.health.ddRunePos then
            local posLabels = { inside_top="Inside \226\128\147 Top", outside_top="Outside \226\128\147 Top", inside_bottom="Inside \226\128\147 Bottom", outside_bottom="Outside \226\128\147 Bottom", horizontal=L["RUNE_POS_HORIZONTAL"] }
            local cur = rOrbsDB.runePointsPosition or "inside_top"
            UIDropDownMenu_SetText(Panel.health.ddRunePos, posLabels[cur] or "Inside \226\128\147 Top")
            UIDropDownMenu_SetSelectedValue(Panel.health.ddRunePos, cur)
        end
        if Panel.health and Panel.health.cbPlayerCombatIcon then Panel.health.cbPlayerCombatIcon:SetChecked(rOrbsDB.playerCombatIconShow ~= false) end
        if Panel.health and Panel.health.cbPlayerRestingIcon then Panel.health.cbPlayerRestingIcon:SetChecked(rOrbsDB.playerRestingIconShow ~= false) end
        if Panel.target and Panel.target.cbTargetCombatIcon  then Panel.target.cbTargetCombatIcon:SetChecked(rOrbsDB.targetCombatIconShow ~= false) end
        -- Player Combat Icon sliders
        if Panel.health and Panel.health.sPCombatSize  then Panel.health.sPCombatSize:SetValue(rOrbsDB.playerCombatIconSize  or 28) end
        if Panel.health and Panel.health.sPCombatOffX  then Panel.health.sPCombatOffX:SetValue(rOrbsDB.playerCombatIconOffX  or 0)  end
        if Panel.health and Panel.health.sPCombatOffY  then Panel.health.sPCombatOffY:SetValue(rOrbsDB.playerCombatIconOffY  or 0)  end
        -- Player Resting Icon sliders
        if Panel.health and Panel.health.sPRestSize    then Panel.health.sPRestSize:SetValue(rOrbsDB.playerRestingIconSize or 28) end
        if Panel.health and Panel.health.sPRestOffX    then Panel.health.sPRestOffX:SetValue(rOrbsDB.playerRestingIconOffX or 0)  end
        if Panel.health and Panel.health.sPRestOffY    then Panel.health.sPRestOffY:SetValue(rOrbsDB.playerRestingIconOffY or 0)  end
        -- Player Leader Icon
        if Panel.health and Panel.health.cbPlayerLeaderIcon then Panel.health.cbPlayerLeaderIcon:SetChecked(rOrbsDB.playerLeaderIconShow ~= false) end
        if Panel.health and Panel.health.sPLeaderSize  then Panel.health.sPLeaderSize:SetValue(rOrbsDB.playerLeaderIconSize or 22) end
        if Panel.health and Panel.health.sPLeaderOffX  then Panel.health.sPLeaderOffX:SetValue(rOrbsDB.playerLeaderIconOffX or 0)  end
        if Panel.health and Panel.health.sPLeaderOffY  then Panel.health.sPLeaderOffY:SetValue(rOrbsDB.playerLeaderIconOffY or 0)  end
        -- Target Combat Icon sliders
        if Panel.target and Panel.target.sTCombatSize  then Panel.target.sTCombatSize:SetValue(rOrbsDB.targetCombatIconSize  or 28) end
        if Panel.target and Panel.target.sTCombatOffX  then Panel.target.sTCombatOffX:SetValue(rOrbsDB.targetCombatIconOffX  or 0)  end
        if Panel.target and Panel.target.sTCombatOffY  then Panel.target.sTCombatOffY:SetValue(rOrbsDB.targetCombatIconOffY  or 0)  end
        -- Target Leader Icon
        if Panel.target and Panel.target.cbTargetLeaderIcon then Panel.target.cbTargetLeaderIcon:SetChecked(rOrbsDB.targetLeaderIconShow ~= false) end
        if Panel.target and Panel.target.sTLeaderSize  then Panel.target.sTLeaderSize:SetValue(rOrbsDB.targetLeaderIconSize or 22) end
        if Panel.target and Panel.target.sTLeaderOffX  then Panel.target.sTLeaderOffX:SetValue(rOrbsDB.targetLeaderIconOffX or 0)  end
        if Panel.target and Panel.target.sTLeaderOffY  then Panel.target.sTLeaderOffY:SetValue(rOrbsDB.targetLeaderIconOffY or 0)  end
        if Panel.health and Panel.health.sFigureScale then Panel.health.sFigureScale:SetValue(rOrbsDB.figureScale or rOrbsDB.angelScale or 1.0) end
        if Panel.health and Panel.health.sFigureOffX  then Panel.health.sFigureOffX:SetValue(rOrbsDB.figureOffX  or rOrbsDB.angelOffX  or 0)   end
        if Panel.health and Panel.health.sFigureOffY  then Panel.health.sFigureOffY:SetValue(rOrbsDB.figureOffY  or rOrbsDB.angelOffY  or 0)   end
        if Panel.power  and Panel.power.sDemonScale  then Panel.power.sDemonScale:SetValue(rOrbsDB.demonScale  or 1.0) end
        if Panel.power  and Panel.power.sDemonOffX   then Panel.power.sDemonOffX:SetValue(rOrbsDB.demonOffX   or 0)   end
        if Panel.power  and Panel.power.sDemonOffY   then Panel.power.sDemonOffY:SetValue(rOrbsDB.demonOffY   or 0)   end
        -- Template section
        if w.cbTemplateShow then w.cbTemplateShow:SetChecked(rOrbsDB["tmplShow_"..cfgKey] or false) end
        if w.ddTemplate then
            local currentKey = rOrbsDB["orbTemplate_"..cfgKey] or "none"
            local dispName = "None (Custom)"
            if ns.orbTemplateList then
                for _, entry in ipairs(ns.orbTemplateList) do
                    if entry.key == currentKey then dispName = entry.label; break end
                end
            end
            UIDropDownMenu_SetText(w.ddTemplate, dispName)
            UIDropDownMenu_SetSelectedValue(w.ddTemplate, currentKey)
        end
        if w.sTmplScale   then w.sTmplScale:SetValue(rOrbsDB["tmplScale_"..cfgKey]   or 1.0) end
        if w.sTmplOffX    then w.sTmplOffX:SetValue(rOrbsDB["tmplOffsetX_"..cfgKey]  or 0.0) end
        if w.sTmplOffZ    then w.sTmplOffZ:SetValue(rOrbsDB["tmplOffsetZ_"..cfgKey]  or 0.0) end
        -- sTmplOpacity removed (duplicate of sOrbModelAlpha)
        -- Gloss texture selector
        if w.cbGlossNew or w.cbGlossClassic then
            local g = rOrbsDB["glossTex_"..cfgKey] or "new"
            if w.cbGlossNew     then w.cbGlossNew:SetChecked(g ~= "classic") end
            if w.cbGlossClassic then w.cbGlossClassic:SetChecked(g == "classic") end
        end
    end
    refreshTab("health", "HEALTH")
    refreshTab("power",  "POWER")
    refreshTab("target", "TARGET_HEALTH")
    refreshTab("tot",    "TOT_HEALTH")
    refreshTab("pet",    "PET_HEALTH")

    -- Party tab controls
    if Panel.party then
        local pw = Panel.party
        if pw.sHpFontSize  then pw.sHpFontSize:SetValue((rOrbsDB and rOrbsDB.partyHpFontSize)  or 9)   end
        if pw.sLowerScale  then pw.sLowerScale:SetValue((rOrbsDB and rOrbsDB.partyLowerScale) or 1.0)   end
        if pw.cbLpShow or pw.cbLpHide or pw.cbLpMouseover then
            local m = (rOrbsDB and rOrbsDB.partyLowerMode) or "show"
            if pw.cbLpShow      then pw.cbLpShow:SetChecked(m == "show")      end
            if pw.cbLpHide      then pw.cbLpHide:SetChecked(m == "hide")      end
            if pw.cbLpMouseover then pw.cbLpMouseover:SetChecked(m == "mouseover") end
        end
        -- Icon controls
        -- Icon show: nil=nie gesetzt=true (default an), false=explizit aus
        local function iconShow(key) return not (rOrbsDB and rOrbsDB[key] == false) end
        if pw.cbRaidIconShow  then pw.cbRaidIconShow:SetChecked(iconShow("partyRaidIconShow"))     end
        if pw.sRaidIconSize   then pw.sRaidIconSize:SetValue((rOrbsDB and rOrbsDB.partyRaidIconSize)  or 18) end
        if pw.sRaidIconOffX   then pw.sRaidIconOffX:SetValue((rOrbsDB and rOrbsDB.partyRaidIconOffX)  or 4)  end
        if pw.sRaidIconOffY   then pw.sRaidIconOffY:SetValue((rOrbsDB and rOrbsDB.partyRaidIconOffY)  or 4)  end
        if pw.cbRoleIconShow  then pw.cbRoleIconShow:SetChecked(iconShow("partyRoleIconShow"))     end
        if pw.sRoleIconSize   then pw.sRoleIconSize:SetValue((rOrbsDB and rOrbsDB.partyRoleIconSize)  or 12) end
        if pw.sRoleIconOffX   then pw.sRoleIconOffX:SetValue((rOrbsDB and rOrbsDB.partyRoleIconOffX)  or 2)  end
        if pw.sRoleIconOffY   then pw.sRoleIconOffY:SetValue((rOrbsDB and rOrbsDB.partyRoleIconOffY)  or -2) end
        if pw.cbLeaderIconShow then pw.cbLeaderIconShow:SetChecked(iconShow("partyLeaderIconShow")) end
        if pw.sLeaderIconSize  then pw.sLeaderIconSize:SetValue((rOrbsDB and rOrbsDB.partyLeaderIconSize) or 13) end
        if pw.sLeaderIconOffX  then pw.sLeaderIconOffX:SetValue((rOrbsDB and rOrbsDB.partyLeaderIconOffX) or -2) end
        if pw.sLeaderIconOffY  then pw.sLeaderIconOffY:SetValue((rOrbsDB and rOrbsDB.partyLeaderIconOffY) or -2) end
        -- Visibility options
        if pw.cbHideRaid   then pw.cbHideRaid:SetChecked(rOrbsDB.partyHideInRaid ~= false)   end
        if pw.cbShowPlayer then pw.cbShowPlayer:SetChecked(rOrbsDB.partyShowPlayer == true)   end
    end
end

-- ============================================================
--  APPLY FUNCTIONS
-- ============================================================

local function GetOrb(orbType)
    if orbType == "HEALTH" then return ns.HealthOrb
    elseif orbType == "POWER" then return ns.PowerOrb
    elseif orbType == "TARGET_HEALTH" then return ns.TargetHealthOrb
    elseif orbType == "TOT_HEALTH" then return ns.ToTHealthOrb
    elseif orbType == "PET_HEALTH" then return ns.PetHealthOrb
    end
    return nil
end

function GUI:ApplyTexture(orbType, texPath)
    local orb = GetOrb(orbType)
    if not orb or not orb.fill then return end
    orb.fill:SetStatusBarTexture(texPath)
    orb.fillTex = orb.fill:GetStatusBarTexture()
end

function GUI:ApplyColor(orbType, r, g, b)
    local orb = GetOrb(orbType)
    if not orb then return end
    if orb.fillTex  then orb.fillTex:SetVertexColor(r, g, b) end
    if orb.spark    then orb.spark:SetVertexColor(r, g, b) end
    if orb.galaxies then for _, t in pairs(orb.galaxies) do t:SetVertexColor(r, g, b) end end
    if orb.bubbles  then for _, t in pairs(orb.bubbles)  do t:SetVertexColor(r, g, b) end end
end

-- Setzt oUF's colorClass-Flag live für TARGET_HEALTH / TOT_HEALTH.
-- Gleicher Mechanismus wie ApplyColorAuto für den Player-Orb:
-- colorClass=true  → oUF bestimmt Klassenfarbe automatisch
-- colorClass=false → postUpdate setzt die konfigurierte Orb-Farbe
function GUI:ApplyTargetClassColor(cfgKey, val)
    local frameKey = (cfgKey == "TARGET_HEALTH") and "rOrbsTargetHealthFrame"
                  or (cfgKey == "TOT_HEALTH")    and "rOrbsToTHealthFrame"
                  or nil
    local tf = frameKey and _G[frameKey]
    if tf and tf.Health then
        tf.Health.colorClass  = val
        tf.Health.colorHealth = val
        if tf.UpdateAllElements then tf:UpdateAllElements("ForceUpdate") end
    end
end

function GUI:ApplyColorAuto(orbType, val)
    if not ns.db or not ns.db.char[orbType] then return end
    -- Persist the setting
    ns.db.char[orbType].filling.colorAuto = val
    if rOrbs_DB_CHAR and rOrbs_DB_CHAR[orbType] then rOrbs_DB_CHAR[orbType].filling.colorAuto = val end
    -- Apply color immediately via Blizzard-native event handlers (no reload needed).
    -- ns.ForceHealthOrbColor / ns.ForcePowerOrbColor are registered in player.lua
    -- and use C_ClassColor / PowerBarColor + proper event listeners.
    if orbType == "HEALTH" then
        if ns.ForceHealthOrbColor then ns.ForceHealthOrbColor() end
    elseif orbType == "POWER" then
        if ns.ForcePowerOrbColor then ns.ForcePowerOrbColor() end
    end
    -- For TARGET_HEALTH, trigger a full element update on the target frame
    if orbType == "TARGET_HEALTH" or orbType == "HEALTH" then
        local tf = _G["rOrbsTargetHealthFrame"]
        if tf and tf.UpdateAllElements then tf:UpdateAllElements("ForceUpdate") end
    end
end

function GUI:ApplyTextShow(orbType, val)
    local orb = GetOrb(orbType)
    if orb and orb.values then
        if val then orb.values:Show() else orb.values:Hide() end
    end
    -- Legacy: HEALTH also affects target if target uses HEALTH settings
    if orbType == "HEALTH" and ns.TargetHealthOrb and ns.TargetHealthOrb.values then
        -- Only apply if TARGET_HEALTH doesn't have its own setting
        if rOrbsDB["textShow_TARGET_HEALTH"] == nil then
            if val then ns.TargetHealthOrb.values:Show() else ns.TargetHealthOrb.values:Hide() end
        end
    end
end

function GUI:ApplyTextAlpha(orbType, val)
    local orb = GetOrb(orbType)
    if orb and orb.values then orb.values:SetAlpha(val) end
end

function GUI:ApplyTextColor(orbType, r, g, b)
    local function ApplyToOrb(orb)
        if not orb or not orb.values then return end
        if orb.values.top    then orb.values.top:SetTextColor(r, g, b) end
        if orb.values.bottom then orb.values.bottom:SetTextColor(r, g, b) end
    end
    ApplyToOrb(GetOrb(orbType))
end

function GUI:ApplyFont(orbType, fontPath)
    local function ApplyToOrb(orb)
        if not orb or not orb.values then return end
        fontPath = fontPath or "Fonts\\FRIZQT__.TTF"
        if orb.values.top    then local _,sz,fl = orb.values.top:GetFont()    ; orb.values.top:SetFont(fontPath, sz or 28, fl or "THINOUTLINE") end
        if orb.values.bottom then local _,sz,fl = orb.values.bottom:GetFont() ; orb.values.bottom:SetFont(fontPath, sz or 16, fl or "THINOUTLINE") end
        -- Also update target name font if this is the target orb
        if orb.nameFrame and orb.nameFrame.text then
            local _,sz,fl = orb.nameFrame.text:GetFont()
            orb.nameFrame.text:SetFont(fontPath, sz or 22, fl or "OUTLINE")
        end
    end
    ApplyToOrb(GetOrb(orbType))
end

function GUI:ApplyFontSize(orbType, topSize)
    local orb = GetOrb(orbType)
    if not orb or not orb.values then return end
    topSize = topSize or 28
    local botSize = math.max(8, topSize - 12)
    local fallbackFont = "Fonts\\FRIZQT__.TTF"
    if orb.values.top then
        local font, _, flags = orb.values.top:GetFont()
        orb.values.top:SetFont(font or fallbackFont, topSize, flags or "THINOUTLINE")
    end
    if orb.values.bottom then
        local font, _, flags = orb.values.bottom:GetFont()
        orb.values.bottom:SetFont(font or fallbackFont, botSize, flags or "THINOUTLINE")
    end
end

function GUI:ApplyLayerAlpha(orbType, layer, val)
    local function ApplyToOrb(orb)
        if not orb then return end
        if layer == "galaxies"  and orb.galaxies  then for _, t in pairs(orb.galaxies)  do t:SetAlpha(val) end end
        if layer == "bubbles"   and orb.bubbles   then for _, t in pairs(orb.bubbles)   do t:SetAlpha(val) end end
        if layer == "spark"     and orb.spark     then orb.spark:SetAlpha(val) end
        if layer == "highlight" and orb.highlight then orb.highlight:SetAlpha(val) end
    end
    ApplyToOrb(GetOrb(orbType))
end

function GUI:ApplyTargetModel(show)
    local orb = ns.TargetHealthOrb
    if not orb then return end
    if not orb.model then
        ns.GUI:BuildTargetModel(orb)
    end
    if orb.model then
        if show then orb.model:Show() else orb.model:Hide() end
    end
end

-- Generic name apply functions (work for TARGET_HEALTH, TOT_HEALTH and PET_HEALTH)
local function GetNameOrb(orbType)
    if orbType == "TARGET_HEALTH" then return ns.TargetHealthOrb
    elseif orbType == "TOT_HEALTH" then return ns.ToTHealthOrb
    elseif orbType == "PET_HEALTH" then return ns.PetHealthOrb
    end
end

function GUI:ApplyNameShow(orbType, val)
    local orb = GetNameOrb(orbType)
    if orb and orb.nameFrame then
        if val then orb.nameFrame:Show() else orb.nameFrame:Hide() end
    end
end

function GUI:ApplyNameFontSize(orbType, size)
    local orb = GetNameOrb(orbType)
    if orb and orb.nameFrame and orb.nameFrame.text then
        local font, _, flags = orb.nameFrame.text:GetFont()
        orb.nameFrame.text:SetFont(font or "Fonts\\FRIZQT__.TTF", size or 22, flags or "OUTLINE")
    end
end

function GUI:ApplyNamePos(orbType, pos, offY)
    local orb = GetNameOrb(orbType)
    if not orb or not orb.nameFrame then return end
    pos  = pos  or "TOP"
    offY = offY or -8
    orb.nameFrame:ClearAllPoints()
    if pos == "BOTTOM" then
        orb.nameFrame:SetPoint("TOP", orb, "BOTTOM", 0, offY)
    else
        orb.nameFrame:SetPoint("BOTTOM", orb, "TOP", 0, offY)
    end
end

-- Legacy aliases
function GUI:ApplyTargetNameShow(val) GUI:ApplyNameShow("TARGET_HEALTH", val) end
function GUI:ApplyTargetNameFontSize(size) GUI:ApplyNameFontSize("TARGET_HEALTH", size) end
function GUI:ApplyTargetNamePos(pos, offY) GUI:ApplyNamePos("TARGET_HEALTH", pos, offY) end

function GUI:ApplyOrbModel(orbType, show)
    local orb = GetOrb(orbType)
    if not orb then return end
    -- Build the model lazily if it doesn't exist yet (same pattern as ApplyTargetModel)
    if not orb.model then
        GUI:BuildTargetModel(orb, orbType)
    end
    if not orb.model then return end
    if show then orb.model:Show() else orb.model:Hide() end
end

function GUI:ApplyOrbModelAlpha(orbType, val)
    local orb = GetOrb(orbType)
    if not orb or not orb.model then return end
    orb.model:SetAlpha(val)
end

function GUI:BuildTargetModel(orb, orbCfgKey)
    if orb.model then return end
    orbCfgKey = orbCfgKey or "TARGET_HEALTH"
    local holder = nil
    if orb.clipFrame then
        for _, child in ipairs({orb.clipFrame:GetChildren()}) do
            holder = child
            break
        end
    end
    if not holder then return end

    local db2 = ns.db
    local orbcfg = db2 and db2.char and (db2.char[orbCfgKey] or db2.char.HEALTH)
    if not orbcfg then return end

    local alphaKey = (orbCfgKey == "TOT_HEALTH") and "totModelAlpha"
                   or (orbCfgKey == "PET_HEALTH") and "petModelAlpha"
                   or "targetModelAlpha"
    local model = CreateFrame("PlayerModel", nil, holder)
    model:SetFrameLevel(holder:GetFrameLevel() + 1)
    model:SetAllPoints(holder)
    model:EnableMouse(false)
    -- tmplOpacity_ is written by the slider and read by RestoreAll; prefer it as source of truth
    model:SetAlpha(rOrbsDB["tmplOpacity_"..orbCfgKey] or rOrbsDB[alphaKey] or orbcfg.model.alpha or 1)
    model.baseConfig = orbcfg.model
    model.orbRef = orb
    model._orbCfgKey = orbCfgKey

    function model:Update()
        local c = self.baseConfig or (db2.char[self._orbCfgKey] or db2.char.HEALTH).model
        local orbScale = self.orbRef and self.orbRef:GetScale() or 1
        local adjustedZoom = c.portraitZoom / orbScale
        self:SetCamDistanceScale(c.camDistanceScale)
        self:SetPosition(0, c.pos_x, c.pos_y)
        self:SetRotation(c.rotation)
        self:SetPortraitZoom(adjustedZoom)
        self:ClearModel()
        self:SetDisplayInfo(c.displayInfo)
    end

    model:SetScript("OnEvent", function(s)
      if s._templateConfig then return end
      s:Update()
    end)
    model:RegisterEvent("PLAYER_ENTERING_WORLD")
    model:SetScript("OnShow", function(s)
      -- Skip if template engine manages this model (e.g. after DialogueUI/camera fade hides UIParent)
      -- Without this check, OnShow fires with wrong scale and shrinks the 3D model
      if s._templateConfig then return end
      C_Timer.After(0, function() if s and s:IsVisible() then s:Update() end end)
    end)
    model:Update()
    orb.model = model

    -- PlayerModel is NOT clipped by clipFrame:SetClipsChildren in WoW.
    -- Hook fill OnValueChanged to hide model when orb is empty.
    if orb.fill then
        orb.fill:HookScript("OnValueChanged", function(bar, value)
            if not orb.model then return end
            if orb.model._templateConfig then return end -- handled by OrbTemplateEngine
            -- Respect the saved model.enable setting
            local orbcfg2 = ns.db and ns.db.char and (ns.db.char[orbCfgKey] or ns.db.char.HEALTH)
            if orbcfg2 and orbcfg2.model and orbcfg2.model.enable == false then return end
            -- Use pcall to safely compare tainted secret number values from GetMinMaxValues
            local ok, isEmpty = pcall(function()
                local _, max = bar:GetMinMaxValues()
                local ratio = (max and max > 0) and (value / max) or 1
                return ratio <= 0
            end)
            if ok then
                if isEmpty then orb.model:Hide() else orb.model:Show() end
            end
        end)
    end

    -- Apply saved enable state: hide if model.enable == false
    local enableState = orbcfg and orbcfg.model and orbcfg.model.enable
    if enableState == false then model:Hide() end
end

function GUI:ApplyPosition(unitKey, x, y, size)
    local frameNames = {
        player="rOrbsPlayerFrame", targetHealth="rOrbsTargetHealthFrame", target="rOrbsTargetFrame", targettarget="rOrbsToTHealthFrame"
    }
    local frame = _G[frameNames[unitKey] or ""]
    if not frame then GUI:Debug(L["ERR_FRAME_NOT_FOUND"]..tostring(unitKey)); return end
    rOrbsDB["pos_"..unitKey.."_x"] = x
    rOrbsDB["pos_"..unitKey.."_y"] = y
    rOrbsDB["size_"..unitKey]      = size
    frame:ClearAllPoints()
    frame:SetPoint("BOTTOM", UIParent, "BOTTOM", x, y)
    if ns.cfg and ns.cfg.units and ns.cfg.units[unitKey] then
        ns.cfg.units[unitKey].size = size
    end
    -- Re-apply figure layout so angel/demon scale with the orb
    if unitKey == "player" and ns.UpdateFigureLayout then
        if ns.FigureFrame and ns.HealthOrb then
            if ns.UpdateFigurePositionLayout then ns.UpdateFigurePositionLayout() end
        end
        if ns.DemonFrame and ns.PowerOrb then
            ns.UpdateFigureLayout(ns.DemonFrame, ns.PowerOrb, "RIGHT", rOrbsDB.demonScale, rOrbsDB.demonOffX, rOrbsDB.demonOffY)
        end
    end
end

function GUI:SetMinimapShown(show)
    rOrbsDB.minimap = rOrbsDB.minimap or {}
    rOrbsDB.minimap.hide = not show
    if ns.MinimapIcon then
        if show then ns.MinimapIcon:Show("rOrbs") else ns.MinimapIcon:Hide("rOrbs") end
    end
end

-- ============================================================
--  INTERFACE OPTIONS INTEGRATION
-- ============================================================

local function RegisterInterfaceOptions()
    local proxy = CreateFrame("Frame", "rOrbsInterfaceOptionsProxy")
    proxy.name = "rOrbs"
    -- No background texture: let the Blizzard settings panel show through (transparent)

    -- Big logo centered at top (nmainbar-style)
    local logo = proxy:CreateTexture(nil, "ARTWORK")
    logo:SetTexture("Interface\\AddOns\\rOrbs\\media\\logo_big")
    logo:SetSize(500, 237)
    logo:SetPoint("TOP", proxy, "TOP", 0, -28)

    -- Addon title below logo
    local t1 = proxy:CreateFontString(nil, "ARTWORK", "GameFontNormalHuge")
    t1:SetPoint("TOP", logo, "BOTTOM", 0, -10)
    t1:SetTextColor(C.accent[1], C.accent[2], C.accent[3])
    t1:SetText(L["ADDON_NAME"])

    -- Author sub-line
    local subLbl = proxy:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
    subLbl:SetPoint("TOP", t1, "BOTTOM", 0, -4)
    subLbl:SetTextColor(C.accentDim[1], C.accentDim[2], C.accentDim[3], 1)
    subLbl:SetText("by zork")

    -- Description
    local descLbl = proxy:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
    descLbl:SetPoint("TOP", subLbl, "BOTTOM", 0, -8)
    descLbl:SetWidth(440)
    descLbl:SetJustifyH("CENTER")
    descLbl:SetTextColor(C.textDim[1], C.textDim[2], C.textDim[3], 1)
    descLbl:SetText(L["ADDON_DESC"] or "Animated orb-style unit frames with modular orb engine.")

    -- Styled open-settings button (nmainbar-style, rOrbs red palette)
    local openBtn = CreateFrame("Button", nil, proxy, "BackdropTemplate")
    openBtn:SetSize(240, 40)
    openBtn:SetPoint("TOP", descLbl, "BOTTOM", 0, -20)
    openBtn:SetBackdrop({
        bgFile   = "Interface\\Buttons\\WHITE8X8",
        edgeFile = "Interface\\Buttons\\WHITE8X8",
        tile = false, edgeSize = 1,
        insets = { left=0, right=0, top=0, bottom=0 },
    })
    openBtn:SetBackdropColor(C.accent[1]*0.18, C.accent[2]*0.06, C.accent[3]*0.06, 1)
    openBtn:SetBackdropBorderColor(C.accent[1]*0.70, 0.08, 0.08, 1)
    local openLbl = openBtn:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    openLbl:SetPoint("CENTER")
    openLbl:SetTextColor(C.accent[1], C.accent[2], C.accent[3], 1)
    openLbl:SetText(L["SETTINGS_TITLE"])
    openBtn:SetScript("OnEnter", function(s)
        s:SetBackdropColor(C.accent[1]*0.38, 0.06, 0.06, 1)
        s:SetBackdropBorderColor(C.accentHi[1], 0.15, 0.15, 1)
        openLbl:SetTextColor(1, 1, 1, 1)
    end)
    openBtn:SetScript("OnLeave", function(s)
        s:SetBackdropColor(C.accent[1]*0.18, C.accent[2]*0.06, C.accent[3]*0.06, 1)
        s:SetBackdropBorderColor(C.accent[1]*0.70, 0.08, 0.08, 1)
        openLbl:SetTextColor(C.accent[1], C.accent[2], C.accent[3], 1)
    end)
    openBtn:SetScript("OnClick", function()
        -- Use C_Timer.After(0) so WoW completes the click-event cycle cleanly
        -- before hiding the settings panel. Calling SettingsPanel:Hide() directly
        -- from inside an OnClick of a panel button leaves WoW's keyboard-routing
        -- state inconsistent (a sub-frame stays keyboard-active), which causes
        -- the key-lock bug identical to the one fixed in nMainbar.
        C_Timer.After(0, function()
            if SettingsPanel and SettingsPanel:IsShown() then
                HideUIPanel(SettingsPanel)
            elseif InterfaceOptionsFrame and InterfaceOptionsFrame:IsShown() then
                HideUIPanel(InterfaceOptionsFrame)
            end
            if GameMenuFrame and GameMenuFrame:IsShown() then
                HideUIPanel(GameMenuFrame)
            end
            ns.GUI:Open()
        end)
    end)

    -- Thin gradient accent line under button
    local accentL = proxy: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.accent[1], C.accent[2], C.accent[3], 0.6))
    local accentR = proxy:CreateTexture(nil, "ARTWORK")
    accentR:SetHeight(1)
    accentR:SetWidth(240)
    accentR:SetPoint("TOP", openBtn, "BOTTOM", 0, -6)
    accentR:SetGradient("HORIZONTAL",
        CreateColor(C.accent[1], C.accent[2], C.accent[3], 0.6),
        CreateColor(0, 0, 0, 0))

    if IS_NEW_SETTINGS then
        local category = Settings.RegisterCanvasLayoutCategory(proxy, "rOrbs")
        rOrbs.categoryID = category:GetID()
        Settings.RegisterAddOnCategory(category)
    else
        if InterfaceOptions_AddCategory then
            InterfaceOptions_AddCategory(proxy)
        end
    end
end

-- ============================================================
--  INIT
-- ============================================================

ns.RegisterInit("rOrbs_GUI", function()
    db = ns.db
    rOrbsDB = rOrbsDB or {}
    rOrbsDB.minimap = rOrbsDB.minimap or { hide = false }
    ns.debug = rOrbsDB.debug == true
    RegisterInterfaceOptions()

    -- After orbs spawn: apply target model visibility, text color, font size
    C_Timer.After(0.3, function()
        -- Target model
        if ns.TargetHealthOrb then
            if rOrbsDB.targetModelShow ~= false then
                GUI:BuildTargetModel(ns.TargetHealthOrb, "TARGET_HEALTH")
            end
            if ns.TargetHealthOrb.model then
                ns.TargetHealthOrb.model:SetAlpha(rOrbsDB["tmplOpacity_TARGET_HEALTH"] or rOrbsDB.targetModelAlpha or 1)
                if rOrbsDB.targetModelShow == false then
                    ns.TargetHealthOrb.model:Hide()
                end
            end
        end

        -- Apply saved orb model visibility (health, power)
        for _, pair in ipairs({
            { orbType = "HEALTH",  orb = ns.HealthOrb },
            { orbType = "POWER",   orb = ns.PowerOrb  },
        }) do
            if pair.orb and pair.orb.model then
                local cfg2 = ns.db and ns.db.char and ns.db.char[pair.orbType]
                if cfg2 and cfg2.model then
                    if cfg2.model.enable == false then
                        pair.orb.model:Hide()
                    end
                    -- tmplOpacity_ is the authoritative key (also read by RestoreAll)
                    pair.orb.model:SetAlpha(rOrbsDB["tmplOpacity_"..pair.orbType] or cfg2.model.alpha or 1)
                end
            end
        end

        -- ToT model
        if ns.ToTHealthOrb then
            if rOrbsDB.totModelShow ~= false and GUI.BuildTargetModel then
                GUI:BuildTargetModel(ns.ToTHealthOrb, "TOT_HEALTH")
            end
            if ns.ToTHealthOrb.model then
                ns.ToTHealthOrb.model:SetAlpha(rOrbsDB["tmplOpacity_TOT_HEALTH"] or rOrbsDB.totModelAlpha or 1)
                if rOrbsDB.totModelShow == false then
                    ns.ToTHealthOrb.model:Hide()
                end
            end
        end

        -- Pet model
        if ns.PetHealthOrb then
            if rOrbsDB.petModelShow ~= false and GUI.BuildTargetModel then
                GUI:BuildTargetModel(ns.PetHealthOrb, "PET_HEALTH")
            end
            if ns.PetHealthOrb.model then
                ns.PetHealthOrb.model:SetAlpha(rOrbsDB["tmplOpacity_PET_HEALTH"] or rOrbsDB.petModelAlpha or 1)
                if rOrbsDB.petModelShow == false then
                    ns.PetHealthOrb.model:Hide()
                end
            end
        end

        -- Apply saved text colors, alpha, show/hide, font size
        for _, cfgKey in ipairs({"HEALTH","POWER","TARGET_HEALTH","TOT_HEALTH","PET_HEALTH"}) do
            local tc = rOrbsDB["textColor_"..cfgKey]
            if tc then GUI:ApplyTextColor(cfgKey, tc.r, tc.g, tc.b) end
            local ta = rOrbsDB["textAlpha_"..cfgKey]
            if ta then GUI:ApplyTextAlpha(cfgKey, ta) end
            local ts = rOrbsDB["textShow_"..cfgKey]
            if ts == false then GUI:ApplyTextShow(cfgKey, false) end
            local fs = rOrbsDB["fontSize_"..cfgKey]
            if fs then GUI:ApplyFontSize(cfgKey, fs) end
        end

        -- Apply saved name settings (Target, ToT and Pet)
        for _, pair in ipairs({
            { orbType = "TARGET_HEALTH", show = "targetNameShow", fs = "targetNameFontSize", pos = "targetNamePos", offY = "targetNameOffY" },
            { orbType = "TOT_HEALTH",    show = "totNameShow",    fs = "totNameFontSize",    pos = "totNamePos",    offY = "totNameOffY"    },
            { orbType = "PET_HEALTH",    show = "petNameShow",    fs = "petNameFontSize",    pos = "petNamePos",    offY = "petNameOffY"    },
        }) do
            if rOrbsDB[pair.show] == false then
                GUI:ApplyNameShow(pair.orbType, false)
            end
            if rOrbsDB[pair.fs] then
                GUI:ApplyNameFontSize(pair.orbType, rOrbsDB[pair.fs])
            end
            GUI:ApplyNamePos(pair.orbType, rOrbsDB[pair.pos] or "TOP", rOrbsDB[pair.offY] or -8)
        end

        -- Re-apply figure layout from saved values
        if ns.ApplyFigureMode then
            ns.ApplyFigureMode()
        elseif ns.UpdateFigureLayout then
            if ns.FigureFrame and ns.HealthOrb then
                if ns.UpdateFigurePositionLayout then ns.UpdateFigurePositionLayout() end
            end
            if ns.ApplyPowerFigureMode then
                ns.ApplyPowerFigureMode()
            elseif ns.DemonFrame and ns.PowerOrb then
                ns.UpdateFigureLayout(ns.DemonFrame, ns.PowerOrb, "RIGHT",
                    rOrbsDB.demonScale or 1.0,
                    rOrbsDB.demonOffX  or -74,
                    rOrbsDB.demonOffY  or 4)
            end
        end

        -- Restore orb templates
        if ns.OrbTemplateEngine then
            C_Timer.After(0.5, function()
                ns.OrbTemplateEngine:RestoreAll()
            end)
        end
    end)
end)
